B站番剧真实评分 大数据量修复

BiliBili 番剧真实评分

// ==UserScript==
// @name         B站番剧真实评分 大数据量修复
// @version      1.0.3
// @description  BiliBili 番剧真实评分
// @author       Star0
// @match        *://www.bilibili.com/bangumi/media/*
// @namespace    http://tampermonkey.net/
// ==/UserScript==

(() => {
    'use strict';

    let allData = [];
    let totalCnt = { short: 0, long: 0 }
    let render = null, rmDialog = null, mid;
    let isPaused = false;
    let avgDisplay = null;
    try { mid = location.href.match(/media\/md(\d+)/)[1] } catch { }
    if (!mid) { throw new Error('未进入番剧详情页面') }

    async function getScore(next, type) {
        let res, url = `https://api.bilibili.com/pgc/review/${type}/list?media_id=${mid}&ps=12575&sort=0`;
        if (next) url += `&cursor=${next}`;
        try { res = await fetch(url, { 'method': 'GET' }); }
        catch { return false; }
        const { data } = await res.json();
        if (totalCnt[type] == 0)
            totalCnt[type] = data.total;
        return data;
    }

    async function score(type) {
        try {
            // 获取初始数据
            const initialData = await getScore(undefined, type);
            if (!initialData || !initialData.list) {
                throw new Error('Failed to get initial data');
            }
            submitList(initialData.list);
            let next = initialData.next;

            while (true) {
                if (isPaused) {
                    await new Promise(resolve => {
                        const checkPause = setInterval(() => {
                            if (!isPaused) {
                                clearInterval(checkPause);
                                resolve();
                            }
                        }, 100);
                    });
                }

                try {
                    const data = await getScore(next, type);
                    if (!data || !data.list) {
                        throw new Error('Failed to get data');
                    }
                    submitList(data.list);
                    render(type);
                    next = data.next;
                    if (next == 0) return;
                } catch (error) {
                    console.error('Error fetching data:', error);
                    isPaused = true;
                    const pauseBtn = document.querySelector('#pauseButton');
                    if (pauseBtn) {
                        pauseBtn.innerText = '继续(出错自动暂停)';
                    }
                    if (avgDisplay) {
                        avgDisplay.innerHTML = `当前平均分: ${calculateAverage()}<br><span style="color:red">获取数据出错,已自动暂停</span>`;
                    }
                    await new Promise(resolve => {
                        const checkResume = setInterval(() => {
                            if (!isPaused) {
                                clearInterval(checkResume);
                                resolve();
                            }
                        }, 100);
                    });
                    if (avgDisplay) {
                        avgDisplay.innerHTML = `当前平均分: ${calculateAverage()}`;
                    }
                    continue;
                }
            }
        } catch (error) {
            console.error('Fatal error in score function:', error);
            if (avgDisplay) {
                avgDisplay.innerHTML = `<span style="color:red">发生致命错误,请刷新页面重试</span>`;
            }
            throw error;
        }
    }

    function calculateAverage() {
        if (allData.length === 0) return "0.00000";
        const total = allData.reduce((p, v) => p + v, 0);
        return (total / allData.length).toFixed(5);
    }

    function updateStars(score) {
        const starLc = parseInt(Math.round(score / 2));
        const starHc = 5 - starLc;
        const starsDom = document.getElementsByClassName('review-stars')[0];
        starsDom.innerHTML = '';
        for (let i = 0; i < starLc; i++) {
            const star = document.createElement('i');
            star.className = 'icon-star icon-star-light';
            starsDom.appendChild(star);
        }
        for (let i = 0; i < starHc; i++) {
            const star = document.createElement('i');
            star.className = 'icon-star icon-star-half';
            starsDom.appendChild(star);
        }
    }

    function updateDisplays() {
        const currentAvg = calculateAverage();
        document.getElementsByClassName('media-info-score-content')[0].innerText = currentAvg;
        updateStars(currentAvg);
        if (avgDisplay) {
            avgDisplay.innerText = `当前平均分: ${currentAvg}`;
        }
    }

    function submitList(list) {
        allData.push(...list.map(item => item.score));
        if (allData.length % 100 === 0) {
            updateDisplays();
        }
    }

    function preRender() {
        const maskBox = document.createElement('div');
        const mainBox = document.createElement('div');
        const wrapS = document.createElement('div');
        const wrapL = document.createElement('div');
        const textS = document.createElement('div');
        const textL = document.createElement('div');
        const boxS = document.createElement('div');
        const boxL = document.createElement('div');
        const progS = document.createElement('div');
        const progL = document.createElement('div');
        const countS = document.createElement('div');
        const countL = document.createElement('div');
        const controlArea = document.createElement('div');
        const pauseBtn = document.createElement('button');
        const avgText = document.createElement('div');

        document.body.appendChild(maskBox);
        maskBox.appendChild(mainBox);
        mainBox.appendChild(wrapS);
        mainBox.appendChild(wrapL);
        mainBox.appendChild(controlArea);
        controlArea.appendChild(pauseBtn);
        controlArea.appendChild(avgText);
        wrapS.appendChild(textS);
        wrapL.appendChild(textL);
        wrapS.appendChild(boxS);
        wrapL.appendChild(boxL);
        boxS.appendChild(progS);
        boxL.appendChild(progL);
        wrapS.appendChild(countS);
        wrapL.appendChild(countL);

        maskBox.style.position = 'fixed';
        maskBox.style.width = '100%';
        maskBox.style.height = '100%';
        maskBox.style.background = 'rgba(0,0,0,0.8)';
        maskBox.style.top = '0';
        maskBox.style.left = '0';
        maskBox.style.zIndex = '999';
        maskBox.style.display = 'flex';
        maskBox.style.alignItems = 'center';
        maskBox.style.justifyContent = 'center';

        mainBox.style.width = '655px';
        mainBox.style.height = '250px';
        mainBox.style.background = '#fff';
        mainBox.style.borderRadius = '6px';
        mainBox.style.padding = '51px 0';

        wrapS.style.width = wrapL.style.width = '655px';
        wrapS.style.height = wrapL.style.height = '100px';
        wrapS.style.display = wrapL.style.display = 'flex';
        wrapS.style.alignItems = wrapL.style.alignItems = 'center';
        wrapS.style.justifyContent = wrapL.style.justifyContent = 'center';

        textS.innerText = '短评:';
        textL.innerText = '长评:';
        textL.style.fontSize = textS.style.fontSize = '14px';
        textL.style.color = textS.style.color = '#333';
        textL.style.marginRight = textS.style.marginRight = '16px';

        boxL.style.width = boxS.style.width = '400px';
        boxL.style.height = boxS.style.height = '32px';
        boxL.style.background = boxS.style.background = '#eee';
        boxL.style.position = boxS.style.position = 'relative';

        progL.style.position = progS.style.position = 'absolute';
        progL.style.left = progS.style.left = '0';
        progL.style.top = progS.style.top = '0';
        progL.style.width = progS.style.width = '0%';
        progL.style.height = progS.style.height = '100%';
        progL.style.background = progS.style.background = '#ff85ad';

        countS.style.marginLeft = countL.style.marginLeft = '10px';
        countS.style.width = countL.style.width = '100px';

        controlArea.style.display = 'flex';
        controlArea.style.alignItems = 'center';
        controlArea.style.justifyContent = 'center';
        controlArea.style.marginTop = '20px';
        controlArea.style.gap = '20px';

        pauseBtn.id = 'pauseButton';
        pauseBtn.style.padding = '5px 15px';
        pauseBtn.style.cursor = 'pointer';
        pauseBtn.style.minWidth = '80px';
        pauseBtn.innerText = '暂停';

        avgText.style.fontSize = '14px';
        avgText.style.color = '#333';
        avgText.style.fontWeight = 'bold';
        avgText.innerText = '当前平均分: 0.00000';
        avgDisplay = avgText;

        pauseBtn.onclick = () => {
            isPaused = !isPaused;
            pauseBtn.innerText = isPaused ? '继续' : '暂停';
            if (!isPaused && avgDisplay) {
                avgDisplay.innerHTML = `当前平均分: ${calculateAverage()}`;
            }
        };

        render = (type) => {
            const node = type == 'long' ? progL : progS;
            const countNode = type == 'long' ? countL : countS;
            let width, count;
            if (type == 'long') {
                count = allData.length - totalCnt.short;
                width = `${count * 100 / totalCnt.long}%`;
            } else {
                count = allData.length;
                width = `${count * 100 / totalCnt.short}%`;
            }
            node.style.width = width;
            countNode.innerText = `${count}/${type == 'long' ? totalCnt.long : totalCnt.short}`;
        }

        rmDialog = () => {
            allData = [];
            totalCnt = { short: 0, long: 0 };
            document.body.removeChild(maskBox);
        }
    }

    async function execute() {
        preRender();
        await score('short');
        await score('long');
        const finalAvg = calculateAverage();
        document.getElementsByClassName('media-info-score-content')[0].innerText = finalAvg;
        updateStars(finalAvg);
        rmDialog();
    }

    function addButton() {
        let btnArea = document.getElementsByClassName('media-info-btns')[0];
        let followBtn = document.getElementsByClassName('bangumi-btn')[0];
        let exeBtn = document.createElement('div');
        let exeInner = document.createElement('div');
        btnArea.insertBefore(exeBtn, followBtn.nextSibling);
        exeBtn.appendChild(exeInner);
        exeBtn.className = 'bangumi-btn';
        exeBtn.onclick = execute;
        exeInner.className = 'btn-follow';
        exeInner.innerText = '真实评分';
    }

    addButton();
})();