Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Soyjak.st File Hash Randomizer
- // @namespace http://tampermonkey.net/
- // @version 1.2
- // @description Adds a "Hash Rand" checkbox to file uploads on soyjak.st that randomizes the file hash by appending randomized bytes.
- // @author You
- // @match https://soyjak.st/*
- // @grant none
- // ==/UserScript==
- (function () {
- 'use strict';
- // -------------------------------------------------------------------------
- // Inject Hash Rand checkbox into a .tmb-container
- // -------------------------------------------------------------------------
- function injectCheckbox(container) {
- if (container.querySelector('.sf-hashrand-label')) return;
- const flags = container.querySelectorAll('.tmb-flag');
- if (!flags.length) return;
- const wrapper = document.createElement('div');
- wrapper.className = 'tmb-flag';
- wrapper.innerHTML = `<label class="sf-hashrand-label" style="cursor:pointer;">Hash Rand<input class="file-hashrand" type="checkbox"></label>`;
- flags[flags.length - 1].insertAdjacentElement('afterend', wrapper);
- }
- // -------------------------------------------------------------------------
- // Observe DOM for new .tmb-container elements
- // -------------------------------------------------------------------------
- function processContainer(container) {
- injectCheckbox(container);
- }
- // Initial sweep
- document.querySelectorAll('.tmb-container').forEach(processContainer);
- // Observer for dynamically added nodes (Normal reply box)
- new MutationObserver((mutations) => {
- for (const m of mutations) {
- for (const node of m.addedNodes) {
- if (!(node instanceof Element)) continue;
- if (node.classList.contains('tmb-container')) processContainer(node);
- else node.querySelectorAll('.tmb-container').forEach(processContainer);
- }
- }
- }).observe(document.body, { childList: true, subtree: true });
- // Interval sweep to catch dynamically spawned/cloned forms like the Quick Reply box
- setInterval(() => {
- document.querySelectorAll('.tmb-container:not(:has(.sf-hashrand-label))').forEach(processContainer);
- }, 500);
- // -------------------------------------------------------------------------
- // Generate a randomized byte array
- // -------------------------------------------------------------------------
- function getRandomBytes(length) {
- const bytes = new Uint8Array(length);
- crypto.getRandomValues(bytes);
- return bytes;
- }
- // -------------------------------------------------------------------------
- // Append randomized bytes to a File to alter its hash
- // -------------------------------------------------------------------------
- async function randomizeFileHash(file) {
- const randomBytes = getRandomBytes(64);
- const originalBuffer = await file.arrayBuffer();
- const newBuffer = new Uint8Array(originalBuffer.byteLength + randomBytes.byteLength);
- newBuffer.set(new Uint8Array(originalBuffer), 0);
- newBuffer.set(randomBytes, originalBuffer.byteLength);
- return new File([newBuffer], file.name, { type: file.type });
- }
- // -------------------------------------------------------------------------
- // Network interception — apply hash randomization on submit
- // -------------------------------------------------------------------------
- const origOpen = XMLHttpRequest.prototype.open;
- const origSend = XMLHttpRequest.prototype.send;
- XMLHttpRequest.prototype.open = function (method, url, ...rest) {
- this._interceptUrl = url;
- return origOpen.call(this, method, url, ...rest);
- };
- XMLHttpRequest.prototype.send = async function (body) {
- if (this._interceptUrl?.includes('/post.php') && body instanceof FormData) {
- // Tiny delay ensures the Quick Reply box has fully finalized its DOM
- // elements before we scan for the checkboxes
- await new Promise(r => setTimeout(r, 100));
- body = await patchFormData(body);
- }
- return origSend.call(this, body);
- };
- const origFetch = window.fetch;
- window.fetch = async function (input, init) {
- const url = typeof input === 'string' ? input : input?.url;
- if (url?.includes('/post.php') && init?.body instanceof FormData) {
- await new Promise(r => setTimeout(r, 100));
- init = { ...init, body: await patchFormData(init.body) };
- }
- return origFetch.call(this, input, init);
- };
- async function patchFormData(fd) {
- const containers = [...document.querySelectorAll('.tmb-container')];
- const newFd = new FormData();
- let fileIndex = 0;
- for (const [key, value] of fd.entries()) {
- if (value instanceof File) {
- const container = containers[fileIndex];
- // VICHAN CLONE FIX:
- // Because the Quick Reply box is a clone of the main form, vichan
- // ties the file data to the main form's index. We must check if ANY
- // container has the box ticked to bypass the clone disconnect.
- let shouldRandomize = container?.querySelector('.file-hashrand')?.checked ?? false;
- if (!shouldRandomize) {
- shouldRandomize = document.querySelector('.file-hashrand:checked') !== null;
- }
- fileIndex++;
- let outFile = value;
- if (shouldRandomize) {
- outFile = await randomizeFileHash(value);
- }
- newFd.append(key, outFile);
- } else {
- newFd.append(key, value);
- }
- }
- return newFd;
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment