Summary Files Commits Branches @commit @diff @stats
path: root / youtube-invidious-link-replacer.js
blob: 1348eace2153a1830e884bacc95cd3a99c96ae39 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// ==UserScript==
// @name        YouTube to Invidious video link replacer
// @description Replace video links in the youtube site to their Invidious equivalents
//
// @version     1.1
// @license     MIT
// @author      Syndamia
//
// @source      https://gitlab.com/Syndamia/userscripts/-/blob/main/youtube-invidious-link-replacer.js
// @supportURL  https://gitlab.com/Syndamia/userscripts/-/issues
// @homepage    https://gitlab.com/Syndamia/userscripts/
// @icon        https://upload.wikimedia.org/wikipedia/commons/a/a0/Invidious-logo.svg
//
// @namespace   Syndamia
// @match       *://www.youtube.com/*
// @run-at      document-start
// ==/UserScript==

const invidiousInstance = 'yewtu.be';



// Wait for ytd-app element to appear
const ytdObserver = new MutationObserver(function(mutations) {
    /* Detect when ytd-app element is present */

    // Thanks https://stackoverflow.com/a/45267350/12036073
    if (document.getElementsByTagName('ytd-app').length > 0) {
        disableCertainClickEventListeners(); // Refer to function for information

        /* When you open a link in YouTube, the site doesn't fully reload, but the ytd-app element
         * (which contains the whole UI) is updated (and the website URL also updates).
         * Also, YouTube's infinite scroll updates the ytd-app element, so the best way to detect
         * when we need to fix links is when ytd-app changes.
         */
        const observer = new MutationObserver(replaceAnchors);
        observer.observe(document.getElementsByTagName('ytd-app')[0], { childList: true, attributes: true, subtree: true, });

        ytdObserver.disconnect();
    }
});

ytdObserver.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});

function replaceAnchors() {
    for (var anchor of document.getElementsByTagName('ytd-app')[0].getElementsByTagName('a')) {
        if (anchor.href.indexOf('www.youtube.com/watch') > 0) {
            anchor.href = anchor.href.replace('www.youtube.com/watch', invidiousInstance + '/watch');
        }
    }
}

/* As already meantioned, opening a link in YouTube doesn't work like clicking on a normal anchor,
 * it updates the ytd-app element and website URL. When clicking on a video, a special eventListener(s)
 * handles loading the YouTube video page and if we don't disable it(them), even with anchors that href to
 * a page outside YouTube, we'll still load the YouTube video as normal.
 */
function disableCertainClickEventListeners() {
    /* It's not enough to only override the HTMLAnchorElement addEventListener function,
     * from testing it seems that there are other elements that also have the appropriate event listeners
     * which trigger opening of the YouTube player
     */

    // Thanks https://stackoverflow.com/a/57437878/12036073
    HTMLElement.prototype.oldaddEventListener = HTMLElement.prototype.addEventListener;
    HTMLElement.prototype.addEventListener = function(event, handler, placeholder) {
        if (event === 'click') {
            /* The functions that actually handles clicking on videos to open player is this:
             * (c){if(a[b])a[b](c,c.detail);else console.warn("listener method `"+b+"` not defined")}
             * As far as I have tested, '_handleNative$$module$third_party$javascript$polymer$v2$polymer$lib$utils$gestures' and functions like:
             * (a){a.target===this.liveBadge.element&&(this.api.seekTo(Infinity),this.api.playVideo())}
             * don't play a direct role in the click video -> open player process.
             */
            if (handler.toString().indexOf('c', 7) === 9 && this.tagName !== 'YT-ICON-BUTTON') { // The second condition allows some buttons to work
                return;
            }
        }
        this.oldaddEventListener(event, handler, placeholder);
    }
}