条目讨论页显示用户评价

也可以干别的

目前為 2024-12-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name         条目讨论页显示用户评价
// @namespace    https://bgm.tv/group/topic/411796
// @version      0.0.1
// @description  也可以干别的
// @author       mmv
// @include      http*://bgm.tv/subject/topic/*
// @include      http*://bgm.tv/blog/*
// @include      http*://bgm.tv/ep/*
// @include      http*://bgm.tv/character/*
// @include      http*://bgm.tv/group/topic/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bgm.tv
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const styleSheet = document.createElement("style");
    styleSheet.innerText = `
        .ccf-star { margin-left: 5px; }
        .ccf-status { margin-left: 5px; color: #999; font-size: 12px; font-weight: normal; }
        .ccf-comment {
            margin-left: 5px;
            position: relative;
            cursor: help;
        }
        .ccf-comment::after {
            content: attr(data-comment);
            position: absolute;
            top: 100%;
            left: 0;
            background-color: rgba(254, 254, 254, 0.9);
            box-shadow: inset 0 1px 1px hsla(0, 0%, 100%, 0.3), inset 0 -1px 0 hsla(0, 0%, 100%, 0.1), 0 2px 4px hsla(0, 0%, 0%, 0.2);
            backdrop-filter: blur(5px);
            border-radius: 5px;
            padding: 5px;
            width: 250px;
            z-index: 1000;
            font-weight: normal;
            font-size: 12px;
            color: rgba(0, 0, 0, .7);
            cursor: text;
            transform: scale(0);
        }
        .ccf-comment:hover::after {
            transform: scale(1);
        }
        html[data-theme="dark"] .ccf-comment::after {
            background: rgba(80, 80, 80, 0.7);
            color: rgba(255, 255, 255, .7);
        }
        .loader {
            margin-left: 5px;
            border: 2px solid transparent; /* Light grey */
            border-top: 2px solid #F09199; /* Blue */
            border-radius: 50%;
            width: 10px;
            height: 10px;
            animation: spin 2s linear infinite;
            display: inline-block;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    `;
    document.head.appendChild(styleSheet);

    if (location.pathname.startsWith('/subject/topic') || location.pathname.startsWith('/ep')) {

        const userLinks = document.querySelectorAll('.inner strong a');
        const subject_id = document.querySelector('#subject_inner_info a').href.split('/').pop();
        if (!userLinks || !subject_id) return;
        lazyRender(userLinks, subject_id);

    } else if (location.pathname.startsWith('/blog')) {

        const userLinks = [document.querySelector('#pageHeader a')].concat([...document.querySelectorAll('#columnA .inner strong a')]);
        const relatedSubjects = document.querySelectorAll('#related_subject_list .ll a');
        if (!userLinks || !relatedSubjects) return;
        multiSubjectsRender(userLinks, relatedSubjects);

    } else if (location.pathname.startsWith('/character')) {

        const userLinks = document.querySelectorAll('.inner strong a');
        const castSubjects = document.querySelectorAll('.browserList .inner a[href^="/subject/"]');
        if (!userLinks || !castSubjects) return;
        multiSubjectsRender(userLinks, castSubjects);

    } else if (location.pathname.startsWith('/group/topic')) {

        const userLinks = document.querySelectorAll('#columnInSubjectA .inner strong a');
        if (!userLinks) return;
        window.ccf = subject_id => {
            lazyRender(userLinks, subject_id, true);
        }

    }

    async function getUserData(username, subject_id) {
        const cachedData = sessionStorage.getItem(`userData_${username}_${subject_id}`);
        if (cachedData) return JSON.parse(cachedData);

        const response = await fetch(`https://api.bgm.tv/v0/users/${username}/collections/${subject_id}`);
        if (response.ok) {
            const data = await response.json();
            sessionStorage.setItem(`userData_${username}_${subject_id}`, JSON.stringify(data));
            return data;
        } else if (response.status === 404) {
            const data = { not_found: true }
            sessionStorage.setItem(`userData_${username}_${subject_id}`, JSON.stringify(data));
            return data;
        } else {
            throw new Error('Network response was not ok: ', response);
        }
    }

    async function renderUserData(userLink, subject_id, showSubject=false) {
        const username = userLink.href.split('/').pop();
        let html = '';
        const loader = document.createElement('div');
        loader.classList.add('loader');
        userLink.after(loader);

        try {
            const data = await getUserData(username, subject_id);
            const { not_found, subject_type, rate, type, ep_status, vol_status, comment, subject } = data;
            const name = subject?.name;

            loader.remove();

            if (!not_found) {
                const verb = ['读', '看', '听', '玩', '', '看'][subject_type - 1];
                if (rate && rate !== 0) {
                    html += `<span class="ccf-star starstop-s"><span class="starlight stars${rate}"></span></span>`;
                }
                if (type) {
                    html += `<span class="ccf-status">${[`想${verb}`, `${verb}过`, `在${verb}`, '搁置', '抛弃'][type-1]}${
                        showSubject && name || ''
                    }</span>`;
                }
                if (ep_status) {
                    html += `<span class="ccf-status">${verb}到ep${ep_status}</span>`;
                }
                if (vol_status) {
                    html += `<span class="ccf-status">${verb}到vol${ep_status}</span>`;
                }
                if (comment) {
                    html += `<span class="ccf-comment" data-comment="${comment.replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;')}">💬</span>`;
                }

                html = `<span id="ccf-${subject_id}">${html}</span>`;
                userLink.insertAdjacentHTML('afterend', html);
            } else {
                userLink.insertAdjacentElement('afterend', loadBtn('未标记该条目', userLink, subject_id));
            }

        } catch (error) {
            console.error('Error fetching user data:', error);
            userLink.insertAdjacentElement('afterend', loadBtn('加载失败', userLink, subject_id));
        }

        userLink.parentElement.querySelector('.loader')?.remove();
    }

    function loadBtn(text, userLink, subject_id) {
        const btn = document.createElement('span');
        btn.classList.add('ccf-status');
        btn.textContent = text;
        btn.style.cursor = 'pointer';
        btn.addEventListener('click', () => {
            const username = userLink.href.split('/').pop();
            sessionStorage.removeItem(`userData_${username}_${subject_id}`);
            btn.remove();
            renderUserData(userLink, subject_id);
        });
        return btn;
    }

    function lazyRender(userLinks, subject_id, showSubject) {
        const observer = new IntersectionObserver(entries => {
            if (entries[0].intersectionRatio <= 0) return;
            for (const { isIntersecting, target } of entries) {
                if (!isIntersecting) continue;
                observer.unobserve(target);
                renderUserData(target, subject_id, showSubject);
            }
        });
        for (const userLink of userLinks) observer.observe(userLink);
    }

    function multiSubjectsRender(userLinks, subjectLinks) {
        if (subjectLinks.length === 1) {
            lazyRender(userLinks, subjectLinks[0].href.split('/').pop());
            return;
        }
        for (const subjectLink of subjectLinks) {
            const frag = document.createDocumentFragment();
            const br = document.createElement('br');
            const btn = document.createElement('a');
            btn.href = 'javascript:;';
            btn.textContent = '显示评价';
            btn.classList.add('l');
            btn.addEventListener('click', function() {
                const status = document.createElement('span');
                status.classList.add('ccf-status');
                status.textContent = '已显示本作评价';
                this.insertAdjacentElement('afterend', status);
                this.remove();
                lazyRender(userLinks, subjectLink.href.split('/').pop(), true);
            });
            frag.append(br, btn);
            subjectLink.after(frag);
        }
    }

})();