kwaschny / unwanted-twitch

Hide unwanted streams, games, categories and channels on: twitch.tv
https://kwaschny.net
MIT License
102 stars 13 forks source link

Support for all tags instead of just the visible ones #124

Open Kryptortio opened 1 month ago

Kryptortio commented 1 month ago

I realize this is listed as not possible in the FAQ but I'll provide a POC snippet that shows how it can be done.

(function() {
    let channels = document.querySelectorAll('.tw-tower > div:not(#directory-rectangle)');

    let reactVar = Object.keys(channels[0]).filter(
            function(el, ind, arr){ return el.startsWith('__reactFiber$') }
        )[0];

    channels.forEach((el) => {
        let node = el.firstChild.firstChild;
        let tags = node[reactVar].child.memoizedProps.cardProps.tagListProps.freeformTags;
        let taglist = '';
        tags.forEach((tagEl) => {
            taglist += `${tagEl.name} (${tagEl.id}),`
        });
        console.log(taglist);
    });
})();

The snippet above looks at the React component properties to get all the tags and prints a list. This should be possible to integrate in the add-on instead of just looping over the visible tags. Also the technique could solve other similar issues. For example to get the sidebar channel titles you can modify the above snippet slightly like this:

let channels = document.querySelectorAll([aria-label="Followed Channels"] .tw-transition-group > div, [aria-label="Recommended Channels"] .tw-transition-group > div, [aria-label$="Viewers Also Watch"] .tw-transition-group > div );

console.log(el[reactVar].child.memoizedProps.stream.content.broadcaster.broadcastSettings.title);

If needed you can install the react developer tools to be able to inspect nodes so you can see what properties they contain. Or in Firefox you can inspect as normal, right click on an element and select "Show DOM properties" then you can expand the element pasted in the console and view the "__reactFiber$xxxxxxxx" property there.

kwaschny commented 1 month ago

I never bothered to use the internal React props. I'd rather not rely on implementation details, especially with Twitch frontend devs going back and forth. And while using the rendered elements is not exactly a better solution either, it has been working surprisingly well.

Thanks for pointing out the option, but I'm not going to refactor and maintain the data retrieval code for it.

I'll keep the issue open, so people can see there is a solution available.

Kryptortio commented 1 month ago

It's certainly possible that Twitch will break it. I'll just add it as a userscript then so it's easier to use if someone wants to do that.

To use you install an add-on like Tampermonkey and then install the script (create new and paste it). Then you can edit it and change the tags as needed (tagToBan1, tagToBan2). You can also change removeFullyOnMatch if you prefer completely hiding matches instead of just making them fade out.

// ==UserScript==
// @name         Twitch tag blacklist
// @namespace    https://www.twitch.tv/tag-blacklist
// @version      0.1
// @description  Twitch tag blacklist
// @author       You
// @match        https://www.twitch.tv/directory/*
// ==/UserScript==
(function() {

    // Blacklist for tags edit this manually:
    let tagBlackList = [
    /*       ↓   Add tags to blacklist here  ↓             */
        'tagToBan1',
        'tagToBan2',
    /*       ↑   Add tags to blacklist here  ↑             */
    ];
    let removeFullyOnMatch = false; // Change to true to completely hide matches for blacklist

    tagBlackList = new Set( tagBlackList.map(value => value.toLowerCase()));

    function checkChannels() {
        let channels = document.querySelectorAll('.tw-tower > div:not(#directory-rectangle)');

        // Find react property name
        let reactVar = Object.keys(channels[0]).filter(
                function(el, ind, arr){ return el.startsWith('__reactFiber$') }
            )[0];

        // Loop all channels
        channels.forEach((node) => {
            if(node[reactVar].child == null) return;
            let tags = node[reactVar].child.memoizedProps.item.tags;

            let blackListMatch = false;

            // Loop tags for this channel
            tags.forEach((tagEl) => {
                if(tagBlackList.has(tagEl.name.toLowerCase())) blackListMatch = true;
            });

            // Did it have a tag from the blacklist?
            if(blackListMatch) {
                if(removeFullyOnMatch) {
                    node.parentNode.removeChild(node);
                } else {
                    node.style.opacity = "0.5";
                    node.style.filter = "grayscale(1)";
                }
            } else {
                node.style.opacity = "unset";
                node.style.filter = "unset";
            }
        });

    }

    setInterval( () => {
            checkChannels();
    },1000);
})();