Linux.do clochat.com discourse Base64 script

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

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

QingJ © 2025

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