Greasy Fork is available in English.

Joyn.de Advanced Live Filter

Advanced Filter Options for the Live TV Page on Joyn.de

// ==UserScript==
// @name            Joyn.de Advanced Live Filter
// @name:de         Joyn.de Erweiterter Live Filter
// @version         1.0.0
// @description     Advanced Filter Options for the Live TV Page on Joyn.de
// @description:de  Erweiterte Filtermöglichkeiten für die Live-TV-Seite auf Joyn.de
// @icon            https://www.joyn.de/favicon.ico
// @author          TalkLounge (https://github.com/TalkLounge)
// @namespace       https://github.com/TalkLounge/joyn.de-advanced-live-filter
// @license         MIT
// @match           https://www.joyn.de/*
// @grant           GM.setValue
// @grant           GM.getValue
// @run-at          document-start
// ==/UserScript==

(function () {
    'use strict';

    function newElement(tagName, attributes, content) {
        var tag = document.createElement(tagName);
        for (var key in attributes || {}) {
            if (attributes[key] !== undefined && attributes[key] !== null) {
                tag.setAttribute(key, attributes[key]);
            }
        }
        tag.innerHTML = content || "";
        return tag;
    }

    function injectStyling() {
        const s = document.createElement("style");
        s.innerHTML = `#JALF {
            position: absolute;
            top: 32px;
            left: 50%;
            transform: translateX(-50%);
        }

        #JALF h2 {
            font-size: 28px;
            font-family: Campton-Bold, Helvetica, sans-serif;
            margin-bottom: 1.75%;
            text-align: center;
        }

        #JALF input[type="text"] {
            color: white;
            border: 0px;
            background: rgba(255, 255, 255, 0.2);
            line-height: 18px;
            margin-bottom: 1.75%;
            width: 100%;
        }

        #JALF input[type="checkbox"] {
            display: none;
        }

        #JALF input[type="checkbox"] + label {
            position: relative;
            padding-left: 20px;
            display: inline-block;
            margin-right: 10px;
        }

        #JALF input[type="checkbox"] + label:before {
            content: "";
            position: absolute;
            left: 0;
            width: 14px;
            height: 14px;
            background: rgba(255, 255, 255, 0.2);
            cursor: pointer;
        }

        #JALF input[type="checkbox"]:checked + label:before {
            content: "✔";
            text-align: center;
        }

        #JALF .last {
            margin: 0px !important;
        }`;
        document.head.append(s);
    }

    async function injectFilters() {
        const div = newElement("div", {id: "JALF"});

        const h2 = newElement("h2", null, "Filter");
        div.append(h2);

        const input = newElement("input", {type: "text", id: "JALF-Search", placeholder: "Search...", value: await GM.getValue("search") || ""});
        input.addEventListener("input", filter);
        div.append(input);

        const br = newElement("br");
        div.append(br);

        const check1 = newElement("input", {type: "checkbox", id: "JALF-ChannelOnly", checked: await GM.getValue("isChannelOnly") ? true : undefined});
        check1.addEventListener("change", filter);
        div.append(check1);

        const label1 = newElement("label", {for: "JALF-ChannelOnly"}, "Search Channels only");
        div.append(label1);

        const check2 = newElement("input", {type: "checkbox", id: "JALF-Plus", checked: await GM.getValue("isPlus") ? true : undefined});
        check2.addEventListener("change", filter);
        div.append(check2);

        const label2 = newElement("label", {for: "JALF-Plus"}, "Show Plus Channels");
        div.append(label2);

        const check3 = newElement("input", {type: "checkbox", id: "JALF-VOD", checked: await GM.getValue("isVOD") ? true : undefined});
        check3.addEventListener("change", filter);
        div.append(check3);

        const label3 = newElement("label", {for: "JALF-VOD"}, "Show VOD Channels");
        div.append(label3);

        const check4 = newElement("input", {type: "checkbox", id: "JALF-SD", checked: await GM.getValue("isSD") || await GM.getValue("isSD") == undefined ? true : undefined});
        check4.addEventListener("change", filter);
        div.append(check4);

        const label4 = newElement("label", {for: "JALF-SD", class: "last"}, "Show SD Channels");
        div.append(label4);

        document.querySelector("header").append(div);
    }

    function filter() {
        const [search, isChannelOnly, isPlus, isVOD, isSD] = [document.querySelector("#JALF-Search").value, document.querySelector("#JALF-ChannelOnly").checked, document.querySelector("#JALF-Plus").checked, document.querySelector("#JALF-VOD").checked, document.querySelector("#JALF-SD").checked];
        GM.setValue("search", search);
        GM.setValue("isChannelOnly", isChannelOnly);
        GM.setValue("isPlus", isPlus);
        GM.setValue("isVOD", isVOD);
        GM.setValue("isSD", isSD);

        for (const child of document.querySelector("article div ol").children) {
            const foundChannel = child.querySelector("img").alt.toLowerCase().includes(search.toLowerCase());
            const foundProgramm = [...child.querySelectorAll("h6")].find(item => item.textContent.toLowerCase().includes(search.toLowerCase()));
            const foundPlus = child.querySelector("header").firstChild.tagName == "svg";
            const foundVOD = child.querySelector("header").children[1]?.tagName == "svg" && child.querySelector("header").children[1]?.children.length > 1;
            const foundHD = child.querySelector("header").lastChild.tagName == "svg" && child.querySelector("header").lastChild.children.length == 1;

            if (
                ((isChannelOnly && foundChannel) || (!isChannelOnly && (foundChannel || foundProgramm))) &&
                ((isPlus) || (!isPlus && !foundPlus)) &&
                ((isVOD) || (!isVOD && !foundVOD)) &&
                ((isSD) || (!isSD && foundHD))
            ) {
                child.style.display = "flex";
            } else {
                child.style.display = "none";
            }
        }
    }

    async function backInit() {
        if (! window.location.href.startsWith("https://www.joyn.de/play/live-tv") || ! document.querySelector("article")) {
            return init();
        }

        await new Promise(r => setTimeout(r, 500));
        return backInit();
    }

    async function init() {
        if (! window.location.href.startsWith("https://www.joyn.de/play/live-tv") || ! document.querySelector("article")) {
            await new Promise(r => setTimeout(r, 500));
            return init();
        }

        injectStyling();

        const observer = new MutationObserver(async (mutationList, observer) => {
            for (const mutation of mutationList) {
                if (mutation.addedNodes[0]?.tagName != "ASIDE") {
                    continue;
                }

                injectFilters();
                await new Promise(r => setTimeout(r, 50));
                [...document.querySelector("article div ol").querySelectorAll("h6")].forEach(item => item.parentNode.setAttribute("title", item.innerText));
                filter();
                document.querySelector("article div ol").scrollTop = 0;
                break;
            }
        });
        observer.observe(document.querySelector("article"), { childList: true });
        backInit();
    }

    init();
})();