asheroto / The Epoch Times - Ads & Paywall Removal

// ==UserScript==
// @name            The Epoch Times - Ads & Paywall Removal
// @namespace       The Epoch Times - Ads & Paywall Removal
// @version         1.2
// @description     Removes ads and paywall on The Epoch Times.
// @author          asheroto
// @license         MIT
// @match           https://www.theepochtimes.com/*
// @icon            https://www.theepochtimes.com/favicon.ico
// @grant           GM_addElement
// @grant           GM_log
// ==/UserScript==

// ==OpenUserScript==
// @author          asheroto
// ==/OpenUserScript==

/* jshint esversion: 8 */

(function () {

  // Prefix for console log
  const logPrefix = "[Ads & Paywall Removal]";

  // Console logging function
  function doLog(msg) {
    console.log(logPrefix + " " + msg);
  }

  // Function to wait until an element exists
  async function waitForElement(selector, callback, checkFrequencyInMs, timeoutInMs) {
    doLog("Waiting for element: " + selector);
    let startTimeInMs = Date.now();
    while (document.querySelector(selector) == null) {
      await new Promise(resolve => setTimeout(resolve, checkFrequencyInMs));
      if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs) {
        doLog("Completed timeout waiting for element: " + selector);
        return;
      }
    }
    doLog("Element found: " + selector);
    callback();
  }

  // List of CSS changes (instant ad removal)
  const css = `
    #ad_right_top_300x250_1 { display: none !important; }
    #article_ad_right_middle_300x250_1,
    #article_ad_right_middle_300x250_2,
    #article_ad_right_top_300x250_1,
    #article_ad_right_top_300x250_2,
    #in_article_ads_0,
    #in_article_ads_1,
    #in_article_ads_2,
    #in_article_ads_3,
    #in_article_ads_4,
    #in_article_ads_5,
    #in_article_ads_6,
    #in_article_ads_7,
    #in_article_ads_8,
    #inside_ad_336x280_1,
    #inside_ad_336x280_2,
    #landing-page { display: none !important; }
    #main { height: unset !important; overflow: unset !important; }
    #main > div { border-top: unset; margin-top: 0px; }
    #modal-COMMON { display: none !important; }
    #partnership { display: none !important; }
    .home-wall { display: none !important; }
    .login_wrapper { display: none !important; }
    .right_col.noprint > div { margin: unset !important; }
    .soft_stikcy { display: none !important; }
    .top_ad { display: none !important; }
    #footer { display: block !important; }
  `;

  // Apply the styles above to document stylesheet
  async function applyStyles(css) {
    const head = document.head || document.getElementsByTagName("head")[0];
    const style = document.createElement("style");
    head.appendChild(style);
    style.appendChild(document.createTextNode(css));
    doLog("Applied styles");
  }

  // Every 0.5 seconds, check if any item from the blacklist array is included in the src value of an item, and if so, hide that item, ends loop after 5 seconds
  async function removeElements() {
    const blacklist = ["doubleclick.", "amazon-adsystem", "adnxs", "ads."];
    const tags = ["script", "iframe"];

    let repeat = setInterval(function () {
      tags.forEach(function (item) {
        const elements = Array.from(document.getElementsByTagName(item));
        elements.forEach(function (src) {
          blacklist.forEach(function (b) {
            if (src.src.includes(b)) {
              src.style.setProperty('display', 'none', 'important');
              doLog("Hidden: " + item + " " + src.src);
            }
          });
        });
      });
    }, 500);

    // Clear tag removal after 5 seconds
    setTimeout(function () {
      clearInterval(repeat);
      doLog("Stopped removal loop");
    }, 5000);
  }

  // Modal will be hidden in CSS above, but this will help it out a little and enable the scrollbars again
  async function modalHelper() {
    waitForElement("#modal-COMMON", function () {
      const modal = document.querySelector("#modal-COMMON");
      modal.className = modal.className.replaceAll("is-open", "is-closed");
      document.body.classList.remove("free_user", "hidden");
      document.body.style.overflow = "";
      doLog("Removed modal, enabled scrollbars");
    }, 250, 5000);
  }

  // Hide any items that have a prefix listed in the adListIds array
  async function hideElements() {
    const adListIds = ["in_article", "inside_ad", "article_ad", "ad_"];
    let repeat = setInterval(function () {
      adListIds.forEach(function (adId) {
        const selector = '[id^="' + adId + '"], [class^="' + adId + '"]';
        const isId = selector.startsWith('[id=');
        const elements = Array.from(document.querySelectorAll(selector));
        elements.forEach(function (element) {
          element.style.setProperty('display', 'none', 'important');
          doLog("Hidden: " + (isId ? "ID " : "Class ") + adId);
        });
      });
    }, 500);

    // Clear tag removal after 5 seconds
    setTimeout(function () {
      clearInterval(repeat);
      doLog("Stopped removal loop");
    }, 5000);
  }

  applyStyles(css);
  removeElements();
  modalHelper();
  hideElements();

})();