Greasy Fork is available in English.

[DEPRECATED] Twitch Adblock

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

< 脚本[DEPRECATED] Twitch Adblock的反馈

评价:好评 - 脚本一切正常

§
发表于:2020-11-19
编辑于:2020-11-20

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();
        }
    }

}
§
发表于:2020-11-19

PS: I didn't implement your last update

§
发表于:2020-11-19

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.

§
发表于:2020-11-19

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.

§
发表于:2020-11-19

@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)

§
发表于:2020-11-19

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

§
发表于:2020-11-19

Yes ! Now it is working properly thank you :)

rend作者
§
发表于:2020-11-19

I implemented this into the main version.

§
发表于:2020-11-20

@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 !

§
发表于:2020-11-20
编辑于:2020-11-20

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;
§
发表于:2020-11-20

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).

rend作者
§
发表于:2020-11-20

Updated with those fixes

§
发表于:2020-11-20

Possible to get double click fullscreen to work?

§
发表于:2020-11-20

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

§
发表于:2020-11-20
编辑于:2020-11-20

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
§
发表于:2020-11-20

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
§
发表于:2020-11-20

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)

§
发表于:2020-11-20

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 !

§
发表于:2020-11-20
编辑于:2020-11-20

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

§
发表于:2020-11-20

@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.

§
发表于:2020-11-20

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

§
发表于:2020-11-20

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.

§
发表于:2020-11-20

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 ?

§
发表于:2020-11-20

@rend can you please merge ?

§
发表于:2020-11-20

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

Deleted user 707265
§
发表于:2020-11-20

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.

§
发表于:2020-11-21
编辑于:2020-11-21

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
§
发表于:2020-11-21

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

§
发表于:2020-11-21

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

rend作者
§
发表于:2020-11-21

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

Changes suggestions can now be made via pull requests.

§
发表于:2020-11-22
编辑于:2020-11-22

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

发表回复

登录以发表回复。