- // ==UserScript==
- // @name WeChat Plus
- // @namespace http://tampermonkey.net/
- // @version 0.1.0
- // @description 针对微信公众号文章的增强脚本
- // @author PRO-2684
- // @match https://mp.weixin.qq.com/s/*
- // @run-at document-start
- // @icon https://res.wx.qq.com/a/wx_fed/assets/res/MjliNWVm.svg
- // @license gpl-3.0
- // @grant unsafeWindow
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_deleteValue
- // @grant GM_registerMenuCommand
- // @grant GM_unregisterMenuCommand
- // @grant GM_addValueChangeListener
- // @require https://github.com/PRO-2684/GM_config/releases/download/v1.2.1/config.min.js#md5=525526b8f0b6b8606cedf08c651163c2
- // ==/UserScript==
-
- (function () {
- 'use strict';
- const { name, version } = GM_info.script;
- const idPrefix = "wechat-plus-";
- const $ = document.querySelector.bind(document);
- const debug = console.debug.bind(console, `[${name}@${version}]`);
- const error = console.error.bind(console, `[${name}@${version}]`);
- const configDesc = {
- $default: {
- autoClose: false,
- },
- viewCover: {
- name: "🖼️ 查看封面",
- title: "在新标签页中打开封面",
- type: "action",
- },
- showSummary: {
- name: "📄 显示摘要",
- type: "bool",
- },
- allowCopy: {
- name: "📋 允许复制",
- title: "允许复制所有内容",
- type: "bool",
- },
- hideBottomBar: {
- name: "⬇️ 隐藏底栏",
- title: "隐藏毫无作用的底栏",
- type: "bool",
- },
- blockReport: {
- name: "🚫 屏蔽上报*",
- title: "屏蔽信息上报,避免隐私泄露,需要刷新页面生效",
- type: "bool",
- },
- };
- const config = new GM_config(configDesc);
-
- // Helper functions
- /**
- * Resolves when the document is ready.
- */
- async function onReady() {
- return new Promise((resolve) => {
- if (document.readyState === "complete") {
- resolve();
- } else {
- document.addEventListener("DOMContentLoaded", () => {
- resolve();
- }, { once: true });
- }
- });
- }
- /**
- * Toggles the given style on or off.
- */
- function toggleStyle(id, toggle) {
- const existing = document.getElementById(idPrefix + id);
- if (existing && !toggle) {
- existing.remove();
- } else if (!existing && toggle) {
- const styleElement = document.createElement("style");
- styleElement.id = idPrefix + id;
- styleElement.textContent = styles[id];
- document.head.appendChild(styleElement);
- }
- }
-
- // Main functions
- function viewCover() {
- const meta = $("meta[property='og:image']");
- const url = meta?.content;
- if (url) {
- window.open(url, "_blank");
- } else {
- alert("Cannot find cover image URL.");
- }
- }
- function showSummary(show) {
- const block = $("#meta_content");
- if (!block) {
- error("Cannot find meta content block.");
- return;
- }
- const summary = block.querySelector("#summary");
- if (summary && !show) {
- summary.remove();
- } else if (!summary && show) {
- const meta = $("meta[name='description']");
- const description = meta?.content;
- if (!description) {
- error("Cannot find summary description.");
- return;
- }
- const summary = document.createElement("span");
- summary.id = "summary";
- summary.style.display = "block";
- summary.style.borderLeft = "0.2em solid";
- summary.style.paddingLeft = "0.5em";
- summary.classList.add("rich_media_meta", "rich_media_meta_text");
- summary.textContent = description;
- block.appendChild(summary);
- }
- }
- function allowCopy(allow) {
- const body = document.body;
- body.classList.toggle("use-femenu", !allow);
- }
- const hideBottomBar = "#unlogin_bottom_bar { display: none !important; }" +
- "body#activity-detail { padding-bottom: 0 !important; }";
- function blockReport() {
- function shouldBlock(url) {
- const blockList = new Set([
- // Additional info, like albums, etc.
- // "mp.weixin.qq.com/mp/getappmsgext",
-
- // CSP report, can't be blocked by UserScript - Use [uBlock Origin](https://github.com/gorhill/uBlock) to block it
- // "mp.weixin.qq.com/mp/fereport",
-
- // Will return error anyway (errmsg: "no session")
- "mp.weixin.qq.com/mp/appmsg_comment",
- "mp.weixin.qq.com/mp/relatedsearchword",
- "mp.weixin.qq.com/mp/getbizbanner",
- // Information collection
- "mp.weixin.qq.com/mp/getappmsgad",
- "mp.weixin.qq.com/mp/jsmonitor",
- "mp.weixin.qq.com/mp/wapcommreport",
- "mp.weixin.qq.com/mp/appmsgreport",
- "badjs.weixinbridge.com/badjs",
- "badjs.weixinbridge.com/report",
- "open.weixin.qq.com/pcopensdk/report",
- ]);
- url = new URL(url, location.href);
- const identifier = url.hostname + url.pathname;
- return blockList.has(identifier);
- }
- // Overwrite `XMLHttpRequest`
- const originalOpen = unsafeWindow.XMLHttpRequest.prototype.open;
- unsafeWindow.XMLHttpRequest.prototype.open = function (...args) {
- const url = args[1];
- if (shouldBlock(url)) {
- debug("Blocked opening:", url);
- this._url = url;
- } else {
- return originalOpen.apply(this, args);
- }
- }
- const originalSet = unsafeWindow.XMLHttpRequest.prototype.setRequestHeader;
- unsafeWindow.XMLHttpRequest.prototype.setRequestHeader = function (...args) {
- if (this._url) {
- debug("Blocked setting header:", this._url, ...args);
- } else {
- return originalSet.apply(this, args);
- }
- }
- const originalSend = unsafeWindow.XMLHttpRequest.prototype.send;
- unsafeWindow.XMLHttpRequest.prototype.send = function (...args) {
- if (this._url) {
- debug("Blocked sending:", this._url, ...args);
- } else {
- return originalSend.apply(this, args);
- }
- }
- // Filter setting `src` of images
- const { get, set } = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, "src");
- Object.defineProperty(HTMLImageElement.prototype, "src", {
- get() {
- return get.call(this);
- },
- set(url) {
- if (shouldBlock(url)) {
- debug("Blocked image url:", url);
- return url;
- } else {
- return set.call(this, url);
- }
- },
- });
- }
-
- // Once: Functions that are called once when the script is loaded.
- if (config.get("blockReport")) {
- blockReport();
- }
-
- // Actions: Functions that are called when the user clicks on it.
- const actions = {
- viewCover,
- };
- config.addEventListener("get", (e) => {
- const action = actions[e.detail.prop];
- if (action) {
- action();
- }
- });
-
- // Callbacks: Functions that are called when the config is changed.
- const callbacks = {
- showSummary,
- allowCopy,
- };
- onReady().then(() => {
- for (const [prop, callback] of Object.entries(callbacks)) {
- callback(config.get(prop));
- }
- });
-
- // Styles: CSS styles that can be toggled on and off.
- const styles = {
- hideBottomBar,
- };
- for (const prop of Object.keys(styles)) {
- toggleStyle(prop, config.get(prop));
- }
-
- config.addEventListener("set", (e) => {
- const callback = callbacks[e.detail.prop];
- if (callback) {
- onReady().then(() => {
- callback(e.detail.after);
- });
- }
- if (e.detail.prop in styles) {
- toggleStyle(e.detail.prop, e.detail.after);
- }
- });
- })();
-