[DEPRECATED] Twitch Adblock

[DEPRECATED] Please use https://github.com/odensc/ttv-ublock instead

< Feedback on [DEPRECATED] Twitch Adblock

Review: Good - script works

§
Posted: 19-11-2020
Edited: 20-11-2020

This post is about community helping with updates

Hey, I just updated your script for adding buttons and making them work (fullscreen & studio mode) and remove logo ! I also optimized a little thing in the code that matters in the observer

It also remove the top title of the embeded stream (when we hover if with the mouse) which is not on the normal twitch player.

Here is the code:

// ==UserScript==
// @name         TWT Adblock
// @version      1.2.0
// @description  [Working as of 11/19/2020] Blocks Twitch livestream ads
// @author       FTwitch & someone
// @include      https://www.twitch.tv/*
// @include      https://cdn.embedly.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    if (window.location.origin == "https://cdn.embedly.com") {
        document.getElementsByTagName("html")[0].style = "overflow: hidden";
        return;
    }

    var lastStreamer, oldHtml;

    var observer = new MutationObserver(function (mutations, observer) {
        var container = document.querySelector(".video-player .tw-absolute");

        if (!container)
            return;

        if (window.location.pathname.indexOf("/directory") == 0)
            return;

        //Opti (we don't care of the numbers updating)
        if(mutations.length === 1 && mutations[0].target.classList.contains("tw-animated-number--monospaced"))
            return;

        var streamerName = window.location.pathname.replace("/", "");
        let website = location.host;
        var iframeUrl = `https://player.${website.replace("www.", "")}/?channel=${streamerName}&muted=false&parent=cdn.embedly.com&parent${website}`;
        var existingIframe = container.getElementsByTagName("iframe")[0];

        if ((!streamerName && !lastStreamer) || streamerName.indexOf("videos/") == 0) {
            lastStreamer = null;
            for (let el of container.children)
                el.hidden = false;

            if (existingIframe) {
                existingIframe.src = "";
                existingIframe.hidden = true;
            }

            return;
        }
        else if (!streamerName)
            return;

        for (let el of container.children) {
            if (el.tagName != "IFRAME")
                el.hidden = true;

            if (el.tagName == "VIDEO")
                el.src = "";
        }

        if (!existingIframe) {
            existingIframe = document.createElement("iframe");
            existingIframe.src = iframeUrl;
            existingIframe.style = "width: 100%; height: 100%";
            container.appendChild(existingIframe);
            loadIframe(existingIframe)
        }
        else if (streamerName != lastStreamer) {
            existingIframe.src = iframeUrl;
            existingIframe.hidden = false;
            loadIframe(existingIframe)
        }

        lastStreamer = streamerName
    });

    //Because, when first loading the page, this element is not always already loaded at this stage.
    let interval = setInterval(()=>{
        let element = document.querySelector(".root-scrollable__wrapper.tw-full-width.tw-relative");
        if(element){
            clearInterval(interval);
            observer.observe(element, { attributes: false, childList: true, subtree: true });
        }
    }, 50);

})();


function loadIframe(iframe){
    iframe.onload = ()=>{
        let ifWindow = iframe.contentWindow;
        let logo = ifWindow.document.querySelector(`[data-a-target="player-${ifWindow.location.host.split(".")[1]}-logo-button"`)
        logo.remove();

        let fs = ifWindow.document.querySelectorAll(".tw-inline-flex.tw-relative.tw-tooltip-wrapper")[3]

        let clone = fs.cloneNode(true);
        fs.parentElement.appendChild(clone);

        //Remove top title (which is not in the normal twt viewer)
        ifWindow.document.querySelector('.top-bar.tw-absolute.tw-flex.tw-flex-grow-1.tw-justify-content-between.tw-left-0.tw-right-0.tw-top-0').remove();

        fs.children[1].innerText = "Mode Studio (alt+T)"
        fs.querySelector("path").outerHTML = '<path fill-rule="evenodd" d="M2 15V5a2 2 0 012-2h12a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2zm2 0V5h7v10H4zm9 0h3V5h-3v10z" clip-rule="evenodd"></path>';
        fs.onclick = ()=>{
            document.querySelector('[data-a-target="player-theatre-mode-button"').click();
        }
        clone.onclick = ()=>{
            document.querySelector('[data-a-target="player-fullscreen-button"').click();
        }
    }

}
§
Posted: 19-11-2020

PS: I didn't implement your last update

§
Posted: 19-11-2020

I had to change "M2 15V5a2 2 0 012-2h12a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2zm2 0V5h7v10H4zm9 0h3V5h-3v10z" to my own for fullscreen to work.

§
Posted: 19-11-2020

I think not everyone has the same html output, can't find some elements for some reason so i had to put my own elements to make this work, it's not universal.

§
Posted: 19-11-2020

@N3ars I made it for french twitch version. You have to change the labels to your language (I didn't have the time to do that for multi language but it shouldn't be that hard)

§
Posted: 19-11-2020

I just updated the script to make it work with any language @N3ars

§
Posted: 19-11-2020

Yes ! Now it is working properly thank you :)

rendAuthor
§
Posted: 19-11-2020

I implemented this into the main version.

§
Posted: 20-11-2020

@rend don't use aria-label to target buttons (since it change for each language)
And keep the interval i put for the observer instantiation because, for some ppl (like me (i'm on edge)) it doesn't work without.

Keep up good work !

§
Posted: 20-11-2020
Edited: 20-11-2020

And don't forget to keep that in the code (for optimizations purposes)

//Opti (we don't care of the numbers updating)
if(mutations.length === 1 && mutations[0].target.classList.contains("tw-animated-number--monospaced"))
   return;
§
Posted: 20-11-2020

And there is no need to use embedy since we can modify iframes content in the script (if twitch or embedy try to block it)
We can load any website and then delete everything in them and replace it by an iframe if we can modify headers of the response requests with the script or an extension (if the website don't allow iframe embedding).

rendAuthor
§
Posted: 20-11-2020

Updated with those fixes

§
Posted: 20-11-2020

Possible to get double click fullscreen to work?

§
Posted: 20-11-2020

I'll work on that in 1h or smthg like that

§
Posted: 20-11-2020
Edited: 20-11-2020

Here is an update enhancind some things @rend if you can put it into master

It adds double click for fullscreen, optimize some things and take the good translation for the Theater button (and add the good placeholder when hovering it), hide the white flash when loading a stream and show stream informations on fullscreen mode

I did put //# after every changed line (you can also use a text diff checker)

(function() {
    if (window.location.origin == "https://cdn.embedly.com") {
        document.getElementsByTagName("html")[0].style = "overflow: hidden";

        window.addEventListener("message", (event) => {
            window.parent.postMessage(event.data, "*");
        });
    }
    else if (window.location.origin == "https://player.twitch.tv") {
        //More optimized
        let interval = setInterval(()=> {//#
            var logo = document.querySelector('[data-a-target="player-twitch-logo-button"]');
            var card = document.getElementsByClassName("tw-card")[0];
            var panel = document.getElementsByClassName("stream-info-social-panel")[0];
            var fullscreenButton = document.querySelector('[data-a-target="player-fullscreen-button"]');

            //We copy the button from the real interface (so we have the title in the good language)
            let realTheaterButton = window.parent.document.querySelector('[data-a-target="player-theatre-mode-button"]');//#


            var theaterButton = realTheaterButton.parentElement.cloneNode(true).getElementsByTagName("button")[0];

            if (!logo || !card || !panel || !fullscreenButton)
                return;

            clearInterval(interval);//#

            logo.remove();
            card.style.display = "none";//#
            window.parent.addEventListener("fullscreenchange", _ => card.style.display = window.parent.document.fullscreenElement ? "" : "none");//#
            panel.remove();

            fullscreenButton.parentElement.parentElement.insertBefore(theaterButton.parentElement, fullscreenButton.parentElement);//#

            //#removed lines here

            theaterButton.removeAttribute('disabled');
            fullscreenButton.removeAttribute('disabled');
            theaterButton.className = theaterButton.className.split("--disabled").join("");
            fullscreenButton.className = fullscreenButton.className.split("--disabled").join("");

            fullscreenButton.onclick = function () {
                window.parent.postMessage("fullscreen", "*");
            }

            theaterButton.onclick = function () {
                window.parent.postMessage("theater", "*");
            }

            //Show the iframe once it is loaded
            window.parent.document.getElementById("embed-adblock").style.visibility = "";//#

            //Double click
            let treshold = 400;//#
            let lastClick = -1;//#
            document.querySelector(".click-handler").addEventListener("click", () => {//#
                let now = Date.now();//#
                //If the user took less than 400ms to double click
                if(now < lastClick+treshold){//#
                    fullscreenButton.click();//Go fullscreen//#

                    //So that if the user triple click, it will not go full screen and then roll back
                    lastClick = -1;//#
                }else//#
                    lastClick = now;//#
            });//#
        }, 50);//#
    } else {
        var lastStreamer, oldHtml;

        window.addEventListener("message", (event) => {
            if (event.data.eventName == 'UPDATE_STATE' && event.data.params.quality) {//#
                if (/^((?:160|360|480|720|1080)p(?:30|60)|chunked)$/.test(event.data.params.quality))//#
                    localStorage.setItem('embAdbQuality', event.data.params.quality);//#
            }else if (event.data == "fullscreen")//#
                document.querySelector(`[data-a-target="player-fullscreen-button"]`).click();
            else if (event.data == "theater")
                document.querySelector(`[data-a-target="player-theatre-mode-button"]`).click();
        });

        var observer = new MutationObserver(function (mutations, observer) {
            var container = document.querySelector(".video-player .tw-absolute");

            if (!container)
                return;

            if (window.location.pathname.indexOf("/directory") == 0)
                return;

            if(mutations.length === 1 && mutations[0].target.classList.contains("tw-animated-number--monospaced"))
                return;

            var streamerName = window.location.pathname.replace("/", "");
            //var twitchUrl = `https://player.twitch.tv/?channel=${streamerName}&muted=false&parent=cdn.embedly.com&quality=chunked`
            //var iframeUrl = `https://cdn.embedly.com/widgets/media.html?src=${encodeURIComponent(twitchUrl)}&type=text%2Fhtml&card=1&schema=twitch`;


            var quality = localStorage["embAdbQuality"] || "chunked";//#
            var iframeUrl = `https://player.twitch.tv/?channel=${streamerName}&muted=false&parent=twitch.tv&quality=${quality}`; //#

            var existingIframe = document.getElementById("embed-adblock");

            if ((!streamerName && !lastStreamer) || streamerName.indexOf("videos/") == 0) {
                lastStreamer = null;

                for (let el of container.children)
                    el.hidden = false;

                if (existingIframe) {
                    existingIframe.src = "";
                    existingIframe.style.visibility = "hidden";//#
                    existingIframe.hidden = true;
                }

                return;
            }
            else if (!streamerName)
                return;

            for (let el of container.children) {
                if (el.tagName != "IFRAME")
                    el.hidden = true;

                if (el.tagName == "VIDEO")
                    el.src = "";
            }

            if (!existingIframe) {
                existingIframe = document.createElement("iframe");
                existingIframe.id = "embed-adblock";
                existingIframe.style = "width: 100%; height: 100%";
                existingIframe.src = iframeUrl;
                //hide iframe when loading (to avoid white screen)
                existingIframe.style.visibility = "hidden";//#
                container.appendChild(existingIframe);
            }
            else if (streamerName != lastStreamer) {
                //hide iframe when loading (to avoid white screen)
                existingIframe.style.visibility = "hidden";//#
                existingIframe.src = iframeUrl;
                existingIframe.hidden = false;
            }

            lastStreamer = streamerName
        });

        var observeInterval = setInterval(() => {
            var observee = document.getElementsByClassName("root-scrollable__wrapper tw-full-width tw-relative")[0];

            if (!observee)
                return;

            observer.observe(observee, { attributes: false, childList: true, subtree: true });
            clearInterval(observeInterval);
        }, 100);
    }
})();
Deleted user 707265
§
Posted: 20-11-2020

Going into the player settings and clicking on one of the options in the submenus trigger the fullscreen for me.
e.g: Settings > Quality > 480p

Also, I've added a bit of code to remember the stream quality


window.addEventListener("message", (event) => {
    if (event.data.eventName == 'UPDATE_STATE' && event.data.params.quality) {
        if (/^((?:160|360|480|720|1080)p(?:30|60)|chunked)$/.test(event.data.params.quality))
            localStorage.setItem('embAdbQuality', event.data.params.quality);
    }
    if (event.data == "fullscreen")
        document.querySelector(`[data-a-target="player-fullscreen-button"]`).click();
    else if (event.data == "theater")
        document.querySelector(`[data-a-target="player-theatre-mode-button"]`).click();
});

...

var quality = localStorage["embAdbQuality"] || "chunked";
var iframeUrl = `https://player.twitch.tv/?channel=${streamerName}&muted=false&parent=twitch.tv&quality=${quality}`; // Not using an intermediate stream for now since it's faster
§
Posted: 20-11-2020

Well done @chway !
I fixed your problem with fullscreen triggering for nothing and implemented your solution in my last post.

(you can copy paste my last post for the fix)

§
Posted: 20-11-2020

Hi there, is it possible to add some features like ffz audio compressor, ffz reset button also info on the top left like viewers, game playing, streamer name on fullscreen mode ... ? thanks !

§
Posted: 20-11-2020
Edited: 20-11-2020

Hey, I added the streamer name and stream informations on fullscreen mode in the last comment I made with code

§
Posted: 20-11-2020

@jean Robert
I did copy past your last code, and now i do have blackscreen with audio only seems like its running but i can't see the stream.

§
Posted: 20-11-2020

@n3ars wait for the update of the main script, it will be easier ^^

§
Posted: 20-11-2020

Yes sure ! Imma wait for the main ^^, question tho do you think this can be patched by twitch or this trick gonna last longer than ttv ublock ?
anyway thank yall for your great work and investment.

§
Posted: 20-11-2020

Do anyone think it would be possible to modify requests made by the original twtch player to make them look like embeded player's requests to bypass ads ?

§
Posted: 20-11-2020

@rend can you please merge ?

§
Posted: 20-11-2020

You guys should make a repo on github just saying ^^

Deleted user 707265
§
Posted: 20-11-2020

else if (event.data.eventName == 'UPDATE_STATE' && event.data.params.quality && !document.hidden) {
    if (/^((?:160|360|480|720|1080)p(?:30|60)|chunked)$/.test(event.data.params.quality))
        window.localStorage.setItem("embedQuality", event.data.params.quality);
}

Twitch lower the quality when a tab loses focus, need the !document.hidden to stop saving the quality during that time.

§
Posted: 21-11-2020
Edited: 21-11-2020

Yes, i've also got this problem ^^
Just, I think you should put 960p & 50fps in your regex so it become
/^((?:160|360|480|720|960|1080)p(?:30|50|60)|chunked)$/
@chway

Deleted user 707265
§
Posted: 21-11-2020

And possibly 48, not sure if Twitch treat those as 50fps or not..

§
Posted: 21-11-2020

Anyone know how to make this work with m.twitch.tv?

rendAuthor
§
Posted: 21-11-2020

https://github.com/r3nderer/Twitch-Embed-Adblock

Changes suggestions can now be made via pull requests.

§
Posted: 22-11-2020
Edited: 22-11-2020

Audio compressor from FFZ is back with these updates, any chance for the Reset Player button?

Post reply

Sign in to post a reply.