让MCN闪耀,让荣誉的勋章更容易看到!

发现更大的世界和更多的软广!

질문, 리뷰하거나, 이 스크립트를 신고하세요.
// ==UserScript==
// @name         让MCN闪耀,让荣誉的勋章更容易看到!
// @namespace    mcn.is.very.very.good
// @version      1.0.2
// @description  发现更大的世界和更多的软广!
// @author       Dislike soft AD
// @license MIT
// @match        https://www.zhihu.com/*
// @grant        unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_setClipboard
// ==/UserScript==


(async () => {
    'use strict';

    // 请不要将这里改为 true,否则将会在机构前增加一个emoji表情,这可能会增加攻击性。
    const useIcon = false;

    const setCache = (name, data) => {
        const cache = JSON.parse(localStorage.getItem('mcnCache')) || {};
        cache[name] = data;
        localStorage.setItem('mcnCache', JSON.stringify(cache));
        updateMenu();
    }

    const getCache = (name) => {
        const cache = JSON.parse(localStorage.getItem('mcnCache')) || {};
        return cache[name];
    }

    const setNoMcn = (name) => {
        const noMcn = new Set(JSON.parse(localStorage.getItem('noMcn')) || []);
        noMcn.add(name);
        localStorage.setItem('noMcn', JSON.stringify(Array.from(noMcn)));
    }

    const isNoMcn = (name) => {
        const noMcn = new Set(JSON.parse(localStorage.getItem('noMcn')) || []);
        return noMcn.has(name);
    }

    const promiseMap = {};

    const getAuthorMcn = async (token, manual = false) => {
        const cache = getCache(token);
        if (cache?.mcn) return cache.mcn;
        if (isNoMcn(token)) return false; // false is no mcn, null is no record
        if (promiseMap[token]) {
            return promiseMap[token];
        }
        const autoLoad = localStorage.getItem('mcnAutoLoad') || false;
        if (!autoLoad && !manual) {
            return null;
        }
        promiseMap[token] = new Promise(async (resolve) => {
            const url = `https://www.zhihu.com/people/${token}`;
            const html = await fetch(url)
                .then(response => response.text());
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const initialData = doc.querySelector('#js-initialData');
            const json = initialData.textContent;
            const data = JSON.parse(json);
            console.log(token);
            const mcn = data?.initialState?.entities?.users?.[token]?.mcnCompany;
            if (mcn) {
                setCache(token, {
                    mcn,
                    nickname: data?.initialState?.entities?.users?.[token]?.name ?? token,
                });
            } else {
                setNoMcn(token);
            }
            resolve(mcn || false);
            promiseMap[token] = null;
        });
        return promiseMap[token];
    }

    const clearBadge = (mcnBadge) => {
        mcnBadge.onclick = null;
        mcnBadge.style.cursor = null;
        mcnBadge.style.border = null;
        mcnBadge.style.backgroundColor = null;
        mcnBadge.className = 'mcn-badge';
        mcnBadge.textContent = 'Loading...';
        mcnBadge.style.marginLeft = '5px';
        mcnBadge.style.display = 'inline-block';
        mcnBadge.style.color = '#DDD';
        mcnBadge.style.fontSize = '12px';
        mcnBadge.style.padding = '2px';
        mcnBadge.style.borderRadius = '3px';
    }

    const updateBadge = (mcnBadge, token, mcn) => {
        clearBadge(mcnBadge);
        if (mcn) {
            mcnBadge.textContent = mcn;
            mcnBadge.style.marginLeft = '5px';
            mcnBadge.style.display = 'inline-block';
            mcnBadge.style.color = '#FFFFFF';
            mcnBadge.style.backgroundColor = '#FF0000';
            if (useIcon) {
                mcnBadge.textContent = '🐕‍🦺' + mcn;
                mcnBadge.style.backgroundColor = null;
                mcnBadge.style.color = "#FF0000";
            }
            mcnBadge.title = "如果是被包养了,就不要谈独立人格"
        } else if (mcn === false) {
            mcnBadge.textContent = "No MCN";
        } else {
            mcnBadge.textContent = "点击加载MCN";
            mcnBadge.style.cursor = 'pointer';
            mcnBadge.style.color = '#aaa';
            mcnBadge.style.backgroundColor = '#FFFFFF';
            mcnBadge.style.border = '1px solid #aaa';
            mcnBadge.style.padding = '1px';
            mcnBadge.onclick = async () => {
                clearBadge(mcnBadge);
                const mcn = await getAuthorMcn(token, true);
                updateBadge(mcnBadge, token, mcn);
            };
        }
    }

    const addMcnBadge = async (authorDom) => {
        if (authorDom.querySelector('.mcn-badge')) return;
        const box = authorDom.querySelector('.AuthorInfo-head');
        const mcnBadge = document.createElement('span');
        box.appendChild(mcnBadge);
        const userLink = authorDom.querySelector('.UserLink-link');
        if (!userLink) return;
        const link = userLink.getAttribute('href');
        const token = link.split('/').pop();
        clearBadge(mcnBadge);
        const mcn = await getAuthorMcn(token);
        updateBadge(mcnBadge, token, mcn);
    }

    const addQuestionerMcnBadge = async (dom) => {
        console.log('addQuestionerMcnBadge', dom);
        if (!dom) return;
        if(dom.querySelector('.mcn-badge')) return;
        const link = dom.querySelector('.BrandQuestionSymbol-brandLink');
        if (!link) return;
        const token = link.getAttribute('href').split('/').pop();
        console.log('token', token);
        const mcnBadge = document.createElement('span');
        // push BrandQuestionSymbol-brandLink next
        dom.insertBefore(mcnBadge, link.nextSibling);
        clearBadge(mcnBadge, token, null);
        const mcn = await getAuthorMcn(token);
        updateBadge(mcnBadge, token, mcn);
    }

    const blockAuthor = async (name) => {
        const url = `https://www.zhihu.com/api/v4/members/${name}/actions/block`;
        const response = await fetch(url, {
            method: 'POST',
            credentials: 'include',
        });
        console.log(response);
        return response.status === 204;
    }

    const getMcnAuthorMap = () => {
        const cache = JSON.parse(localStorage.getItem('mcnCache')) || {};
        const mcnMap = {};
        for (const [name, data] of Object.entries(cache)) {
            if (!mcnMap[data.mcn]) {
                mcnMap[data.mcn] = [];
            }
            mcnMap[data.mcn].push({ token: name, ...data });
        }
        return mcnMap;
    }

    if (typeof unsafeWindow === 'undefined') {
        window.unsafeWindow = window;
    }

    const mcn = unsafeWindow.mcn = () => {
        const mcnMap = getMcnAuthorMap();
        console.group('MCN Map');
        for (const [mcn, list] of Object.entries(mcnMap)) {
            console.groupCollapsed(mcn + ' (' + list.length + ')');
            for (const data of list) {
                console.log(data.token, "\t", data.nickname, "\t", `https://www.zhihu.com/people/${data.token}`);
            }
            console.groupEnd();
        }
        console.groupEnd();
    }

    const blockMcn = unsafeWindow.blockMcn = async (name) => {
        const mcnMap = getMcnAuthorMap();
        const authors = mcnMap[name] || [];
        if (!authors.length) {
            console.error('没有找到已记录的MCN作者 ' + name);
            return;
        }
        for (const author of authors) {
            const result = await blockAuthor(author.token);
            if (result) {
                console.log(`已屏蔽 ${author.token} ${author.nickname}`);
            } else {
                console.error(`屏蔽失败 ${author.token} ${author.nickname}`);
            }
        };
        console.log("全部完成");
        alert("全部完成");
    }

    const headDom = document.querySelector('head');
    const hiddenContent = unsafeWindow.hiddenContent = () => {
        const style = document.createElement('style');
        style.id = 'hiddenContent';
        style.textContent = `
        .RichContent {
            display: none !important;
        }
        .LabelContainer-wrapper {
            display: none !important;
        }
        `;
        headDom.appendChild(style);
    }
    const showContent = unsafeWindow.showContent = () => {
        const style = document.querySelector('style#hiddenContent');
        if (style) {
            style.remove();
        }
    }

    let observer;
    function runObserver() {
        if (observer) observer.disconnect();
        const handle = (node) => {
            if (node.classList?.contains('AuthorInfo')) {
                setTimeout(() => {
                    addMcnBadge(node);
                });
            }
            if (node.classList?.contains('BrandQuestionSymbol')) {
                setTimeout(() => {
                    addQuestionerMcnBadge(node);
                });
            }
        }
        // MutationObserver
        observer = new MutationObserver((mutationsList, observer) => {
            for (let mutation of mutationsList) {
                if (mutation.type === "attributes") {
                    handle(mutation.target);
                } else {
                    for (const node of mutation.addedNodes) {
                        handle(node);
                        if (node.childNodes) {
                            const nodeIterator = document.createNodeIterator(node);
                            let childNode = nodeIterator.nextNode();
                            while (childNode) {
                                handle(childNode);
                                childNode = nodeIterator.nextNode();
                            }
                        }
                    }
                }
            }
        });
        const targetNode = window.document.documentElement;
        observer.observe(targetNode, { childList: true, subtree: true });
    }

    const mcnMenuId = [];
    let showMcnList = false;
    const updateMenu = () => {
        if (typeof GM_registerMenuCommand === 'undefined') {
            return;
        }

        try {
            mcnMenuId.forEach(id => {
                GM_unregisterMenuCommand(id);
            });

            const isAuto = localStorage.getItem('mcnAutoLoad') || false;
            const autoLoadId = GM_registerMenuCommand(`自动加载MCN(当前:${isAuto ? '自动': '手动'})`, function (event) {
                localStorage.setItem('mcnAutoLoad', isAuto ? '' : '1');
                updateMenu();
            });

            const hasHCstyle = document.querySelector('style#hiddenContent');
            if (hasHCstyle) {
                mcnMenuId.push(GM_registerMenuCommand("显示回答正文(当前:隐藏)", function (event) {
                    showContent();
                    updateMenu();
                }));
            } else {
                mcnMenuId.push(GM_registerMenuCommand("隐藏回答正文(当前:显示)", function (event) {
                    hiddenContent();
                    updateMenu();
                }));
            }

            mcnMenuId.push(GM_registerMenuCommand("复制表格", function (event) {
                const mcnMap = getMcnAuthorMap();
                const textList = [];
                for (const [mcn, list] of Object.entries(mcnMap)) {
                    for (const data of list) {
                        textList.push(`${mcn}\t${data.token}\t${data.nickname}\thttps://www.zhihu.com/people/${data.token}`);
                    }
                }
                GM_setClipboard(textList.join('\n'));
            }));

            mcnMenuId.push(GM_registerMenuCommand("清理缓存", function (event) {
                if (!confirm('确定要清理缓存吗?')) return;
                localStorage.removeItem('mcnCache');
                localStorage.removeItem('noMcn');
                updateMenu();
            }));


            mcnMenuId.push(GM_registerMenuCommand(`${showMcnList ? '▼' : '▶'} 显示MCN列表${showMcnList ? '' : ' (需再次打开菜单)'}`, function (event) {
                showMcnList = !showMcnList;
                updateMenu();
            }, { autoClose: true }));

            if (showMcnList) {
                const mcnMap = getMcnAuthorMap();
                for (const [mcn, list] of Object.entries(mcnMap)) {
                    const id = GM_registerMenuCommand(`拉黑 ${mcn} (${list.length})`, async () => {
                        if (!confirm(`确定要拉黑 ${mcn} (${list.length}) 吗?\n${list.map(v => v.nickname).join(', ')}`)) {
                            return;
                        }
                        await blockMcn(mcn);
                    });
                    mcnMenuId.push(id);
                }
            }

        } catch (error) {
            console.error(error);
        }
    }

    if (typeof GM_registerMenuCommand !== 'undefined') {
        updateMenu();
    }
    runObserver();
    addQuestionerMcnBadge();
    const authorDomList = document.querySelectorAll('.AuthorInfo');
    for (const authorDom of authorDomList) {
        await addMcnBadge(authorDom);
    }
})();