CCLiveClean

Hide almost CC live Element.

// ==UserScript==
// @name		CC直播间净化
// @description		隐藏CC直播页面中的大部分广告, 并且当直播结束跳转其他直播间时, 自动关闭页面
// @name:en		CCLiveClean
// @description:en		Hide almost CC live Element. 
// @author		Yiero
// @version		1.1.0
// @match		https://cc.163.com/*
// @match		https://act/m/daily/anchor_end_countdown/*
// @grant		GM_addStyle
// @grant		GM_setValue
// @grant		GM_getValue
// @grant		GM_deleteValue
// @grant		GM_listValues
// @grant		GM_registerMenuCommand
// @grant		GM_unregisterMenuCommand
// @run-at		document-start
// @icon		https://cc.163.com/favicon.ico
// @namespace		https://github.com/AliubYiero/TamperMonkeyScripts/
// @license		GPL
// ==/UserScript==
var __defProp = Object.defineProperty;

var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
    enumerable: true,
    configurable: true,
    writable: true,
    value: value
}) : obj[key] = value;

var __publicField = (obj, key, value) => {
    __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
    return value;
};

class Info {
    constructor(projectName) {
        __publicField(this, "projectName");
        __publicField(this, "header");
        this.projectName = projectName;
        this.header = `[${projectName}]`;
    }
    log(...msg) {
        (() => {})(...this.contentInfo(...msg));
    }
    info(...msg) {
        console.info(...this.contentInfo(...msg));
    }
    warn(...msg) {
        console.warn(...this.contentInfo(...msg));
    }
    error(...msg) {
        console.error(...this.contentInfo(...msg));
    }
    contentInfo(...msg) {
        return [ this.header, `[${(new Date).toLocaleString("zh-ch")}]`, ...msg ];
    }
}

class CSSRule {
    constructor() {
        __publicField(this, "cssRuleSet", new Set);
        __publicField(this, "styleDom", document.createElement("style"));
    }
    push(selector, rule) {
        let ruleString = "";
        for (let ruleKey in rule) {
            const ruleValue = rule[ruleKey];
            ruleString += `${ruleKey}:${ruleValue};`;
        }
        this.cssRuleSet.add(`${selector} {${ruleString}}`);
    }
    pushImportant(selector, rule) {
        let ruleString = "";
        for (let ruleKey in rule) {
            let ruleValue = rule[ruleKey];
            if (typeof ruleValue === "string") {
                ruleValue = ruleValue.replace("!important", "");
            }
            ruleString += `${ruleKey}:${ruleValue} !important;`;
        }
        this.cssRuleSet.add(`${selector} {${ruleString}}`);
    }
    pushHide(selector) {
        this.pushImportant(selector, {
            display: "none"
        });
    }
    pushHideList(selectorList) {
        selectorList.forEach((selector => {
            this.pushImportant(selector, {
                display: "none"
            });
        }));
    }
    pushList(ruleList) {
        ruleList.forEach((({selector: selector, rule: rule}) => {
            this.push(selector, rule);
        }));
    }
    pushImportantList(ruleList) {
        ruleList.forEach((({selector: selector, rule: rule}) => {
            this.pushImportant(selector, rule);
        }));
    }
    submit() {
        this.removeAll();
        new Info("AddStyle").log(Array.from(this.cssRuleSet).join(" "));
        this.styleDom = GM_addStyle(Array.from(this.cssRuleSet).join(" "));
    }
    removeAll() {
        if (this.styleDom) {
            this.styleDom.remove();
        }
    }
}

const hideSelectorList = {
    main: [ ".ad-ct", "#webChat", "#js-side-nav", ".index-module_container_1pK9d", "::-webkit-scrollbar" ],
    headerNav: [ ".menu-location", "#my-follow, #my-record, #download, #menu-be-anchor", "#guard-head-avatar-red-dot-msg, .red-dot" ],
    danmuBar: [ "#room-tabs", "#gift-banner", ".gift-simp-banner", ".room-boardcast", ".activity-notify", ".gift_item", ".chat-msg-folder" ],
    liveTitle: [ "#achievement, .live-type, .live-guard, .live-fans-badge-diamond, .anchor-friends", "#plugins2374, #plugins9970, #plugins9670, #plugins9977, #plugins9412, #plugins9997, #plugins9089, #plugins6666, #plugins9217, #plugins2511, #plugins1609, #plugins9913, #plugins1016, #plugins14, #plugins5985, #plugins1353, #plugins1, #plugins9321 " ],
    live: [ "#recommend-module", "#live_left_bottom_box_wrap", ".video-watermark", "#new-player-banner, #player-banner, #new-player-banner, #mounts_player, #mounts_banner", ".gameH5Theater .user-tool-bar" ]
};

const anchor_end_countdownHideSelectorList = {
    live: [ ".ui-wrap" ]
};

const prefSelectorList = {
    main: {
        ".room-main-container": {
            "margin-top": "20px"
        }
    },
    headerNav: {
        ".user-do": {
            "margin-right": "50%",
            transform: "translateX(50%)"
        }
    },
    live: {
        ".page-right-container": {
            width: "100%"
        },
        "#live_player": {
            height: "100%"
        }
    },
    danmuBar: {
        ".chat-list-short": {
            height: "calc(100% - 110px)"
        },
        "#chat-list-con": {
            height: "100%"
        }
    }
};

function addCCNewStyle() {
    const cssRule = new CSSRule;
    cssRule.pushHideList(Object.values(hideSelectorList).flat());
    const transformedPrefSelectorList = Object.entries(Object.values(prefSelectorList).flat().reduce(((result, current) => ({
        ...result,
        ...current
    })))).map((([selector, rule]) => ({
        selector: selector,
        rule: rule
    })));
    cssRule.pushImportantList(transformedPrefSelectorList);
    cssRule.submit();
}

function addCCIframeNewStyle() {
    const cssRule = new CSSRule;
    cssRule.pushHideList(Object.values(anchor_end_countdownHideSelectorList).flat());
    cssRule.submit();
}

function freshListenerPushState(callback, s = 1) {
    let _pushState = window.history.pushState;
    window.history.pushState = function() {
        setTimeout(callback, s * 1e3);
        return _pushState.apply(this, arguments);
    };
}

const live = {
    id: "",
    historyId: ""
};

Object.defineProperty(live, "id", {
    get() {
        const liveIdMatch = document.URL.match(/https:\/\/cc.163.com\/(\d+)/);
        if (liveIdMatch && liveIdMatch[1]) {
            const liveId = liveIdMatch[1];
            sessionStorage.setItem("localLiveId", liveId);
            return liveId;
        }
        return "";
    }
});

Object.defineProperty(live, "historyId", {
    get() {
        return sessionStorage.getItem("localLiveId") || "";
    }
});

function equalLiveId() {
    freshListenerPushState((() => {
        if (live.historyId !== live.id) {
            window.close();
        }
    }));
}

function getElement(parent = document.body, selector, timeoutPerSecond = 0, getElementDelayPerSecond = 0) {
    return new Promise((resolve => {
        let result = parent.querySelector(selector);
        if (result) {
            return resolve(result);
        }
        let timer;
        const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
        if (mutationObserver) {
            const observer = new mutationObserver((mutations => {
                for (let mutation of mutations) {
                    for (let addedNode of mutation.addedNodes) {
                        if (addedNode instanceof Element) {
                            result = addedNode.matches(selector) ? addedNode : addedNode.querySelector(selector);
                            if (result) {
                                observer.disconnect();
                                timer && clearTimeout(timer);
                                setTimeout((() => resolve(result)), getElementDelayPerSecond * 1e3);
                            }
                        }
                    }
                }
            }));
            observer.observe(parent, {
                childList: true,
                subtree: true
            });
            if (timeoutPerSecond > 0) {
                timer = setTimeout((() => {
                    observer.disconnect();
                    return resolve(null);
                }), timeoutPerSecond * 1e3);
            }
        }
    }));
}

document.querySelector.bind(document);

const getEls = document.querySelectorAll.bind(document);

async function selectOriginBanSetting() {
    await getElement(document.body, ".ban-effect-list", 0, 1);
    const banList = getEls(".ban-effect-list > li:not(.selected)");
    banList.forEach((banItem => {
        banItem.click();
    }));
}

function isMatchURL(...regExpList) {
    const matchResultList = [];
    regExpList.forEach((regExp => {
        if (typeof regExp === "string") {
            regExp = new RegExp(regExp);
        }
        matchResultList.push(!!document.URL.match(regExp));
    }));
    return matchResultList.includes(true);
}

class EntryBranch {
    constructor() {
        __publicField(this, "branchList", []);
    }
    add(condition, callback) {
        this.branchList.push([ condition, callback ]);
    }
    run() {
        const entry = this.branchList.find((entry2 => entry2[0]()));
        if (entry) {
            entry[1]();
        }
    }
}

class GMConfigMenu {
    constructor(callback) {
        __publicField(this, "menuId", 0);
        __publicField(this, "callback");
        this.callback = callback;
    }
    open(title) {
        if (this.menuId) {
            this.close();
        }
        this.menuId = GM_registerMenuCommand(title, this.callback);
    }
    close() {
        GM_unregisterMenuCommand(this.menuId);
        this.menuId = 0;
    }
}

class GMStorage {
    constructor(key) {
        __publicField(this, "key");
        this.key = key;
    }
    set(value) {
        dispatchEvent(new CustomEvent("GMStorageUpdate", {
            detail: {
                newValue: value,
                oldValue: this.get(),
                target: this.key
            }
        }));
        GM_setValue(this.key, value);
    }
    get(defaultValue = null) {
        return GM_getValue(this.key, defaultValue);
    }
    remove() {
        dispatchEvent(new CustomEvent("GMStorageUpdate", {
            detail: {
                newValue: null,
                oldValue: this.get(),
                target: this.key
            }
        }));
        GM_deleteValue(this.key);
    }
}

class WhiteList extends GMStorage {
    constructor() {
        super("liveIdWhiteList");
    }
    get whiteList() {
        return this.get([ 361433, 239802416 ]);
    }
    add(liveId) {
        const whiteList2 = this.whiteList;
        whiteList2.push(liveId);
        this.set(whiteList2);
    }
    has(liveId) {
        return this.whiteList.includes(liveId);
    }
    delete(liveId) {
        this.set(this.whiteList.filter((whiteLiveId => whiteLiveId !== liveId)));
    }
}

const whiteList = new WhiteList;

function disabledNotWhiteListUrl(liveId) {
    if (!whiteList.has(liveId)) {
        window.close();
        return;
    }
}

function registerConfigBtn(liveId) {
    new GMConfigMenu((() => {
        const result = prompt(`输入需要添加白名单的直播间的数字Id (网页地址中的数字Id):\n当前白名单:\n[${whiteList.whiteList.join(", ")}]`);
        if (result) {
            whiteList.add(Number(result));
        }
    })).open("添加直播间白名单");
    new GMConfigMenu((() => {
        const result = prompt(`输入需要删除白名单的直播间数字Id(网页地址中的数字Id):\n当前白名单:\n[${whiteList.whiteList.join(", ")}]`, String(liveId || whiteList.whiteList[0] || ""));
        if (result) {
            whiteList.delete(Number(result));
        }
    })).open("删除直播间白名单");
}

async function mainPageEntry() {
    disabledNotWhiteListUrl(Number(live.id));
    registerConfigBtn(Number(live.id));
    addCCNewStyle();
    await selectOriginBanSetting();
    equalLiveId();
}

function iframeEntry() {
    addCCIframeNewStyle();
}

(async () => {
    const entryBranch = new EntryBranch;
    entryBranch.add((() => isMatchURL(/^https?:\/\/cc.163.com\/$/)), registerConfigBtn);
    entryBranch.add((() => isMatchURL(/^https?:\/\/cc.163.com\/(\d+)/)), mainPageEntry);
    entryBranch.add((() => isMatchURL("act/m/daily/anchor_end_countdown/index.html")), iframeEntry);
    entryBranch.run();
})();