您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Base64编解码工具 for Discourse论坛
当前为
- // ==UserScript==
- // @name Discourse Base64 Helper
- // @namespace http://tampermonkey.net/
- // @version 1.2
- // @description Base64编解码工具 for Discourse论坛
- // @author Xavier
- // @match *://linux.do/*
- // @match *://clochat.com/*
- // @grant GM_notification
- // @grant GM_setClipboard
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // @run-at document-idle
- // ==/UserScript==
- (function() {
- 'use strict';
- // 样式注入
- GM_addStyle(`
- .decoded-text {
- cursor: pointer;
- transition: all 0.2s;
- padding: 1px 3px;
- border-radius: 3px;
- background-color: #fff3cd !important;
- color: #664d03 !important;
- }
- .decoded-text:hover {
- background-color: #ffe69c !important;
- }
- @media (prefers-color-scheme: dark) {
- .decoded-text {
- background-color: #332100 !important;
- color: #ffd54f !important;
- }
- .decoded-text:hover {
- background-color: #664d03 !important;
- }
- }
- .menu-item[data-mode="restore"] {
- background: rgba(0, 123, 255, 0.1) !important;
- }
- `);
- // 初始化检测
- if (document.getElementById('base64-helper-root')) return;
- const container = document.createElement('div');
- container.id = 'base64-helper-root';
- document.body.append(container);
- const shadowRoot = container.attachShadow({ mode: 'open' });
- // Shadow DOM样式
- const style = document.createElement('style');
- style.textContent = `
- :host {
- all: initial !important;
- position: fixed !important;
- z-index: 2147483647 !important;
- pointer-events: none !important;
- }
- .base64-helper {
- position: fixed;
- z-index: 2147483647;
- cursor: move;
- font-family: system-ui, -apple-system, sans-serif;
- opacity: 0.5;
- transition: opacity 0.3s ease, transform 0.2s;
- pointer-events: auto !important;
- will-change: transform;
- }
- .base64-helper:hover {
- opacity: 1 !important;
- }
- .main-btn {
- background: #ffffff;
- color: #000000 !important;
- padding: 8px 16px;
- border-radius: 6px;
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
- font-weight: 500;
- user-select: none;
- transition: all 0.2s;
- font-size: 14px;
- cursor: pointer;
- border: none !important;
- pointer-events: auto !important;
- }
- .menu {
- position: absolute;
- bottom: calc(100% + 5px);
- right: 0;
- background: #ffffff;
- border-radius: 6px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- display: none;
- min-width: auto !important;
- width: max-content !important;
- pointer-events: auto !important;
- overflow: hidden;
- }
- .menu-item {
- padding: 8px 12px !important;
- color: #333 !important;
- transition: all 0.2s;
- font-size: 13px;
- cursor: pointer;
- position: relative;
- border-radius: 0 !important;
- isolation: isolate;
- white-space: nowrap !important;
- }
- .menu-item:hover::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: currentColor;
- opacity: 0.1;
- z-index: -1;
- }
- .menu-item:first-child:hover::before {
- border-radius: 6px 6px 0 0;
- }
- .menu-item:last-child:hover::before {
- border-radius: 0 0 6px 6px;
- }
- @media (prefers-color-scheme: dark) {
- .main-btn {
- background: #2d2d2d;
- color: #fff !important;
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
- }
- .menu {
- background: #1a1a1a;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
- }
- .menu-item {
- color: #e0e0e0 !important;
- }
- .menu-item:hover::before {
- opacity: 0.08;
- }
- }
- @keyframes slideIn {
- from { top: -50px; opacity: 0; }
- to { top: 20px; opacity: 1; }
- }
- @keyframes fadeOut {
- from { opacity: 1; }
- to { opacity: 0; }
- }
- `;
- shadowRoot.appendChild(style);
- // 界面元素
- const uiContainer = document.createElement('div');
- uiContainer.className = 'base64-helper';
- const mainBtn = document.createElement('button');
- mainBtn.className = 'main-btn';
- mainBtn.textContent = 'Base64';
- const menu = document.createElement('div');
- menu.className = 'menu';
- const decodeBtn = document.createElement('div');
- decodeBtn.className = 'menu-item';
- decodeBtn.textContent = '解析本页Base64';
- decodeBtn.dataset.mode = 'decode';
- const encodeBtn = document.createElement('div');
- encodeBtn.className = 'menu-item';
- encodeBtn.textContent = '文本转Base64';
- menu.append(decodeBtn, encodeBtn);
- uiContainer.append(mainBtn, menu);
- shadowRoot.appendChild(uiContainer);
- // 核心功能
- let menuVisible = false;
- let isDragging = false;
- let startX, startY, initialX, initialY;
- const originalContents = new Map();
- // 位置管理
- const positionManager = {
- get: () => {
- const saved = GM_getValue('btnPosition');
- if (!saved) return null;
- const maxX = window.innerWidth - uiContainer.offsetWidth - 20;
- const maxY = window.innerHeight - uiContainer.offsetHeight - 20;
- return {
- x: Math.min(Math.max(saved.x, 20), maxX),
- y: Math.min(Math.max(saved.y, 20), maxY)
- };
- },
- set: (x, y) => {
- const pos = {
- x: Math.max(20, Math.min(x, window.innerWidth - uiContainer.offsetWidth - 20)),
- y: Math.max(20, Math.min(y, window.innerHeight - uiContainer.offsetHeight - 20))
- };
- GM_setValue('btnPosition', pos);
- return pos;
- }
- };
- // 初始化位置
- const initPosition = () => {
- const pos = positionManager.get() || {
- x: window.innerWidth - 120,
- y: window.innerHeight - 80
- };
- uiContainer.style.left = `${pos.x}px`;
- uiContainer.style.top = `${pos.y}px`;
- };
- initPosition();
- // 状态重置
- function resetState() {
- if (decodeBtn.dataset.mode === 'restore') {
- restoreOriginalContent();
- decodeBtn.textContent = '解析本页Base64';
- decodeBtn.dataset.mode = 'decode';
- originalContents.clear();
- }
- }
- // 事件监听
- mainBtn.addEventListener('click', function(e) {
- e.stopPropagation();
- menuVisible = !menuVisible;
- menu.style.display = menuVisible ? 'block' : 'none';
- });
- document.addEventListener('click', function(e) {
- if (menuVisible && !shadowRoot.contains(e.target)) {
- menuVisible = false;
- menu.style.display = 'none';
- }
- });
- // 拖拽功能
- mainBtn.addEventListener('mousedown', startDrag);
- document.addEventListener('mousemove', drag);
- document.addEventListener('mouseup', stopDrag);
- function startDrag(e) {
- isDragging = true;
- startX = e.clientX;
- startY = e.clientY;
- const rect = uiContainer.getBoundingClientRect();
- initialX = rect.left;
- initialY = rect.top;
- uiContainer.style.transition = 'none';
- }
- function drag(e) {
- if (!isDragging) return;
- const dx = e.clientX - startX;
- const dy = e.clientY - startY;
- const newX = initialX + dx;
- const newY = initialY + dy;
- const pos = positionManager.set(newX, newY);
- uiContainer.style.left = `${pos.x}px`;
- uiContainer.style.top = `${pos.y}px`;
- }
- function stopDrag() {
- isDragging = false;
- uiContainer.style.transition = 'opacity 0.3s ease';
- }
- // 窗口resize处理
- let resizeTimer;
- window.addEventListener('resize', () => {
- clearTimeout(resizeTimer);
- resizeTimer = setTimeout(() => {
- const pos = positionManager.get();
- if (pos) {
- uiContainer.style.left = `${pos.x}px`;
- uiContainer.style.top = `${pos.y}px`;
- }
- }, 100);
- });
- // 页面导航事件
- window.addEventListener('popstate', resetState);
- window.addEventListener('turbo:render', resetState);
- window.addEventListener('discourse:before-auto-refresh', () => {
- GM_setValue('btnPosition', positionManager.get());
- resetState();
- });
- // 解析功能
- decodeBtn.addEventListener('click', function() {
- if (this.dataset.mode === 'restore') {
- restoreOriginalContent();
- this.textContent = '解析本页Base64';
- this.dataset.mode = 'decode';
- showNotification('已恢复原始内容', 'success');
- menu.style.display = 'none';
- return;
- }
- originalContents.clear();
- let hasValidBase64 = false;
- try {
- document.querySelectorAll('.cooked, .post-body').forEach(element => {
- const regex = /(?<!\w)([A-Za-z0-9+/]{6,}?={0,2})(?!\w)/g;
- let newHtml = element.innerHTML;
- let modified = false;
- Array.from(newHtml.matchAll(regex)).reverse().forEach(match => {
- const original = match[0];
- if (!validateBase64(original)) return;
- try {
- const decoded = decodeBase64(original);
- originalContents.set(element, element.innerHTML);
- newHtml = newHtml.substring(0, match.index) +
- `<span class="decoded-text">${decoded}</span>` +
- newHtml.substring(match.index + original.length);
- hasValidBase64 = modified = true;
- } catch(e) {}
- });
- if (modified) element.innerHTML = newHtml;
- });
- if (!hasValidBase64) {
- showNotification('本页未发现有效Base64内容', 'info');
- originalContents.clear();
- return;
- }
- document.querySelectorAll('.decoded-text').forEach(el => {
- el.addEventListener('click', copyToClipboard);
- });
- decodeBtn.textContent = '恢复本页Base64';
- decodeBtn.dataset.mode = 'restore';
- showNotification('解析完成', 'success');
- } catch (e) {
- showNotification('解析失败: ' + e.message, 'error');
- originalContents.clear();
- }
- menuVisible = false;
- menu.style.display = 'none';
- });
- // 编码功能
- encodeBtn.addEventListener('click', function() {
- const text = prompt('请输入要编码的文本:');
- if (text === null) return;
- try {
- const encoded = encodeBase64(text);
- GM_setClipboard(encoded);
- showNotification('Base64已复制', 'success');
- } catch (e) {
- showNotification('编码失败: ' + e.message, 'error');
- }
- menu.style.display = 'none';
- });
- // 改进的校验函数
- function validateBase64(str) {
- // 基础校验
- const validLength = str.length % 4 === 0;
- const validChars = /^[A-Za-z0-9+/]+={0,2}$/.test(str);
- const validPadding = !(str.includes('=') && !/==?$/.test(str));
- if (!validLength || !validChars || !validPadding) return false;
- // 移除填充后的校验
- const baseStr = str.replace(/=+$/, '');
- if (baseStr.length < 6) return false;
- const hasSpecialChar = /[+/0-9]/.test(baseStr);
- return hasSpecialChar;
- }
- // 工具函数
- function decodeBase64(str) {
- return decodeURIComponent(escape(atob(str)));
- }
- function encodeBase64(str) {
- return btoa(unescape(encodeURIComponent(str)));
- }
- function restoreOriginalContent() {
- originalContents.forEach((html, element) => {
- element.innerHTML = html;
- });
- originalContents.clear();
- }
- function copyToClipboard(e) {
- GM_setClipboard(e.target.innerText);
- showNotification('内容已复制', 'success');
- e.stopPropagation();
- }
- // 通知系统
- function showNotification(text, type) {
- const notification = document.createElement('div');
- notification.style.cssText = `
- position: fixed;
- top: 20px;
- left: 50%;
- transform: translateX(-50%);
- padding: 12px 24px;
- border-radius: 6px;
- background: ${type === 'success' ? '#4CAF50' :
- type === 'error' ? '#f44336' : '#2196F3'};
- color: white;
- z-index: 2147483647;
- animation: slideIn 0.3s forwards, fadeOut 0.3s 2s forwards;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- font-family: system-ui, -apple-system, sans-serif;
- pointer-events: none;
- `;
- notification.textContent = text;
- document.body.appendChild(notification);
- setTimeout(() => notification.remove(), 2300);
- }
- // 防冲突处理
- if (window.hasBase64Helper) return;
- window.hasBase64Helper = true;
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址