omomo / 只看好友/自己参与的评论

// ==UserScript==
// @name         只看好友/自己参与的评论
// @version      0.0.1
// @description  各种评论前增加点击按钮筛选好友/自己参与的评论
// @author       omm
// @include      http*://bgm.tv/*
// @include      http*://chii.in/*
// @include      http*://bangumi.tv/*
// @license      MIT
// @updateURL https://openuserjs.org/meta/omomo/只看好友自己参与的评论.meta.js
// @downloadURL https://openuserjs.org/install/omomo/只看好友自己参与的评论.user.js
// ==/UserScript==


(async function () {
    const commentList = document.querySelector('#comment_list');
    if (!commentList) return;

    const showAnchor = () => {
        if (location.hash.startsWith('#post_')) {
            const anchor = document.getElementById(location.hash.slice(1));
            anchor.hidden = false;
            anchor.scrollIntoView();
        }
    };

    const getId = a => a.href.split('/').pop();
    const selfId = getId(document.querySelector('.avatar'));
    const friends = await getBgmFriends();
    const comments = [...commentList.querySelectorAll('.row_reply')];

    const isWhose = checkId => comment => [...comment.querySelectorAll('.avatar')].some(a => checkId(getId(a)));
    const isMine = isWhose(id => id === selfId);
    const isFriends = isWhose(id => friends.includes(id));

    const storageKeys = {
        mine: 'incheijs_filter_mine',
        friends: 'incheijs_filter_friends'
    };
    const show = {
        mine: localStorage.getItem(storageKeys.mine) === 'true',
        friends: localStorage.getItem(storageKeys.friends) === 'true'
    };
    const saveShow = (key, value) => {
        show[key] = value;
        localStorage.setItem(storageKeys[key], value);
    };

    const myComments = new WeakSet();
    const friendsComments = new WeakSet();
    const whoseComments = (() => {
        let executed = false;
        return () => {
            if (executed) return;
            executed = true;
            for (const comment of comments) {
                if (isMine(comment)) myComments.add(comment);
                if (isFriends(comment)) friendsComments.add(comment);
            }
        };
    })();

    const updFilter = async () => {
        const batchSize = 500; // 每批处理的评论数量
        const totalBatches = Math.ceil(comments.length / batchSize);
        const showAll = !show.mine && !show.friends;

        whoseComments();
        for (let batch = 0; batch < totalBatches; batch++) {
            const startIndex = batch * batchSize;
            const endIndex = startIndex + batchSize;
            if (showAll) {
                for (let i = startIndex; i < endIndex && i < comments.length; i++) {
                    const comment = comments[i];
                    comment.hidden = false;
                }
            } else {
                for (let i = startIndex; i < endIndex && i < comments.length; i++) {
                    const comment = comments[i];
                    const shouldHide = (show.mine || show.friends) &&
                                       !((show.mine && myComments.has(comment)) ||
                                         (show.friends && friendsComments.has(comment)));
                    comment.hidden = shouldHide;
                    // 兼容 [隐藏开播前评论](https://bgm.tv/dev/app/118)
                    if (comment.style.display) comment.style.removeProperty('display');
                }
            }

            if (batch > 0) {
                await new Promise(resolve => setTimeout(resolve, 0));
            }
        }
    };

    if (show.mine || show.friends) {
        updFilter();
        showAnchor();
    }
    window.addEventListener('hashchange', showAnchor);

    const makeBtn = (text, flag) => {
        const li = document.createElement('li');
        const a = document.createElement('a');
        a.href = 'javascript:';
        a.className = `titleTip comments_filter${ show[flag] ? ' selected' : '' }`;
        a.title = `双击默认当前筛选`;
        $(a).tooltip();
        a.textContent = text;

        let clickTimer;
        a.addEventListener('click', e => {
            clearTimeout(clickTimer);
            if (e.detail >= 2) return;

            clickTimer = setTimeout(() => {
                a.classList.toggle('selected');
                show[flag] = a.classList.contains('selected');
                updFilter();
            }, 300);
        });

        a.addEventListener('dblclick', (event) => {
            event.preventDefault();
            clearTimeout(clickTimer);

            const selected = a.classList.contains('selected');
            saveShow(flag, selected);
        });

        li.appendChild(a);
        return li;
    };

    const ul = document.createElement('ul');
    ul.className = 'secTab rr';
    const mineBtn = makeBtn('我', 'mine');
    const friendBtn = makeBtn('好友', 'friends');
    ul.append(mineBtn, friendBtn);

    let line = document.querySelector('.section_line.clear');
    if (!line) {
        line = document.createElement('div');
        line.className = 'section_line clear';
        commentList.before(line);
    }
    const clear = document.createElement('div');
    clear.className = 'clear';
    commentList.before(ul, clear);

    // [在讨论帖子标记出楼主和好友](https://bgm.tv/dev/app/1075)
    async function getBgmFriends() {
        const now = new Date().getTime();
        function getCache(force = false) {
            const predata = localStorage.getItem('bgmFriends');
            if (!predata) return;
            const data = JSON.parse(predata);
            if (data && data.friends) {
                if (force || now - data.stamp < 1800000) {
                    return Object.keys(data.friends);
                }
            }
        }

        let cache = getCache();
        if (cache) return cache;

        await sleep(1000);
        const markGadget = document.querySelector('.chip-warpper');
        if (markGadget) {
            cache = getCache(true);
            if (cache) return cache;
        }

        const newData = {
            stamp: now,
            friends: {}
        };
        try {
            const response = await fetch(`/user/${selfId}/friends`);
            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
            const res = await response.text();

            let filter = /<a href="\/user\/([^"]*)" class="avatar">/g;
            let anchor;
            while ((anchor = filter.exec(res)) !== null) {
                newData.friends[anchor[1]] = true;
            }
            delete newData.friends[selfId];
            localStorage.setItem('bgmFriends', JSON.stringify(newData));
            return Object.keys(newData.friends);
        } catch (error) {
            console.error('请求好友列表时出错:', error);
            return [];
        }
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
})();