【油猴脚本】Base64解码 -选中一键解码,彻底告别切屏复制粘贴

少林功夫好 2026-06-08 10:10 1

佬友分享 AI Key 时,会进行 Base64 编码, 用Key,都得复制→各种操作解码→再复制,切来切去。周末动手搓了个油猴脚本。

完全寄生在论坛自带的交互上:



  • 随便选一段 Base64 密文

  • 论坛会自动弹出那个带“引用”“分享”的小浮动菜单

  • 菜单里多了一个 “^-^ Base64解码” 按钮,点它

  • 解码结果会直接以代码块的形式插入到选区下方,还带 复制 和 关闭 按钮


5Luj56CB5Y2D5LiH6KGM77yM5pG46bG856ys5LiA5p2h77yb5YiH5bGP5LiN6KeE6IyD77yM6ICB5p2/5Lik6KGM55uu44CC







脚本代码

// ==UserScript==
// @name Linux.do Base64解码
// @namespace https://linux.do/
// @version 3.0
// @description 选中Base64文本后,在弹出菜单点击解码,原文下方插入可复制的代码块
// @author hum&&deepseek
// @match https://linux.do/t/topic/*
// @grant none
// ==/UserScript==

(function () {
'use strict';

// 解码 Base64,支持 UTF-8 多字节字符
function decodeBase64ToUnicode(str) {
try {
const binary = atob(str.trim());
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return new TextDecoder('utf-8').decode(bytes);
} catch {
return null;
}
}

// 在选区末尾插入带复制和关闭按钮的代码块
function insertDecodedBlock(decodedText, range) {
// 外层容器,模拟论坛代码块样式
const wrapper = document.createElement('div');
wrapper.className = 'md-codeblock';
Object.assign(wrapper.style, {
position: 'relative',
marginTop: '8px',
marginBottom: '8px'
});

const pre = document.createElement('pre');
const code = document.createElement('code');
code.textContent = decodedText;
pre.appendChild(code);
wrapper.appendChild(pre);

// 按钮工具栏
const toolbar = document.createElement('div');
toolbar.className = 'codeblock-button-wrapper';
Object.assign(toolbar.style, {
position: 'absolute',
top: '4px',
right: '4px',
display: 'flex',
gap: '4px'
});

// --- 复制按钮(完全自己控制)---
const copyBtn = document.createElement('button');
copyBtn.className = 'btn nohighlight btn-flat';
copyBtn.setAttribute('aria-label', '复制解码文本');
copyBtn.innerHTML = '<svg class="fa d-icon d-icon-copy svg-icon fa-width-auto svg-string" width="1em" height="1em" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#copy"></use></svg>';

// 点击复制,并显示“已复制!”
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
navigator.clipboard.writeText(decodedText).then(() => {
copyBtn.innerHTML = '已复制!';
copyBtn.style.pointerEvents = 'none';
setTimeout(() => {
copyBtn.innerHTML = '<svg class="fa d-icon d-icon-copy svg-icon fa-width-auto svg-string" width="1em" height="1em" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#copy"></use></svg>';
copyBtn.style.pointerEvents = '';
}, 2000);
}).catch(() => alert('复制失败,请手动复制'));
});

// --- 关闭按钮 ---
const closeBtn = document.createElement('button');
closeBtn.className = 'btn nohighlight btn-flat';
closeBtn.innerHTML = '✖';
closeBtn.title = '关闭';
closeBtn.addEventListener('click', () => wrapper.remove());

toolbar.appendChild(copyBtn);
toolbar.appendChild(closeBtn);
wrapper.appendChild(toolbar);

// 插入到选区末尾
range.collapse(false);
range.insertNode(wrapper);
range.setStartAfter(wrapper);
range.collapse(true);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}

// 在浮动菜单中添加解码按钮
function addDecodeButton(menu) {
if (menu.dataset.base64DecodeAdded) return;
menu.dataset.base64DecodeAdded = 'true';

// 优先查找 .quote-button 内的 .buttons,兼容多种结构
const btns = menu.querySelector('.quote-button .buttons') || menu.querySelector('.buttons');
if (!btns) return;

const decodeBtn = document.createElement('button');
decodeBtn.className = 'btn btn-icon-text btn-flat';
decodeBtn.title = 'Base64 解码';
decodeBtn.type = 'button';
decodeBtn.innerHTML = '<span class="fa d-icon">🔓</span><span class="d-button-label">Base64解码</span>';

decodeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();

const selection = window.getSelection();
if (!selection.rangeCount || !selection.toString().trim()) {
alert('请先选中一段 Base64 文本');
return;
}

const raw = selection.toString().trim();
const decoded = decodeBase64ToUnicode(raw);
if (decoded === null) {
alert('解码失败,请确认内容是有效的 Base64 编码');
return;
}

insertDecodedBlock(decoded, selection.getRangeAt(0));

// 点击一下 body 关闭浮动菜单
document.body.click();
});

btns.appendChild(decodeBtn);
}

// 监听 #d-menu-portals 内菜单的创建
function startObserver() {
const portal = document.getElementById('d-menu-portals');
if (!portal) {
setTimeout(startObserver, 500);
return;
}

// 处理已存在的菜单
portal.querySelectorAll('div.fk-d-menu').forEach(addDecodeButton);

const observer = new MutationObserver(mutations => {
for (const mut of mutations) {
for (const node of mut.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches && node.matches('div.fk-d-menu')) {
addDecodeButton(node);
}
if (node.querySelectorAll) {
node.querySelectorAll('div.fk-d-menu').forEach(addDecodeButton);
}
}
}
}
});

observer.observe(portal, { childList: true, subtree: true });
}

if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', startObserver);
} else {
startObserver();
}
})();

最新回复 (5)
  • 喵帕丝 06-08 10:16
    1

    确实很方便了,感谢佬的脚本,已经安装了

    “代码千万行,摸鱼第一条;切屏不规范,老板两行目。”

  • dandexing 06-08 10:34
    3

    感谢佬的分享,真的很好用,不用到处切来切去。

  • liufu 06-08 10:36
    4

    已经用上了,很方便,感谢大佬分享

  • Jane Frisk 06-09 10:26
    5

    佬,我的油猴在L站会提示这个问题,请问这该如何解决呢?

  • 冷风 06-09 10:32
    6

    佬,你能用吗,我这个显示脚本启用中但用不了

* 帖子来源Linux.do
返回