NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name YouTube URL Tracker Remover // @description Fixes user-tracking links in the description of YouTube videos // @version 1.2.1 // @author guihkx // @match https://*.youtube.com/* // @license MIT; https://opensource.org/licenses/MIT // @run-at document-start // @noframes // @namespace https://github.com/guihkx // @icon https://s.ytimg.com/yts/img/favicon_48-vflVjB_Qk.png // @downloadURL https://raw.githubusercontent.com/guihkx/user-scripts/master/scripts/youtube-url-tracker-remover.user.js // @updateURL https://raw.githubusercontent.com/guihkx/user-scripts/master/scripts/youtube-url-tracker-remover.user.js // @homepageURL https://github.com/guihkx/user-scripts // @supportURL https://github.com/guihkx/user-scripts/issues // ==/UserScript== /** * Changelog: * * v1.2.1 (2024-01-18): * - Detect timestamps more accurately. * * v1.2.0 (2022-11-27): * - Fix compatibility issues with latest YouTube changes * - Add support for YouTube Shorts * - Major code refactor * * v1.1.2 (2021-10-02): * - Fix script sometimes not injecting on Firefox with Violentmonkey * * v1.1.1 (2021-05-14): * - Fix wrong selector * * v1.1.0 (2021-05-04): * - Remove support for legacy YouTube (Polymer) * * v1.0.0 (2020-04-28): * - First release */ ;(() => { 'use strict' const log = console.log.bind(console, '[YouTube URL Tracker Remover]') const ytEvents = [ // Triggered when the page is updated somehow, e.g. by clicking on another video. 'yt-page-data-updated', // Triggered when the 'Show less' text under the description is clicked. 'yt-text-inline-expander-collapse-clicked', // Triggered when the 'Show more' text under the description is clicked. 'yt-text-inline-expander-expand-clicked' ] const ytShortsEvents = [ // Triggered when you open the description box on YouTube Shorts. 'yt-popup-opened' ]; [...ytEvents, ...ytShortsEvents].forEach(event => { document.addEventListener(event, async e => { const isRegularVideo = window.location.pathname === '/watch' const isShorts = window.location.pathname.startsWith('/shorts/') if ((isRegularVideo && ytShortsEvents.includes(e.type)) || (isShorts && ytEvents.includes(e.type))) { log(`YouTube event triggered: ${e.type}. Ignoring it because it's not useful on this page.`) return } try { const selector = isRegularVideo ? '#description-inline-expander a' : '#description a' removeYoutubeTracking(await querySelectorAllLazy(selector)) } catch (error) { log('Error: Unable to find links in the video description.\n' + 'Underlying error: ' + error + '\n' + 'Possible reasons:\n' + '- There are currently no links in the video description.\n' + '- The script is broken (please open a bug report).') } }) }) function querySelectorAllLazy (selector, intervalMs = 500, maxTries = 6) { return new Promise((resolve, reject) => { let tried = 1 const id = setInterval(() => { if (tried > maxTries) { clearInterval(id) reject(new Error(`The maximum amount of tries (${maxTries}) was exceeded.`)) return } const elements = document.querySelectorAll(selector) if (elements.length > 0) { clearInterval(id) resolve(elements) return } tried++ }, intervalMs) }) } function removeYoutubeTracking (links) { let cleanedUpLinks = 0 for (const link of links) { // Ignore timestamps if (Number.isInteger(link.textContent[0])) { continue } // Ignore hashtags if (link.textContent[0] === '#') { continue } // Ignore mentions if (link.textContent[0] === '@') { continue } // Ignore URLs within the youtube.com domain if (link.pathname !== '/redirect') { continue } const actualUrl = new URLSearchParams(link.search).get('q') if (actualUrl === null) { log('Unable to extract URL from /redirect:', link) continue } link.href = actualUrl cleanedUpLinks++ } if (cleanedUpLinks > 0) { log(`Cleaned up ${cleanedUpLinks} links in the video description.`) } } })()