Last active
October 21, 2025 17:27
-
-
Save zhuziyi1989/c728adf9c1d569e74362ca00818fe979 to your computer and use it in GitHub Desktop.
Revisions
-
zhuziyi1989 revised this gist
Oct 21, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,8 +10,8 @@ // @run-at document-idle // @supportURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @homepageURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @downloadURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/v2ex-base64-enhance.js // @updateURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/v2ex-base64-enhance.js // ==/UserScript== (function () { -
zhuziyi1989 revised this gist
Oct 17, 2025 . 1 changed file with 0 additions and 389 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,389 +0,0 @@ -
zhuziyi1989 revised this gist
Oct 17, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,8 +10,8 @@ // @run-at document-idle // @supportURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @homepageURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @downloadURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/81f269008d5d9e137a04746a9582c21d68a52a45/v2ex-base64-enhance.js // @updateURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/81f269008d5d9e137a04746a9582c21d68a52a45/v2ex-base64-enhance.js // ==/UserScript== (function () { -
zhuziyi1989 revised this gist
Oct 17, 2025 . 1 changed file with 389 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,389 @@ // ==UserScript== // @name V2EX Base64 Enhance 自动解析增强版 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 在 V2EX 帖子详情页自动发现 Base64 字符串并在其后追加解码结果;若解码为 URL 则渲染为可点击链接(在新标签打开)。仅处理帖子正文与回复内容,避免页面卡死。 // @author zhuziyi // @match https://v2ex.com/t/* // @match https://*.v2ex.com/t/* // @grant none // @run-at document-idle // @supportURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @homepageURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @downloadURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/81f269008d5d9e137a04746a9582c21d68a52a45/v2ex-base64-enhance.js // @updateURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/81f269008d5d9e137a04746a9582c21d68a52a45/v2ex-base64-enhance.js // ==/UserScript== (function () { "use strict"; const MIN_B64_LEN = 16; // 最小 Base64 长度阈值,按需调整 const SKIP_TAGS = new Set([ "SCRIPT", "STYLE", "CODE", "PRE", "TEXTAREA", "INPUT", "A", ]); const B64_RE = new RegExp( `([A-Za-z0-9_\\-+\\/]{${MIN_B64_LEN},}={0,2})`, "g" ); const APP_MARK = "data-b64-script"; // 我们插入节点时会打上这个标记 // 记录已处理过的文本节点,避免重复处理 const processedTextNodes = new WeakSet(); function normalizeBase64(b64) { let s = b64.replace(/\s+/g, ""); s = s.replace(/-/g, "+").replace(/_/g, "/"); const pad = s.length % 4; if (pad === 1) return null; if (pad !== 0) s += "=".repeat(4 - pad); return s; } function decodeBase64ToString(b64) { try { const norm = normalizeBase64(b64); if (!norm) return null; const binary = atob(norm); // binary 是 Latin1,需要转为 UTF-8 let percentEncoded = ""; for (let i = 0; i < binary.length; i++) { const code = binary.charCodeAt(i); percentEncoded += "%" + ("00" + code.toString(16)).slice(-2); } try { return decodeURIComponent(percentEncoded); } catch (e) { // 退回为原 binary(可能是 Latin1 文本) return binary; } } catch (e) { return null; } } function looksReadable(s) { if (!s || s.length < 3) return false; if (/[\u4e00-\u9fff]/.test(s)) return true; // 含中文 if (/(https?:\/\/|www\.)/i.test(s)) return true; // 至少有 60% 的可打印 ASCII 字符 const printableCount = (s.match(/[\x20-\x7E]/g) || []).length; if (printableCount / s.length >= 0.6) return true; return false; } function createAppendNode(decodedText) { const wrapper = document.createElement("span"); wrapper.setAttribute(APP_MARK, "1"); wrapper.style.marginLeft = "6px"; wrapper.style.fontSize = "0.95em"; wrapper.style.verticalAlign = "baseline"; const trimmed = decodedText.trim(); const isURL = /^https?:\/\//i.test(trimmed); if (isURL) { // 全角括号包裹的可点链接 const left = document.createTextNode("("); const right = document.createTextNode(")"); const a = document.createElement("a"); a.href = trimmed; a.target = "_blank"; a.rel = "noopener noreferrer"; a.textContent = trimmed; // 不强制设置颜色,保持与站点主题兼容;若需自定义可修改以下两行 a.style.textDecoration = "underline"; a.style.cursor = "pointer"; wrapper.appendChild(left); wrapper.appendChild(a); wrapper.appendChild(right); } else { wrapper.style.color = "#888"; wrapper.textContent = `(${trimmed})`; } return wrapper; } function processTextNode(textNode) { if (!textNode || !textNode.nodeValue) return; if (processedTextNodes.has(textNode)) return; const parent = textNode.parentNode; if (!parent || SKIP_TAGS.has(parent.tagName)) return; // 如果父元素或祖先已是我们插入内容的标记,则跳过(避免在我们的 span 内再次处理) if (parent.closest && parent.closest("[" + APP_MARK + "]")) return; const text = textNode.nodeValue; B64_RE.lastIndex = 0; let match; let lastIndex = 0; const frag = document.createDocumentFragment(); let anyMatch = false; while ((match = B64_RE.exec(text)) !== null) { const b64 = match[1]; // 把前面的纯文本加入 const before = text.slice(lastIndex, match.index); if (before) frag.appendChild(document.createTextNode(before)); // 原 Base64 文本(保持原样) const origNode = document.createTextNode(b64); frag.appendChild(origNode); // 尝试解码并判断是否可读 const decoded = decodeBase64ToString(b64); if (decoded && looksReadable(decoded)) { const appendNode = createAppendNode(decoded); frag.appendChild(appendNode); } lastIndex = match.index + b64.length; anyMatch = true; } if (!anyMatch) { // 标记为已检查过(避免下次又检查同一节点) processedTextNodes.add(textNode); return; } // 尾部文本 const tail = text.slice(lastIndex); if (tail) frag.appendChild(document.createTextNode(tail)); // 替换原 text node(注意:替换后原节点不再存在) try { parent.replaceChild(frag, textNode); } catch (e) { // 如果替换失败(极少见),仍将该节点标记以避免重复 processedTextNodes.add(textNode); return; } // 无需标记新创建的文本节点;但为了避免再次检查同一位置,我们可以标记父元素(轻量) // 在父元素上打一个短期属性,表明它已经被扫描(不会影响页面) try { parent.setAttribute("data-b64-scanned", Date.now().toString()); } catch (e) { // 忽略不可设置属性的情况(例如某些 SVG 节点) } } function walkAndProcess(root) { if (!root) return; const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (!node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; const p = node.parentNode; if (!p) return NodeFilter.FILTER_REJECT; if (SKIP_TAGS.has(p.tagName)) return NodeFilter.FILTER_REJECT; if (p.closest && p.closest("[" + APP_MARK + "]")) return NodeFilter.FILTER_REJECT; if (processedTextNodes.has(node)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; }, }, false ); const nodes = []; let n; while ((n = walker.nextNode())) nodes.push(n); for (const tn of nodes) processTextNode(tn); } // 找到需要处理的区域(帖子正文与回复) function findTargets() { const nodes = []; const main = document.querySelector(".topic_content"); if (main) nodes.push(main); // 回复区常见类名 document .querySelectorAll(".reply_content, .message, .topic .cell .reply_content") .forEach((n) => { if (n) nodes.push(n); }); return nodes; } function initScan() { const targets = findTargets(); if (targets.length === 0) return; for (const t of targets) walkAndProcess(t); } // 监听帖子区域的变化(只对目标区域设置 observer) const observers = []; function initObservers() { const targets = findTargets(); if (targets.length === 0) return; for (const t of targets) { const mo = new MutationObserver((mutations) => { for (const m of mutations) { // 跳过我们自己插入的节点(根节点带标记) if (m.addedNodes && m.addedNodes.length) { for (const node of m.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { // 如果新增节点是我们自己创建的标记子树,跳过 if (node.hasAttribute && node.hasAttribute(APP_MARK)) continue; // 如果新增元素内部含有我们的标记,也跳过 if ( node.querySelector && node.querySelector("[" + APP_MARK + "]") ) continue; // 对新增元素内部做扫描 walkAndProcess(node); } else if (node.nodeType === Node.TEXT_NODE) { // 直接处理文本节点 processTextNode(node); } } } if (m.type === "characterData" && m.target) { processTextNode(m.target); } } }); mo.observe(t, { childList: true, subtree: true, characterData: true }); observers.push(mo); } } // 初次运行 initScan(); initObservers(); // 暴露一个手动触发的函数(控制台可用),方便调试/手动重新扫描 window.__v2ex_b64_rescan = function () { initScan(); console.log("v2ex-base64: manual rescan done"); }; console.log("v2ex-base64 (post-only) loaded"); // ============ 新增:Base64 加密与解密 选中文本 ============ // Base64 编码 function encodeBase64(str) { try { return btoa(unescape(encodeURIComponent(str))); } catch (e) { return str; } } // Base64 解码 function decodeBase64(str) { try { return decodeURIComponent(escape(atob(str))); } catch (e) { return str; } } // ============ 新:自动隐藏按钮 ============ function updateButtonVisibility() { const textarea = document.querySelector("#reply_content"); const encodeBtn = document.getElementById("base64_encode_btn"); const decodeBtn = document.getElementById("base64_decode_btn"); if (!textarea || !encodeBtn || !decodeBtn) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; // 无选区 → 按钮隐藏,但保留占位 if (start === end) { encodeBtn.style.visibility = "hidden"; decodeBtn.style.visibility = "hidden"; } else { encodeBtn.style.visibility = "visible"; decodeBtn.style.visibility = "visible"; } } function addBase64Buttons() { const replyBtn = document.querySelector( 'input[type="submit"][value="回复"]' ); replyBtn.parentElement.style.justifyContent = "flex-start"; replyBtn.parentElement.style.flexDirection = "row-reverse"; const textarea = document.querySelector("#reply_content"); if (!replyBtn || !textarea) return; if (document.getElementById("base64_encode_btn")) return; // 按钮创建器 function createBtn(text, id) { const btn = document.createElement("button"); btn.id = id; btn.textContent = text; btn.style.marginRight = "8px"; btn.style.padding = "5px 8px"; btn.style.border = "1px solid #f5f5f5"; btn.style.background = "#f5f5f5"; btn.style.borderRadius = "5px"; btn.style.cursor = "pointer"; btn.style.fontSize = "12px"; btn.style.lineHeight = "1.4"; btn.style.color = "#9349ff"; btn.style.visibility = "hidden"; // 初始隐藏 return btn; } const encodeBtn = createBtn("🔐 Base64编码", "base64_encode_btn"); encodeBtn.addEventListener("click", (e) => { e.preventDefault(); const start = textarea.selectionStart; const end = textarea.selectionEnd; const txt = textarea.value.substring(start, end); textarea.value = textarea.value.substring(0, start) + encodeBase64(txt) + textarea.value.substring(end); textarea.selectionStart = textarea.selectionEnd = start + encodeBase64(txt).length; textarea.focus(); updateButtonVisibility(); }); const decodeBtn = createBtn("🔓 Base64解码", "base64_decode_btn"); decodeBtn.addEventListener("click", (e) => { e.preventDefault(); const start = textarea.selectionStart; const end = textarea.selectionEnd; const txt = textarea.value.substring(start, end); textarea.value = textarea.value.substring(0, start) + decodeBase64(txt) + textarea.value.substring(end); textarea.selectionStart = textarea.selectionEnd = start + decodeBase64(txt).length; textarea.focus(); updateButtonVisibility(); }); replyBtn.parentNode.insertBefore(encodeBtn, replyBtn.nextSibling); replyBtn.parentNode.insertBefore(decodeBtn, encodeBtn.nextSibling); // 监听 selection 改变 → 控制可见性 ["select", "keyup", "mouseup"].forEach((evt) => { textarea.addEventListener(evt, updateButtonVisibility); }); } setTimeout(addBase64Buttons, 1500); })(); -
zhuziyi1989 revised this gist
Oct 17, 2025 . 1 changed file with 35 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,35 @@ # 评论区回复测试 --- @wangdada #138 怎么方便的解密 Base64 啊?有没有插件推荐一个。 试试我分享的油猴脚本[在 V2EX 帖子详情页自动发现 Base64 字符串并在其后追加解码结果](https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979),测试效果如下。 ① 、未使用插件时显示 <img width="781" height="413" alt="1" src="https://gist.github.com/user-attachments/assets/9795397a-c0ed-4f72-8903-ea3700d00327" /> `[未使用插件时显示](https://i.imgur.com/Joi0n9p.png)` ② 、使用插件后显示为 <img width="784" height="458" alt="2" src="https://gist.github.com/user-attachments/assets/3c9ef4b3-9284-49d3-af1e-740f38e78254" /> `[使用插件后显示为](https://i.imgur.com/J5sqJNo.png)` ③ 、在评论区选中输入的文本可直接解密、加密替换  `[在评论区选中输入的文本可直接解密、加密替换-GIF演示](https://i.imgur.com/qwgmHdN.gif)` 以下为测试数据: 国内打开 aHR0cHM6Ly8xcy5iaWdtZW9rLm1lL3VzZXIjL3JlZ2lzdGVyP2NvZGU9SW92V09wS0Y= 科学上网 aHR0cHM6Ly93d3cuYmlnbWUucHJvL3VzZXIjL3JlZ2lzdGVyP2NvZGU9a2JYRjNrSmU= 我是一个测试文本 5oiR5piv5LiA5Liq5rWL6K+V5paH5pys 我是一个测试邮箱 cm9vdEBnbWFpbC5jb20= -
zhuziyi1989 renamed this gist
Oct 17, 2025 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
zhuziyi1989 revised this gist
Oct 17, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ // ==UserScript== // @name V2EX Base64 Enhance 自动解析增强版 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 在 V2EX 帖子详情页自动发现 Base64 字符串并在其后追加解码结果;若解码为 URL 则渲染为可点击链接(在新标签打开)。仅处理帖子正文与回复内容,避免页面卡死。 -
zhuziyi1989 revised this gist
Oct 17, 2025 . 1 changed file with 389 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +1,389 @@ // ==UserScript== // @name V2EX Base64 自动解析增强版 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 在 V2EX 帖子详情页自动发现 Base64 字符串并在其后追加解码结果;若解码为 URL 则渲染为可点击链接(在新标签打开)。仅处理帖子正文与回复内容,避免页面卡死。 // @author zhuziyi // @match https://v2ex.com/t/* // @match https://*.v2ex.com/t/* // @grant none // @run-at document-idle // @supportURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @homepageURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979 // @downloadURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/97adee1329d4de9f6ef21eeb500006dea9998508/V2EX%2520Base64%2520Enhance.js // @updateURL https://gist.github.com/zhuziyi1989/c728adf9c1d569e74362ca00818fe979/raw/97adee1329d4de9f6ef21eeb500006dea9998508/V2EX%2520Base64%2520Enhance.js // ==/UserScript== (function () { "use strict"; const MIN_B64_LEN = 16; // 最小 Base64 长度阈值,按需调整 const SKIP_TAGS = new Set([ "SCRIPT", "STYLE", "CODE", "PRE", "TEXTAREA", "INPUT", "A", ]); const B64_RE = new RegExp( `([A-Za-z0-9_\\-+\\/]{${MIN_B64_LEN},}={0,2})`, "g" ); const APP_MARK = "data-b64-script"; // 我们插入节点时会打上这个标记 // 记录已处理过的文本节点,避免重复处理 const processedTextNodes = new WeakSet(); function normalizeBase64(b64) { let s = b64.replace(/\s+/g, ""); s = s.replace(/-/g, "+").replace(/_/g, "/"); const pad = s.length % 4; if (pad === 1) return null; if (pad !== 0) s += "=".repeat(4 - pad); return s; } function decodeBase64ToString(b64) { try { const norm = normalizeBase64(b64); if (!norm) return null; const binary = atob(norm); // binary 是 Latin1,需要转为 UTF-8 let percentEncoded = ""; for (let i = 0; i < binary.length; i++) { const code = binary.charCodeAt(i); percentEncoded += "%" + ("00" + code.toString(16)).slice(-2); } try { return decodeURIComponent(percentEncoded); } catch (e) { // 退回为原 binary(可能是 Latin1 文本) return binary; } } catch (e) { return null; } } function looksReadable(s) { if (!s || s.length < 3) return false; if (/[\u4e00-\u9fff]/.test(s)) return true; // 含中文 if (/(https?:\/\/|www\.)/i.test(s)) return true; // 至少有 60% 的可打印 ASCII 字符 const printableCount = (s.match(/[\x20-\x7E]/g) || []).length; if (printableCount / s.length >= 0.6) return true; return false; } function createAppendNode(decodedText) { const wrapper = document.createElement("span"); wrapper.setAttribute(APP_MARK, "1"); wrapper.style.marginLeft = "6px"; wrapper.style.fontSize = "0.95em"; wrapper.style.verticalAlign = "baseline"; const trimmed = decodedText.trim(); const isURL = /^https?:\/\//i.test(trimmed); if (isURL) { // 全角括号包裹的可点链接 const left = document.createTextNode("("); const right = document.createTextNode(")"); const a = document.createElement("a"); a.href = trimmed; a.target = "_blank"; a.rel = "noopener noreferrer"; a.textContent = trimmed; // 不强制设置颜色,保持与站点主题兼容;若需自定义可修改以下两行 a.style.textDecoration = "underline"; a.style.cursor = "pointer"; wrapper.appendChild(left); wrapper.appendChild(a); wrapper.appendChild(right); } else { wrapper.style.color = "#888"; wrapper.textContent = `(${trimmed})`; } return wrapper; } function processTextNode(textNode) { if (!textNode || !textNode.nodeValue) return; if (processedTextNodes.has(textNode)) return; const parent = textNode.parentNode; if (!parent || SKIP_TAGS.has(parent.tagName)) return; // 如果父元素或祖先已是我们插入内容的标记,则跳过(避免在我们的 span 内再次处理) if (parent.closest && parent.closest("[" + APP_MARK + "]")) return; const text = textNode.nodeValue; B64_RE.lastIndex = 0; let match; let lastIndex = 0; const frag = document.createDocumentFragment(); let anyMatch = false; while ((match = B64_RE.exec(text)) !== null) { const b64 = match[1]; // 把前面的纯文本加入 const before = text.slice(lastIndex, match.index); if (before) frag.appendChild(document.createTextNode(before)); // 原 Base64 文本(保持原样) const origNode = document.createTextNode(b64); frag.appendChild(origNode); // 尝试解码并判断是否可读 const decoded = decodeBase64ToString(b64); if (decoded && looksReadable(decoded)) { const appendNode = createAppendNode(decoded); frag.appendChild(appendNode); } lastIndex = match.index + b64.length; anyMatch = true; } if (!anyMatch) { // 标记为已检查过(避免下次又检查同一节点) processedTextNodes.add(textNode); return; } // 尾部文本 const tail = text.slice(lastIndex); if (tail) frag.appendChild(document.createTextNode(tail)); // 替换原 text node(注意:替换后原节点不再存在) try { parent.replaceChild(frag, textNode); } catch (e) { // 如果替换失败(极少见),仍将该节点标记以避免重复 processedTextNodes.add(textNode); return; } // 无需标记新创建的文本节点;但为了避免再次检查同一位置,我们可以标记父元素(轻量) // 在父元素上打一个短期属性,表明它已经被扫描(不会影响页面) try { parent.setAttribute("data-b64-scanned", Date.now().toString()); } catch (e) { // 忽略不可设置属性的情况(例如某些 SVG 节点) } } function walkAndProcess(root) { if (!root) return; const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (!node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; const p = node.parentNode; if (!p) return NodeFilter.FILTER_REJECT; if (SKIP_TAGS.has(p.tagName)) return NodeFilter.FILTER_REJECT; if (p.closest && p.closest("[" + APP_MARK + "]")) return NodeFilter.FILTER_REJECT; if (processedTextNodes.has(node)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; }, }, false ); const nodes = []; let n; while ((n = walker.nextNode())) nodes.push(n); for (const tn of nodes) processTextNode(tn); } // 找到需要处理的区域(帖子正文与回复) function findTargets() { const nodes = []; const main = document.querySelector(".topic_content"); if (main) nodes.push(main); // 回复区常见类名 document .querySelectorAll(".reply_content, .message, .topic .cell .reply_content") .forEach((n) => { if (n) nodes.push(n); }); return nodes; } function initScan() { const targets = findTargets(); if (targets.length === 0) return; for (const t of targets) walkAndProcess(t); } // 监听帖子区域的变化(只对目标区域设置 observer) const observers = []; function initObservers() { const targets = findTargets(); if (targets.length === 0) return; for (const t of targets) { const mo = new MutationObserver((mutations) => { for (const m of mutations) { // 跳过我们自己插入的节点(根节点带标记) if (m.addedNodes && m.addedNodes.length) { for (const node of m.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { // 如果新增节点是我们自己创建的标记子树,跳过 if (node.hasAttribute && node.hasAttribute(APP_MARK)) continue; // 如果新增元素内部含有我们的标记,也跳过 if ( node.querySelector && node.querySelector("[" + APP_MARK + "]") ) continue; // 对新增元素内部做扫描 walkAndProcess(node); } else if (node.nodeType === Node.TEXT_NODE) { // 直接处理文本节点 processTextNode(node); } } } if (m.type === "characterData" && m.target) { processTextNode(m.target); } } }); mo.observe(t, { childList: true, subtree: true, characterData: true }); observers.push(mo); } } // 初次运行 initScan(); initObservers(); // 暴露一个手动触发的函数(控制台可用),方便调试/手动重新扫描 window.__v2ex_b64_rescan = function () { initScan(); console.log("v2ex-base64: manual rescan done"); }; console.log("v2ex-base64 (post-only) loaded"); // ============ 新增:Base64 加密与解密 选中文本 ============ // Base64 编码 function encodeBase64(str) { try { return btoa(unescape(encodeURIComponent(str))); } catch (e) { return str; } } // Base64 解码 function decodeBase64(str) { try { return decodeURIComponent(escape(atob(str))); } catch (e) { return str; } } // ============ 新:自动隐藏按钮 ============ function updateButtonVisibility() { const textarea = document.querySelector("#reply_content"); const encodeBtn = document.getElementById("base64_encode_btn"); const decodeBtn = document.getElementById("base64_decode_btn"); if (!textarea || !encodeBtn || !decodeBtn) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; // 无选区 → 按钮隐藏,但保留占位 if (start === end) { encodeBtn.style.visibility = "hidden"; decodeBtn.style.visibility = "hidden"; } else { encodeBtn.style.visibility = "visible"; decodeBtn.style.visibility = "visible"; } } function addBase64Buttons() { const replyBtn = document.querySelector( 'input[type="submit"][value="回复"]' ); replyBtn.parentElement.style.justifyContent = "flex-start"; replyBtn.parentElement.style.flexDirection = "row-reverse"; const textarea = document.querySelector("#reply_content"); if (!replyBtn || !textarea) return; if (document.getElementById("base64_encode_btn")) return; // 按钮创建器 function createBtn(text, id) { const btn = document.createElement("button"); btn.id = id; btn.textContent = text; btn.style.marginRight = "8px"; btn.style.padding = "5px 8px"; btn.style.border = "1px solid #f5f5f5"; btn.style.background = "#f5f5f5"; btn.style.borderRadius = "5px"; btn.style.cursor = "pointer"; btn.style.fontSize = "12px"; btn.style.lineHeight = "1.4"; btn.style.color = "#9349ff"; btn.style.visibility = "hidden"; // 初始隐藏 return btn; } const encodeBtn = createBtn("🔐 Base64编码", "base64_encode_btn"); encodeBtn.addEventListener("click", (e) => { e.preventDefault(); const start = textarea.selectionStart; const end = textarea.selectionEnd; const txt = textarea.value.substring(start, end); textarea.value = textarea.value.substring(0, start) + encodeBase64(txt) + textarea.value.substring(end); textarea.selectionStart = textarea.selectionEnd = start + encodeBase64(txt).length; textarea.focus(); updateButtonVisibility(); }); const decodeBtn = createBtn("🔓 Base64解码", "base64_decode_btn"); decodeBtn.addEventListener("click", (e) => { e.preventDefault(); const start = textarea.selectionStart; const end = textarea.selectionEnd; const txt = textarea.value.substring(start, end); textarea.value = textarea.value.substring(0, start) + decodeBase64(txt) + textarea.value.substring(end); textarea.selectionStart = textarea.selectionEnd = start + decodeBase64(txt).length; textarea.focus(); updateButtonVisibility(); }); replyBtn.parentNode.insertBefore(encodeBtn, replyBtn.nextSibling); replyBtn.parentNode.insertBefore(decodeBtn, encodeBtn.nextSibling); // 监听 selection 改变 → 控制可见性 ["select", "keyup", "mouseup"].forEach((evt) => { textarea.addEventListener(evt, updateButtonVisibility); }); } setTimeout(addBase64Buttons, 1500); })(); -
zhuziyi1989 created this gist
Oct 17, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@