DouFree

remove some douban restrictions

目前為 2022-07-06 提交的版本,檢視 最新版本

// ==UserScript==
// @name         DouFree
// @namespace    https://www.douban.com/
// @version      0.8
// @license      MIT; https://raw.githubusercontent.com/Sec-ant/DouFree.js/main/LICENSE
// @description  remove some douban restrictions
// @author       Secant
// @match        https://www.douban.com/*
// @require      https://fastly.jsdelivr.net/gh/drrouen/NouBan-js@4e384c593d55ae6c2b002500117fa6f4f33409ed/censoredWords.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/data.min.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/data.t2cn.min.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/bundle-browser.min.js
// @require      https://fastly.jsdelivr.net/gh/Sec-ant/NouBan.js@b9ecd066900762c2e98c4c4d61910087d9b5887a/dist/nouban.js
// @require      https://unpkg.com/@popperjs/core@2
// @require      https://unpkg.com/@stitches/core/dist/index.global.js
// @icon         https://www.douban.com/favicon.ico
// @grant        none
// @run-at       document-start
// ==/UserScript==
/* global NouBan, Popper, stitches */
(function () {
  "use strict";
  const { css } = stitches;
  // #region FUNCTIONS
  function isValidUrl(string) {
    let url;
    try {
      url = new URL(string);
    } catch (_) {
      return false;
    }
    return ["http:", "https:", "file:", "ftp:"].includes(url.protocol);
  }
  // #endregion

  // #region SCRIPT-INTERCEPTION
  const statusScriptSource =
    "https://bafybeifiw2g6rlccgbji2aikdn5ka2ow53emczwftno46eoruijw4nxxpu.ipfs.nftstorage.link/status.js";

  const scriptObserver = new MutationObserver((_, observer) => {
    let found = false;

    // statuses page
    const scripts = document.querySelectorAll("script");
    for (const script of scripts) {
      const scriptContent = script.innerHTML;
      const result = /https[^']+?\/js\/sns\/lifestream\/status\.js/.exec(
        scriptContent
      );
      if (result) {
        const { 0: match, index } = result;
        script.innerHTML =
          scriptContent.slice(0, index) +
          statusScriptSource +
          scriptContent.slice(index + match.length);
        found = true;
        break;
      }
    }

    // main page
    if (!found) {
      const statusScript = document.querySelector(
        'script[src$="/js/sns/lifestream/status.js"]'
      );
      if (statusScript) {
        statusScript.setAttribute("src", statusScriptSource);
        found = true;
      }
    }

    // add buttons
    if (found) {
      [...document.querySelectorAll("div.status-real-wrapper")].forEach(
        (realWrapper) => {
          const reshareButton =
            realWrapper.previousElementSibling.querySelector(
              'a.btn[data-action-type="reshare"]'
            );

          const localReshareButton = document.createElement("a");
          localReshareButton.innerHTML = "本级转发";
          localReshareButton.classList.add("btn");
          localReshareButton.setAttribute("data-action-type", "localReshare");

          reshareButton.after(" \u00a0\u00a0", localReshareButton);
        }
      );
      observer.disconnect();
    }
  });

  scriptObserver.observe(document.documentElement, {
    childList: true,
    subtree: true,
  });
  // #endregion

  // #region INPUT-DETECTION
  /* temporarily disable
  const inputSelectors = [
    "textarea#isay-cont", // 广播
    "input.lite-comment-reply-input", // 广播回复
  ];
  const toolTipStyledClass = css({
    backgroundColor: "FireBrick",
    color: "White",
    padding: "5px 10px",
    borderRadius: "4px",
    fontSize: "13px",
    display: "none",
    overflowWrap: "break-word",
    maxWidth: "160px",
    "&.display": {
      display: "block",
    },
  });
  const wm = new WeakMap();
  const inputObserver = new MutationObserver(() => {
    const inputElems = [
      ...(document.querySelectorAll(inputSelectors.join(",")) || []),
    ];
    for (const inputElem of inputElems) {
      if (wm.has(inputElem)) {
        continue;
      }
      const toolTipElem = document.createElement("div");
      toolTipElem.classList.add(toolTipStyledClass());
      document.body.append(toolTipElem);
      Popper.createPopper(inputElem, toolTipElem, {
        placement: "left-start",
        modifiers: [
          {
            name: "offset",
            options: {
              offset: [0, 8],
            },
          },
        ],
      });
      wm.set(inputElem, undefined);
      inputElem.addEventListener("input", () => {
        const timeout = wm.get(inputElem);
        clearTimeout(timeout);
        const newTimeout = setTimeout(() => {
          const { result, list } = NouBan.censorCheck(inputElem.value);
          if (result) {
            toolTipElem.classList.add("display");
            toolTipElem.innerHTML = [...list].join(" ");
          } else {
            toolTipElem.classList.remove("display");
          }
        }, 200);
        wm.set(inputElem, newTimeout);
      });
    }
  });
  inputObserver.observe(document.documentElement, {
    childList: true,
    subtree: true,
  });
  */
  // #endregion

  // #region FIRE-FOX-BEFORE-SCRIPT-EXECUTE
  let mutated = false;
  document.addEventListener("beforescriptexecute", (e) => {
    const { src } = e.target;
    if (/status\.js$/.test(src) && !mutated) {
      e.preventDefault();
      const scriptElement = document.createElement("script");
      scriptElement.setAttribute("src", src);
      mutated = true;
      e.target.replaceWith(scriptElement);
    }
  });
  // #endregion

  // #region PROXIES
  const OriginalXHR = window.XMLHttpRequest;
  window.XMLHttpRequest = function () {
    return new Proxy(new OriginalXHR(), {
      set(xhr, key, value) {
        if (key in xhr) {
          xhr[key] = value;
        }
        return true;
      },
      get(xhr, key) {
        switch (key) {
          case "responseText":
            if (
              /https:\/\/www\.douban\.com\/j\/status\/comments\?sid=\d+&resp_type=c_dict/.test(
                xhr.responseURL
              )
            ) {
              const newXMLResponseText = xhr.responseText.replace(
                /"url":"([^"]+?)","expanded_url":"([^"]+?)"/g,
                '"url":"$2","expanded_url":"$2"'
              );
              return newXMLResponseText;
            } else if (
              /https:\/\/www\.douban\.com\/j\/(group\/check_content_clean|check_clean_content)/.test(
                xhr.responseURL
              )
            ) {
              const newXMLResponseText = '{"r":0}';
              return newXMLResponseText;
            } else {
              return xhr.responseText;
            }
          default: {
            if (!key in xhr) return undefined;
            let value = xhr[key];
            if (typeof value === "function") {
              value = this[key] || value;
              return (...args) => value.apply(xhr, args);
            } else {
              return value;
            }
          }
        }
      },
    });
  };
  // #endregion

  // #region DOCUMENT-IDLE-SCRIPTS
  document.addEventListener("DOMContentLoaded", () => {
    scriptObserver.disconnect();
    [...document.querySelectorAll('a[href^="https://douc.cc/"]')].forEach(
      (a) => {
        const { href, title, textContent } = a;
        if (title && isValidUrl(title)) {
          a.href = title;
          if (textContent === href) {
            a.textContent = title;
          }
        }
      }
    );
  });
  // #endregion
})();