嗶哩嗶哩(B站|Bilibili)收藏夾Fix (備份影片資訊)

自動備份影片資訊至本地和第三方網站, 失效影片資訊回顯

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

// ==UserScript==
// @name              bilibili favlist backup
// @name:zh-CN        哔哩哔哩(B站|Bilibili)收藏夹Fix (备份视频信息)
// @name:zh-TW        嗶哩嗶哩(B站|Bilibili)收藏夾Fix (備份影片資訊)
// @namespace         http://tampermonkey.net/
// @version           1
// @description       automatically backup info of videos in favlist
// @description:zh-CN 自动备份视频信息至本地和第三方网站, 失效视频信息回显
// @description:zh-TW 自動備份影片資訊至本地和第三方網站, 失效影片資訊回顯
// @author            YTB0710
// @match             https://space.bilibili.com/*
// @connect           bbdownloader.com
// @connect           bilibili.com
// @connect           biliplus.com
// @connect           jijidown.com
// @connect           xbeibeix.com
// @grant             GM_openInTab
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_deleteValue
// @grant             GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    const localizedText = {
        'UPDATES': {
            'zh-CN': '',
            'zh-TW': ''
        },
        'DROPDOWN_COVER': {
            'zh-CN': '封面原图',
            'zh-TW': '封面原圖'
        },
        'DROPDOWN_LOCAL': {
            'zh-CN': '本地备份数据',
            'zh-TW': '本地備份資料'
        },
        'DROPDOWN_JUMP': {
            'zh-CN': '跳转至BJX',
            'zh-TW': '跳轉至BJX'
        },
        'DROPDOWN_RESET': {
            'zh-CN': '重置备份数据',
            'zh-TW': '重置備份資料'
        },
        'ONLY_PROCESS_DISABLED': {
            'zh-CN': '仅处理失效视频',
            'zh-TW': '僅處理失效影片'
        },
        'AUTO_NEXT_PAGE': {
            'zh-CN': '自动点击下一页',
            'zh-TW': '自動點擊下一頁'
        },
        'ENABLE_DEBUG': {
            'zh-CN': '控制台输出日志',
            'zh-TW': '控制台輸出日誌'
        },
        'AV': {
            'zh-CN': 'AV号',
            'zh-TW': 'AV號'
        },
        'BV': {
            'zh-CN': 'BV号',
            'zh-TW': 'BV號'
        },
        'TITLE': {
            'zh-CN': '标题',
            'zh-TW': '標題'
        },
        // 'POSITION_ON_THIS_PAGE': {
        //     'zh-CN': '在本页的位置',
        //     'zh-TW': '在本頁的位置'
        // },
        // 'POSITION_ON_THIS_PAGE_WITH_PROMPT': {
        //     'zh-CN': '在本页的位置(从1开始)',
        //     'zh-TW': '在本頁的位置(從1開始)'
        // },
        'FID_NOT_FOUND_ERROR': {
            'zh-CN': '无法获取当前收藏夹的fid, 刷新页面可能有帮助',
            'zh-TW': '無法獲取當前收藏夾的fid, 重新載入頁面可能有幫助'
        },
        'REQUEST_TIMEOUT_ERROR': {
            'zh-CN': '请求超时',
            'zh-TW': '請求逾時'
        },
        'REQUEST_FAILED_ERROR': {
            'zh-CN': '请求失败',
            'zh-TW': '請求失敗'
        },
        'MULTIPLE_DROPDOWN_FOUND_ERROR': {
            'zh-CN': '同时发现了多个下拉列表开关, 无法确定下拉列表所对应的视频, 刷新页面可能有帮助',
            'zh-TW': '同時發現了多個下拉列表開關, 無法確定下拉列表所對應的影片, 重新載入頁面可能有幫助'
        },
        'TARGET_VIDEO_NOT_FOUND_ERROR': {
            'zh-CN': '无法确定下拉列表所对应的视频, 请反馈该问题',
            'zh-TW': '無法確定下拉列表所對應的影片, 請反饋該問題'
        },
        'UNKNOWN_ERROR': {
            'zh-CN': '发生未知错误, 请反馈该问题',
            'zh-TW': '發生未知錯誤, 請反饋該問題'
        },
    };

    const currentVersion = 1;
    const preferredLanguage = getPreferredLanguage();
    const settings = GM_getValue('settings', {
        version: 0,
        onlyProcessDisabled: false,
        // alwaysGetIntroFromVideoPage: false,
        enableDebug: false,
        usageCount: 0,
        usageCountNewFreshSpace: 0,
    });

    const favlistURLRegex = /https:\/\/space\.bilibili\.com\/\d+\/favlist.*/;
    const fidFromURLRegex = /fid=(\d+)/;
    const BVFromURLRegex = /video\/(\w{12})/;
    const httpsFromURLRegex = /^https?:\/\//;
    const jsonFromBiliplusRegex = /window\.addEventListener\('DOMContentLoaded',function\(\){view\((.+)\);}\);/;

    let AVBVs;
    let fidOfAVBVs;
    let onFavlistPage = false;
    let autoNextPage = false;
    let newFreshSpace;
    let videosPerPage;
    let displayPrompt;
    let processing;
    // let mutations_count = 0;
    // let mutation_count = 0;
    // let first = true;
    let divMessage;
    let activeControllers = new Set();

    const favlistObserver = new MutationObserver((mutations, observer) => {
        if (settings.enableDebug) console.warn('callback favlistObserver');
        if (document.querySelector('div.items')) {
            if (settings.enableDebug) console.warn('disconnect favlistObserver');
            observer.disconnect();
            newFreshSpace = true;
            videosPerPage = window.innerWidth < 1760 ? 40 : 36;
            addControls();
            main();
            if (settings.enableDebug) console.warn('observe itemsObserver');
            itemsObserver.observe(document.querySelector('div.items'), { childList: true, attributes: false, characterData: false });
            if (settings.enableDebug) console.warn('observe bodyChildListObserver');
            bodyChildListObserver.observe(document.body, { childList: true, attributes: false, characterData: false });
            return;
        }
        if (document.querySelector('div.fav-content.section')) {
            if (settings.enableDebug) console.warn('disconnect favlistObserver');
            observer.disconnect();
            newFreshSpace = false;
            videosPerPage = 20;
            addControls();
            if (settings.enableDebug) console.warn('observe favContentSectionObserver');
            favContentSectionObserver.observe(document.querySelector('div.fav-content.section'), { characterData: false, attributeFilter: ['class'] });
            return;
        }
    });

    const itemsObserver = new MutationObserver(async (mutations) => {
        stopAll();
        if (settings.enableDebug) console.warn('callback itemsObserver');
        await delay(100);
        // mainNewFreshSpace();
        // mainNewFreshSpace(mutations);
        main();
    });

    const bodyChildListObserver = new MutationObserver(mutations => {
        if (settings.enableDebug) console.warn('callback bodyChildListObserver');
        if (settings.enableDebug) console.log(mutations);
        for (const mutation of mutations) {
            for (const addedNode of mutation.addedNodes) {
                if (addedNode.nodeType === 1 && addedNode.classList.contains('bili-card-dropdown-popper')) {
                    addDropdown(addedNode);
                    // return;
                }
                if (addedNode.nodeType === 1 && addedNode.classList.contains('vui_toast--wrapper')) {
                    stopAll();
                    if (settings.enableDebug) console.warn('disconncet itemsObserver');
                    itemsObserver.disconnect();
                    // return;
                }
            }
            for (const removedNode of mutation.removedNodes) {
                if (removedNode.nodeType === 1 && removedNode.classList.contains('vui_toast--wrapper')) {
                    stopAll();
                    // mainNewFreshSpace();
                    main();
                    if (settings.enableDebug) console.warn('observe itemsObserver');
                    itemsObserver.observe(document.querySelector('div.items'), { childList: true, attributes: false, characterData: false });
                    // return;
                }
            }
        }
    });

    const favContentSectionObserver = new MutationObserver(mutations => {
        if (settings.enableDebug) console.warn('callback favContentSectionObserver');
        for (const mutation of mutations) {
            if (!mutation.target.classList.contains('loading')) {
                stopAll();
                main();
                return;
            }
        }
    });

    checkURL();

    const originalPushState = history.pushState;
    history.pushState = function (...args) {
        originalPushState.apply(this, args);
        checkURL();
    };

    const originalReplaceState = history.replaceState;
    history.replaceState = function (...args) {
        originalReplaceState.apply(this, args);
        checkURL();
    };

    window.addEventListener('popstate', checkURL);

    function checkURL() {
        if (settings.enableDebug) console.warn('checkURL');
        if (favlistURLRegex.test(location.href)) {
            if (!onFavlistPage) {
                onFavlistPage = true;
                if (settings.enableDebug) console.warn('observe favlistObserver');
                favlistObserver.observe(document.body, { subtree: true, childList: true, attributes: false, characterData: false });
            }
        } else {
            if (onFavlistPage) {
                stopAll();
                onFavlistPage = false;
                if (settings.enableDebug) console.warn('disconnect favlistObserver');
                favlistObserver.disconnect();
                if (settings.enableDebug) console.warn('disconncet itemsObserver');
                itemsObserver.disconnect();
                if (settings.enableDebug) console.warn('disconncet bodyChildListObserver');
                bodyChildListObserver.disconnect();
                if (settings.enableDebug) console.warn('disconncet favContentSectionObserver');
                favContentSectionObserver.disconnect();
            }
        }
    }

    function getPreferredLanguage() {
        const languages = navigator.languages || [navigator.language];
        for (const lang of languages) {
            if (lang === 'zh-CN') {
                return 'zh-CN';
            }
            if (lang === 'zh-TW') {
                return 'zh-TW';
            }
            if (lang === 'zh-HK') {
                return 'zh-TW';
            }
        }
        return 'zh-CN';
    }

    function getLocalizedText(key) {
        return localizedText[key][preferredLanguage];
    }

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

    function getCurrentTs() {
        return Math.floor(Date.now() / 1000);
    }

    function stopAll() {
        for (const controller of activeControllers) {
            controller.abort();
        }
    }

    async function main() {
        if (settings.enableDebug) console.warn('============main============');

        let processing;
        let controller;

        try {

            processing = true;
            controller = new AbortController();
            activeControllers.add(controller);

            let fid;
            if (newFreshSpace) {
                const fidFromURLMatch = location.href.match(fidFromURLRegex);
                if (fidFromURLMatch) {
                    fid = fidFromURLMatch[1];
                } else {
                    // addMessage('-----------------------------');
                    addMessage(getLocalizedText('FID_NOT_FOUND_ERROR'));
                    return;
                }
            } else {
                fid = document.querySelector('.fav-item.cur').getAttribute('fid');
            }

            let pn;
            if (newFreshSpace) {
                const pagenation = document.querySelector('button.vui_pagenation--btn-num.vui_button--active');
                if (!pagenation) {
                    pn = 1;
                } else {
                    pn = parseInt(pagenation.innerText, 10);
                }
            } else {
                pn = parseInt(document.querySelector('li.be-pager-item-active > a').innerText, 10);
            }

            let videos;
            if (newFreshSpace) {
                videos = document.querySelectorAll('div.items__item');
            } else {
                videos = document.querySelectorAll('li.small-item');
            }

            if (fid !== fidOfAVBVs || !AVBVs) {
                if (controller.signal.aborted) {
                    // throw Error();
                    throw new DOMException('', 'AbortError');
                }
                const response = await new Promise((resolve, reject) => {
                    GM.xmlHttpRequest({
                        method: 'GET',
                        url: `https://api.bilibili.com/x/v3/fav/resource/ids?media_id=${fid}`,
                        timeout: 5000,
                        responseType: 'json',
                        onload: (res) => resolve(res),
                        onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
                        ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
                    });
                });
                if (settings.enableDebug) console.warn(`update AVBVs, fid: ${fid}`);
                fidOfAVBVs = fid;
                AVBVs = response.response.data;
            }

            const _AVBVs = structuredClone(AVBVs);

            let apiDetails = {};

            // for (const video of videos) {
            for (const [index, video] of videos.entries()) {

                let as;
                let AV;
                let BV;
                let title;

                try {

                    if (controller.signal.aborted) {
                        // throw Error();
                        throw new DOMException('', 'AbortError');
                    }

                    let disabled = false;
                    if (newFreshSpace && !video.querySelector('.bili-cover-card__stats')) {
                        disabled = true;
                    } else if (video.classList.contains('disabled')) {
                        disabled = true;
                    }

                    if (settings.onlyProcessDisabled && !disabled) {
                        continue;
                    }

                    as = video.querySelectorAll('a');

                    const divTitleNewFreshSpace = video.querySelector('.bili-video-card__title');

                    if (controller.signal.aborted) {
                        // throw Error();
                        throw new DOMException('', 'AbortError');
                    }

                    if (newFreshSpace) {
                        BV = as[0].getAttribute('href').match(BVFromURLRegex)[1];
                    } else {
                        BV = video.getAttribute('data-aid');
                    }

                    AV = _AVBVs.find(_AVBV => _AVBV.bvid === BV).id;

                    title = as[1].innerText;

                    if (settings.enableDebug) console.warn('============video============');
                    if (settings.enableDebug) console.log(`index: ${index + 1}`);
                    if (settings.enableDebug) consoleAVBVTitle('log', AV, BV, title);

                    let spanFavTime;
                    let divTCABJX;

                    if (newFreshSpace) {
                        const divSubtitle = document.createElement('div');
                        divSubtitle.classList.add('bili-video-card__subtitle');
                        video.querySelector('div.bili-video-card__details').appendChild(divSubtitle);
                        spanFavTime = document.createElement('span');
                        spanFavTime.innerText = `收藏于:`;
                        divSubtitle.appendChild(spanFavTime);

                        divTCABJX = document.createElement('div');
                        divTCABJX.style.marginLeft = 'auto';
                        divTCABJX.style.display = 'block';
                        divSubtitle.appendChild(divTCABJX);
                    } else {
                        const divMetaPubdate = video.querySelector('div.meta.pubdate');
                        const metaText = divMetaPubdate.innerText;
                        divMetaPubdate.innerHTML = null;

                        spanFavTime = document.createElement('span');
                        // spanPubdate.innerText = metaText;
                        // spanPubdate.innerText = metaText.replace(': ',': ');
                        spanFavTime.innerText = metaText.replace(': ', ':');
                        spanFavTime.style.width = 'auto';
                        divMetaPubdate.appendChild(spanFavTime);

                        // divTCABJX = document.createElement('div');
                        // divTCABJX.style.marginLeft = 'auto';
                        // // divTCABJX.style.display = 'block';
                        // divMetaPubdate.appendChild(divTCABJX);

                        divTCABJX = divMetaPubdate;
                    }

                    const spanX = document.createElement('span');
                    spanX.innerText = 'X';
                    if (!newFreshSpace) {
                        // spanX.style.marginRight = '11px';
                        spanX.style.marginRight = '9px';
                    }
                    spanX.style.float = 'right';
                    spanX.style.fontWeight = 'bold';
                    spanX.style.cursor = 'pointer';
                    spanX.addEventListener('click', () => {
                        try {
                            GM_openInTab(`https://xbeibeix.com/video/${BV}`, { active: true, insert: false, setParent: true });
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanX);

                    const spanJ = document.createElement('span');
                    spanJ.innerText = 'J';
                    spanJ.style.marginRight = '3px';
                    spanJ.style.float = 'right';
                    spanJ.style.fontWeight = 'bold';
                    spanJ.style.cursor = 'pointer';
                    spanJ.addEventListener('click', () => {
                        try {
                            GM_openInTab(`https://www.jijidown.com/video/${BV}`, { active: true, insert: false, setParent: true });
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanJ);

                    const spanB = document.createElement('span');
                    spanB.innerText = 'B';
                    spanB.style.marginRight = '3px';
                    spanB.style.float = 'right';
                    spanB.style.fontWeight = 'bold';
                    spanB.style.cursor = 'pointer';
                    spanB.addEventListener('click', () => {
                        try {
                            GM_openInTab(`https://www.biliplus.com/video/${BV}`, { active: true, insert: false, setParent: true });
                            GM_openInTab(`https://www.biliplus.com/video/av${AV}`, { insert: false, setParent: true });
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanB);

                    const spanA = document.createElement('span');
                    spanA.innerText = 'A';
                    spanA.style.marginRight = '3px';
                    spanA.style.float = 'right';
                    spanA.style.fontWeight = 'bold';
                    spanA.style.cursor = 'pointer';
                    spanA.addEventListener('click', () => {
                        try {
                            // GM_openInTab(`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${fid}&pn=${pn}&ps=${videosPerPage}&keyword=&order=mtime&type=0&tid=0&platform=web`, { active: true, insert: false, setParent: true });
                            GM_openInTab(`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${fid}&pn=${(pn - 1) * videosPerPage + index + 1}&ps=1&order=mtime`, { active: true, insert: false, setParent: true });
                            GM_openInTab(`https://api.bilibili.com/x/v3/fav/resource/infos?resources=${AV}%3A2&folder_id=${fid}`, { insert: false, setParent: true });
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanA);

                    let backup = GM_getValue(BV, {
                        AV: null,
                        BV: null,
                        title: null,
                        intro: null,
                        cover: null,
                        upperUid: null,
                        upperName: null,
                        upperAvatar: null,
                        timeUpload: null,
                        timePublish: null,
                        timeFavorite: null,
                        firstFrame: null,
                        api: null,
                        biliplus: null,
                        jijidown: null,
                        xbeibeix: null,
                        preferredTitle: null,
                        preferredCover: null
                    });

                    let functions = [];

                    try {

                        if (!backup.api
                            || (backup.api.value === false && getCurrentTs() - backup.api.ts > 3600 * 1)
                            || (backup.api.value === true && getCurrentTs() - backup.api.ts > 3600 * 1)) {
                            await getFromApi(AV, BV, title, backup, spanA, apiDetails, fid, pn, disabled, spanFavTime);

                        } else {
                            spanA.style.color = backup.api.value ? '#00ff00' : '#ff0000';
                            if (backup.timeFavorite) {
                                const target = backup.timeFavorite.find(i => i.fid === fid);
                                if (target) {
                                    // spanFavTime.innerText = `收藏于: ${new Date(target.value * 1000).toLocaleString()}`;
                                    // spanFavTime.innerText = `收藏于: ${formatTimeFavorite1(target.value)}`;
                                    // spanFavTime.innerText = `收藏于: ${formatTimeFavorite2(new Date(target.value * 1000))}`;
                                    spanFavTime.innerText = `收藏于:${formatTimeFavorite2(new Date(target.value * 1000))}`;
                                    // spanFavTime.innerText = `收藏于: ${formatTimeFavorite3(target.value)}`;
                                }
                            }
                        }

                        if (!backup.jijidown
                            || (backup.jijidown.value === false && getCurrentTs() - backup.jijidown.ts > 3600 * 24 * 30)
                            || (backup.jijidown.value === true && getCurrentTs() - backup.jijidown.ts > 3600 * 24 * 30)) {
                            functions.push(getFromJijidown(AV, BV, title, backup, spanJ));

                        } else {
                            spanJ.style.color = backup.jijidown.value ? '#00ff00' : '#ff0000';
                        }

                        if (!backup.xbeibeix
                            || (backup.xbeibeix.value === false && getCurrentTs() - backup.xbeibeix.ts > 3600 * 24 * 30)
                            || (backup.xbeibeix.value === true && getCurrentTs() - backup.xbeibeix.ts > 3600 * 24 * 30)) {
                            functions.push(getFromXbeibeix(AV, BV, title, backup, spanX));

                        } else {
                            spanX.style.color = backup.xbeibeix.value ? '#00ff00' : '#ff0000';
                        }

                        if (!backup.biliplus
                            || (backup.biliplus.value === false && getCurrentTs() - backup.biliplus.ts > 3600 * 24 * 30)
                            || (backup.biliplus.value === true && getCurrentTs() - backup.biliplus.ts > 3600 * 24 * 30)) {
                            functions.push(getFromBiliplus(AV, BV, title, backup, spanB));

                        } else {
                            spanB.style.color = backup.biliplus.value ? '#00ff00' : '#ff0000';
                        }

                        if (functions.length) {
                            if (controller.signal.aborted) {
                                // throw Error();
                                throw new DOMException('', 'AbortError');
                            }
                            await Promise.all(functions);
                            if (settings.enableDebug) console.warn('GM_setValue BJX');
                            if (settings.enableDebug) console.warn(`index: ${index + 1}`);
                            if (settings.enableDebug) consoleAVBVTitle('warn', AV, BV, title);
                            if (settings.enableDebug) console.warn(backup);
                            GM_setValue(BV, backup);
                        }

                    } catch (error) {
                        // if (controller.signal.aborted) {
                        if (error.name === 'AbortError') {
                            throw error;
                        } else {
                            // addMessage('-----------------------------');
                            addMessage(getLocalizedText('UNKNOWN_ERROR'));
                            // addMessage(`fid: ${fid} pn: ${pn} index: ${index + 1}`, true);
                            // addMessage(`${getLocalizedText('POSITION_ON_THIS_PAGE' + displayPrompt)}: ${index + 1}`, true);
                            addMessageAVBVTitle(AV, BV, title);
                            // addMessage(error.message, true);
                            addMessage(error.stack, true);
                            // console.error(`fid: ${fid} pn: ${pn} index: ${index + 1}`);
                            console.error(`index: ${index + 1}`);
                            consoleAVBVTitle('error', AV, BV, title);
                            console.error(error);
                            if (as[1]) {
                                as[1].style.color = '#ff0000';
                            }
                        }
                    }

                    // let mutiOther = 0;
                    // if (backup.intro && backup.intro.length > 1) {
                    //     mutiOther = 1;
                    // }
                    // if (backup.timeUpload && backup.timeUpload.length > 1) {
                    //     mutiOther = 2;
                    // }
                    // if (backup.timePublish && backup.timePublish.length > 1) {
                    //     mutiOther = 2;
                    // }

                    let picture;
                    let avifSource;
                    let webpSource;
                    let imgElement;

                    if (newFreshSpace) {
                        imgElement = video.querySelector('img');
                    } else {
                        picture = video.querySelector('picture');
                        avifSource = picture.querySelector('source[type="image/avif"]');
                        webpSource = picture.querySelector('source[type="image/webp"]');
                        imgElement = picture.querySelector('img');
                    }

                    if (disabled) {
                        if (newFreshSpace) {
                            if (backup.cover) {
                                imgElement.setAttribute('src', `//${backup.cover[backup.cover.length - 1].value}@672w_378h_1c.avif`);
                            }

                        } else {
                            video.classList.remove('disabled');
                            as[0].classList.remove('disabled');
                            // spanPubdate.style.textDecoration = 'line-through';

                            if (backup.cover) {
                                avifSource.setAttribute('srcset', `//${backup.cover[backup.cover.length - 1].value}@320w_200h_1c_!web-space-favlist-video.avif`);
                                webpSource.setAttribute('srcset', `//${backup.cover[backup.cover.length - 1].value}@320w_200h_1c_!web-space-favlist-video.webp`);
                                imgElement.setAttribute('src', `//${backup.cover[backup.cover.length - 1].value}@320w_200h_1c_!web-space-favlist-video.webp`);
                            }
                        }

                        as[1].style.textDecoration = 'line-through';
                        if (backup.title) {
                            as[1].textContent = backup.title[backup.title.length - 1].value;
                            if (newFreshSpace) {
                                divTitleNewFreshSpace.setAttribute('title', backup.title[backup.title.length - 1].value);
                            } else {
                                as[1].setAttribute('title', backup.title[backup.title.length - 1].value);
                            }
                            // } else {
                            // as[1].textContent = '/////////////////////////////';
                            // as[1].textContent = '';
                        }
                    }

                    if (backup.cover && backup.cover.length > 1) {

                        const spanC = document.createElement('span');
                        spanC.innerText = 'C';
                        spanC.style.marginRight = '3px';
                        spanC.style.float = 'right';
                        spanC.style.fontWeight = 'bold';
                        spanC.style.cursor = 'pointer';
                        spanC.style.color = '#000000';
                        let i = backup.cover.length - 2;
                        spanC.addEventListener('click', () => {
                            try {
                                if (i < 0) {
                                    i = backup.cover.length - 1;
                                }

                                if (newFreshSpace) {
                                    imgElement.setAttribute('src', `//${backup.cover[i].value}@672w_378h_1c.avif`);
                                } else {
                                    avifSource.setAttribute('srcset', `//${backup.cover[i].value}@320w_200h_1c_!web-space-favlist-video.avif`);
                                    webpSource.setAttribute('srcset', `//${backup.cover[i].value}@320w_200h_1c_!web-space-favlist-video.webp`);
                                    imgElement.setAttribute('src', `//${backup.cover[i].value}@320w_200h_1c_!web-space-favlist-video.webp`);
                                }

                                if (i !== backup.cover.length - 1) {
                                    spanC.style.color = '#999999';
                                } else {
                                    spanC.style.color = '#000000';
                                }

                                i--;

                            } catch (error) {
                                catchUnknownError(error);
                            }
                        });
                        divTCABJX.appendChild(spanC);
                    }

                    if (backup.title && backup.title.length > 1) {

                        const spanT = document.createElement('span');
                        spanT.innerText = 'T';
                        spanT.style.marginRight = '3px';
                        spanT.style.float = 'right';
                        spanT.style.fontWeight = 'bold';
                        spanT.style.cursor = 'pointer';
                        spanT.style.color = '#000000';
                        let i = backup.title.length - 2;
                        spanT.addEventListener('click', () => {
                            try {
                                if (i < 0) {
                                    i = backup.title.length - 1;
                                }

                                as[1].textContent = backup.title[i].value;

                                if (newFreshSpace) {
                                    divTitleNewFreshSpace.setAttribute('title', backup.title[i].value);
                                } else {
                                    as[1].setAttribute('title', backup.title[i].value);
                                }

                                if (i !== backup.title.length - 1) {
                                    spanT.style.color = '#999999';
                                } else {
                                    spanT.style.color = '#000000';
                                }

                                i--;

                            } catch (error) {
                                catchUnknownError(error);
                            }
                        });
                        divTCABJX.appendChild(spanT);
                    }

                    // if (mutiOther) {
                    //     const spanO = document.createElement('span');
                    //     spanO.innerText = 'O';
                    //     spanO.style.marginRight = '3px';
                    //     spanO.style.float = 'right';
                    //     spanO.style.fontWeight = 'bold';
                    //     spanO.style.cursor = 'pointer';
                    //     spanO.style.color = mutiOther === 1 ? '#000000' : '#ff0000';
                    //     spanO.addEventListener('click', () => {
                    //         try {
                    //             const data = GM_getValue(BV, {});
                    //             // const json = JSON.stringify(data, null, 8);
                    //             const json = JSON.stringify(data);
                    //             // GM_openInTab('data:text/plain;charset=utf-8,' + encodeURIComponent(json), { active: true, insert: false, setParent: true });
                    //             GM_openInTab('data:application/json;charset=utf-8,' + encodeURIComponent(json), { active: true, insert: false, setParent: true });
                    //         } catch (error) {
                    //             catchUnknownError(error);
                    //         }
                    //     });
                    //     divTCABJX.appendChild(spanO);
                    // }

                    if (controller.signal.aborted) {
                        // throw Error();
                        throw new DOMException('', 'AbortError');
                    }

                    if (!newFreshSpace) {
                        const ul = video.querySelector('ul.be-dropdown-menu');
                        addDropdown(ul, AV, BV);
                    }

                    if (functions.length === 1) {
                        await delay(300);
                    }

                } catch (error) {
                    // if (controller.signal.aborted) {
                    if (error.name === 'AbortError') {
                        throw error;
                    } else {
                        // addMessage('-----------------------------');
                        addMessage(getLocalizedText('UNKNOWN_ERROR'));
                        // addMessage(`fid: ${fid} pn: ${pn} index: ${index + 1}`, true);
                        // addMessage(`${getLocalizedText('POSITION_ON_THIS_PAGE' + displayPrompt)}: ${index + 1}`, true);
                        addMessageAVBVTitle(AV, BV, title);
                        // addMessage(error.message, true);
                        addMessage(error.stack, true);
                        // console.error(`fid: ${fid} pn: ${pn} index: ${index + 1}`);
                        console.error(`index: ${index + 1}`);
                        consoleAVBVTitle('error', AV, BV, title);
                        console.error(error);
                        if (as[1]) {
                            as[1].style.color = '#ff0000';
                        }
                    }
                }
            }

            if (autoNextPage) {
                await clickNextPage();
            }

        } catch (error) {
            // if (!controller.signal.aborted) {
            if (error.name !== 'AbortError') {
                catchUnknownError(error);
            }
        } finally {
            processing = false;
            activeControllers.delete(controller);
        }
    }

    function formatTimeFavorite1(t) {
        let e = Math.floor(+new Date / 1e3) - t;
        let n = (new Date).getFullYear();
        let i = new Date(1e3 * t).getFullYear();
        let r = new Date(1e3 * t).getMonth() + 1;
        let o = new Date(1e3 * t).getDate();
        return e < 60 ? "刚刚"
            : e >= 60 && e < 3600 ? Math.floor(e / 60) + "分钟前"
                : e >= 3600 && e < 86400 ? Math.floor(e / 60 / 60) + "小时前"
                    : 86400 === e ? "昨天"
                        : e > 86400 && i === n ? r + "-" + o
                            : i < n ? i + "-" + r + "-" + o
                                : void 0;
    }

    function formatTimeFavorite2(t) {
        let e = new Date();
        let n = e.getTime();
        let r = t.getTime();
        let o = n - r;
        return o < 6e4 ? "刚刚"
            : o < 36e5 ? Math.floor(o / 6e4) + "分钟前"
                : o < 864e5 ? Math.floor(o / 36e5) + "小时前"
                    : r >= new Date(e.getFullYear(), e.getMonth(), e.getDate() - 1).getTime() ? "昨天"
                        : r >= new Date(e.getFullYear(), 0, 1).getTime() ? (t.getMonth() + 1) + "-" + t.getDate()
                            // : o < 63072e6 ? t.getFullYear() + "-" + (t.getMonth() + 1) + "-" + t.getDate()
                            //     : "2年前";
                            : t.getFullYear() + "-" + (t.getMonth() + 1) + "-" + t.getDate();
    }

    function formatTimeFavorite3(t) {
        const date = new Date(t * 1000);
        const year = String(date.getFullYear()).slice(2);
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        const seconds = String(date.getSeconds()).padStart(2, '0');
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    }

    // function decodeHTMLEntities(str) {
    //     return str.replace(/&[a-zA-Z]+;/g, match => {
    //         const textarea = document.createElement('textarea');
    //         textarea.innerHTML = match;
    //         return textarea.value;
    //     });
    // }

    function decodeHTMLEntities(str) {
        return new DOMParser().parseFromString(`<!doctype html><body>${str}`, 'text/html').body.textContent;
    }

    function addMessage(msg, smallFontSize) {
        let px;
        if (smallFontSize) {
            px = newFreshSpace ? 11 : 10;
        } else {
            px = newFreshSpace ? 13 : 12;
        }
        const p = document.createElement('p');
        p.innerHTML = msg;
        p.style.fontSize = `${px}px`;
        divMessage.appendChild(p);
        p.scrollIntoView({ behavior: 'instant', block: 'nearest' });
    }

    function clearMessage() {
        while (divMessage.firstChild) {
            divMessage.removeChild(divMessage.firstChild);
        }
    }

    function addControls() {
        const newFreshSpaceAppend = newFreshSpace ? '-newFreshSpace' : '';

        let displayUpdate = false;
        if (settings.version !== currentVersion) {
            if (settings.version) {
                displayUpdate = true;
            }
            settings.version = currentVersion;
            GM_setValue('settings', settings);
        }

        const usageCount = settings[newFreshSpace ? 'usageCountNewFreshSpace' : 'usageCount'];
        displayPrompt = usageCount < 10 ? '_WITH_PROMPT' : '';
        if (displayPrompt) {
            settings[newFreshSpace ? 'usageCountNewFreshSpace' : 'usageCount']++;
            GM_setValue('settings', settings);
        }

        const style = document.createElement('style');
        style.textContent = `
            .fix-div {
                padding: 2px;
            }
            .fix-div-newFreshSpace {
                padding: 2px 0;
            }
            .fix-divControls {
                border-top: 1px solid #e4e9f0;
            }
            .fix-labelA, .fix-labelA-newFreshSpace {
                line-height: 1;
            }
            .fix-divMessage {
                line-height: 1.5;
            }
        `;
        document.head.appendChild(style);

        const divSide = document.querySelector(newFreshSpace ? 'div.favlist-aside' : 'div.fav-sidenav');
        if (!newFreshSpace && divSide.querySelector('a.watch-later')) {
            divSide.querySelector('a.watch-later').style.borderBottom = '1px solid #eeeeee';
        }

        const divControls = document.createElement('div');
        divControls.classList.add('fix-div' + newFreshSpaceAppend);
        if (!newFreshSpace) {
            divControls.classList.add('fix-divControls');
        }
        divSide.appendChild(divControls);

        const divLabelA = document.createElement('div');
        divLabelA.classList.add('fix-div' + newFreshSpaceAppend);
        divControls.appendChild(divLabelA);

        const labelA = document.createElement('label');
        labelA.classList.add('fix-labelA' + newFreshSpaceAppend);
        labelA.innerText = getLocalizedText('ONLY_PROCESS_DISABLED');
        divLabelA.appendChild(labelA);

        const checkboxA = document.createElement('input');
        checkboxA.type = 'checkbox';
        checkboxA.checked = settings.onlyProcessDisabled;
        checkboxA.addEventListener('change', () => {
            try {
                settings.onlyProcessDisabled = checkboxA.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelA.insertAdjacentElement('afterbegin', checkboxA);

        const divLabelB = document.createElement('div');
        divLabelB.classList.add('fix-div' + newFreshSpaceAppend);
        divControls.appendChild(divLabelB);

        const labelB = document.createElement('label');
        labelB.classList.add('fix-labelA' + newFreshSpaceAppend);
        labelB.innerText = getLocalizedText('AUTO_NEXT_PAGE');
        divLabelB.appendChild(labelB);

        const checkboxB = document.createElement('input');
        checkboxB.type = 'checkbox';
        checkboxB.checked = autoNextPage;
        checkboxB.addEventListener('change', () => {
            try {
                autoNextPage = checkboxB.checked;
                if (autoNextPage && !processing) {
                    clickNextPage();
                }
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelB.insertAdjacentElement('afterbegin', checkboxB);

        const divLabelC = document.createElement('div');
        divLabelC.classList.add('fix-div' + newFreshSpaceAppend);
        divControls.appendChild(divLabelC);

        const labelC = document.createElement('label');
        labelC.classList.add('fix-labelA' + newFreshSpaceAppend);
        labelC.innerText = getLocalizedText('ENABLE_DEBUG');
        divLabelC.appendChild(labelC);

        const checkboxC = document.createElement('input');
        checkboxC.type = 'checkbox';
        checkboxC.checked = settings.enableDebug;
        checkboxC.addEventListener('change', () => {
            try {
                settings.enableDebug = checkboxC.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelC.insertAdjacentElement('afterbegin', checkboxC);

        divMessage = document.createElement('div');
        divMessage.classList.add('fix-div' + newFreshSpaceAppend);
        divMessage.classList.add('fix-divMessage');
        divControls.appendChild(divMessage);

        if (displayUpdate) {
            setTimeout(() => {
                addMessage(getLocalizedText('UPDATES'));
            }, 300);
        }
    }

    function addDropdown(dropdownContainer, AV, BV) {
        if (settings.enableDebug) console.warn('========addDropdown========');
        try {
            if (newFreshSpace) {
                const biliCardDropdownVisible = document.querySelectorAll('.bili-card-dropdown--visible');
                if (biliCardDropdownVisible.length !== 1) {
                    // addMessage('-----------------------------');
                    addMessage(getLocalizedText('MULTIPLE_DROPDOWN_FOUND_ERROR'));
                    return;
                }

                let divTargetVideo;
                try {
                    divTargetVideo = document.querySelector('div.items__item:has(.bili-card-dropdown--visible)');
                } catch {
                    const items = document.querySelectorAll('div.items__item');
                    for (const item of items) {
                        if (item.contains(biliCardDropdownVisible[0])) {
                            divTargetVideo = item;
                            break;
                        }
                    }
                }
                if (!divTargetVideo) {
                    // addMessage('-----------------------------');
                    addMessage(getLocalizedText('TARGET_VIDEO_NOT_FOUND_ERROR'));
                    return;
                }

                if (settings.onlyProcessDisabled && divTargetVideo.querySelector('.bili-cover-card__stats')) {
                    return;
                }

                BV = biliCardDropdownVisible[0].parentNode.querySelector('a').getAttribute('href').match(BVFromURLRegex)[1];
                AV = AVBVs.find(AVBV => AVBV.bvid === BV).id;

            } else {
                if (dropdownContainer.lastElementChild.classList.contains('backup')) {
                    return;
                }
            }

            if (!newFreshSpace) {
                dropdownContainer.lastElementChild.classList.add('be-dropdown-item-delimiter');
            }

            const backup = GM_getValue(BV, {});

            if (backup.cover) {
                const dropdownCover = document.createElement(newFreshSpace ? 'div' : 'li');
                dropdownCover.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
                if (!newFreshSpace) {
                    dropdownCover.classList.add('backup');
                }
                dropdownCover.textContent = getLocalizedText('DROPDOWN_COVER');
                dropdownCover.addEventListener('click', () => {
                    try {
                        if (newFreshSpace) {
                            dropdownContainer.classList.remove('visible');
                        }
                        GM_openInTab(`https://${backup.cover[backup.cover.length - 1].value}`, { active: true, insert: true, setParent: true });
                        for (let i = backup.cover.length - 2; i >= 0; i--) {
                            GM_openInTab(`https://${backup.cover[i].value}`, { insert: false, setParent: true });
                        }

                    } catch (error) {
                        catchUnknownError(error);
                    }
                });
                dropdownContainer.appendChild(dropdownCover);
            }

            const dropdownLocal = document.createElement('div');
            dropdownLocal.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
            if (!newFreshSpace) {
                dropdownLocal.classList.add('backup');
            }
            dropdownLocal.textContent = getLocalizedText('DROPDOWN_LOCAL');
            dropdownLocal.addEventListener('click', () => {
                try {
                    if (newFreshSpace) {
                        dropdownContainer.classList.remove('visible');
                    }
                    const data = GM_getValue(BV, {});
                    // const json = JSON.stringify(data, null, 8);
                    const json = JSON.stringify(data);
                    // GM_openInTab('data:text/plain;charset=utf-8,' + encodeURIComponent(json), { active: true, insert: false, setParent: true });
                    GM_openInTab('data:application/json;charset=utf-8,' + encodeURIComponent(json), { active: true, insert: false, setParent: true });

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            dropdownContainer.appendChild(dropdownLocal);

            const dropdownJump = document.createElement('div');
            dropdownJump.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
            if (!newFreshSpace) {
                dropdownJump.classList.add('backup');
            }
            dropdownJump.textContent = getLocalizedText('DROPDOWN_JUMP');
            dropdownJump.addEventListener('click', () => {
                try {
                    if (newFreshSpace) {
                        dropdownContainer.classList.remove('visible');
                    }
                    GM_openInTab(`https://www.biliplus.com/video/${BV}`, { active: true, insert: false, setParent: true });
                    GM_openInTab(`https://www.biliplus.com/video/av${AV}`, { insert: false, setParent: true });
                    GM_openInTab(`https://www.jijidown.com/video/${BV}`, { insert: false, setParent: true });
                    GM_openInTab(`https://xbeibeix.com/video/${BV}`, { insert: false, setParent: true });

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            dropdownContainer.appendChild(dropdownJump);

            const dropdownReset = document.createElement('div');
            dropdownReset.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
            if (!newFreshSpace) {
                dropdownReset.classList.add('backup');
            }
            dropdownReset.textContent = getLocalizedText('DROPDOWN_RESET');
            dropdownReset.addEventListener('click', () => {
                try {
                    if (newFreshSpace) {
                        dropdownContainer.classList.remove('visible');
                    }
                    GM_deleteValue(BV);

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            dropdownContainer.appendChild(dropdownReset);

        } catch (error) {
            catchUnknownError(error);
        }
    }

    async function clickNextPage() {
        if (newFreshSpace) {
            const pager = Array.from(document.querySelectorAll('button.vui_pagenation--btn-side')).find(b => b.innerText === '下一页');
            if (pager && !pager.classList.contains('vui_button--disabled')) {
                await delay(5000);
                if (autoNextPage) {
                    pager.click();
                }
            }

        } else {
            const pager = document.querySelector('li.be-pager-next');
            if (pager && !pager.classList.contains('be-pager-disabled')) {
                await delay(5000);
                if (autoNextPage) {
                    pager.click();
                }
            }

        }
    }

    function updateStoreArray(_backup, _key, _value, _ts = 0, _from) {
        if (!_backup[_key]) {
            _backup[_key] = [];
            const data = { value: _value, ts: _ts, from: _from };
            if (settings.enableDebug) console.log(`init ${_key}`);
            if (settings.enableDebug) console.log(data);
            _backup[_key].push(data);

        } else {
            const target = _backup[_key].find(i => i.value === _value);
            if (target) {
                if (settings.enableDebug) console.log(`${_key} exists`);
                if (target.ts === 0 && _ts) {
                    target.ts = _ts;
                    target.from = _from;
                    if (settings.enableDebug) console.warn(`overwrite ${_key} ts`);
                }

            } else {
                const data = { value: _value, ts: _ts, from: _from };
                if (settings.enableDebug) console.log(`new ${_key}`);
                if (settings.enableDebug) console.log(data);
                _backup[_key].push(data);
                _backup[_key].sort((a, b) => a.ts - b.ts);
            }
        }
    }

    async function getFromApi(AV, BV, title, backup, spanA, apiDetails, fid, pn, disabled, spanFavTime) {

        if (!backup.AV) {
            backup.AV = AV;
        }
        if (!backup.BV) {
            backup.BV = BV;
        }

        if (!apiDetails.value) {
            const response = await new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'GET',
                    url: `https://api.bilibili.com/x/v3/fav/resource/list?media_id=${fid}&pn=${pn}&ps=${videosPerPage}&keyword=&order=mtime&type=0&tid=0&platform=web`,
                    timeout: 5000,
                    responseType: 'json',
                    onload: (res) => resolve(res),
                    onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
                    ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
                });
            });

            apiDetails.value = response.response.data.medias;
        }

        const apiDetail = apiDetails.value.find(a => a.bvid === BV);

        if (!apiDetail) {
            throw Error('apiDetail not found in apidetails');
        }

        if (disabled) {
            if (!backup.api || backup.api.value === false) {
                backup.api = { value: false, ts: getCurrentTs() };
                spanA.style.color = '#ff0000';
            }

        } else {
            backup.api = { value: true, ts: getCurrentTs() };
            spanA.style.color = '#00ff00';
        }

        if (settings.enableDebug) console.warn('getFromApi');
        // if (settings.enableDebug) console.log(`index: ${index + 1}`);
        if (settings.enableDebug) consoleAVBVTitle('log', AV, BV, title);
        if (settings.enableDebug) console.log(apiDetail);

        if (!disabled) {
            updateStoreArray(backup, 'title', apiDetail.title, getCurrentTs(), 'api');
            updateStoreArray(backup, 'cover', apiDetail.cover.replace(httpsFromURLRegex, ''), getCurrentTs(), 'api');
        }

        // if (settings.alwaysGetIntroFromVideoPage) {
        //     const response = await new Promise((resolve, reject) => {
        //         GM.xmlHttpRequest({
        //             method: 'GET',
        //             url: `https://www.bilibili.com/video/${BV}`,
        //             timeout: 5000,
        //             onload: (res) => resolve(res),
        //             onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
        //             ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
        //         });
        //     });

        //     if (settings.enableDebug) console.log(response);
        //     // if (settings.enableDebug) console.warn('innerHTML');
        //     // if (settings.enableDebug) console.warn(response.responseXML.querySelector('div.basic-desc-info').innerHTML);
        //     // if (settings.enableDebug) console.warn('innerText');
        //     // if (settings.enableDebug) console.warn(response.responseXML.querySelector('div.basic-desc-info').innerText);
        //     // if (settings.enableDebug) console.warn('textContent');
        //     // if (settings.enableDebug) console.warn(response.responseXML.querySelector('div.basic-desc-info').textContent);

        //     // if (settings.enableDebug) console.warn(decodeHTMLEntities(response.responseXML.querySelector('span.desc-info-text').innerHTML) === response.responseXML.querySelector('span.desc-info-text').innerText);
        //     // if (settings.enableDebug) console.warn(response.responseXML.querySelector('span.desc-info-text').textContent === response.responseXML.querySelector('span.desc-info-text').innerText);
        //     // if (settings.enableDebug) console.warn(response.responseXML.querySelector('span.desc-info-text').textContent === decodeHTMLEntities(response.responseXML.querySelector('span.desc-info-text').innerHTML));
        //     if (response.responseXML.querySelector('div.basic-desc-info')) {
        //         updateStoreArray(backup, 'intro', response.responseXML.querySelector('div.basic-desc-info').innerText, getCurrentTs(), 'videoPage');
        //         if (!response.responseXML.querySelector('div.basic-desc-info').innerText.includes(apiDetail.intro)) {
        //             // addMessage('-----------------------------');
        //             addMessage(getLocalizedText('UNKNOWN_ERROR'));
        //             // addMessage(`${getLocalizedText('POSITION_ON_THIS_PAGE' + displayPrompt)}: ${index + 1}`, true);
        //             addMessageAVBVTitle(AV, BV, title);
        //             addMessage('intro from video page conflicts with apiDetail', true);
        //             addMessage('getFromApi', true);
        //             // console.error(`index: ${index + 1}`);
        //             consoleAVBVTitle('error', AV, BV, title);
        //             console.error('intro from video page conflicts with apiDetail');
        //             console.error('getFromApi');
        //             updateStoreArray(backup, 'intro', apiDetail.intro, getCurrentTs(), 'api');
        //         }

        //     } else {
        //         updateStoreArray(backup, 'intro', apiDetail.intro, getCurrentTs(), 'api');
        //     }

        // } else {
        if (!disabled && apiDetail.intro.length >= 250) {
            if (settings.enableDebug) console.log('apiDetail.intro.length >= 250');
            const response = await new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'GET',
                    url: `https://www.bilibili.com/video/${BV}`,
                    timeout: 5000,
                    onload: (res) => resolve(res),
                    onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
                    ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
                });
            });

            if (settings.enableDebug) console.log(response);
            // if (settings.enableDebug) console.warn('innerHTML');
            // if (settings.enableDebug) console.warn(response.responseXML.querySelector('div.basic-desc-info').innerHTML);
            // if (settings.enableDebug) console.warn('innerText');
            // if (settings.enableDebug) console.warn(response.responseXML.querySelector('div.basic-desc-info').innerText);
            // if (settings.enableDebug) console.warn('textContent');
            // if (settings.enableDebug) console.warn(response.responseXML.querySelector('div.basic-desc-info').textContent);

            // if (settings.enableDebug) console.warn(decodeHTMLEntities(response.responseXML.querySelector('span.desc-info-text').innerHTML) === response.responseXML.querySelector('span.desc-info-text').innerText);
            // if (settings.enableDebug) console.warn(response.responseXML.querySelector('span.desc-info-text').textContent === response.responseXML.querySelector('span.desc-info-text').innerText);
            // if (settings.enableDebug) console.warn(response.responseXML.querySelector('span.desc-info-text').textContent === decodeHTMLEntities(response.responseXML.querySelector('span.desc-info-text').innerHTML));
            if (response.responseXML.querySelector('div.basic-desc-info')) {
                updateStoreArray(backup, 'intro', response.responseXML.querySelector('div.basic-desc-info').innerText, getCurrentTs(), 'videoPage');
                if (!response.responseXML.querySelector('div.basic-desc-info').innerText.includes(apiDetail.intro)) {
                    // // addMessage('-----------------------------');
                    // addMessage(getLocalizedText('UNKNOWN_ERROR'));
                    // // addMessage(`${getLocalizedText('POSITION_ON_THIS_PAGE' + displayPrompt)}: ${index + 1}`, true);
                    // addMessageAVBVTitle(AV, BV, title);
                    // addMessage('intro from video page conflicts with apiDetail', true);
                    // addMessage('getFromApi', true);
                    // // console.error(`index: ${index + 1}`);
                    // consoleAVBVTitle('error', AV, BV, title);
                    // console.error('intro from video page conflicts with apiDetail');
                    // console.error('getFromApi');
                    updateStoreArray(backup, 'intro', apiDetail.intro, getCurrentTs(), 'api');
                }

            } else {
                updateStoreArray(backup, 'intro', apiDetail.intro, getCurrentTs(), 'api');
            }

        } else {
            updateStoreArray(backup, 'intro', apiDetail.intro, getCurrentTs(), 'api');
        }
        // }

        if (!backup.upperUid) {
            backup.upperUid = apiDetail.upper.mid;
        }
        updateStoreArray(backup, 'upperName', apiDetail.upper.name, getCurrentTs(), 'api');
        updateStoreArray(backup, 'upperAvatar', apiDetail.upper.face.replace(httpsFromURLRegex, ''), getCurrentTs(), 'api');
        // if (!backup.timeUpload) {
        //     backup.timeUpload = apiDetail.ctime;
        // }
        // if (!backup.timePublish) {
        //     backup.timePublish = apiDetail.pubtime;
        // }
        updateStoreArray(backup, 'timeUpload', apiDetail.ctime, getCurrentTs(), 'api');
        updateStoreArray(backup, 'timePublish', apiDetail.pubtime, getCurrentTs(), 'api');

        if (!backup.timeFavorite) {
            backup.timeFavorite = [];
            const data = { value: apiDetail.fav_time, fid: fid };
            if (settings.enableDebug) console.log('init timeFavorite');
            if (settings.enableDebug) console.log(data);
            backup.timeFavorite.push(data);

        } else {
            const target = backup.timeFavorite.find(i => i.fid === fid);
            if (target) {
                if (target.value !== apiDetail.fav_time) {
                    if (settings.enableDebug) console.warn('update timeFavorite');
                    target.value = apiDetail.fav_time;
                }
            } else {
                const data = { value: apiDetail.fav_time, fid: fid };
                if (settings.enableDebug) console.log('new timeFavorite');
                if (settings.enableDebug) console.log(data);
                backup.timeFavorite.push(data);
                backup.timeFavorite.sort((a, b) => a.value - b.value);
            }
        }

        spanFavTime.innerText = `收藏于:${formatTimeFavorite2(new Date(apiDetail.fav_time * 1000))}`;

        if (settings.enableDebug) console.warn('GM_setValue Api');
        // if (settings.enableDebug) console.warn(`index: ${index + 1}`);
        if (settings.enableDebug) consoleAVBVTitle('warn', AV, BV, title);
        if (settings.enableDebug) console.warn(backup);
        GM_setValue(BV, backup);

    }

    async function getFromBiliplus(AV, BV, title, backup, spanB) {

        const response = await new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: 'GET',
                url: `https://www.biliplus.com/video/av${AV}`,
                timeout: 5000,
                onload: (res) => resolve(res),
                onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
                ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
            });
        });

        const json = JSON.parse(response.response.match(jsonFromBiliplusRegex)[1]);
        if (json.title) {
            backup.biliplus = { value: true, ts: getCurrentTs() };
            spanB.style.color = '#00ff00';
            if (settings.enableDebug) console.warn('getFromBiliplus');
            // if (settings.enableDebug) console.log(`index: ${index + 1}`);
            if (settings.enableDebug) consoleAVBVTitle('log', AV, BV, title);
            if (settings.enableDebug) console.log(json);

            updateStoreArray(backup, 'title', json.title, json.lastupdatets, 'biliplus');
            updateStoreArray(backup, 'cover', json.pic.replace(httpsFromURLRegex, ''), json.lastupdatets, 'biliplus');
            updateStoreArray(backup, 'intro', json.description, json.lastupdatets, 'biliplus');
            updateStoreArray(backup, 'upperName', json.author, json.lastupdatets, 'biliplus');
            updateStoreArray(backup, 'timePublish', json.created, json.lastupdatets, 'biliplus');

            if (json.v2_app_api) {
                updateStoreArray(backup, 'title', json.v2_app_api.title, json.lastupdatets, 'biliplusV2');
                updateStoreArray(backup, 'cover', json.v2_app_api.pic.replace(httpsFromURLRegex, ''), json.lastupdatets, 'biliplusV2');
                updateStoreArray(backup, 'intro', json.v2_app_api.desc, json.lastupdatets, 'biliplusV2');
                updateStoreArray(backup, 'upperName', json.v2_app_api.owner.name, json.lastupdatets, 'biliplusV2');
                updateStoreArray(backup, 'upperAvatar', json.v2_app_api.owner.face.replace(httpsFromURLRegex, ''), json.lastupdatets, 'biliplusV2');
                updateStoreArray(backup, 'timeUpload', json.v2_app_api.ctime, json.lastupdatets, 'biliplusV2');
                updateStoreArray(backup, 'timePublish', json.v2_app_api.pubdate, json.lastupdatets, 'biliplusV2');
                if (json.v2_app_api.first_frame) {
                    updateStoreArray(backup, 'firstFrame', json.v2_app_api.first_frame.replace(httpsFromURLRegex, ''), json.lastupdatets, 'biliplusV2');
                }
            }

        } else {
            backup.biliplus = { value: false, ts: getCurrentTs() };
            spanB.style.color = '#ff0000';
            if (settings.enableDebug) console.warn('getFromBiliplus');
            // if (settings.enableDebug) console.warn(`index: ${index + 1}`);
            if (settings.enableDebug) consoleAVBVTitle('warn', AV, BV, title);
            if (settings.enableDebug) console.warn(json);
        }

    }

    async function getFromJijidown(AV, BV, title, backup, spanJ) {

        let retryCount = 0;
        while (true) {
            const response = await new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'GET',
                    url: `https://www.jijidown.com/api/v1/video_bv/get_info?id=${BV.slice(2)}`,
                    timeout: 5000,
                    responseType: 'json',
                    onload: (res) => resolve(res),
                    onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
                    ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
                });
            });

            const json = response.response;
            if (json.upid > 0) {
                backup.jijidown = { value: true, ts: getCurrentTs() };
                spanJ.style.color = '#00ff00';
                if (settings.enableDebug) console.warn('getFromJijidown');
                // if (settings.enableDebug) console.log(`index: ${index + 1}`);
                if (settings.enableDebug) consoleAVBVTitle('log', AV, BV, title);
                if (settings.enableDebug) console.log(json);

                updateStoreArray(backup, 'title', json.title, json.ltime, 'jijidown');
                updateStoreArray(backup, 'cover', json.img.replace(httpsFromURLRegex, ''), json.ltime, 'jijidown');
                updateStoreArray(backup, 'intro', decodeHTMLEntities(json.desc.replaceAll('<br/>', '\n')), json.ltime, 'jijidown');
                if (json.up.id > 0) {
                    updateStoreArray(backup, 'upperName', json.up.author, json.ltime, 'jijidown');
                    updateStoreArray(backup, 'upperAvatar', json.up.avatar.replace(httpsFromURLRegex, ''), json.ltime, 'jijidown');
                }
                return;

            } else if (json.msg === 'loading') {
                retryCount++;
                if (settings.enableDebug) console.warn('getFromJijidown');
                // if (settings.enableDebug) console.warn(`index: ${index + 1}`);
                if (settings.enableDebug) consoleAVBVTitle('warn', AV, BV, title);
                if (settings.enableDebug) console.warn(`retryCount: ${retryCount}`);
                if (settings.enableDebug) console.warn(json);

                if (retryCount > 4) {
                    // addMessage('-----------------------------');
                    addMessageAVBVTitle(AV, BV, title);
                    addMessage('retryCount > 4', true);
                    addMessage('getFromJijidown', true);
                    // console.error(`index: ${index + 1}`);
                    consoleAVBVTitle('error', AV, BV, title);
                    console.error('retryCount > 4');
                    console.error('getFromJijidown');
                    return;
                }

            } else {
                backup.jijidown = { value: false, ts: getCurrentTs() };
                spanJ.style.color = '#ff0000';
                if (settings.enableDebug) console.warn('getFromJijidown');
                // if (settings.enableDebug) console.warn(`index: ${index + 1}`);
                if (settings.enableDebug) consoleAVBVTitle('warn', AV, BV, title);
                if (settings.enableDebug) console.warn(json);
                return;
            }

            await delay(600);
        }
    }

    async function getFromXbeibeix(AV, BV, title, backup, spanX) {

        const response = await new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: 'GET',
                url: `https://xbeibeix.com/video/${BV}`,
                timeout: 5000,
                onload: (res) => resolve(res),
                onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))),
                ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR')))
            });
        });

        if (response.finalUrl !== 'https://xbeibeix.com/') {
            backup.xbeibeix = { value: true, ts: getCurrentTs() };
            spanX.style.color = '#00ff00';
            if (settings.enableDebug) console.warn('getFromXbeibeix');
            // if (settings.enableDebug) console.log(`index: ${index + 1}`);
            if (settings.enableDebug) consoleAVBVTitle('log', AV, BV, title);
            if (settings.enableDebug) console.log(response);

            updateStoreArray(backup, 'title', response.responseXML.querySelector('h5.fw-bold').innerText, undefined, 'xbeibeix');
            updateStoreArray(backup, 'cover', response.responseXML.querySelector('div.col-4 > img').getAttribute('src').replace(httpsFromURLRegex, ''), undefined, 'xbeibeix');
            updateStoreArray(backup, 'intro', decodeHTMLEntities(response.responseXML.querySelector('div.col-8 > textarea').innerText), undefined, 'xbeibeix');
            updateStoreArray(backup, 'upperName', response.responseXML.querySelector('div.input-group.mb-2 > input').value, undefined, 'xbeibeix');

        } else {
            backup.xbeibeix = { value: false, ts: getCurrentTs() };
            spanX.style.color = '#ff0000';
            if (settings.enableDebug) console.warn('getFromXbeibeix');
            // if (settings.enableDebug) console.warn(`index: ${index + 1}`);
            if (settings.enableDebug) consoleAVBVTitle('warn', AV, BV, title);
            if (settings.enableDebug) console.warn(response);
        }

    }

    function consoleAVBVTitle(type, AV, BV, title) {
        switch (type) {
            case 'log':
                console.log(`AV: ${AV}`);
                console.log(`BV: ${BV}`);
                console.log(`title: ${title.slice(0, 13)}`);
                break;
            case 'warn':
                console.warn(`AV: ${AV}`);
                console.warn(`BV: ${BV}`);
                console.warn(`title: ${title.slice(0, 13)}`);
                break;
            case 'error':
                console.error(`AV: ${AV}`);
                console.error(`BV: ${BV}`);
                if (title) {
                    console.error(`title: ${title.slice(0, 13)}`);
                }
                break;
            default:
                throw Error('invalid type');
        }
    }

    function addMessageAVBVTitle(AV, BV, title) {
        addMessage(`${getLocalizedText('AV')}: ${AV}`, true);
        addMessage(`${getLocalizedText('BV')}: ${BV}`, true);
        if (title) {
            addMessage(`${getLocalizedText('TITLE')}: ${title.slice(0, 13)}`, true);
        }
    }

    function catchUnknownError(error) {
        // addMessage('-----------------------------');
        addMessage(getLocalizedText('UNKNOWN_ERROR'));
        // addMessage(error.message, true);
        addMessage(error.stack, true);
        console.error(error);
    }

    // async function mainNewFreshSpace(mutations) {
    async function mainNewFreshSpace() {

        // mutations_count++;
        // console.error('mutations_count' + mutations_count);
        // for (const mutation of mutations) {
        //     mutation_count++;
        //     console.warn('mutation_count' + mutation_count);
        //     console.log(mutation);
        // }

        // if (first) {
        //     first = false;
        // } else {
        //     mutations = mutations.reverse();
        // }

        // for (const mutation of mutations) {
        //     if (mutation.addedNodes.length) {

        // const items = document.querySelectorAll('.items__item');
        // for (const item of items) {

        // console.log(mutation.addedNodes[0].querySelectorAll('a'));
        // console.log(mutation.addedNodes[0].querySelectorAll('a')[1].innerText);
        // mutation.addedNodes[0].querySelectorAll('a')[1].style.color = 'red';

        // }
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址