// ==UserScript== // @name UnsafeYT-1.81 // @namespace unsafe-yt-userscript // @license MIT // @version 1.81 // @description Full port of UnsafeYT content script to Tampermonkey. Automatically toggles ON when a valid token is detected in the video description (first line starts with "token:"). Made By ChatGPT // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @grant none // @run-at document-idle // @downloadURL https://update.greasyfork.icu/scripts/552976/UnsafeYT-181.user.js // @updateURL https://update.greasyfork.icu/scripts/552976/UnsafeYT-181.meta.js // ==/UserScript== /* ===================================================================================== SUMMARY - Full WebGL (video) + AudioGraph pipeline restored from the extension's content script. - Auto-detects tokens in the video's description when the first line begins with "token:". - Adds two buttons by the Like/Dislike bar: 1) Toggle Effects (transparent bg, white text, outline red=off / green=on) 2) Enter Token (transparent bg, white text) — manual prompt - Default: OFF. If token auto-detected, the script will automatically enable effects. - Large number of comments and clear structure for easy review. - Added a custom gain slider (credits goes to ChatGPT for suggesting and contributing to this userscript) ===================================================================================== */ (function () { "use strict"; /************************************************************************ * SECTION A — CONFIG & SHADERS (embedded) ************************************************************************/ // Vertex shader (screen quad) - WebGL2 (#version 300 es) const VERT_SHADER_SRC = `#version 300 es in vec2 a_position; in vec2 a_texCoord; out vec2 v_texCoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; }`; // Fragment shader (the decoding/visual effect). This is the original .frag you gave. const FRAG_SHADER_SRC = `#version 300 es precision highp float; in vec2 v_texCoord; out vec4 fragColor; uniform sampler2D u_sampler; uniform sampler2D u_shuffle; vec2 getNormal( vec2 uv ){ vec2 offset = vec2(0.0065,0.0065); vec2 center = round((uv+offset)*80.0)/80.0; return (center - (uv+offset))*80.0; } float getAxis( vec2 uv ){ vec2 normal = getNormal( uv ); float axis = abs(normal.x) > 0.435 ? 1.0 : 0.0; return abs(normal.y) > 0.4 ? 2.0 : axis; } float getGrid( vec2 uv ){ float axis = getAxis( uv ); return axis > 0.0 ? 1.0 : 0.0; } vec4 getColor( vec2 uv ){ vec2 shuffle_sample = texture(u_shuffle, uv).rg; vec2 base_new_uv = uv + shuffle_sample; vec4 c = texture(u_sampler, base_new_uv); return vec4(1.0 - c.rgb, c.a); } vec4 getGridFix( vec2 uv ){ vec2 normal = getNormal( uv ); vec4 base = getColor( uv ); vec4 outline = getColor( uv + normal*0.002 ); float grid = getGrid( uv ); return mix(base,outline,grid); } vec4 getSmoothed( vec2 uv, float power, float slice ){ vec4 result = vec4(0.0,0.0,0.0,0.0); float PI = 3.14159265; float TAU = PI*2.0; for( float i=0.0; i < 8.0; i++ ){ float angle = ((i/8.0)*TAU) + (PI/2.0) + slice; vec2 normal = vec2(sin(angle),cos(angle)*1.002); result += getGridFix( uv + normal*power ); } return result/8.0; } void main() { vec2 uv = vec2(v_texCoord.x, -v_texCoord.y + 1.0); float axis = getAxis( uv ); float grid = axis > 0.0 ? 1.0 : 0.0; float slices[3] = float[3](0.0,0.0,3.14159265); vec4 main = getGridFix( uv ); vec4 outline = getSmoothed( uv, 0.001, slices[int(axis)] ); main = mix(main,outline,grid); fragColor = main; }`; /************************************************************************ * SECTION B — GLOBAL STATE (clear names) ************************************************************************/ // Token / state let currentToken = ""; // the decode token (from description or manual) let savedDescription = ""; // last observed description (avoid repeated parsing) let isRendering = false; // whether effects currently active // Video / WebGL / Audio objects (reset in cleanup) let activeCanvas = null; let activeGl = null; let activeAudioCtx = null; let activeSrcNode = null; let activeGainNode = null; let activeOutputGainNode = null; let activeNotchFilters = []; let resizeIntervalId = null; let renderFrameId = null; let originalVideoContainerStyle = null; let resizeCanvasListener = null; let currentNode = null; // URL tracking (YouTube SPA) let currentUrl = location.href.split("&")[0].split("#")[0]; /************************************************************************ * SECTION C — SMALL UTILITIES (readable, documented) ************************************************************************/ /** * deterministicHash(s, prime, modulus) * - Deterministic numeric hash scaled to [0,1) * - Used by the shuffle map generator */ function deterministicHash(s, prime = 31, modulus = Math.pow(2, 32)) { let h = 0; modulus = Math.floor(modulus); for (let i = 0; i < s.length; i++) { const charCode = s.charCodeAt(i); h = (h * prime + charCode) % modulus; if (h < 0) h += modulus; } return h / modulus; } /** * _generateUnshuffleOffsetMapFloat32Array(seedToken, width, height) * - Produces a Float32Array of length width*height*2 containing * normalized offsets used by the shader to unshuffle pixels. */ function _generateUnshuffleOffsetMapFloat32Array(seedToken, width, height) { if (width <= 0 || height <= 0) { throw new Error("Width and height must be positive integers."); } if (typeof seedToken !== 'string' || seedToken.length === 0) { throw new Error("Seed string is required for deterministic generation."); } const totalPixels = width * height; // Two independent deterministic hashes const startHash = deterministicHash(seedToken, 31, Math.pow(2, 32) - 1); const stepSeed = seedToken + "_step"; const stepHash = deterministicHash(stepSeed, 37, Math.pow(2, 32) - 2); // Angle and increment used to produce per-index pseudo-random numbers const startAngle = startHash * Math.PI * 2.0; const angleIncrement = stepHash * Math.PI / Math.max(width, height); // Generate values and their original index const indexedValues = []; for (let i = 0; i < totalPixels; i++) { const value = Math.sin(startAngle + i * angleIncrement); indexedValues.push({ value: value, index: i }); } // Sort by value to create a deterministic 'shuffle' permutation indexedValues.sort((a, b) => a.value - b.value); // pLinearized maps originalIndex -> shuffledIndex const pLinearized = new Array(totalPixels); for (let k = 0; k < totalPixels; k++) { const originalIndex = indexedValues[k].index; const shuffledIndex = k; pLinearized[originalIndex] = shuffledIndex; } // Create the offset map: for each original pixel compute where it should sample from const offsetMapFloats = new Float32Array(totalPixels * 2); for (let oy = 0; oy < height; oy++) { for (let ox = 0; ox < width; ox++) { const originalLinearIndex = oy * width + ox; const shuffledLinearIndex = pLinearized[originalLinearIndex]; const sy_shuffled = Math.floor(shuffledLinearIndex / width); const sx_shuffled = shuffledLinearIndex % width; // offsets normalized relative to texture size (so shader can add to UV) const offsetX = (sx_shuffled - ox) / width; const offsetY = (sy_shuffled - oy) / height; const pixelDataIndex = (oy * width + ox) * 2; offsetMapFloats[pixelDataIndex] = offsetX; offsetMapFloats[pixelDataIndex + 1] = offsetY; } } return offsetMapFloats; } /** * sleep(ms) — small helper to await timeouts */ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } /************************************************************************ * SECTION D — TOKEN DETECTION (strict: first line must start with "token:") ************************************************************************/ /** * extractTokenFromText(text) * - If text's first line starts with "token:" (case-insensitive), returns token after colon. * - Otherwise returns empty string. */ function extractTokenFromText(text) { if (!text) return ""; const trimmed = text.trim(); const firstLine = trimmed.split(/\r?\n/)[0] || ""; if (firstLine.toLowerCase().startsWith("token:")) { return firstLine.substring(6).trim(); } return ""; } /** * Robust token finder: * - Looks in many known selectors (regular watch + Shorts) * - Probes likely shadow-root hosts used by Shorts/reels * - Checks JSON-LD