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?

返信を投稿

返信を投稿するにはログインしてください。