Encode/Decode Base64 or Hex with Copy Option

Encode or decode Base64 or Hex strings and copy to clipboard

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

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