看到很多佬们都分享了自己的apikey,但很多都要多过一步base64 decode,感觉有点麻烦,就让ai跑了一个油猴一键解码并复制
// ==UserScript==
// @name linux.do 选中文本 Base64 解码
// @namespace https://linux.do/
// @version 1.0.0
// @description 在 linux.do 选中文本弹出的引用按钮栏中添加 Base64 解码按钮
// @author b1xcy
// @match https://linux.do/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
"use strict";
const BUTTON_CLASS = "btn btn-icon-text btn-flat base64-decode";
const BUTTON_MARKER = "data-base64-decode-btn";
function getSelectedText() {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return "";
return selection.toString().trim();
}
function normalizeBase64(input) {
let text = input
.replace(/\s+/g, "")
.replace(/-/g, "+")
.replace(/_/g, "/");
// 去掉常见包装,如 data:...;base64,xxxx
const dataUrlMatch = text.match(/base64,(.+)$/i);
if (dataUrlMatch) text = dataUrlMatch[1];
// 去掉可能的引号
text = text.replace(/^["']|["']$/g, "");
// 补齐 padding
const pad = text.length % 4;
if (pad) text += "=".repeat(4 - pad);
return text;
}
function decodeBase64(input) {
const cleaned = normalizeBase64(input);
if (!cleaned) {
throw new Error("没有可解码的内容");
}
// atob 得到 binary string,再按 UTF-8 还原
const binary = atob(cleaned);
const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
try {
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
} catch (_) {
// 回退:直接返回 latin1 字符串
return binary;
}
}
async function copyText(text) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
return true;
}
} catch (_) {
// fall through
}
const ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.cssText = "position:fixed;left:-9999px;top:-9999px;";
document.body.appendChild(ta);
ta.select();
let ok = false;
try {
ok = document.execCommand("copy");
} catch (_) {
ok = false;
}
ta.remove();
return ok;
}
function showResult(decoded, copied) {
// 简单 toast,不依赖站点 UI
let toast = document.getElementById("ld-base64-decode-toast");
if (!toast) {
toast = document.createElement("div");
toast.id = "ld-base64-decode-toast";
toast.style.cssText = [
"position:fixed",
"z-index:999999",
"left:50%",
"bottom:24px",
"transform:translateX(-50%)",
"max-width:min(720px,90vw)",
"max-height:40vh",
"overflow:auto",
"padding:12px 14px",
"border-radius:10px",
"background:#1d1c1d",
"color:#fff",
"box-shadow:0 8px 24px rgba(0,0,0,.28)",
"font:13px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif",
"white-space:pre-wrap",
"word-break:break-word",
"display:none",
].join(";");
document.body.appendChild(toast);
}
const tip = copied ? "已复制到剪贴板" : "解码成功(复制失败,请手动复制)";
toast.textContent = `${tip}\n\n${decoded}`;
toast.style.display = "block";
clearTimeout(showResult._timer);
showResult._timer = setTimeout(() => {
toast.style.display = "none";
}, 5000);
}
function showError(message) {
let toast = document.getElementById("ld-base64-decode-toast");
if (!toast) {
// 复用 showResult 的创建逻辑
showResult("", false);
toast = document.getElementById("ld-base64-decode-toast");
}
toast.textContent = `Base64 解码失败:${message}`;
toast.style.display = "block";
clearTimeout(showResult._timer);
showResult._timer = setTimeout(() => {
toast.style.display = "none";
}, 3000);
}
async function onDecodeClick(event) {
event.preventDefault();
event.stopPropagation();
const selected = getSelectedText();
if (!selected) {
showError("请先选中一段文本");
return;
}
try {
const decoded = decodeBase64(selected);
const copied = await copyText(decoded);
showResult(decoded, copied);
} catch (err) {
showError(err && err.message ? err.message : "无效的 Base64 内容");
}
}
function createDecodeButton() {
const button = document.createElement("button");
button.className = BUTTON_CLASS;
button.type = "button";
button.title = "Base64 解码";
button.setAttribute(BUTTON_MARKER, "1");
button.innerHTML = [
'<svg class="fa d-icon d-icon-code svg-icon fa-width-auto svg-string" width="1em" height="1em" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">',
'<use href="#code"></use>',
"</svg>",
' <span class="d-button-label">Base64 解码</span>',
].join("");
button.addEventListener("click", onDecodeClick);
return button;
}
function injectButton(container) {
if (!(container instanceof HTMLElement)) return;
if (!container.classList.contains("buttons")) return;
// 只处理包含引用相关按钮的弹出栏,避免误伤其它 .buttons
if (
!container.querySelector(".insert-quote, .copy-quote, .quote-button, button")
) {
return;
}
if (container.querySelector(`[${BUTTON_MARKER}]`)) return;
const btn = createDecodeButton();
// 插到「复制引用」后面;没有就追加到末尾
const copyBtn = container.querySelector(".copy-quote");
if (copyBtn && copyBtn.parentNode === container) {
copyBtn.insertAdjacentElement("afterend", btn);
} else {
container.appendChild(btn);
}
}
function scan(root = document) {
root.querySelectorAll?.(".buttons").forEach(injectButton);
if (root instanceof HTMLElement && root.matches?.(".buttons")) {
injectButton(root);
}
}
// 初次扫描
scan(document);
// Discourse 是动态渲染,监听弹层出现
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLElement)) continue;
scan(node);
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
代码无任何外带操作,可以放心用