Guest User

Untitled

a guest
Aug 29th, 2026
110
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.91 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Soyjak.st File Hash Randomizer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Adds a "Hash Rand" checkbox to file uploads on soyjak.st that randomizes the file hash by appending randomized bytes.
  6. // @author You
  7. // @match https://soyjak.st/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. // -------------------------------------------------------------------------
  15. // Inject Hash Rand checkbox into a .tmb-container
  16. // -------------------------------------------------------------------------
  17. function injectCheckbox(container) {
  18. if (container.querySelector('.sf-hashrand-label')) return;
  19. const flags = container.querySelectorAll('.tmb-flag');
  20. if (!flags.length) return;
  21.  
  22. const wrapper = document.createElement('div');
  23. wrapper.className = 'tmb-flag';
  24. wrapper.innerHTML = `<label class="sf-hashrand-label" style="cursor:pointer;">Hash Rand<input class="file-hashrand" type="checkbox"></label>`;
  25. flags[flags.length - 1].insertAdjacentElement('afterend', wrapper);
  26. }
  27.  
  28. // -------------------------------------------------------------------------
  29. // Observe DOM for new .tmb-container elements
  30. // -------------------------------------------------------------------------
  31. function processContainer(container) {
  32. injectCheckbox(container);
  33. }
  34.  
  35. // Initial sweep
  36. document.querySelectorAll('.tmb-container').forEach(processContainer);
  37.  
  38. // Observer for dynamically added nodes (Normal reply box)
  39. new MutationObserver((mutations) => {
  40. for (const m of mutations) {
  41. for (const node of m.addedNodes) {
  42. if (!(node instanceof Element)) continue;
  43. if (node.classList.contains('tmb-container')) processContainer(node);
  44. else node.querySelectorAll('.tmb-container').forEach(processContainer);
  45. }
  46. }
  47. }).observe(document.body, { childList: true, subtree: true });
  48.  
  49. // Interval sweep to catch dynamically spawned/cloned forms like the Quick Reply box
  50. setInterval(() => {
  51. document.querySelectorAll('.tmb-container:not(:has(.sf-hashrand-label))').forEach(processContainer);
  52. }, 500);
  53.  
  54. // -------------------------------------------------------------------------
  55. // Generate a randomized byte array
  56. // -------------------------------------------------------------------------
  57. function getRandomBytes(length) {
  58. const bytes = new Uint8Array(length);
  59. crypto.getRandomValues(bytes);
  60. return bytes;
  61. }
  62.  
  63. // -------------------------------------------------------------------------
  64. // Append randomized bytes to a File to alter its hash
  65. // -------------------------------------------------------------------------
  66. async function randomizeFileHash(file) {
  67. const randomBytes = getRandomBytes(64);
  68. const originalBuffer = await file.arrayBuffer();
  69. const newBuffer = new Uint8Array(originalBuffer.byteLength + randomBytes.byteLength);
  70.  
  71. newBuffer.set(new Uint8Array(originalBuffer), 0);
  72. newBuffer.set(randomBytes, originalBuffer.byteLength);
  73.  
  74. return new File([newBuffer], file.name, { type: file.type });
  75. }
  76.  
  77. // -------------------------------------------------------------------------
  78. // Network interception — apply hash randomization on submit
  79. // -------------------------------------------------------------------------
  80. const origOpen = XMLHttpRequest.prototype.open;
  81. const origSend = XMLHttpRequest.prototype.send;
  82.  
  83. XMLHttpRequest.prototype.open = function (method, url, ...rest) {
  84. this._interceptUrl = url;
  85. return origOpen.call(this, method, url, ...rest);
  86. };
  87.  
  88. XMLHttpRequest.prototype.send = async function (body) {
  89. if (this._interceptUrl?.includes('/post.php') && body instanceof FormData) {
  90. // Tiny delay ensures the Quick Reply box has fully finalized its DOM
  91. // elements before we scan for the checkboxes
  92. await new Promise(r => setTimeout(r, 100));
  93. body = await patchFormData(body);
  94. }
  95. return origSend.call(this, body);
  96. };
  97.  
  98. const origFetch = window.fetch;
  99. window.fetch = async function (input, init) {
  100. const url = typeof input === 'string' ? input : input?.url;
  101. if (url?.includes('/post.php') && init?.body instanceof FormData) {
  102. await new Promise(r => setTimeout(r, 100));
  103. init = { ...init, body: await patchFormData(init.body) };
  104. }
  105. return origFetch.call(this, input, init);
  106. };
  107.  
  108. async function patchFormData(fd) {
  109. const containers = [...document.querySelectorAll('.tmb-container')];
  110. const newFd = new FormData();
  111. let fileIndex = 0;
  112.  
  113. for (const [key, value] of fd.entries()) {
  114. if (value instanceof File) {
  115. const container = containers[fileIndex];
  116.  
  117. // VICHAN CLONE FIX:
  118. // Because the Quick Reply box is a clone of the main form, vichan
  119. // ties the file data to the main form's index. We must check if ANY
  120. // container has the box ticked to bypass the clone disconnect.
  121. let shouldRandomize = container?.querySelector('.file-hashrand')?.checked ?? false;
  122. if (!shouldRandomize) {
  123. shouldRandomize = document.querySelector('.file-hashrand:checked') !== null;
  124. }
  125.  
  126. fileIndex++;
  127.  
  128. let outFile = value;
  129.  
  130. if (shouldRandomize) {
  131. outFile = await randomizeFileHash(value);
  132. }
  133.  
  134. newFd.append(key, outFile);
  135. } else {
  136. newFd.append(key, value);
  137. }
  138. }
  139. return newFd;
  140. }
  141.  
  142. })();
Advertisement
Add Comment
Please, Sign In to add comment