萌娘百科缓存部分Api请求

缓存部分Api请求结果以提升速度减少WAF几率

安裝腳本?
作者推薦腳本

您可能也會喜歡 JustMoeComments

安裝腳本
// ==UserScript==
// @name         萌娘百科缓存部分Api请求
// @namespace    https://github.com/gui-ying233/mwApiCache
// @version      3.8.0
// @description  缓存部分Api请求结果以提升速度减少WAF几率
// @author       鬼影233
// @license      MIT
// @match        zh.moegirl.org.cn/*
// @match        mzh.moegirl.org.cn/*
// @match        commons.moegirl.org.cn/*
// @match        library.moegirl.org.cn/*
// @match        en.moegirl.org.cn/*
// @match        ja.moegirl.org.cn/*
// @icon         http://moegirl.org.cn/favicon.ico
// @supportURL   https://github.com/gui-ying233/mwApiCache/issues
// @run-at       document-start
// @grant        none
// ==/UserScript==

(() => {
	"use strict";
	if (new URLSearchParams(window.location.search).get("safemode")) return;
	const ver = 2;
	const log = (type, ...args) => {
		console.debug(
			`%cmwApiCache-${type}\n${args.join("\n")}`,
			"border-left:1em solid #4E3DA4;background-color:#3C2D73;color:#D9D9D9;padding:1em"
		);
	};
	log("Beg", new Date().toISOString());
	log("Ver", ver);
	log("Svd", +window.localStorage.getItem("mwApiCache-Svd"));
	let win;
	window.XMLHttpRequest = class extends window.XMLHttpRequest {
		constructor() {
			super();
			this.addEventListener("load", () => {
				const url = new URL(this.responseURL);
				if (
					url.hostname.match(
						/^(?:m?zh|commons|library|en|ja)\.moegirl\.org\.cn$/
					) &&
					this.status === 200 &&
					this.responseText.match(
						"https://ssl.captcha.qq.com/TCaptcha.js"
					)
				) {
					if (win) win.focus();
					else {
						win = window.open(
							url,
							"_blank",
							"width=360,height=360"
						);
						win.addEventListener(
							"DOMContentLoaded",
							() => {
								if (
									![...win.document.scripts].some(
										({ src }) =>
											src ===
											"https://ssl.captcha.qq.com/TCaptcha.js"
									)
								) {
									win.close();
									win = null;
								}
								const id = setInterval(() => {
									if (
										!win?.performance
											.getEntriesByType("navigation")
											.some(nav => nav.type === "reload")
									)
										return;
									clearInterval(id);
									if (
										location.hostname ===
										win.location.hostname
									)
										document.cookie = win.document.cookie;
									win.close();
									win = null;
								}, 50);
							},
							{ passive: true, once: true }
						);
					}
				}
			});
		}
	};
	const id = setInterval(async () => {
		if (!window.mediaWiki?.Api?.prototype) return;
		clearInterval(id);
		const originalMediaWikiApiPost = window.mediaWiki.Api.prototype.post;
		await window.$.ready;
		const cfg = window.mediaWiki.config;
		const userName = cfg.get("wgUserName");
		window.addEventListener(
			"storage",
			e => {
				if (!e.key.startsWith("mwApiCache-")) return;
				if (e.newValue) {
					if (
						e.key === "mwApiCache-Svd" &&
						+e.newValue <= +e.oldValue
					)
						return;
					e.storageArea.setItem(e.key, e.newValue);
					log("Set", e.key, e.newValue);
				} else {
					e.storageArea.removeItem(e.key);
					log("Del", e.key);
				}
			},
			{ passive: true }
		);
		const timestamp = Date.now();
		for (const key in window.localStorage) {
			if (!key.startsWith("mwApiCache-")) continue;
			if (key === "mwApiCache-Svd") continue;
			const cache = JSON.parse(window.localStorage.getItem(key));
			if (cache.timestamp < timestamp || cache.ver !== ver) {
				log("Del", key);
				window.localStorage.removeItem(key);
			}
		}
		const bc = new BroadcastChannel("mwApiCache");
		bc.addEventListener("message", e => {
			if (e.data === "init")
				Object.keys(sessionStorage).forEach(
					key =>
						key.startsWith("mwApiCache-") &&
						bc.postMessage({
							key: key.replace("mwApiCache-", ""),
							value: JSON.parse(sessionStorage.getItem(key)),
						})
				);
			else {
				const { key, value } = e.data;
				window.sessionStorage.setItem(
					`mwApiCache-${key}`,
					JSON.stringify(value)
				);
				log("Set", key, JSON.stringify(value));
			}
		});
		bc.postMessage("init");
		const second = 1000,
			minute = second * 60,
			hour = minute * 60,
			day = hour * 24;
		const getCache = (t, method, arg, ms = day * 7) => {
			const _arg = JSON.stringify(arg);
			const cache = JSON.parse(
				window[ms ? "localStorage" : "sessionStorage"].getItem(
					`mwApiCache-${_arg}`
				)
			);
			if (!cache) {
				const res = method.call(
					t,
					Object.assign(
						{
							maxage: ms / 1000,
							smaxage: ms / 1000,
						},
						arg
					)
				);
				res.then(_res => {
					log("Set", _arg, JSON.stringify(_res));
					const key = `mwApiCache-${_arg}`;
					const value = {
						ver,
						timestamp: timestamp + ms,
						res: _res,
					};
					window[ms ? "localStorage" : "sessionStorage"].setItem(
						key,
						JSON.stringify(value)
					);
					if (!ms) bc.postMessage({ key: _arg, value });
					return _res;
				});
				return res;
			}
			window.localStorage.setItem(
				"mwApiCache-Svd",
				+window.localStorage.getItem("mwApiCache-Svd") + 1
			);
			log("Get", _arg);
			const promise = $()
				.promise()
				.then(() => cache.res);
			promise.abort = () => {};
			return promise;
		};
		const apiFilter = (t, method, [payload, ...args]) => {
			const arg = JSON.stringify(payload);
			switch (arg) {
				case `{"action":"query","ususers":"${userName}","meta":["userinfo","siteinfo"],"list":["users"],"uiprop":["rights"],"siprop":["specialpagealiases"],"usprop":["blockinfo"]}`:
				case '{"action":"query","meta":"siteinfo","siprop":"specialpagealiases","formatversion":2,"uselang":"content","maxage":3600}':
				case '{"action":"query","meta":"userinfo","uiprop":["groups","rights"]}':
				case `{"action":"query","prop":"revisions","titles":"User:${userName}/codemirror-mediawiki.json","rvprop":"content","rvlimit":1}`:
				case '{"action":"paraminfo","modules":"main","helpformat":"html","uselang":"zh"}':
				case '{"action":"paraminfo","modules":"json","helpformat":"html","uselang":"zh"}':
				case '{"action":"query","meta":"siteinfo","siprop":["general","namespaces"]}':
					return getCache(t, method, payload);
				case `{"action":"query","meta":"allmessages","ammessages":["Editnotice-${cfg.get(
					"wgNamespaceNumber"
				)}","Editnotice-${cfg.get("wgNamespaceNumber")}-${cfg
					.get("wgPageName")
					.replaceAll("_", " ")
					.replace(
						`${
							cfg.get("wgFormattedNamespaces")[
								cfg.get("wgNamespaceNumber")
							]
						}:`,
						""
					)}"],"amenableparser":1}`:
					return getCache(t, method, payload, 3 * day);
				case `{"action":"query","prop":"revisions|info","inprop":"protection|watched","format":"json","pageids":${cfg.get(
					"wgArticleId"
				)}}`:
					return getCache(t, method, payload, 0);
				case '{"action":"query","meta":"notifications","formatversion":2,"notfilter":"!read","notprop":"list","notformat":"model","notlimit":"max"}':
					return getCache(t, method, payload, 5 * minute - 1);
				default:
					if (
						/^{"action":"query","meta":"allmessages","ammessages":\[".*?"\],"amlang":"zh","formatversion":2}$/.test(
							arg
						) ||
						/^{"action":"parse","text":"<span id=\\"mw_editnotice_test_var\\".+?","preview":true,"disablelimitreport":true,"disableeditsection":true,"disabletoc":true}$/.test(
							arg
						)
					)
						return getCache(t, method, payload);
					if (
						/^{"action":"query","meta":"allmessages","ammessages":\["Editnotice-\d+","Editnotice-\d+-.+"],"amenableparser":1}$/.test(
							arg
						)
					)
						return getCache(t, method, payload, 3 * day);
					if (
						payload?.action === "compare" ||
						/^{"action":"query","prop":"revisions\|info","inprop":"protection\|watched","format":"json","pageids":\d+}$/.test(
							arg
						)
					)
						return getCache(t, method, payload, 0);
					if (payload?.maxage ?? payload?.smaxage)
						return getCache(
							t,
							method,
							payload,
							(payload?.maxage ?? payload?.smaxage) * 1000
						);
					log("Ign", arg);
					return method.apply(t, [payload, ...args]);
			}
		};
		window.mediaWiki.Api.prototype.get = function (...args) {
			return apiFilter(this, originalMediaWikiApiPost, args);
		};
		window.mediaWiki.Api.prototype.post = function (...args) {
			return apiFilter(this, originalMediaWikiApiPost, args);
		};
	}, 5);
})();