Joyn.de Advanced Live Filter

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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