Uber Eats ad blocker

Removes ads and annoyances on the Uber Eats website.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Uber Eats ad blocker
// @namespace    http://tampermonkey.net/
// @author       yotann
// @version      0.3
// @license      CC-PDDC
// @description  Removes ads and annoyances on the Uber Eats website.
// @match        https://www.ubereats.com/*
// @run-at       document-start
// @inject-into  page
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  function hookPrototype(prototype, overrides) {
    // Set the prototype of the overrides, so `super` refers to an unmodified copy of the original prototype.
    Object.setPrototypeOf(overrides, Object.create(Object.getPrototypeOf(prototype), Object.getOwnPropertyDescriptors(prototype)));
    Object.defineProperties(prototype, Object.getOwnPropertyDescriptors(overrides));
  }

  hookPrototype(Response.prototype, {
    async json() {
      return JSON.parse(await this.text());
    },
    async text() {
      return rewriteResponseText(this.status, this.url, await (super.text()));
    },
  });

  hookPrototype(XMLHttpRequest.prototype, {
    get response() {
      switch (this.responseType) {
      case '':
      case 'text':
        return this.responseText;
      case 'json':
        return JSON.parse(rewriteResponseText(this.status, this.responseURL, JSON.stringify(super.response)));
      default: // Not hooked
        return super.response;
      }
    },
    get responseText() {
      // It would make more sense to use the request URL instead of responseURL, but we can't access it here.
      return rewriteResponseText(this.status, this.responseURL, super.responseText);
    },
  });

  function childPromise(parent, getChild) {
    return new Promise(resolve => {
      const attempt = getChild();
      if (attempt) {
        return resolve(attempt);
      }
      new MutationObserver((mutationList, observer) => {
        const attempt = getChild();
        if (attempt) {
          observer.disconnect();
          resolve(attempt);
        }
      }).observe(parent, { childList: true });
    });
  }

  (async () => {
    const body = await childPromise(document, () => document.body);
    // __FILE_UPLOAD__ is the next element after __REACT_QUERY_STATE__,
    // so once it exists __REACT_QUERY_STATE__ should be fully loaded.
    await childPromise(body, () => document.getElementById('__FILE_UPLOAD__'));
    const queryStateElement = await childPromise(body, () => document.getElementById('__REACT_QUERY_STATE__'));
    console.log('Rewriting __REACT_QUERY_STATE__');
    const queryState = JSON.parse(decodeURIComponent(
      queryStateElement.textContent.replace(/\\u[0-9a-f]{4}/gi, (x => JSON.parse(`"${x}"`)))));
    queryState.queries.forEach(query => { query.state = rewriteQueryState(query.queryKey[0], query.state); });
    queryStateElement.textContent = JSON.stringify(queryState).replace(/[%\\]/g, encodeURIComponent)
      .replace(/["&/<>\u2028\u2029]/g, (x => '\\u' + x.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')));
  })();

  function rewriteResponseText(status, url, responseText) {
    if (!url || url == '') {
      // Seems to be necessary to stop /ramendca responses from getting through.
      return "";
    }
    url = new URL(url);
    // Pop-up ads on /feed (these are EventStreams being read incrementally).
    if (/^\/ramen\w*\/events\/recv$/.test(url.pathname)) {
      console.log("Blocking EventStream: " + url.pathname);
      return "";
    }
    const apiPrefix = "/_p/api/";
    if (status != 200 || !url.pathname.startsWith(apiPrefix)) {
      return responseText;
    }
    return JSON.stringify(rewriteQueryState(url.pathname.substr(apiPrefix.length), JSON.parse(responseText)));
  }

  function rewriteQueryState(endpoint, query) {
    if (query.status != 'success') {
      return query;
    }
    console.log('Rewriting query state: ' + endpoint);
    switch (endpoint) {
      case 'checkoutOrdersByDraftOrdersV1':
      case 'getActiveOrdersV1':
        // Ads on order tracking page
        query.data.orders.forEach(order => {
          order.feedCards = order.feedCards.filter(feedCard => feedCard.type != "eaterMessagePayload");
        });
        break;
      case 'getCheckoutPresentationV1':
        if (query.data.checkoutPayloads) {
          // Uber One ad on /checkout, above "Place Order" button
          if (query.data.checkoutPayloads.passBanner) {
            query.data.checkoutPayloads.passBanner.banners = [];
          }
          // "Complete your order" popup
          query.data.checkoutPayloads.upsellFeed = null;
        }
        break;
      case 'getFeedV1':
        // Ad carousel on /feed
        query.data.feedAffixes = [];
        break;
      case 'getLatestPendingRatingV1':
        // "How was your order" popup
        query.data = null;
        break;
      case 'getSearchFeedV1':
        // Sponsored results
        query.data.feedItems = query.data.feedItems.filter(feedItem => !feedItem?.store?.storeAd);
        break;
      case 'getSearchSuggestionsV1':
        // Sponsored results
        query.data = query.data.filter(suggestion => !suggestion?.store?.storeAd);
        break;
      case 'getStoreV1':
        // Uber One promo at top of store page
        Object.entries(query.data.catalogSectionsMap)
          .forEach(([key, entries]) => {
            if (entries.length > 0 && entries[0].type == "EATER_MESSAGE" && entries[0].catalogSectionUUID == "4df1ee8b-46bb-493e-a432-0a97192ccb52") {
              entries.shift();
            }
          });
        query.data.pinnedInfo = null;
        break;
      case 'getUserV1':
        // Uber One message in sidebar
        if (query.data.subscriptionMeta) {
          query.data.subscriptionMeta.subtitle = "";
        }
        break;
      case 'getUserPromoDetailsV1':
        // Referral promo in sidebar
        query.data.giveGetDetails = null;
        query.data.giveGetDetailsV2 = null;
        query.data.giveGetLandingPages = {};
        query.data.groceryGiveGetDetails = null;
        break;
    }
    return query;
  }
})();