Encode/Decode Base64 or Hex with Copy Option

Encode or decode Base64 or Hex strings and copy to clipboard

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