Encode/Decode Base64 or Hex with Copy Option

Encode or decode Base64 or Hex strings and copy to clipboard

当前为 2024-08-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Encode/Decode Base64 or Hex with Copy Option
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.7
  5. // @description Encode or decode Base64 or Hex strings and copy to clipboard
  6. // @author itisme
  7. // @include https://*
  8. // @include http://*
  9. // @icon https://data.voz.vn/styles/next/xenforo/smilies/popopo/sexy_girl.png?v=01
  10. // @license GPL-3.0
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13. (function() {
  14. 'use strict';
  15. GM_addStyle(`
  16. :root {
  17. --overlay-bg: rgba(0, 0, 0, 0.6);
  18. --overlay-color: #fff;
  19. --font-color: #333;
  20. --close-color: #f44336;
  21. --close-hover-color: #d32f2f;
  22. --button-bg: #007bff;
  23. --button-active-bg: #0056b3;
  24. --copy-bg: #28a745;
  25. --copy-active-bg: #195427;
  26. --copy-hover-bg: #218838;
  27. --clear-bg: #dc3545;
  28. --clear-active-bg: #c82333;
  29. --shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  30. --transition: 0.3s ease;
  31. }
  32.  
  33. .my-overlay-container {
  34. position: fixed;
  35. top: 0;
  36. left: 0;
  37. width: 100%;
  38. height: 100%;
  39. background: var(--overlay-bg);
  40. display: flex;
  41. justify-content: center;
  42. align-items: center;
  43. z-index: 9999;
  44. max-height: vh;
  45. overflow: auto;
  46. }
  47.  
  48. .my-overlay {
  49. background: var(--overlay-color);
  50. padding: 20px;
  51. border-radius: 8px;
  52. width: 100%;
  53. max-width: 600px;
  54. position: relative;
  55. box-shadow: var(--shadow);
  56. transition: var(--transition);
  57. }
  58.  
  59. .my-overlay-title {
  60. display: flex;
  61. justify-content: space-between;
  62. align-items: center;
  63. margin-bottom: 15px;
  64. position: relative;
  65. }
  66.  
  67. .my-overlay-title-text {
  68. font-size: 24px;
  69. font-weight: 600;
  70. color: var(--font-color);
  71. }
  72.  
  73. .my-overlay-titleCloser {
  74. font-size: 30px;
  75. cursor: pointer;
  76. color: var(--close-color);
  77. transition: var(--transition);
  78. }
  79.  
  80. .my-overlay-titleCloser:hover {
  81. color: var(--close-hover-color);
  82. }
  83.  
  84. .my-overlay-content {
  85. display: flex;
  86. flex-direction: column;
  87. }
  88.  
  89. .my-overlay-content label {
  90. margin-bottom: 5px;
  91. font-weight: bold;
  92. }
  93.  
  94. .my-overlay-content select,
  95. .my-overlay-content textarea {
  96. width: 100%;
  97. margin-bottom: 15px;
  98. padding: 10px;
  99. border: 1px solid #ccc;
  100. border-radius: 5px;
  101. box-sizing: border-box;
  102. }
  103.  
  104. .my-overlay-content textarea {
  105. height: 100px;
  106. font-size: 14px;
  107. line-height: 1.4;
  108. resize: vertical;
  109. }
  110.  
  111. .my-button-container {
  112. display: flex;
  113. gap: 15px;
  114. }
  115.  
  116. .my-button-container button {
  117. padding: 12px 20px;
  118. font-size: 14px;
  119. border: none;
  120. border-radius: 5px;
  121. cursor: pointer;
  122. flex: 1;
  123. transition: background-color var(--transition), box-shadow var(--transition);
  124. box-shadow: var(--shadow);
  125. }
  126.  
  127. #actionButton {
  128. background-color: var(--button-bg);
  129. color: #fff;
  130. }
  131.  
  132. #actionButton:hover,
  133. #actionButton:active {
  134. background-color: var(--button-active-bg);
  135. }
  136.  
  137. #copyButton {
  138. background-color: var(--copy-bg);
  139. color: #fff;
  140. }
  141.  
  142. #copyButton:hover {
  143. background-color: var(--copy-hover-bg);
  144. }
  145.  
  146. #copyButton:active {
  147. background-color: var(--copy-active-bg);
  148. }
  149.  
  150. #copyButton.flash {
  151. animation: flash 1s;
  152. background-color: var(--copy-bg);
  153. }
  154.  
  155. @keyframes flash {
  156. 0% { background-color: var(--copy-bg); }
  157. 100% { background-color: var(--copy-active-bg); }
  158. }
  159.  
  160. .custom-popup-link {
  161. position: fixed; /* Đảm bảo nút luôn nằm cố định trên màn hình */
  162. width: 40px;
  163. height: 40px;
  164. background-image: url('https://data.voz.vn/styles/next/xenforo/smilies/popopo/sexy_girl.png?v=01');
  165. background-size: cover;
  166. background-position: center;
  167. border: none;
  168. cursor: pointer;
  169. z-index: 10000;
  170. user-select: none;
  171. background-color: transparent; /* Đảm bảo nút không có màu nền */
  172. }
  173. `);
  174. const createToggleButton = () => {
  175. const toggleButton = document.createElement('button');
  176. toggleButton.className = 'custom-popup-link';
  177. document.body.appendChild(toggleButton);
  178. // Load saved position
  179. const savedPosition = JSON.parse(localStorage.getItem('toggleButtonPosition')) || {
  180. top: 680,
  181. left: 1460
  182. };
  183. Object.assign(toggleButton.style, {
  184. top: `${savedPosition.top}px`,
  185. left: `${savedPosition.left}px`
  186. });
  187. makeDraggable(toggleButton);
  188. };
  189. const makeDraggable = (element) => {
  190. if(!element) return console.error('Phần tử không tồn tại.');
  191. let offsetX, offsetY, startX, startY, isDragging = false;
  192. const moveElement = (e) => {
  193. if(!isDragging && (Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5)) {
  194. isDragging = true;
  195. }
  196. if(isDragging) {
  197. Object.assign(element.style, {
  198. left: `${e.clientX - offsetX}px`,
  199. top: `${e.clientY - offsetY}px`
  200. });
  201. }
  202. };
  203. const stopDragging = (e) => {
  204. if(isDragging) {
  205. localStorage.setItem('toggleButtonPosition', JSON.stringify({
  206. top: parseInt(element.style.top, 10),
  207. left: parseInt(element.style.left, 10)
  208. }));
  209. } else if(e.button === 0) {
  210. toggleOverlay();
  211. }
  212. document.removeEventListener('mousemove', moveElement);
  213. document.removeEventListener('mouseup', stopDragging);
  214. isDragging = false;
  215. };
  216. element.addEventListener('mousedown', (e) => {
  217. if(e.button !== 0) return;
  218. offsetX = e.clientX - element.offsetLeft;
  219. offsetY = e.clientY - element.offsetTop;
  220. startX = e.clientX;
  221. startY = e.clientY;
  222. document.addEventListener('mousemove', moveElement);
  223. document.addEventListener('mouseup', stopDragging);
  224. });
  225. };
  226. const toggleOverlay = () => {
  227. const overlayContainer = document.querySelector('.my-overlay-container');
  228. if(overlayContainer) {
  229. overlayContainer.style.display = (overlayContainer.style.display === 'none') ? 'flex' : 'none';
  230. } else if(typeof createOverlay === 'function') {
  231. createOverlay();
  232. const newOverlayContainer = document.querySelector('.my-overlay-container');
  233. if(newOverlayContainer) {
  234. newOverlayContainer.style.display = 'flex';
  235. } else {
  236. console.error('Không thể tìm thấy phần tử .my-overlay-container sau khi tạo.');
  237. }
  238. } else {
  239. console.error('Hàm createOverlay không tồn tại.');
  240. }
  241. };
  242. const createOverlay = () => {
  243. const overlayContainer = document.createElement('div');
  244. overlayContainer.className = 'my-overlay-container';
  245. overlayContainer.innerHTML = `
  246. <div class="my-overlay">
  247. <div class="my-overlay-title">
  248. <span class="my-overlay-title-text">Encode/Decode Input</span>
  249. <a class="my-overlay-titleCloser" role="button" aria-label="Close">×</a>
  250. </div>
  251. <div class="my-overlay-content">
  252. <label>Select Operation:</label>
  253. <select id="operationType">
  254. <option value="decode">Decode</option>
  255. <option value="encode">Encode</option>
  256. </select>
  257. <label>Select Type:</label>
  258. <select id="decodeType">
  259. <option value="hex">Hex</option>
  260. <option value="base64">Base64</option>
  261. <option value="url">URL</option>
  262. </select>
  263. <textarea id="inputString" placeholder="Enter text..."></textarea>
  264. <textarea id="resultOutput" readonly placeholder="Result..."></textarea>
  265. <div class="my-button-container">
  266. <button id="actionButton">Decode/Encode</button>
  267. <button id="copyButton">Copy</button>
  268. </div>
  269. </div>
  270. </div>
  271. `;
  272. document.body.appendChild(overlayContainer);
  273. const closeButton = document.querySelector('.my-overlay-titleCloser');
  274. closeButton.addEventListener('click', () => {
  275. overlayContainer.style.display = 'none';
  276. document.getElementById('inputString').value = '';
  277. document.getElementById('resultOutput').value = '';
  278. document.getElementById('operationType').value = "decode";
  279. document.getElementById('decodeType').value = "hex";
  280. });
  281. const encodeHex = s => Array.from(s, c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ').toUpperCase();
  282. const decodeHex = h => {
  283. const cleanedHex = h.replace(/\s+/g, '');
  284. return /^[a-fA-F0-9]+$/.test(cleanedHex) ? cleanedHex.match(/../g).map(b => String.fromCharCode(parseInt(b, 16))).join('') : h;
  285. };
  286. const decodeBase64 = b => /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/.test(b) ? atob(b) : b;
  287. const encodeBase64 = b => btoa(b);
  288. document.getElementById('actionButton').addEventListener('click', () => {
  289. const op = document.getElementById('operationType').value;
  290. const type = document.getElementById('decodeType').value;
  291. const input = document.getElementById('inputString').value.trim();
  292. if(!input) return alert('Vui lòng nhập chuỗi.');
  293. try {
  294. const result = (type === 'base64') ? (op === 'decode' ? decodeBase64(input) : encodeBase64(input)) : (type === 'hex') ? (op === 'decode' ? decodeHex(input) : encodeHex(input)) : (op === 'decode' ? decodeURIComponent(input) : encodeURIComponent(input));
  295. if(result === input) throw new Error('Invalid input');
  296. document.getElementById('resultOutput').value = result;
  297. } catch {
  298. alert('Lỗi: Chuỗi không hợp lệ.');
  299. }
  300. });
  301. document.getElementById('copyButton').addEventListener('click', async () => {
  302. const resultOutput = document.getElementById('resultOutput');
  303. const textToCopy = resultOutput.value.trim();
  304. if(!textToCopy) {
  305. alert('Không có gì để sao chép!');
  306. return;
  307. }
  308. try {
  309. await navigator.clipboard.writeText(textToCopy);
  310. alert('Đã sao chép vào clipboard!');
  311. const copyButton = document.getElementById('copyButton');
  312. copyButton.classList.add('flash');
  313. setTimeout(() => copyButton.classList.remove('flash'), 1000);
  314. } catch (err) {
  315. console.error('Error copying to clipboard:', err);
  316. alert('Lỗi khi sao chép vào clipboard.');
  317. }
  318. });
  319. };
  320. createToggleButton();
  321. })();