您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
可在水源论坛 base64 解码选中内容
// ==UserScript== // @name 水源解码 // @namespace CCCC_David // @version 0.1.3 // @description 可在水源论坛 base64 解码选中内容 // @author CCCC_David // @match https://shuiyuan.sjtu.edu.cn/* // @grant none // @license MIT // ==/UserScript== (() => { 'use strict'; // From Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com // License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. // Modified class attribute to fit in. const DECODE_ICON = '<svg class="fa d-icon svg-icon svg-string" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17v80c0 13.3 10.7 24 24 24h80c13.3 0 24-10.7 24-24V448h40c13.3 0 24-10.7 24-24V384h40c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zm40-176c-22.1 0-40-17.9-40-40s17.9-40 40-40s40 17.9 40 40s-17.9 40-40 40z"/></svg>'; // Parameters. const APPEND_DECODE_BUTTON_TARGET_CLASS = 'buttons'; // Utility functions. const escapeRegExpOutsideCharacterClass = (s) => s.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); const allowedPolicy = window.trustedTypes?.createPolicy?.('allowedPolicy', {createHTML: (x) => x}); const createTrustedHTML = (html) => (allowedPolicy ? allowedPolicy.createHTML(html) : html); const utf8Decoder = new TextDecoder('utf-8', {fatal: true}); const htmlParser = new DOMParser(); const isBinaryString = (s) => s.split('').every((c) => c.charCodeAt(0) < 256); const decodeUTF8BinaryString = (s) => { // Assuming input is binary string. const byteArray = new Uint8Array(s.split('').map((c) => c.charCodeAt(0))); try { return utf8Decoder.decode(byteArray); } catch { return null; } }; const decodeBase64AndURI = (data) => { let result = data, prevResult = data; // eslint-disable-next-line no-constant-condition while (true) { const tempResult = result; try { result = atob(result); } catch { break; } prevResult = tempResult; if (result === prevResult) { break; } } if (isBinaryString(result)) { result = decodeUTF8BinaryString(result) ?? prevResult; } try { result = decodeURIComponent(result); } catch { } return result; }; const lookupShortURLs = async (shortURLs) => { if (!shortURLs) { return new Map(); } try { const response = await fetch('/uploads/lookup-urls', { method: 'POST', body: shortURLs.map((url) => `short_urls%5B%5D=${encodeURIComponent(url)}`).join('&'), headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Discourse-Present': 'true', 'Discourse-Logged-In': 'true', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').content, }, mode: 'same-origin', credentials: 'include', }); if (!response.ok) { // eslint-disable-next-line no-console console.error(`lookupShortURLs fetch failure: ${response.status}${response.statusText ? ` ${response.statusText}` : ''}`); return new Map(); } const result = await response.json(); return new Map(result.map((item) => [item.short_url, item.url])); } catch (e) { // eslint-disable-next-line no-console console.error(e); return new Map(); } }; const renderContent = async (content) => { // First cook the content. const cookedContent = await window.require('discourse/lib/text').cookAsync(content); let tree; try { tree = htmlParser.parseFromString(cookedContent, 'text/html'); } catch (e) { // eslint-disable-next-line no-console console.error(e); return '<font color="red">(Parse error)</font>'; } // Extract all short URLs and look up in batch. const shortURLs = []; for (const el of tree.querySelectorAll('img[data-orig-src], source[data-orig-src]')) { shortURLs.push(el.getAttribute('data-orig-src')); } for (const el of tree.querySelectorAll('a[data-orig-href]')) { shortURLs.push(el.getAttribute('data-orig-href')); } const shortURLMapping = await lookupShortURLs(shortURLs); // Replace short URLs with real URLs. for (const el of tree.querySelectorAll('img[data-orig-src], source[data-orig-src]')) { const src = el.getAttribute('data-orig-src'); if (shortURLMapping.has(src)) { el.src = shortURLMapping.get(src); el.removeAttribute('data-orig-src'); } } for (const el of tree.querySelectorAll('a[data-orig-href]')) { const href = el.getAttribute('data-orig-href'); if (shortURLMapping.has(href)) { el.href = shortURLMapping.get(href); el.removeAttribute('data-orig-href'); } } return tree.body.innerHTML; }; const convertSelection = async () => { const selection = window.getSelection(); const selectionString = selection.toString(); const {anchorNode, focusNode} = selection; if (!selectionString || !anchorNode || !focusNode) { return; } let targetNode; if (anchorNode === focusNode) { targetNode = anchorNode; } else if (anchorNode.contains(focusNode)) { targetNode = focusNode; } else if (focusNode.contains(anchorNode)) { targetNode = anchorNode; } else { targetNode = focusNode; } if (targetNode.outerHTML === undefined) { targetNode = targetNode.parentNode; } targetNode.outerHTML = createTrustedHTML(await renderContent(decodeBase64AndURI(selectionString))); selection.removeAllRanges(); }; const addDecodeButton = (quoteButtonContainer) => { if (!quoteButtonContainer?.matches?.('div') || quoteButtonContainer.closest('.error-page') || document.getElementById('decode-selection-button')) { return; } const decodeButtonContainer = document.createElement('span'); decodeButtonContainer.innerHTML = createTrustedHTML(` <button title="解码" id="decode-selection-button" class="btn-flat btn btn-icon-text" type="button"> ${DECODE_ICON} <span class="d-button-label">解码</span> </button> `); quoteButtonContainer.appendChild(decodeButtonContainer); const decodeSelectionButton = document.getElementById('decode-selection-button'); decodeSelectionButton.addEventListener('click', () => { decodeSelectionButton.disabled = true; setTimeout(() => { decodeSelectionButton.disabled = false; }, 1000); convertSelection(); }); }; const matchRegExp = new RegExp(`(?:^|\\s)${escapeRegExpOutsideCharacterClass(APPEND_DECODE_BUTTON_TARGET_CLASS)}(?:\\s|$)`, 'u'); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.matches?.(`.${APPEND_DECODE_BUTTON_TARGET_CLASS}`)) { addDecodeButton(node); } for (const el of node.getElementsByClassName?.(APPEND_DECODE_BUTTON_TARGET_CLASS) || []) { addDecodeButton(el); } } } else if (mutation.type === 'attributes') { if (mutation.attributeName === 'class') { if (!matchRegExp.test(mutation.oldValue ?? '') && mutation.target.matches?.(`.${APPEND_DECODE_BUTTON_TARGET_CLASS}`)) { addDecodeButton(mutation.target); } } } } }); observer.observe(document.documentElement, { subtree: true, childList: true, attributeFilter: ['class'], attributeOldValue: true, }); for (const el of document.getElementsByClassName(APPEND_DECODE_BUTTON_TARGET_CLASS)) { addDecodeButton(el); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址