Skip to content
This repository has been archived by the owner on Mar 8, 2021. It is now read-only.

We broke again boys #11

Closed
Jta26 opened this issue Nov 18, 2020 · 175 comments
Closed

We broke again boys #11

Jta26 opened this issue Nov 18, 2020 · 175 comments

Comments

@Jta26
Copy link

Jta26 commented Nov 18, 2020

I'd love to contribute to keeping this updated, cause these ads are killing me.

1.1.1 seems to be not working, McDonald's is telling me to not use the ketchup in my fridge again.

Edit from maintainer:

#11 (comment)

@Bucky420
Copy link

yes it is fully broken

@luffydev
Copy link

having ads again since 20 minutes

@FutureHits3Dev
Copy link

same here... Started getting ads about 10 minutes ago. I can't stream surf.

@julesclear
Copy link

Same here :)

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

Question for @odensc, does this just spoof being an adsense crawler bot by setting the useragent header to Mediapartners-Google? Specifically setting it on the POST to the video-edge ttvnw.net link?

@odensc
Copy link
Owner

odensc commented Nov 18, 2020

Yes it sets the user-agent on any request to ttvnw.net. I've tried a few other UAs that worked before, but they don't seem to anymore. They may have patched the whole user-agent bypass route, but I'm looking into it more when I have the time

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

Yes it sets the user-agent on any request to ttvnw.net. I've tried a few other UAs that worked before, but they don't seem to anymore. They may have patched the whole user-agent bypass route, but I'm looking into it more when I have the time

Yeah, they probably implemented something like this
https://developers.google.com/search/docs/advanced/verifying-googlebot

Edit: they could have also just said screw it and are showing ads to web crawlers, considering that every web crawler user-agent is broken.

@revunix
Copy link

revunix commented Nov 18, 2020

This works for me:
LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com)

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

This works for me:
LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com)

image

It no work :(

@odensc
Copy link
Owner

odensc commented Nov 18, 2020

I've seen people post "working" user agents but they don't work for me. I think their account is just in a period where they have no prerolls because they've already watched one recently. You have to test it in incognito mode.

@nol166
Copy link

nol166 commented Nov 18, 2020

Yes it sets the user-agent on any request to ttvnw.net. I've tried a few other UAs that worked before, but they don't seem to anymore. They may have patched the whole user-agent bypass route, but I'm looking into it more when I have the time

(compatible; Mediapartners-Google/2.1; +http://www.google.com/bot.html)

^ This mobile version of Mediapartners user-agent works for me, anyone else test?

Seems to work so far

Update Doesn't work in incognito

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

I've seen people post "working" user agents but they don't work for me. I think their account is just in a period where they have no prerolls because they've already watched one recently. You have to test it in incognito mode.

I think this is the case too. Neither the linkedin bot or the mobile google bot are working for me. I'm not letting the ads finish playing, so it gives me another one.

@revunix
Copy link

revunix commented Nov 18, 2020

@Jta26 i also have uBlock active. do you?

@codydbgt checked it in incognito mode. LinkedInBot works for me.

PS: reload the plugin

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

@Jta26 i also have uBlock active. do you?

@codydbgt checked it in incognito mode. LinkedInBot works for me.

Yeah, ublock is active, and I'm looking at the request headers and it's in there.

@PSSGCSim
Copy link

They could be rolling out the patch gradually

@odensc
Copy link
Owner

odensc commented Nov 18, 2020

@PSSGCSim Yes this is a possibility too. could be a staged rollout. Previously when people were reporting it as broken last time, it was still working for me until about an hour later.

@revunix
Copy link

revunix commented Nov 18, 2020

Fun fact, i use NextDNS on my iPhone with AdBlock Filter and i never get ads with the official Twitch App ... but what user-agent is it in there app?!

@odensc
Copy link
Owner

odensc commented Nov 18, 2020

@revunix Mobile doesn't seem to have ads as often/at all.

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

@revunix Mobile doesn't seem to have ads as often/at all.

Same thing when running on something like a Chromecast.

@luffydev
Copy link

@revunix Mobile doesn't seem to have ads as often/at all.

I use Twitch app on my Samsung and i've ads

@luffydev
Copy link

LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com) seem to work for me after extension and twitch reloading

@pijcab
Copy link

pijcab commented Nov 18, 2020

God dang it, so we ARE gg for now huh...

@rmanky
Copy link

rmanky commented Nov 18, 2020

Temporary (and very janky) solution that replaces the video with an embed version (that doesn't get prerolls or midrolls?):

Open your Inspector, go to the Console, run the following:

let player = document.querySelector('.video-player')

const [url] = window.location.href.split('/').slice(-1)
player.outerHTML = "<iframe class='video-player' src='https://player.twitch.tv/?channel=" + url + "&parent=www.twitch.tv' data-a-target='video-player' data-a-player-type='site' data-test-selector='video-player__video-layout'></iframe>"
  • Theatre mode doesn't work
  • Switching channels doesn't work (need to refresh the page, and run the script again
  • Midrolls might still come through, haven't watched long enough Watched Hasan for a while, no ad even when he said he was running one...

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

Temporary (and very janky) solution that replaces the video with an embed version (that doesn't get prerolls):

Open your Inspector, got to the Console, run the following:

let player = document.querySelector('.video-player')

const [url] = window.location.href.split('/').slice(-1)
player.outerHTML = "<iframe class='video-player' src='https://player.twitch.tv/?channel=" + url + "&parent=www.twitch.tv' data-a-target='video-player' data-a-player-type='site' data-test-selector='video-player__video-layout'></iframe>"
  • Theatre mode doesn't work
  • Switching channels doesn't work (need to refresh the page, and run the script again
  • Midrolls might still come through, haven't watched long enough

This I think is a good temporary solution

edit:
We can probably fix some of the issues you've lined up. Like listening for a change in the url bar to reload the iframe with the new stream.

@luffydev
Copy link

This works for me:
LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com)

Yes for the moment modifying header in the extension settings and reloading extension and Twitch working for the moment

@revunix
Copy link

revunix commented Nov 18, 2020

This works for me:
LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com)

meh .. i got ads again.

tested now Googlebot/2.1 (+http://www.google.com/bot.html) and this "works"

@revunix Mobile doesn't seem to have ads as often/at all.

@odensc do you have a good user-agent to test?

@justsayingbro
Copy link

I just got out of my cave and discovered theres no ads on the HLS version of twitch (https://twitchls.com), so if you are looking for a good blocker since a week like me, turns out we dont even need one :')
GL guys and keep up the good work bc im pretty sure it won't last long before we get ads here too

@Jta26
Copy link
Author

Jta26 commented Nov 18, 2020

I just got out of my cave and discovered theres no ads on the HLS version of twitch (https://twitchls.com), so if you are looking for a good blocker since a week like me, turns out we dont even need one :')
GL guys and keep up the good work bc im pretty sure it won't last long before we get ads here too

I don't like this one cause I like to switch streams constantly, and the twitch UI is good for that.

edit: incognito w/ ublock and the extension has started working with the linkedin user-agent.

@rmanky
Copy link

rmanky commented Nov 22, 2020

I guess I'll just share my custom version that I've been adding stuff too, here are the changes
(note: this is a tampermonkey script, haven't switched to addon yet)

  • make the live indicator purple instead of red (just an easy way to tell the embed is running)
  • mute the original stream, set it's quality to the lowest, and means that channel points will be collected
  • made some changes around vod/clip detection, still a little jank (the first clip will sometimes show a loading indicator, but will play anyways)
  • remember your audio compressor preference (code borrowed from @ImJeanRobert)

bugs/features that probably won't exist?

  • sometimes if you take a weird path through twitch the embed won't show up, just refresh
  • twitch addons/overlays on top of streams - don't see this happening with the embed method without alot more work
    (we should probably move away from embed method tbh, but it's fun hacking stuff together for now)

here it is:

// ==UserScript==
// @name         Twitch Adblock
// @version      2.0.0
// @description  all my homies hate twitch
// @author       rmanky
// @include      https://www.twitch.tv/*
// @include      https://player.twitch.tv/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

const compressor_off = 'M850 202.3C877.7 202.3 900 224.6 900 252.3V745.5C900 773.2 877.7 795.5 850 795.5S800 773.2 800 745.5V252.3C800 224.6 822.3 202.3 850 202.3ZM570 167.8C597.7 167.8 620 190.1 620 217.8V780C620 807.7 597.7 830 570 830S520 807.7 520 780V217.8C520 190.1 542.3 167.8 570 167.8ZM710 264.4C737.7 264.4 760 286.7 760 314.4V683.3C760 711 737.7 733.3 710 733.3S660 711 660 683.3V314.4C660 286.7 682.3 264.4 710 264.4ZM430 98.1C457.7 98.1 480 120.4 480 148.1V849.6C480 877.3 457.7 899.6 430 899.6S380 877.3 380 849.6V148.1C380 120.4 402.3 98.1 430 98.1ZM290 217.2C317.7 217.2 340 239.5 340 267.2V730.5C340 758.2 317.7 780.5 290 780.5S240 758.2 240 730.5V267.2C240 239.5 262.3 217.2 290 217.2ZM150 299.6C177.7 299.6 200 321.9 200 349.6V648.1C200 675.8 177.7 698.1 150 698.1S100 675.8 100 648.1V349.6C100 321.9 122.3 299.6 150 299.6Z';
const compressor_on = 'M850 200C877.7 200 900 222.3 900 250V750C900 777.7 877.7 800 850 800S800 777.7 800 750V250C800 222.3 822.3 200 850 200ZM570 250C597.7 250 620 272.3 620 300V700C620 727.7 597.7 750 570 750S520 727.7 520 700V300C520 272.3 542.3 250 570 250ZM710 225C737.7 225 760 247.3 760 275V725C760 752.7 737.7 775 710 775S660 752.7 660 725V275C660 247.3 682.3 225 710 225ZM430 250C457.7 250 480 272.3 480 300V700C480 727.7 457.7 750 430 750S380 727.7 380 700V300C380 272.3 402.3 250 430 250ZM290 225C317.7 225 340 247.3 340 275V725C340 752.7 317.7 775 290 775S240 752.7 240 725V275C240 247.3 262.3 225 290 225ZM150 200C177.7 200 200 222.3 200 250V750C200 777.7 177.7 800 150 800S100 777.7 100 750V250C100 222.3 122.3 200 150 200Z';

(function() {
    if (window.location.origin == "https://player.twitch.tv") {
        var modified = false;

        var observer = new MutationObserver(function (mutations, observer) {
            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 settingsButton = document.querySelector('[data-a-target="player-settings-button"]');
            var fullscreenButton = document.querySelector('[data-a-target="player-fullscreen-button"]');
            var clickHandler = document.querySelector('.click-handler');
            var live = document.querySelector('.tw-channel-status-text-indicator--live');

            if (!logo || !card || !panel || !fullscreenButton || !live || !settingsButton || !clickHandler)
                return;

            var theaterButton = settingsButton.parentElement.cloneNode(true).querySelector("button");

            observer.disconnect();

            logo.remove();
            card.remove();
            panel.remove();

            live.style.backgroundColor = "var(--color-accent)";

            theaterButton.getElementsByTagName("g")[0].innerHTML = `<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>`;
            theaterButton.parentElement.getElementsByClassName("tw-tooltip")[0].innerHTML = 'Theatre Mode (alt+t)';
            fullscreenButton.parentElement.parentElement.insertBefore(theaterButton.parentElement, fullscreenButton.parentElement);

            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", "*");
            }

            clickHandler.ondblclick = function () {
                window.parent.postMessage("fullscreen", "*");
            }

            //Mute button is just needed to grab parent for placement
            var muteButton = document.querySelector('[data-a-target="player-mute-unmute-button"]');
            var compressorButton = muteButton.parentElement.cloneNode(true);
            //On Chrome atleast, appending to end is fine and places it to right of volume slides
            muteButton.parentElement.parentElement.appendChild(compressorButton);

            //Formatting stuff
            compressorButton.querySelector(".tw-tooltip").innerText = 'Audio Compressor';
            compressorButton.querySelector("svg").setAttribute("viewBox", "0 0 1000 1000");
            compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_off}" clip-rule="evenodd"></path>`;
            compressorButton.setAttribute("data-active", 'false');

            let video = document.querySelector('video');
            video.context = new window.AudioContext();
            video.source = video.context.createMediaElementSource(video);
            video.compressor = video.context.createDynamicsCompressor();

            //Default values from FFZ
            video.compressor.threshold.setValueAtTime(-50, video.context.currentTime);
            video.compressor.knee.setValueAtTime(40, video.context.currentTime);
            video.compressor.ratio.setValueAtTime(12, video.context.currentTime);
            video.compressor.attack.setValueAtTime(0, video.context.currentTime);
            video.compressor.release.setValueAtTime(0.25, video.context.currentTime);

            //Compressor is disabled by default, can prob store preference locally if needed
            video.source.connect(video.context.destination);

            compressorButton.onclick = function () {
                const active = compressorButton.getAttribute('data-active');
                toggleCompressor(compressorButton, video, active);
            }

            const initial = localStorage.getItem('compressor');
            if(initial && initial === 'false') {
                toggleCompressor(compressorButton, video, initial);
            }
        });

        observer.observe(document.body, { attributes: false, childList: true, subtree: true });
    }
    else {
        var lastStreamer, oldHtml;

        window.addEventListener("message", (event) => {
            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") != -1)
                return;

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

            var streamerName = window.location.pathname.replace("/", "");
            var quality = "chunked";
            //var twitchUrl = `https://player.twitch.tv/?channel=${streamerName}&muted=false&parent=cdn.embedly.com&quality=${quality}`
            //var iframeUrl = `https://cdn.embedly.com/widgets/media.html?src=${encodeURIComponent(twitchUrl)}&type=text%2Fhtml&card=1&schema=twitch`;
            var iframeUrl = `https://player.twitch.tv/?channel=${streamerName.split("/")[0]}&muted=false&parent=twitch.tv&quality=${quality}`; // Not using an intermediate stream for now since it's faster
            var existingIframe = document.getElementById("embed-adblock");

            if ((!streamerName && !lastStreamer) || videoOrClip()) {
                lastStreamer = null;

                for (let el of container.children) {
                    el.hidden = false;
                    if(el.tagName == "VIDEO") {
                        el.muted = false;
                    }
                }

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

                return;
            }
            else if (!streamerName) {
                return;
            }

            for (let el of container.children) {
                //don't set the src to empty, instead mute it and click a few buttons to set the quality
                if (el.tagName != "IFRAME" && el.tagName != "VIDEO") {
                    el.hidden = true;
                }
                else if (el.tagName == "VIDEO" && window.location.pathname.indexOf("/videos/") == -1) {
                    el.muted = true;
                    el.onloadedmetadata = function() {
                        if(document.querySelector('[data-a-target="player-settings-button"]') && !videoOrClip()) {
                            document.querySelector('[data-a-target="player-settings-button"]').click();
                        }
                        // time values are arbitrary, could use more mutationObservers but idk...
                        // maybe a set
                        let menu = setInterval(() => {
                            if(document.querySelector('[data-a-target="player-settings-menu-item-quality"]') && !videoOrClip()) {
                                document.querySelector('[data-a-target="player-settings-menu-item-quality"]').click();
                                clearInterval(menu);
                            }
                        }, 1000);
                        let quality = setInterval(() => {
                            if(document.querySelector('[data-a-target="player-settings-menu"]') && !videoOrClip()) {
                                document.querySelector('[data-a-target="player-settings-menu"]').lastChild.querySelector('input').click();
                                // if we were already on lowest, just close the menu
                                if(document.querySelector('[data-a-target="player-settings-menu-item-quality"]')) {
                                    document.querySelector('[data-a-target="player-settings-button"]').click();
                                }
                                clearInterval(quality);
                            }
                        }, 1000);

                        return true;
                    }
                }
            }

            if (!existingIframe) {
                existingIframe = document.createElement("iframe");
                existingIframe.id = "embed-adblock";
                existingIframe.style = "width: 100%; height: 100%; visibility: hidden;"
                existingIframe.src = iframeUrl;
                existingIframe.onload = () => { existingIframe.style.visibility = "visible"; };

                // Put the iframe first, instead of last
                container.prepend(existingIframe);
            }
            else if (streamerName != lastStreamer) {
                existingIframe.src = iframeUrl;
                existingIframe.hidden = false;
            }

            lastStreamer = streamerName
        });

        observer.observe(document.getElementsByClassName("root-scrollable__wrapper tw-full-width tw-relative")[0], { attributes: false, childList: true, subtree: true });
    }
})();

function toggleCompressor(compressorButton, video, active) {
    if(active === 'false') {
        localStorage.setItem('compressor', 'false');
        compressorButton.querySelector(".tw-tooltip").innerText = 'Disable Audio Compressor';
        compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_on}" clip-rule="evenodd"></path>`;
        compressorButton.setAttribute('data-active', 'true');
        video.source.disconnect(video.context.destination);
        video.source.connect(video.compressor);
        video.compressor.connect(video.context.destination);
    } else if(active === 'true'){
        localStorage.setItem('compressor', 'true');
        compressorButton.querySelector(".tw-tooltip").innerText = 'Audio Compressor';
        compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_off}" clip-rule="evenodd"></path>`;
        compressorButton.setAttribute('data-active', 'false');
        video.compressor.disconnect(video.context.destination);
        video.source.disconnect(video.compressor);
        video.source.connect(video.context.destination);
    }
}

function videoOrClip() {
    return window.location.pathname.indexOf("/videos/") != -1 || window.location.pathname.indexOf("/clip/") != -1;
}

I got homework to do, so will probably be afk for a while

@djg007
Copy link

djg007 commented Nov 22, 2020

I guess I'll just share my custom version that I've been adding stuff too, here are the changes
(note: this is a tampermonkey script, haven't switched to addon yet)

  • make the live indicator purple instead of red (just an easy way to tell the embed is running)
  • mute the original stream, set it's quality to the lowest, and means that channel points will be collected
  • made some changes around vod/clip detection, still a little jank (the first clip will sometimes show a loading indicator, but will play anyways)
  • remember your audio compressor preference (code borrowed from @ImJeanRobert)

bugs/features that probably won't exist?

  • sometimes if you take a weird path through twitch the embed won't show up, just refresh
  • twitch addons/overlays on top of streams - don't see this happening with the embed method without alot more work
    (we should probably move away from embed method tbh, but it's fun hacking stuff together for now)

here it is:

// ==UserScript==
// @name         Twitch Adblock
// @version      2.0.0
// @description  all my homies hate twitch
// @author       rmanky
// @include      https://www.twitch.tv/*
// @include      https://player.twitch.tv/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

const compressor_off = 'M850 202.3C877.7 202.3 900 224.6 900 252.3V745.5C900 773.2 877.7 795.5 850 795.5S800 773.2 800 745.5V252.3C800 224.6 822.3 202.3 850 202.3ZM570 167.8C597.7 167.8 620 190.1 620 217.8V780C620 807.7 597.7 830 570 830S520 807.7 520 780V217.8C520 190.1 542.3 167.8 570 167.8ZM710 264.4C737.7 264.4 760 286.7 760 314.4V683.3C760 711 737.7 733.3 710 733.3S660 711 660 683.3V314.4C660 286.7 682.3 264.4 710 264.4ZM430 98.1C457.7 98.1 480 120.4 480 148.1V849.6C480 877.3 457.7 899.6 430 899.6S380 877.3 380 849.6V148.1C380 120.4 402.3 98.1 430 98.1ZM290 217.2C317.7 217.2 340 239.5 340 267.2V730.5C340 758.2 317.7 780.5 290 780.5S240 758.2 240 730.5V267.2C240 239.5 262.3 217.2 290 217.2ZM150 299.6C177.7 299.6 200 321.9 200 349.6V648.1C200 675.8 177.7 698.1 150 698.1S100 675.8 100 648.1V349.6C100 321.9 122.3 299.6 150 299.6Z';
const compressor_on = 'M850 200C877.7 200 900 222.3 900 250V750C900 777.7 877.7 800 850 800S800 777.7 800 750V250C800 222.3 822.3 200 850 200ZM570 250C597.7 250 620 272.3 620 300V700C620 727.7 597.7 750 570 750S520 727.7 520 700V300C520 272.3 542.3 250 570 250ZM710 225C737.7 225 760 247.3 760 275V725C760 752.7 737.7 775 710 775S660 752.7 660 725V275C660 247.3 682.3 225 710 225ZM430 250C457.7 250 480 272.3 480 300V700C480 727.7 457.7 750 430 750S380 727.7 380 700V300C380 272.3 402.3 250 430 250ZM290 225C317.7 225 340 247.3 340 275V725C340 752.7 317.7 775 290 775S240 752.7 240 725V275C240 247.3 262.3 225 290 225ZM150 200C177.7 200 200 222.3 200 250V750C200 777.7 177.7 800 150 800S100 777.7 100 750V250C100 222.3 122.3 200 150 200Z';

(function() {
    if (window.location.origin == "https://player.twitch.tv") {
        var modified = false;

        var observer = new MutationObserver(function (mutations, observer) {
            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 settingsButton = document.querySelector('[data-a-target="player-settings-button"]');
            var fullscreenButton = document.querySelector('[data-a-target="player-fullscreen-button"]');
            var clickHandler = document.querySelector('.click-handler');
            var live = document.querySelector('.tw-channel-status-text-indicator--live');

            if (!logo || !card || !panel || !fullscreenButton || !live || !settingsButton || !clickHandler)
                return;

            var theaterButton = settingsButton.parentElement.cloneNode(true).querySelector("button");

            observer.disconnect();

            logo.remove();
            card.remove();
            panel.remove();

            live.style.backgroundColor = "var(--color-accent)";

            theaterButton.getElementsByTagName("g")[0].innerHTML = `<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>`;
            theaterButton.parentElement.getElementsByClassName("tw-tooltip")[0].innerHTML = 'Theatre Mode (alt+t)';
            fullscreenButton.parentElement.parentElement.insertBefore(theaterButton.parentElement, fullscreenButton.parentElement);

            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", "*");
            }

            clickHandler.ondblclick = function () {
                window.parent.postMessage("fullscreen", "*");
            }

            //Mute button is just needed to grab parent for placement
            var muteButton = document.querySelector('[data-a-target="player-mute-unmute-button"]');
            var compressorButton = muteButton.parentElement.cloneNode(true);
            //On Chrome atleast, appending to end is fine and places it to right of volume slides
            muteButton.parentElement.parentElement.appendChild(compressorButton);

            //Formatting stuff
            compressorButton.querySelector(".tw-tooltip").innerText = 'Audio Compressor';
            compressorButton.querySelector("svg").setAttribute("viewBox", "0 0 1000 1000");
            compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_off}" clip-rule="evenodd"></path>`;
            compressorButton.setAttribute("data-active", 'false');

            let video = document.querySelector('video');
            video.context = new AudioContext();
            video.source = video.context.createMediaElementSource(video);
            video.compressor = video.context.createDynamicsCompressor();

            //Default values from FFZ
            video.compressor.threshold.setValueAtTime(-50, video.context.currentTime);
            video.compressor.knee.setValueAtTime(40, video.context.currentTime);
            video.compressor.ratio.setValueAtTime(12, video.context.currentTime);
            video.compressor.attack.setValueAtTime(0, video.context.currentTime);
            video.compressor.release.setValueAtTime(0.25, video.context.currentTime);

            //Compressor is disabled by default, can prob store preference locally if needed
            video.source.connect(video.context.destination);

            compressorButton.onclick = function () {
                const active = compressorButton.getAttribute('data-active');
                toggleCompressor(compressorButton, video, active);
            }

            const initial = localStorage.getItem('compressor');
            if(initial && initial === 'false') {
                toggleCompressor(compressorButton, video, initial);
            }
        });

        observer.observe(document.body, { attributes: false, childList: true, subtree: true });
    }
    else {
        var lastStreamer, oldHtml;

        window.addEventListener("message", (event) => {
            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") != -1)
                return;

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

            var streamerName = window.location.pathname.replace("/", "");
            var quality = "chunked";
            //var twitchUrl = `https://player.twitch.tv/?channel=${streamerName}&muted=false&parent=cdn.embedly.com&quality=${quality}`
            //var iframeUrl = `https://cdn.embedly.com/widgets/media.html?src=${encodeURIComponent(twitchUrl)}&type=text%2Fhtml&card=1&schema=twitch`;
            var iframeUrl = `https://player.twitch.tv/?channel=${streamerName.split("/")[0]}&muted=false&parent=twitch.tv&quality=${quality}`; // Not using an intermediate stream for now since it's faster
            var existingIframe = document.getElementById("embed-adblock");

            if ((!streamerName && !lastStreamer) || videoOrClip()) {
                lastStreamer = null;

                for (let el of container.children) {
                    el.hidden = false;
                    if(el.tagName == "VIDEO") {
                        el.muted = false;
                    }
                }

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

                return;
            }
            else if (!streamerName) {
                return;
            }

            for (let el of container.children) {
                //don't set the src to empty, instead mute it and click a few buttons to set the quality
                if (el.tagName != "IFRAME" && el.tagName != "VIDEO") {
                    el.hidden = true;
                }
                else if (el.tagName == "VIDEO" && window.location.pathname.indexOf("/videos/") == -1) {
                    el.muted = true;
                    el.onloadedmetadata = function() {
                        if(document.querySelector('[data-a-target="player-settings-button"]') && !videoOrClip()) {
                            document.querySelector('[data-a-target="player-settings-button"]').click();
                        }
                        // time values are arbitrary, could use more mutationObservers but idk...
                        // maybe a set
                        let menu = setInterval(() => {
                            if(document.querySelector('[data-a-target="player-settings-menu-item-quality"]') && !videoOrClip()) {
                                document.querySelector('[data-a-target="player-settings-menu-item-quality"]').click();
                                clearInterval(menu);
                            }
                        }, 1000);
                        let quality = setInterval(() => {
                            if(document.querySelector('[data-a-target="player-settings-menu"]') && !videoOrClip()) {
                                document.querySelector('[data-a-target="player-settings-menu"]').lastChild.querySelector('input').click();
                                // if we were already on lowest, just close the menu
                                if(document.querySelector('[data-a-target="player-settings-menu-item-quality"]')) {
                                    document.querySelector('[data-a-target="player-settings-button"]').click();
                                }
                                clearInterval(quality);
                            }
                        }, 1000);

                        return true;
                    }
                }
            }

            if (!existingIframe) {
                existingIframe = document.createElement("iframe");
                existingIframe.id = "embed-adblock";
                existingIframe.style = "width: 100%; height: 100%; visibility: hidden;"
                existingIframe.src = iframeUrl;
                existingIframe.onload = () => { existingIframe.style.visibility = "visible"; };

                // Put the iframe first, instead of last
                container.prepend(existingIframe);
            }
            else if (streamerName != lastStreamer) {
                existingIframe.src = iframeUrl;
                existingIframe.hidden = false;
            }

            lastStreamer = streamerName
        });

        observer.observe(document.getElementsByClassName("root-scrollable__wrapper tw-full-width tw-relative")[0], { attributes: false, childList: true, subtree: true });
    }
})();

function toggleCompressor(compressorButton, video, active) {
    if(active === 'false') {
        localStorage.setItem('compressor', 'false');
        compressorButton.querySelector(".tw-tooltip").innerText = 'Disable Audio Compressor';
        compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_on}" clip-rule="evenodd"></path>`;
        compressorButton.setAttribute('data-active', 'true');
        video.source.disconnect(video.context.destination);
        video.source.connect(video.compressor);
        video.compressor.connect(video.context.destination);
    } else if(active === 'true'){
        localStorage.setItem('compressor', 'true');
        compressorButton.querySelector(".tw-tooltip").innerText = 'Audio Compressor';
        compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_off}" clip-rule="evenodd"></path>`;
        compressorButton.setAttribute('data-active', 'false');
        video.compressor.disconnect(video.context.destination);
        video.source.disconnect(video.compressor);
        video.source.connect(video.context.destination);
    }
}

function videoOrClip() {
    return window.location.pathname.indexOf("/videos/") != -1 || window.location.pathname.indexOf("/clip/") != -1;
}

I got homework to do, so will probably be afk for a while

Thank You So Much! Having the compressor is a life saver. Appreciate everyone in here for all their work.

@MisterIkkus
Copy link

MisterIkkus commented Nov 22, 2020

Where do I put that code? This is all quite confusing to me, so I appreciate any help of how to use the current best fix.

@djg007
Copy link

djg007 commented Nov 22, 2020

Where do I put that code? This is all quite confusing to me, so I appreciate any help of how to use the current best fix.

Need the browser extension TamperMonkey. Create a new script and paste the code.

@MisterIkkus
Copy link

Thanks very much!

@ImJeanRobert
Copy link

@DKnightX91 These errors do not do anything so no need to catch them ^^
@rmanky There is now a repo for the TamperMonkey script, please go there to share updates ^^

@strong-code
Copy link
Contributor

Updated @rmanky script to fix the button ordering (maybe this was only a Firefox issue?) and add the FFZ middle click to mute functionality. Would love if chrome/brave users could test and make sure nothing is broken. Also we should probably have a new repo to track the changes for the userscript since this original repo is out of scope at this point. Maybe submit a PR to @r3nderer repo?

This includes the version bump 2.0.0 -> 2.1.0 as well

// ==UserScript==
// @name         Twitch Adblock
// @version      2.1.0
// @description  all my homies hate twitch
// @author       rmanky
// @include      https://www.twitch.tv/*
// @include      https://player.twitch.tv/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

const compressor_off = 'M850 202.3C877.7 202.3 900 224.6 900 252.3V745.5C900 773.2 877.7 795.5 850 795.5S800 773.2 800 745.5V252.3C800 224.6 822.3 202.3 850 202.3ZM570 167.8C597.7 167.8 620 190.1 620 217.8V780C620 807.7 597.7 830 570 830S520 807.7 520 780V217.8C520 190.1 542.3 167.8 570 167.8ZM710 264.4C737.7 264.4 760 286.7 760 314.4V683.3C760 711 737.7 733.3 710 733.3S660 711 660 683.3V314.4C660 286.7 682.3 264.4 710 264.4ZM430 98.1C457.7 98.1 480 120.4 480 148.1V849.6C480 877.3 457.7 899.6 430 899.6S380 877.3 380 849.6V148.1C380 120.4 402.3 98.1 430 98.1ZM290 217.2C317.7 217.2 340 239.5 340 267.2V730.5C340 758.2 317.7 780.5 290 780.5S240 758.2 240 730.5V267.2C240 239.5 262.3 217.2 290 217.2ZM150 299.6C177.7 299.6 200 321.9 200 349.6V648.1C200 675.8 177.7 698.1 150 698.1S100 675.8 100 648.1V349.6C100 321.9 122.3 299.6 150 299.6Z';
const compressor_on = 'M850 200C877.7 200 900 222.3 900 250V750C900 777.7 877.7 800 850 800S800 777.7 800 750V250C800 222.3 822.3 200 850 200ZM570 250C597.7 250 620 272.3 620 300V700C620 727.7 597.7 750 570 750S520 727.7 520 700V300C520 272.3 542.3 250 570 250ZM710 225C737.7 225 760 247.3 760 275V725C760 752.7 737.7 775 710 775S660 752.7 660 725V275C660 247.3 682.3 225 710 225ZM430 250C457.7 250 480 272.3 480 300V700C480 727.7 457.7 750 430 750S380 727.7 380 700V300C380 272.3 402.3 250 430 250ZM290 225C317.7 225 340 247.3 340 275V725C340 752.7 317.7 775 290 775S240 752.7 240 725V275C240 247.3 262.3 225 290 225ZM150 200C177.7 200 200 222.3 200 250V750C200 777.7 177.7 800 150 800S100 777.7 100 750V250C100 222.3 122.3 200 150 200Z';

(function() {
    if (window.location.origin == "https://player.twitch.tv") {
        var modified = false;

        var observer = new MutationObserver(function (mutations, observer) {
            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 settingsButton = document.querySelector('[data-a-target="player-settings-button"]');
            var fullscreenButton = document.querySelector('[data-a-target="player-fullscreen-button"]');
            var clickHandler = document.querySelector('.click-handler');
            var live = document.querySelector('.tw-channel-status-text-indicator--live');

            if (!logo || !card || !panel || !fullscreenButton || !live || !settingsButton || !clickHandler)
                return;

            var theaterButton = settingsButton.parentElement.cloneNode(true).querySelector("button");

            observer.disconnect();

            logo.remove();
            card.remove();
            panel.remove();

            live.style.backgroundColor = "var(--color-accent)";

            theaterButton.getElementsByTagName("g")[0].innerHTML = `<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>`;
            theaterButton.parentElement.getElementsByClassName("tw-tooltip")[0].innerHTML = 'Theatre Mode (alt+t)';
            fullscreenButton.parentElement.insertBefore(theaterButton.parentElement, fullscreenButton);

            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", "*");
            }

            clickHandler.ondblclick = function () {
                window.parent.postMessage("fullscreen", "*");
            }

            //Mute button is just needed to grab parent for placement
            var muteButton = document.querySelector('[data-a-target="player-mute-unmute-button"]');
            var compressorButton = muteButton.parentElement.cloneNode(true);
            //On Chrome atleast, appending to end is fine and places it to right of volume slides
            muteButton.parentElement.parentElement.appendChild(compressorButton);

            //Formatting stuff
            compressorButton.querySelector(".tw-tooltip").innerText = 'Audio Compressor';
            compressorButton.querySelector("svg").setAttribute("viewBox", "0 0 1000 1000");
            compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_off}" clip-rule="evenodd"></path>`;
            compressorButton.setAttribute("data-active", 'false');

            let video = document.querySelector('video');
            video.context = new AudioContext();
            video.source = video.context.createMediaElementSource(video);
            video.compressor = video.context.createDynamicsCompressor();

            //Default values from FFZ
            video.compressor.threshold.setValueAtTime(-50, video.context.currentTime);
            video.compressor.knee.setValueAtTime(40, video.context.currentTime);
            video.compressor.ratio.setValueAtTime(12, video.context.currentTime);
            video.compressor.attack.setValueAtTime(0, video.context.currentTime);
            video.compressor.release.setValueAtTime(0.25, video.context.currentTime);

            //Compressor is disabled by default, can prob store preference locally if needed
            video.source.connect(video.context.destination);

            compressorButton.onclick = function () {
                const active = compressorButton.getAttribute('data-active');
                toggleCompressor(compressorButton, video, active);
            }

            //Middle-click on iframe for mute/unmute
            document.onmousedown = function(e) {
                if (e && (e.which == 2 || e.button == 4 )) {
                    e.preventDefault();
                    muteButton.click();
                }
            }

            const initial = localStorage.getItem('compressor');
            if(initial && initial === 'false') {
                toggleCompressor(compressorButton, video, initial);
            }
        });

        observer.observe(document.body, { attributes: false, childList: true, subtree: true });
    }
    else {
        var lastStreamer, oldHtml;

        window.addEventListener("message", (event) => {
            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") != -1)
                return;

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

            var streamerName = window.location.pathname.replace("/", "");
            var quality = "chunked";
            //var twitchUrl = `https://player.twitch.tv/?channel=${streamerName}&muted=false&parent=cdn.embedly.com&quality=${quality}`
            //var iframeUrl = `https://cdn.embedly.com/widgets/media.html?src=${encodeURIComponent(twitchUrl)}&type=text%2Fhtml&card=1&schema=twitch`;
            var iframeUrl = `https://player.twitch.tv/?channel=${streamerName.split("/")[0]}&muted=false&parent=twitch.tv&quality=${quality}`; // Not using an intermediate stream for now since it's faster
            var existingIframe = document.getElementById("embed-adblock");

            if ((!streamerName && !lastStreamer) || videoOrClip()) {
                lastStreamer = null;

                for (let el of container.children) {
                    el.hidden = false;
                    if(el.tagName == "VIDEO") {
                        el.muted = false;
                    }
                }

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

                return;
            }
            else if (!streamerName) {
                return;
            }

            for (let el of container.children) {
                //don't set the src to empty, instead mute it and click a few buttons to set the quality
                if (el.tagName != "IFRAME" && el.tagName != "VIDEO") {
                    el.hidden = true;
                }
                else if (el.tagName == "VIDEO" && window.location.pathname.indexOf("/videos/") == -1) {
                    el.muted = true;
                    el.onloadedmetadata = function() {
                        if(document.querySelector('[data-a-target="player-settings-button"]') && !videoOrClip()) {
                            document.querySelector('[data-a-target="player-settings-button"]').click();
                        }
                        // time values are arbitrary, could use more mutationObservers but idk...
                        // maybe a set
                        let menu = setInterval(() => {
                            if(document.querySelector('[data-a-target="player-settings-menu-item-quality"]') && !videoOrClip()) {
                                document.querySelector('[data-a-target="player-settings-menu-item-quality"]').click();
                                clearInterval(menu);
                            }
                        }, 1000);
                        let quality = setInterval(() => {
                            if(document.querySelector('[data-a-target="player-settings-menu"]') && !videoOrClip()) {
                                document.querySelector('[data-a-target="player-settings-menu"]').lastChild.querySelector('input').click();
                                // if we were already on lowest, just close the menu
                                if(document.querySelector('[data-a-target="player-settings-menu-item-quality"]')) {
                                    document.querySelector('[data-a-target="player-settings-button"]').click();
                                }
                                clearInterval(quality);
                            }
                        }, 1000);

                        return true;
                    }
                }
            }

            if (!existingIframe) {
                existingIframe = document.createElement("iframe");
                existingIframe.id = "embed-adblock";
                existingIframe.style = "width: 100%; height: 100%; visibility: hidden;"
                existingIframe.src = iframeUrl;
                existingIframe.onload = () => { existingIframe.style.visibility = "visible"; };

                // Put the iframe first, instead of last
                container.prepend(existingIframe);
            }
            else if (streamerName != lastStreamer) {
                existingIframe.src = iframeUrl;
                existingIframe.hidden = false;
            }

            lastStreamer = streamerName
        });

        observer.observe(document.getElementsByClassName("root-scrollable__wrapper tw-full-width tw-relative")[0], { attributes: false, childList: true, subtree: true });
    }
})();

function toggleCompressor(compressorButton, video, active) {
    if(active === 'false') {
        localStorage.setItem('compressor', 'false');
        compressorButton.querySelector(".tw-tooltip").innerText = 'Disable Audio Compressor';
        compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_on}" clip-rule="evenodd"></path>`;
        compressorButton.setAttribute('data-active', 'true');
        video.source.disconnect(video.context.destination);
        video.source.connect(video.compressor);
        video.compressor.connect(video.context.destination);
    } else if(active === 'true'){
        localStorage.setItem('compressor', 'true');
        compressorButton.querySelector(".tw-tooltip").innerText = 'Audio Compressor';
        compressorButton.querySelector("g").innerHTML = `<path fill-rule="evenodd" d="${compressor_off}" clip-rule="evenodd"></path>`;
        compressorButton.setAttribute('data-active', 'false');
        video.compressor.disconnect(video.context.destination);
        video.source.disconnect(video.compressor);
        video.source.connect(video.context.destination);
    }
}

function videoOrClip() {
    return window.location.pathname.indexOf("/videos/") != -1 || window.location.pathname.indexOf("/clip/") != -1;
}

@rmanky
Copy link

rmanky commented Nov 22, 2020

@r3nderer Opened a pull request, tested briefly on Firefox and Chrome

@nexerq
Copy link

nexerq commented Nov 22, 2020

I currently don't have time to open a PR, but what seems to be working for me is:

  • Set Origin on video-weaver urls to https://player.twitch.tv (should be just adding an extra if to modify header)
  • Use https://github.com/odensc/ttv-ublock/blob/main/twitch-videoad.js again, with player type param set to embed (this should probably be put into the extension itself to avoid messing with ublock settings)

@odensc
Copy link
Owner

odensc commented Nov 22, 2020

@nexerq Thanks, this is what I suspected would work but haven't had the time to test it yet. I'll see if I can implement this into the extension tonight.

@godrider
Copy link

Can this feature from FFZ be implemented? It stops working with the 1.4.1 userscript.

9klDgxk

@odensc
Copy link
Owner

odensc commented Nov 22, 2020

Hi folks, sorry for my small hiatus. 2.0.0 has now been released which fakes being an embedded player through a slightly less invasive method than the UserScript (thanks everybody for their work on that in the meantime!).

Firefox:

New version has already been approved in the addon store. Go to your Addons page and check for updates in the top right.

image

Chrome:

The version in the extension store was approved. It should have automatically updated (check if the version is 2.0.0). If not - removing and reinstalling the extension should update it.


If you don't have the extension installed already, please check the README for instructions.

@odensc odensc closed this as completed Nov 22, 2020
@pquerner
Copy link

And ads are back..

@r3ndd
Copy link

r3ndd commented Nov 22, 2020

I don't think the version in the store has updated yet. The zip folder in the above post is working for me.

@rmanky
Copy link

rmanky commented Nov 22, 2020

the userscript was fun while it lasted 😄

@odensc
Copy link
Owner

odensc commented Nov 22, 2020

@pquerner Any info? Like... Chrome/Firefox? Using the UserScript or extension? Extension version? You should not be seeing ads with the latest version of the extension.

@TwinDragon
Copy link

If you're on Firefox, you may have to actually remove and then add the extension again to update. Firefox is weird with extension updates for reasons unknown to me.

@pquerner
Copy link

Using the userscript v1.4.1 on Chrome Version 86.0.4240.80.
I'll try updating the browseraddon. (I thought it now used the same embed workaround?)

@odensc
Copy link
Owner

odensc commented Nov 22, 2020

@pquerner The extension now fakes being an embed player via network interception (overriding the Origin header), instead of actually putting an embedded Twitch player inside the page. Same idea, different route.

@sean8102
Copy link

sean8102 commented Nov 22, 2020

I'm on Edge Chromium with the extension installed from the chrome web store. It auto updated to version 2 and once again is working perfectly. I removed the Tampermonkey scripts I was using in the meantime so it's just Ublock and this extension doing the job. Thank you so much for your quick work fighting Twitch's battle on ad blocking. Funny enough I just got blocked from a chat for 30 seconds by automod for mentioning ad blocking lol.

@hiccups1980
Copy link

@odensc
What about channel points? Embed won't let us collect those.

@Johnex
Copy link

Johnex commented Nov 22, 2020

@hiccups1980 I am using the updated extension with embed and i'm getting channel points without issues...

@Suprcheese
Copy link

What about channel points? Embed won't let us collect those.

Channel points accrual has been functioning for me, FWIW.

@FlaminSarge
Copy link

Any idea about Drops functioning/not functioning with this enabled? E.g. Overwatch has a 'watch 6 hours, get some in-game stuff' thing going on right now, so it'd be a good time to test that. I already got my drops back when the ext stopped working (after which I disabled it), otherwise I'd do so.

@odensc
Copy link
Owner

odensc commented Nov 24, 2020

@FlaminSarge you'd have to try, but I don't see why it wouldn't work

@Suprcheese
Copy link

I can confirm drops are working just fine when using v2.1.0 of this extension.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.