Linux.do Base64 script

Base64编解码工具,支持位置记忆、夜间模式和拖动功能

目前为 2025-04-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Linux.do Base64 script
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5.1
  5. // @description Base64编解码工具,支持位置记忆、夜间模式和拖动功能
  6. // @author Xavier
  7. // @match https://linux.do/*
  8. // @grant GM_setClipboard
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // ==/UserScript==
  12.  
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // 创建主容器
  18. const container = document.createElement('div');
  19. container.id = 'b64Container';
  20. Object.assign(container.style, {
  21. position: 'fixed',
  22. zIndex: 9999,
  23. borderRadius: '8px',
  24. fontFamily: 'inherit',
  25. cursor: 'move'
  26. });
  27.  
  28. // 初始化位置
  29. const savedPosition = GM_getValue('b64Position', null);
  30. if (savedPosition) {
  31. container.style.left = `${savedPosition.x}px`;
  32. container.style.top = `${savedPosition.y}px`;
  33. container.style.bottom = 'auto';
  34. container.style.right = 'auto';
  35. } else {
  36. container.style.bottom = '20px';
  37. container.style.right = '20px';
  38. container.style.left = 'auto';
  39. container.style.top = 'auto';
  40. }
  41.  
  42. // 主触发器
  43. const trigger = document.createElement('div');
  44. trigger.innerHTML = 'Base64';
  45. Object.assign(trigger.style, {
  46. padding: '8px 16px',
  47. borderRadius: '6px',
  48. fontSize: '14px',
  49. cursor: 'pointer',
  50. transition: 'all 0.2s',
  51. backgroundColor: 'var(--primary)',
  52. color: 'var(--secondary)',
  53. boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
  54. opacity: '0.5', // 设置默认透明度
  55. });
  56.  
  57. // 添加鼠标悬停效果
  58. trigger.onmouseover = () => {
  59. trigger.style.opacity = '1'; // 悬停时变为不透明
  60. };
  61.  
  62. trigger.onmouseout = () => {
  63. trigger.style.opacity = '0.5'; // 鼠标移出后恢复透明度
  64. };
  65.  
  66. // 拖动功能
  67. let isDragging = false;
  68. let offsetX = 0;
  69. let offsetY = 0;
  70.  
  71. container.addEventListener('mousedown', (e) => {
  72. isDragging = true;
  73. const rect = container.getBoundingClientRect();
  74. offsetX = e.clientX - rect.left;
  75. offsetY = e.clientY - rect.top;
  76. });
  77.  
  78. document.addEventListener('mousemove', (e) => {
  79. if (isDragging) {
  80. const x = e.clientX - offsetX;
  81. const y = e.clientY - offsetY;
  82. container.style.left = `${x}px`;
  83. container.style.top = `${y}px`;
  84. container.style.right = 'auto';
  85. container.style.bottom = 'auto';
  86. }
  87. });
  88.  
  89. document.addEventListener('mouseup', () => {
  90. if (isDragging) {
  91. const rect = container.getBoundingClientRect();
  92. GM_setValue('b64Position', {
  93. x: rect.left,
  94. y: rect.top
  95. });
  96. }
  97. isDragging = false;
  98. });
  99.  
  100. // 下拉菜单容器
  101. const menu = document.createElement('div');
  102. menu.style.cssText = `
  103. position: absolute;
  104. bottom: 100%;
  105. right: 0;
  106. width: 140px;
  107. background: var(--secondary);
  108. border-radius: 6px;
  109. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  110. margin-bottom: 8px;
  111. opacity: 0;
  112. transform: translateY(10px);
  113. transition: all 0.25s ease;
  114. pointer-events: none;
  115. overflow: hidden;
  116. `;
  117.  
  118. // 创建菜单项
  119. const createMenuItem = (text) => {
  120. const item = document.createElement('div');
  121. item.textContent = text;
  122. item.style.cssText = `
  123. padding: 10px 16px;
  124. font-size: 13px;
  125. cursor: pointer;
  126. color: var(--primary);
  127. transition: all 0.2s;
  128. text-align: left;
  129. line-height: 1.4;
  130. `;
  131.  
  132. // 根据按钮文本设置不同圆角
  133. if (text === '解析本页 Base64') {
  134. item.style.borderRadius = '6px 6px 0 0'; // 上边圆角
  135. } else if (text === '文本转 Base64') {
  136. item.style.borderRadius = '0 0 6px 6px'; // 下边圆角
  137. }
  138.  
  139. item.onmouseenter = () => {
  140. item.style.background = 'rgba(0,0,0,0.08)';
  141. };
  142.  
  143. item.onmouseleave = () => {
  144. item.style.background = '';
  145. };
  146.  
  147. return item;
  148. };
  149.  
  150. const decodeItem = createMenuItem('解析本页 Base64');
  151. const encodeItem = createMenuItem('文本转 Base64');
  152.  
  153. menu.append(decodeItem, encodeItem);
  154. container.append(trigger, menu);
  155.  
  156. // 菜单切换逻辑
  157. const toggleMenu = (show) => {
  158. menu.style.opacity = show ? 1 : 0;
  159. menu.style.transform = show ? 'translateY(0)' : 'translateY(10px)';
  160. menu.style.pointerEvents = show ? 'all' : 'none';
  161. };
  162.  
  163. // 主题适配
  164. const updateTheme = () => {
  165. const isDark = document.body.getAttribute('data-theme') === 'dark';
  166. trigger.style.backgroundColor = isDark ? 'var(--primary)' : 'var(--secondary)';
  167. trigger.style.color = isDark ? 'var(--secondary)' : 'var(--primary)';
  168. trigger.style.opacity = '0.5'; // 确保透明度在主题切换时更新
  169. };
  170.  
  171. // 统一提示函数
  172. function showNotification(message, isSuccess) {
  173. const notification = document.createElement('div');
  174. notification.className = 'b64-copy-notification';
  175. notification.textContent = message;
  176. notification.style.background = isSuccess ? 'rgba(0, 200, 0, 0.9)' : 'rgba(200, 0, 0, 0.9)';
  177. document.body.appendChild(notification);
  178.  
  179. setTimeout(() => {
  180. notification.remove();
  181. }, 2500);
  182. }
  183.  
  184. // 复制提示样式
  185. const notificationStyle = document.createElement('style');
  186. notificationStyle.textContent = `
  187. .b64-copy-notification {
  188. position: fixed;
  189. top: 20px;
  190. left: 50%;
  191. transform: translateX(-50%);
  192. background: rgba(0, 200, 0, 0.9);
  193. color: white;
  194. padding: 10px 20px;
  195. border-radius: 4px;
  196. font-size: 14px;
  197. z-index: 10000;
  198. animation: fadeOut 2s forwards;
  199. animation-delay: 0.5s;
  200. }
  201.  
  202. @keyframes fadeOut {
  203. from { opacity: 1; }
  204. to { opacity: 0; }
  205. }
  206. `;
  207. document.head.appendChild(notificationStyle);
  208.  
  209. // 解码功能
  210. let isParsed = false;
  211. let originalTexts = {};
  212.  
  213. const decodeBase64 = () => {
  214. const base64Regex = /([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?/g;
  215.  
  216. if (isParsed) {
  217. Object.entries(originalTexts).forEach(([wrapperId, text]) => {
  218. const wrapper = document.getElementById(wrapperId);
  219. if (wrapper) {
  220. const textNode = document.createTextNode(text);
  221. wrapper.replaceWith(textNode);
  222. }
  223. });
  224. originalTexts = {};
  225. isParsed = false;
  226. showNotification('已恢复原始内容', true);
  227. return;
  228. }
  229.  
  230. let decodeSuccess = false;
  231.  
  232. document.querySelectorAll('div.post, div.cooked').forEach(container => {
  233. const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
  234.  
  235. while (walker.nextNode()) {
  236. const node = walker.currentNode;
  237. if (node.textContent.length > 20) {
  238. const decodedContent = node.textContent.replace(base64Regex, match => {
  239. try {
  240. const decoded = decodeURIComponent(escape(atob(match)));
  241. if (decoded !== match) {
  242. decodeSuccess = true;
  243. return `<span class="b64-decoded" style="color: #FF7F28; background: rgba(255, 127, 40, 0.1); cursor: pointer; transition: all 0.2s; padding: 1px 2px; border-radius: 3px;">${decoded}</span>`;
  244. }
  245. return match;
  246. } catch (e) {
  247. return match;
  248. }
  249. });
  250.  
  251. if (decodedContent !== node.textContent) {
  252. const wrapper = document.createElement('span');
  253. const wrapperId = `b64-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
  254. wrapper.id = wrapperId;
  255. wrapper.innerHTML = decodedContent;
  256.  
  257. wrapper.querySelectorAll('.b64-decoded').forEach(span => {
  258. span.onclick = (e) => {
  259. GM_setClipboard(span.textContent);
  260. showNotification('已复制到剪贴板', true);
  261. e.target.style.opacity = '0.7';
  262. setTimeout(() => e.target.style.opacity = '', 500);
  263. };
  264. });
  265.  
  266. originalTexts[wrapperId] = node.textContent;
  267. node.replaceWith(wrapper);
  268. }
  269. }
  270. }
  271. });
  272.  
  273. if (decodeSuccess) {
  274. isParsed = true;
  275. showNotification('解析成功', true);
  276. } else {
  277. showNotification('解析失败,未发现有效Base64', false);
  278. }
  279. };
  280.  
  281. // 编码功能
  282. const encodeBase64 = () => {
  283. const text = prompt('请输入要编码的文本:');
  284. if (text) {
  285. try {
  286. const encoded = btoa(unescape(encodeURIComponent(text)));
  287. GM_setClipboard(encoded);
  288. showNotification('编码成功已复制', true);
  289. } catch (e) {
  290. showNotification('编码失败:无效字符', false);
  291. }
  292. } else {
  293. showNotification('编码已取消', false);
  294. }
  295. };
  296.  
  297. // 事件绑定
  298. trigger.addEventListener('click', (e) => {
  299. e.stopPropagation();
  300. toggleMenu(menu.style.opacity === '0');
  301. });
  302.  
  303. decodeItem.addEventListener('click', () => {
  304. decodeBase64();
  305. toggleMenu(false);
  306. });
  307.  
  308. encodeItem.addEventListener('click', () => {
  309. encodeBase64();
  310. toggleMenu(false);
  311. });
  312.  
  313. // 初始化
  314. document.body.appendChild(container);
  315. updateTheme();
  316.  
  317. // 主题变化监听
  318. new MutationObserver(updateTheme).observe(document.body, {
  319. attributes: true,
  320. attributeFilter: ['data-theme']
  321. });
  322.  
  323. // 点击外部关闭
  324. document.addEventListener('click', (e) => {
  325. if (!container.contains(e.target)) toggleMenu(false);
  326. });
  327.  
  328. // 监听前进和后退事件,重置解析状态
  329. function resetParseState() {
  330. isParsed = false;
  331. originalTexts = {};
  332. }
  333. window.addEventListener('popstate', resetParseState);
  334.  
  335. // 全局样式
  336. const style = document.createElement('style');
  337. style.textContent = `
  338. .b64-decoded:hover {
  339. background: rgba(255, 127, 40, 0.2) !important;
  340. }
  341. .b64-decoded:active {
  342. background: rgba(255, 127, 40, 0.3) !important;
  343. }
  344. `;
  345. document.head.appendChild(style);
  346.  
  347. // 确保原有复制提示兼容性
  348. function showCopyNotification() {
  349. showNotification('已复制到剪贴板', true);
  350. }
  351. })();

QingJ © 2025

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