Base64 文本解码器

选中文本时自动检测并解码 Base64 编码的内容,支持一键复制解码结果

  1. // ==UserScript==
  2. // @name Base64 文本解码器
  3. // @name:en Base64 Text Decoder
  4. // @namespace https://github.com/eep
  5. // @version 1.0.2
  6. // @description 选中文本时自动检测并解码 Base64 编码的内容,支持一键复制解码结果
  7. // @description:en Automatically detects and decodes Base64 encoded text when selected, with one-click copy feature
  8. // @author EEP
  9. // @license MIT
  10. // @match *://*/*
  11. // @grant GM_addStyle
  12. // @run-at document-end
  13. // @supportURL https://github.com/eep/base64-decoder/issues
  14. // @homepageURL https://github.com/eep/base64-decoder
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. let floatingWindow = null;
  21. let decodedText = '';
  22.  
  23. // 创建悬浮窗口元素
  24. function createFloatingWindow() {
  25. if (floatingWindow) return floatingWindow;
  26.  
  27. floatingWindow = document.createElement('div');
  28. floatingWindow.style.cssText = `
  29. position: absolute;
  30. padding: 10px;
  31. background: rgba(0, 0, 0, 0.8);
  32. color: white;
  33. border-radius: 5px;
  34. font-size: 14px;
  35. z-index: 2147483647;
  36. display: none;
  37. cursor: pointer;
  38. user-select: none;
  39. pointer-events: auto;
  40. max-width: 80%;
  41. word-wrap: break-word;
  42. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  43. left: 0;
  44. top: 0;
  45. `;
  46. floatingWindow.textContent = '';
  47. document.body.appendChild(floatingWindow);
  48.  
  49. // 添加复制功能
  50. floatingWindow.addEventListener('click', async (e) => {
  51. e.stopPropagation();
  52. if (decodedText) {
  53. try {
  54. await navigator.clipboard.writeText(decodedText);
  55. const originalText = floatingWindow.textContent;
  56. floatingWindow.textContent = '复制成功!';
  57. setTimeout(() => {
  58. floatingWindow.textContent = originalText;
  59. }, 1000);
  60. } catch (err) {
  61. console.error('复制失败:', err);
  62. }
  63. }
  64. });
  65.  
  66. return floatingWindow;
  67. }
  68.  
  69. // 判断字符串是否为纯英文
  70. function isEnglishOnly(text) {
  71. // 先对文本进行trim处理
  72. text = text.trim();
  73. // 检查是否只包含合法的 base64 字符
  74. if (!/^[A-Za-z0-9+/=]+$/.test(text)) {
  75. return false;
  76. }
  77. // 如果包含空格,则认为是普通英文句子
  78. if (text.includes(' ')) {
  79. return false;
  80. }
  81. // 检查长度是否为 4 的倍数
  82. if (text.length % 4 !== 0) {
  83. return false;
  84. }
  85. // 检查填充字符的位置是否正确
  86. const paddingIndex = text.indexOf('=');
  87. if (paddingIndex !== -1) {
  88. // 确保等号只出现在末尾,且最多只有两个
  89. const paddingCount = text.length - paddingIndex;
  90. if (paddingCount > 2 || paddingIndex !== text.length - paddingCount) {
  91. return false;
  92. }
  93. }
  94. return true;
  95. }
  96.  
  97. // 尝试base64解码
  98. function tryBase64Decode(text) {
  99. try {
  100. const decoded = atob(text.trim());
  101. // 检查解码后的文本是否包含过多不可打印字符
  102. const unprintableChars = decoded.split('').filter((char) => {
  103. const code = char.charCodeAt(0);
  104. return code < 32 || code > 126;
  105. }).length;
  106.  
  107. // 如果不可打印字符超过总长度的 20%,认为不是有效的文本
  108. if (unprintableChars / decoded.length > 0.2) {
  109. return null;
  110. }
  111. return decoded;
  112. } catch (e) {}
  113. return null;
  114. }
  115.  
  116. // 用于存储延时器ID
  117. let decodeTimer = null;
  118.  
  119. // 处理选择事件
  120. document.addEventListener('selectionchange', () => {
  121. // 清除之前的延时器
  122. if (decodeTimer) {
  123. clearTimeout(decodeTimer);
  124. }
  125.  
  126. const selection = window.getSelection();
  127. const text = selection.toString().trim(); // 先对选中文本进行trim处理
  128.  
  129. // 如果没有选中文本或不符合解码条件,则隐藏窗口
  130. if (!text || !/^[A-Za-z0-9+/=]+$/.test(text) || !isEnglishOnly(text)) {
  131. if (floatingWindow) {
  132. floatingWindow.style.display = 'none';
  133. }
  134. return;
  135. }
  136.  
  137. if (text && /^[A-Za-z0-9+/=]+$/.test(text) && isEnglishOnly(text)) {
  138. // 设置200ms延时
  139. decodeTimer = setTimeout(() => {
  140. const decoded = tryBase64Decode(text);
  141. if (decoded) {
  142. const range = selection.getRangeAt(0);
  143. const rect = range.getBoundingClientRect();
  144.  
  145. decodedText = decoded;
  146. const window = createFloatingWindow();
  147. window.textContent = `Decoded: ${decoded}`;
  148. window.style.display = 'block';
  149.  
  150. // 计算窗口位置,考虑页面滚动
  151. const scrollX =
  152. window.scrollX ||
  153. window.pageXOffset ||
  154. document.documentElement.scrollLeft;
  155. const scrollY =
  156. window.scrollY ||
  157. window.pageYOffset ||
  158. document.documentElement.scrollTop;
  159.  
  160. // 使用选区位置信息
  161. let finalLeft = Math.round(rect.left + scrollX);
  162. let finalTop = Math.round(rect.bottom + scrollY + 5); // 在选中文本下方5px处
  163.  
  164. // 获取浮动窗口的尺寸
  165. const windowWidth = window.offsetWidth;
  166. const windowHeight = window.offsetHeight;
  167.  
  168. // 确保窗口不会超出视口右边界
  169. const maxRight = document.documentElement.clientWidth + scrollX - 10;
  170. if (finalLeft + windowWidth > maxRight) {
  171. finalLeft = maxRight - windowWidth;
  172. }
  173. // 确保不会超出左边界
  174. finalLeft = Math.max(scrollX + 10, finalLeft);
  175.  
  176. // 确保窗口不会超出视口底部边界
  177. const maxBottom = window.innerHeight + scrollY - 10;
  178. if (finalTop + windowHeight > maxBottom) {
  179. // 如果下方空间不足,尝试显示在选中文本上方
  180. finalTop = rect.top + scrollY - windowHeight - 5;
  181. if (finalTop < scrollY + 10) {
  182. // 如果上方空间也不足,则调整窗口宽度并显示在下方
  183. finalTop = rect.bottom + scrollY + 5;
  184. window.style.maxWidth = '50%';
  185. }
  186. } else {
  187. window.style.maxWidth = '80%';
  188. }
  189.  
  190. window.style.left = `${finalLeft}px`;
  191. window.style.top = `${finalTop}px`;
  192. }
  193. }, 200);
  194. }
  195. });
  196.  
  197. // 点击页面其他地方时隐藏悬浮窗口
  198. document.addEventListener('mousedown', (e) => {
  199. if (
  200. floatingWindow &&
  201. e.target !== floatingWindow &&
  202. !floatingWindow.contains(e.target)
  203. ) {
  204. floatingWindow.style.display = 'none';
  205. }
  206. });
  207.  
  208. // 防止选中文本时触发窗口隐藏
  209. floatingWindow &&
  210. floatingWindow.addEventListener('mousedown', (e) => {
  211. e.stopPropagation();
  212. });
  213. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址