您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
可在水源论坛 base64 解码选中内容
当前为
// ==UserScript== // @name 水源解码 // @namespace CCCC_David // @version 0.1.1 // @description 可在水源论坛 base64 解码选中内容 // @author CCCC_David // @match https://shuiyuan.sjtu.edu.cn/* // @grant none // @license MIT // ==/UserScript== (() => { 'use strict'; // From Font Awesome Free v5.15 by @fontawesome - https://fontawesome.com // License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) // Modified class attribute to fit in. const DECODE_ICON = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="key" class="d-icon svg-icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M512 176.001C512 273.203 433.202 352 336 352c-11.22 0-22.19-1.062-32.827-3.069l-24.012 27.014A23.999 23.999 0 0 1 261.223 384H224v40c0 13.255-10.745 24-24 24h-40v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24v-78.059c0-6.365 2.529-12.47 7.029-16.971l161.802-161.802C163.108 213.814 160 195.271 160 176 160 78.798 238.797.001 335.999 0 433.488-.001 512 78.511 512 176.001zM336 128c0 26.51 21.49 48 48 48s48-21.49 48-48-21.49-48-48-48-48 21.49-48 48z"></path></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. // eslint-disable-next-line no-undef const cookedContent = await 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?.nodeName.toLowerCase() !== 'div' || document.getElementById('decode-selection-button')) { return; } const decodeButtonContainer = document.createElement('span'); decodeButtonContainer.innerHTML = createTrustedHTML(` <button aria-label="解码" 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); document.getElementById('decode-selection-button').addEventListener('click', convertSelection); }; const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { for (const el of mutation.addedNodes) { if (el.classList?.contains(APPEND_DECODE_BUTTON_TARGET_CLASS)) { addDecodeButton(el); } } } else if (mutation.type === 'attributes') { if (!mutation.oldValue?.match(new RegExp(`(?:^|\\s)${escapeRegExpOutsideCharacterClass(APPEND_DECODE_BUTTON_TARGET_CLASS)}(?:\\s|$)`, 'u')) && mutation.target.classList?.contains(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); } })();