Greasy Fork

Greasy Fork is available in English.

章节讨论吐槽加强

章节讨论中置顶显示自己的吐槽,高亮回复过的章节格子

当前为 2024-11-08 提交的版本,查看 最新版本

// ==UserScript==
// @name         章节讨论吐槽加强
// @namespace    https://bgm.tv/group/topic/408098
// @version      0.1.0
// @description  章节讨论中置顶显示自己的吐槽,高亮回复过的章节格子
// @author       oo
// @include      http*://bgm.tv/*
// @include      http*://chii.in/*
// @include      http*://bangumi.tv/*
// @license      MIT
// ==/UserScript==

(async function () {

    async function getDOM(url) {
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error('HTTP request failed');
            const html = await response.text();
            const dom = new DOMParser().parseFromString(html, 'text/html');
            return dom;
        } catch (error) {
            console.error('章节讨论置顶自己的吐槽: Error fetching and parsing page:', error);
        }
    }

    // Microsoft Copilot start
    function randomDelay() {
        return new Promise(resolve => {
            setTimeout(resolve, Math.random() * 2000 + 1000);
        });
    }

    function waitForElm(elem) {
        return new Promise(resolve => {
            if (elem) {
                return resolve(elem);
            }

            const observer = new MutationObserver(mutations => {
                if (elem) {
                    resolve(elem);
                    observer.disconnect();
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }

    const cacheHandler = {
        get(target, key) {
            const data = JSON.parse(localStorage.getItem(target.storageKey) || '{}');
            const now = Date.now();

            if (data[key] && now < data[key].expiry) {
                return data[key].value;
            } else {
                delete data[key];
                localStorage.setItem(target.storageKey, JSON.stringify(data));
                return undefined;
            }
        },
        set(target, key, value) {
            const now = Date.now();
            const threeMonths = 3 * 30 * 24 * 60 * 60 * 1000;
            const expiry = now + threeMonths;

            const data = JSON.parse(localStorage.getItem(target.storageKey) || '{}');
            data[key] = { value, expiry };
            localStorage.setItem(target.storageKey, JSON.stringify(data));

            return true;
        }
    };
    const cache = new Proxy({ storageKey: 'incheijs_ep_cache' }, cacheHandler);
    // end

    // 章节讨论页
    if (location.pathname.startsWith('/ep')) {
        let replies = getRepliesFromDOM(document);
        //console.log(replies)
        const id = location.pathname.split('/')[2];
        if (replies[0]) {
            document.getElementById('reply_wrapper').before(...replies.map(elem => {
                const clone = elem.cloneNode(true);
                clone.id += '_clone';
                return clone;
            }));
            cache[id] = true;
        } else {
            cache[id] = false;
        }
        // 点击回复按钮
        document.querySelector('.inputBtn').addEventListener('click', async () => {
            replies = await waitForElm(getRepliesFromDOM(document));
            if (replies[0]) cache[id] = true;
        });
        // 侧栏其他章节,无法直接判断是否看过,只取缓存不检查
        const epElems = document.querySelectorAll('.sideEpList li a');
        for (const elem of epElems) {
            const url = elem.href;
            const id = url.split('/')[4];
            //console.log(cache[id])
            if (cache[id] === true) elem.style.color = 'blueviolet';
            //console.log(elem + 'rendered')
        }
    }

    function getRepliesFromDOM(dom) {
        return [...dom.querySelectorAll('#comment_list .row_reply')].filter(comment => comment.dataset.itemUser === dom.querySelector('.avatar').href.split('/').at(-1));
    }

    // 动画条目页
    const subjectID = location.pathname.match(/(?<=subject\/)\d+/)?.[0];
    if (subjectID) {
        const type = document.querySelector('.focus').href.split('/')[3];
        if (type === 'anime') {
            renderChecks();
        }
    }

    // 首页
    if (location.pathname === '/') {
        renderChecks();
    }

    async function renderChecks() {
        //console.log('rendering...')
        const watchedEpElems = document.querySelectorAll('.load-epinfo.epBtnWatched');
        for (const elem of watchedEpElems) {
            const url = elem.href;
            const id = url.split('/')[4];
            if (cache[id] === true) {
                mark(elem);
                //console.log(elem);
            } else if (cache[id] !== false) {
                const dom = await getDOM(url);
                if (getRepliesFromDOM(dom)[0]) {
                    //console.log(elem);
                    mark(elem);
                    cache[id] = true;
                } else {
                    cache[id] = false;
                }
                await randomDelay();
            }
            //console.log(elem + ' rendered')
        }
        function mark(elem) {
            elem.style.background = 'blueviolet';
            elem.style.borderColor = 'blueviolet';
            //elem.addEventListener('mouseenter', () => {
            //    const popup = document.querySelector('#cluetip-inner .tip');
            //    console.log(popup);
            //})
        }
    }

})();