哔哩哔哩(B站|Bilibili)收藏夹Fix (备份视频信息)

自动备份视频信息至本地和第三方网站, 失效视频信息回显

// ==UserScript==
// @name              bilibili favlist backup
// @name:zh-CN        哔哩哔哩(B站|Bilibili)收藏夹Fix (备份视频信息)
// @name:zh-TW        哔哩哔哩(B站|Bilibili)收藏夹Fix (备份视频信息)
// @namespace         http://tampermonkey.net/
// @version           31
// @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           beibeigame.com
// @connect           bilibili.com
// @connect           biliplus.com
// @connect           jiji.moe
// @connect           jijidown.com
// @connect           xbeibeix.com
// @grant             GM_openInTab
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_deleteValue
// @grant             GM_listValues
// @grant             GM_getValues
// @grant             GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    const updates = '更新内容:<br>' +
        '新增: 设置从B站接口获取额外数据启用更新时, 只更新可能换源的视频还是总是更新所有视频的功能';

    const version = 31;

    const favlistURLRegex = /https:\/\/space\.bilibili\.com\/\d+\/favlist.*/;
    const localeTimeStringRegex = /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/;
    const getFidFromURLRegex = /fid=(\d+)/;
    const getUIDFromURLRegex = /https:\/\/space\.bilibili\.com\/(\d+)/;
    const getBVFromURLRegex = /video\/(BV\w{10})/;
    const getHttpsFromURLRegex = /^(https?:\/\/|\/\/)/;
    const getJsonFromBiliplusRegex = /window\.addEventListener\('DOMContentLoaded',function\(\){view\((.+)\);}\);/;
    const getParamsWithSignFromBiliplusRegex = /api\/view_all(.+)'/;
    const getFilenameFromURLRegex = /[^/]+(?:\.[a-zA-Z0-9]+)$/;
    const getAvifFromURLRegex = /@.*/;

    let onFavlistPage = false;
    let autoNextPage = false;
    let autoNextFavlist = false;
    let debug = false;
    let newFreshSpace;
    let classAppendNewFreshSpace;
    let pageSize;
    let firstTimeMain = true;
    let divMessage;
    let divMessageHeightFixed = false;
    let order = 'mtime';
    const activeControllers = new Set();

    let getFromApiExtraDelay = true;

    let getFromBiliplusExtraCounter = 0;
    setInterval(() => {
        if (getFromBiliplusExtraCounter > 0) {
            getFromBiliplusExtraCounter--;
        }
    }, 1000);

    const sortedKeys = [
        'BV',
        'AV',
        'title',
        'intro',
        'cover',
        'upperUID',
        'upperName',
        'upperAvatar',
        'timeUpload',
        'timePublish',
        'timeFavorite',
        'dynamic',
        'pages',
        'api',
        'apiExtra',
        'biliplus',
        'biliplusExtra',
        'jijidown',
        'jijidownExtra',
        'xbeibeix',
    ];

    const settings = GM_getValue('settings', {
        processNormal: true,
        processDisabled: true,
        getFromApi: true,
        getFromApiUpdate: true,
        getFromApiUpdateInterval: 5,
        getFromApiExtra: false,
        getFromApiExtraThumbnails: false,
        getFromApiExtraUpdate: true,
        getFromApiExtraUpdateAll: false,
        getFromApiExtraUpdateInterval: 5,
        getFromBiliplus: false,
        getFromBiliplusUpdate: false,
        getFromBiliplusUpdateInterval: 10,
        getFromBiliplusExtra: false,
        getFromBiliplusExtraUpdate: false,
        getFromBiliplusExtraUpdateInterval: 10,
        getFromJijidown: false,
        getFromJijidownUpdate: false,
        getFromJijidownUpdateInterval: 10,
        getFromJijidownExtra: false,
        getFromJijidownExtraUpdate: false,
        getFromJijidownExtraUpdateInterval: 10,
        getFromJijidownURL: 'www.jijidown.com',
        getFromXbeibeix: false,
        getFromXbeibeixUpdate: false,
        getFromXbeibeixUpdateInterval: 10,
        getFromXbeibeixURL: 'xbeibeix.com',
        autoNextPageInterval: 5,
        requestTimeout: 10,
        delayBeforeMain: 300,
        appendDropdownCover: true,
        appendDropdownLocal: true,
        appendDropdownJump: true,
        appendDropdownReset: true,
        TCABJXOpacity: 100,
        displayAdvancedControls: false,
        exportBackupWithoutTsFrom: false,
        version: 0,
        defaultUID: null,
        defaultFavlistFid: null,
    });

    ///////////////////////////////////////////////////////////////////////////////////
    // debug = true;
    // getFromApiExtraDelay = false;
    // settings.delayBeforeMain = 0;
    // settings.processNormal = false;
    // settings.processDisabled = false;
    // settings.getFromApi = false;
    // v9
    if (typeof settings.defaultFavlistFid === 'string') {
        settings.defaultFavlistFid = parseInt(settings.defaultFavlistFid, 10);
        GM_setValue('settings', settings);
    }
    // v10
    if (settings.hasOwnProperty('enableGetFromJiji')) {
        settings.enableGetFromJijidown = settings.enableGetFromJiji;
        delete settings.enableGetFromJiji;
        GM_setValue('settings', settings);
    }
    // v10
    if (settings.hasOwnProperty('enableGetFromBbdownloader')) {
        settings.enableGetFromXbeibeix = settings.enableGetFromBbdownloader;
        delete settings.enableGetFromBbdownloader;
        GM_setValue('settings', settings);
    }
    // v10
    if (!settings.hasOwnProperty('getFromJijidownURL')) {
        settings.getFromJijidownURL = 'www.jijidown.com';
        GM_setValue('settings', settings);
    }
    // v10
    if (!settings.hasOwnProperty('getFromXbeibeixURL')) {
        settings.getFromXbeibeixURL = 'xbeibeix.com';
        GM_setValue('settings', settings);
    }
    // v13
    if (settings.hasOwnProperty('enableDebug')) {
        delete settings.enableDebug;
        GM_setValue('settings', settings);
    }
    // v14
    if (!settings.hasOwnProperty('enableGetFromApiExtra')) {
        settings.enableGetFromApiExtra = false;
        GM_setValue('settings', settings);
    }
    // v14
    if (!settings.hasOwnProperty('enableGetFromJijidownExtra')) {
        settings.enableGetFromJijidownExtra = false;
        GM_setValue('settings', settings);
    }
    // v16
    if (!settings.enableGetFromApi && settings.enableGetFromApiExtra) {
        settings.enableGetFromApiExtra = false;
        GM_setValue('settings', settings);
    }
    // v19
    if (!settings.hasOwnProperty('displayAdvancedControls')) {
        settings.displayAdvancedControls = false;
        settings.advancedEnableUpdateGetFromApi = true;
        settings.advancedEnableUpdateGetFromApiExtra = true;
        settings.advancedEnableUpdateGetFromBiliplus = false;
        settings.advancedEnableUpdateGetFromJijidown = false;
        settings.advancedEnableUpdateGetFromJijidownExtra = false;
        settings.advancedEnableUpdateGetFromXbeibeix = false;
        settings.advancedIntervalGetFromApi = 5;
        settings.advancedIntervalGetFromApiExtra = 5;
        settings.advancedIntervalGetFromBiliplus = 10;
        settings.advancedIntervalGetFromJijidown = 10;
        settings.advancedIntervalGetFromJijidownExtra = 10;
        settings.advancedIntervalGetFromXbeibeix = 10;
        settings.advancedIntervalAutoNextPage = 5;
        GM_setValue('settings', settings);
    }
    // v19
    if (settings.hasOwnProperty('advancedDisableUpdateGetFromApi')) {
        settings.advancedEnableUpdateGetFromApi = !settings.advancedDisableUpdateGetFromApi;
        settings.advancedEnableUpdateGetFromApiExtra = !settings.advancedDisableUpdateGetFromApiExtra;
        settings.advancedEnableUpdateGetFromBiliplus = !settings.advancedDisableUpdateGetFromBiliplus;
        settings.advancedEnableUpdateGetFromJijidown = !settings.advancedDisableUpdateGetFromJijidown;
        settings.advancedEnableUpdateGetFromJijidownExtra = !settings.advancedDisableUpdateGetFromJijidownExtra;
        settings.advancedEnableUpdateGetFromXbeibeix = !settings.advancedDisableUpdateGetFromXbeibeix;
        delete settings.advancedDisableUpdateGetFromApi;
        delete settings.advancedDisableUpdateGetFromApiExtra;
        delete settings.advancedDisableUpdateGetFromBiliplus;
        delete settings.advancedDisableUpdateGetFromJijidown;
        delete settings.advancedDisableUpdateGetFromJijidownExtra;
        delete settings.advancedDisableUpdateGetFromXbeibeix;
        GM_setValue('settings', settings);
    }
    // v20
    if (settings.hasOwnProperty('advancedIntervalAutoNextPage')) {
        settings.enableUpdateGetFromApi = settings.advancedEnableUpdateGetFromApi;
        settings.enableUpdateGetFromApiExtra = settings.advancedEnableUpdateGetFromApiExtra;
        settings.enableUpdateGetFromBiliplus = settings.advancedEnableUpdateGetFromBiliplus;
        settings.enableUpdateGetFromJijidown = settings.advancedEnableUpdateGetFromJijidown;
        settings.enableUpdateGetFromJijidownExtra = settings.advancedEnableUpdateGetFromJijidownExtra;
        settings.enableUpdateGetFromXbeibeix = settings.advancedEnableUpdateGetFromXbeibeix;
        settings.intervalGetFromApi = settings.advancedIntervalGetFromApi;
        settings.intervalGetFromApiExtra = settings.advancedIntervalGetFromApiExtra;
        settings.intervalGetFromBiliplus = settings.advancedIntervalGetFromBiliplus;
        settings.intervalGetFromJijidown = settings.advancedIntervalGetFromJijidown;
        settings.intervalGetFromJijidownExtra = settings.advancedIntervalGetFromJijidownExtra;
        settings.intervalGetFromXbeibeix = settings.advancedIntervalGetFromXbeibeix;
        settings.intervalAutoNextPage = settings.advancedIntervalAutoNextPage;
        delete settings.advancedEnableUpdateGetFromApi;
        delete settings.advancedEnableUpdateGetFromApiExtra;
        delete settings.advancedEnableUpdateGetFromBiliplus;
        delete settings.advancedEnableUpdateGetFromJijidown;
        delete settings.advancedEnableUpdateGetFromJijidownExtra;
        delete settings.advancedEnableUpdateGetFromXbeibeix;
        delete settings.advancedIntervalGetFromApi;
        delete settings.advancedIntervalGetFromApiExtra;
        delete settings.advancedIntervalGetFromBiliplus;
        delete settings.advancedIntervalGetFromJijidown;
        delete settings.advancedIntervalGetFromJijidownExtra;
        delete settings.advancedIntervalGetFromXbeibeix;
        delete settings.advancedIntervalAutoNextPage;
        GM_setValue('settings', settings);
    }
    // v20
    if (settings.hasOwnProperty('advancedRequestTimeout')) {
        settings.requestTimeout = settings.advancedRequestTimeout;
        delete settings.advancedRequestTimeout;
        GM_setValue('settings', settings);
    }
    // v20
    if (!settings.hasOwnProperty('requestTimeout')) {
        settings.requestTimeout = 10;
        GM_setValue('settings', settings);
    }
    // v20
    if (settings.hasOwnProperty('advancedExportBackupWithoutTsFrom')) {
        settings.exportBackupWithoutTsFrom = settings.advancedExportBackupWithoutTsFrom;
        delete settings.advancedExportBackupWithoutTsFrom;
        GM_setValue('settings', settings);
    }
    // v20
    if (!settings.hasOwnProperty('exportBackupWithoutTsFrom')) {
        settings.exportBackupWithoutTsFrom = false;
        GM_setValue('settings', settings);
    }
    // v20
    if (!settings.hasOwnProperty('enableGetThumbnails')) {
        settings.enableGetThumbnails = false;
        GM_setValue('settings', settings);
    }
    // v21
    if (!settings.hasOwnProperty('delayBeforeMain')) {
        settings.delayBeforeMain = 300;
        settings.appendDropdownCover = true;
        settings.appendDropdownLocal = true;
        settings.appendDropdownJump = true;
        settings.appendDropdownReset = true;
        GM_setValue('settings', settings);
    }
    // v22
    if (!settings.hasOwnProperty('enableGetFromBiliplusExtra')) {
        settings.enableGetFromBiliplusExtra = false;
        settings.enableUpdateGetFromBiliplusExtra = false;
        settings.intervalGetFromBiliplusExtra = 10;
        GM_setValue('settings', settings);
    }
    // v28
    if (!settings.hasOwnProperty('TCABJXOpacity')) {
        settings.TCABJXOpacity = 100;
        settings.enableDelayGetFromBiliplusExtra = true;
        GM_setValue('settings', settings);
    }
    // v31
    if (!settings.hasOwnProperty('getFromApiExtraUpdateAll')) {
        settings.getFromApiExtraUpdateAll = false;
        settings.getFromApi = settings.enableGetFromApi;
        settings.getFromApiUpdate = settings.enableUpdateGetFromApi;
        settings.getFromApiUpdateInterval = settings.intervalGetFromApi;
        settings.getFromApiExtra = settings.enableGetFromApiExtra;
        settings.getFromApiExtraThumbnails = settings.enableGetThumbnails;
        settings.getFromApiExtraUpdate = settings.enableUpdateGetFromApiExtra;
        settings.getFromApiExtraUpdateInterval = settings.intervalGetFromApiExtra;
        settings.getFromBiliplus = settings.enableGetFromBiliplus;
        settings.getFromBiliplusUpdate = settings.enableUpdateGetFromBiliplus;
        settings.getFromBiliplusUpdateInterval = settings.intervalGetFromBiliplus;
        settings.getFromBiliplusExtra = settings.enableGetFromBiliplusExtra;
        settings.getFromBiliplusExtraUpdate = settings.enableUpdateGetFromBiliplusExtra;
        settings.getFromBiliplusExtraUpdateInterval = settings.intervalGetFromBiliplusExtra;
        settings.getFromJijidown = settings.enableGetFromJijidown;
        settings.getFromJijidownUpdate = settings.enableUpdateGetFromJijidown;
        settings.getFromJijidownUpdateInterval = settings.intervalGetFromJijidown;
        settings.getFromJijidownExtra = settings.enableGetFromJijidownExtra;
        settings.getFromJijidownExtraUpdate = settings.enableUpdateGetFromJijidownExtra;
        settings.getFromJijidownExtraUpdateInterval = settings.intervalGetFromJijidownExtra;
        settings.getFromXbeibeix = settings.enableGetFromXbeibeix;
        settings.getFromXbeibeixUpdate = settings.enableUpdateGetFromXbeibeix;
        settings.getFromXbeibeixUpdateInterval = settings.intervalGetFromXbeibeix;
        settings.autoNextPageInterval = settings.intervalAutoNextPage;
        delete settings.enableGetFromApi;
        delete settings.enableUpdateGetFromApi;
        delete settings.intervalGetFromApi;
        delete settings.enableGetFromApiExtra;
        delete settings.enableGetThumbnails;
        delete settings.enableUpdateGetFromApiExtra;
        delete settings.intervalGetFromApiExtra;
        delete settings.enableGetFromBiliplus;
        delete settings.enableUpdateGetFromBiliplus;
        delete settings.intervalGetFromBiliplus;
        delete settings.enableGetFromBiliplusExtra;
        delete settings.enableDelayGetFromBiliplusExtra;
        delete settings.enableUpdateGetFromBiliplusExtra;
        delete settings.intervalGetFromBiliplusExtra;
        delete settings.enableGetFromJijidown;
        delete settings.enableUpdateGetFromJijidown;
        delete settings.intervalGetFromJijidown;
        delete settings.enableGetFromJijidownExtra;
        delete settings.enableUpdateGetFromJijidownExtra;
        delete settings.intervalGetFromJijidownExtra;
        delete settings.enableGetFromXbeibeix;
        delete settings.enableUpdateGetFromXbeibeix;
        delete settings.intervalGetFromXbeibeix;
        delete settings.intervalAutoNextPage;
        GM_setValue('settings', settings);
    }
    ///////////////////////////////////////////////////////////////////////////////////

    const favlistObserver = new MutationObserver(async (_mutations, observer) => {
        if (debug) console.debug('callback favlistObserver');
        if (document.querySelector('div.items')) {
            if (debug) console.debug('disconnect favlistObserver');
            observer.disconnect();
            newFreshSpace = true;
            classAppendNewFreshSpace = '-newFreshSpace';
            pageSize = window.innerWidth < 1760 ? 40 : 36;
            initControls();
            if (!firstTimeMain) {
                await delay(settings.delayBeforeMain + 300);
                main();
            }
            if (debug) console.debug('observe itemsObserver');
            itemsObserver.observe(document.querySelector('div.items'), { childList: true, attributes: false, characterData: false });
            if (debug) console.debug('observe bodyChildListObserver');
            bodyChildListObserver.observe(document.body, { childList: true, attributes: false, characterData: false });
            if (debug) console.debug('observe radioFilterObserver');
            radioFilterObserver.observe(document.querySelector('div.fav-list-header-filter__left > div'), { subtree: true, characterData: false, attributeFilter: ['class'] });
            if (debug) console.debug('observe headerFilterLeftChildListObserver');
            headerFilterLeftChildListObserver.observe(document.querySelector('div.fav-list-header-filter__left'), { childList: true, attributes: false, characterData: false });
            return;
        }
        if (document.querySelector('div.fav-content.section')) {
            if (debug) console.debug('disconnect favlistObserver');
            observer.disconnect();
            newFreshSpace = false;
            classAppendNewFreshSpace = '';
            pageSize = 20;
            initControls();
            if (debug) console.debug('observe favContentSectionObserver');
            favContentSectionObserver.observe(document.querySelector('div.fav-content.section'), { characterData: false, attributeFilter: ['class'] });
            return;
        }
    });

    const itemsObserver = new MutationObserver(async () => {
        abortActiveControllers();
        if (debug) console.debug('callback itemsObserver');
        if (settings.delayBeforeMain) {
            await delay(settings.delayBeforeMain);
        }
        main();
    });

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

    const radioFilterObserver = new MutationObserver(mutations => {
        if (debug) console.debug('callback radioFilterObserver');
        if (debug) console.debug(mutations);
        for (const mutation of mutations) {
            if (mutation.target.classList.contains('radio-filter__item--active')) {
                const orderText = mutation.target.innerText;
                if (orderText.includes('收藏')) {
                    order = 'mtime';
                } else if (orderText.includes('播放')) {
                    order = 'view';
                } else if (orderText.includes('投稿')) {
                    order = 'pubtime';
                } else {
                    addMessage('无法确定各个视频的排序方式, 请反馈该问题', false, 'red');
                }
                if (debug) console.log(`order: ${order}`);
            }
        }
    });

    const headerFilterLeftChildListObserver = new MutationObserver(mutations => {
        if (debug) console.debug('callback headerFilterLeftChildListObserver');
        if (debug) console.debug(mutations);
        for (const mutation of mutations) {
            for (const addedNode of mutation.addedNodes) {
                if (addedNode.nodeType === 1 && addedNode.classList.contains('radio-filter')) {
                    order = 'mtime';
                    if (debug) console.log(`order: ${order}`);
                    if (debug) console.debug('observe radioFilterObserver');
                    radioFilterObserver.observe(addedNode, { subtree: true, characterData: false, attributeFilter: ['class'] });
                }
            }
            for (const removedNode of mutation.removedNodes) {
                if (removedNode.nodeType === 1 && removedNode.classList.contains('radio-filter')) {
                    if (debug) console.debug('disconncet radioFilterObserver');
                    radioFilterObserver.disconnect();
                }
            }
        }
    });

    const favContentSectionObserver = new MutationObserver(mutations => {
        if (debug) console.debug('callback favContentSectionObserver');
        for (const mutation of mutations) {
            if (!mutation.target.classList.contains('loading')) {
                abortActiveControllers();
                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 (debug) console.debug('checkURL');
        if (favlistURLRegex.test(location.href)) {
            if (!onFavlistPage) {
                onFavlistPage = true;
                autoNextPage = false;
                autoNextFavlist = false;
                if (debug) console.debug('observe favlistObserver');
                favlistObserver.observe(document.body, { subtree: true, childList: true, attributes: false, characterData: false });
            }
        } else {
            if (onFavlistPage) {
                abortActiveControllers();
                onFavlistPage = false;
                if (debug) console.debug('disconnect favlistObserver');
                favlistObserver.disconnect();
                if (debug) console.debug('disconncet itemsObserver');
                itemsObserver.disconnect();
                if (debug) console.debug('disconncet bodyChildListObserver');
                bodyChildListObserver.disconnect();
                if (debug) console.debug('disconncet radioFilterObserver');
                radioFilterObserver.disconnect();
                if (debug) console.debug('disconncet headerFilterLeftChildListObserver');
                headerFilterLeftChildListObserver.disconnect();
                if (debug) console.debug('disconncet favContentSectionObserver');
                favContentSectionObserver.disconnect();
            }
        }
    }

    async function main() {

        if (debug) console.log('============main============');

        let controller;
        firstTimeMain = false;

        try {

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

            let currentFavlist;
            if (newFreshSpace) {
                currentFavlist = document.querySelector('div.vui_sidebar-item--active');
                if (!document.querySelector('div.fav-collapse').contains(currentFavlist)) {
                    if (debug) console.log('不处理特殊收藏夹');
                    return;
                }
            } else {
                currentFavlist = document.querySelector('.fav-item.cur');
                if (!document.querySelector('div.nav-container').contains(currentFavlist)) {
                    if (debug) console.log('不处理特殊收藏夹');
                    return;
                }
            }

            let fid;
            if (newFreshSpace) {
                const getFidFromURLMatch = location.href.match(getFidFromURLRegex);
                if (getFidFromURLMatch) {
                    fid = parseInt(getFidFromURLMatch[1], 10);
                    if (!settings.defaultFavlistFid && !currentFavlist.parentNode.getAttribute('id')) {
                        settings.defaultFavlistFid = fid;
                        GM_setValue('settings', settings);
                    }
                } else if (settings.defaultFavlistFid) {
                    fid = settings.defaultFavlistFid;
                } else {
                    throw ['无法获取当前收藏夹的fid, 刷新页面可能有帮助'];
                }

            } else {
                fid = parseInt(currentFavlist.getAttribute('fid'), 10);
            }

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

            if (!settings.defaultUID) {
                settings.defaultUID = parseInt(location.href.match(getUIDFromURLRegex)[1], 10);
                GM_setValue('settings', settings);
            }

            let searchKeyword = '';
            const inputKeyword = document.querySelector(newFreshSpace ? 'input.fav-list-header-filter__search' : 'input.search-fav-input');
            if (inputKeyword) {
                searchKeyword = inputKeyword.value;
            }

            let searchType;
            const divType = document.querySelector(newFreshSpace ? 'div.vui_input__prepend' : 'div.search-types');
            let typeText = '当前';
            if (divType) {
                typeText = divType.innerText;
            }
            if (!searchKeyword) {
                typeText = '当前';
            }
            if (typeText.includes('当前')) {
                searchType = 0;
            } else if (typeText.includes('全部')) {
                searchType = 1;
            } else {
                throw ['无法确定搜索的范围为当前收藏夹还是全部收藏夹, 请反馈该问题'];
            }

            const videos = document.querySelectorAll(newFreshSpace ? 'div.items__item' : 'li.small-item');

            const apiDetails = {};

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

                let as;
                const AVBVTitle = {
                    AV: null,
                    BV: null,
                    title: null
                };

                try {

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

                    if (video.querySelector(newFreshSpace ? 'div.bili-cover-card__tags' : 'div.ogv-corner-tag')) {
                        if (debug) console.log('不处理特殊视频');
                        continue;
                    }

                    let disabled = false;
                    if (newFreshSpace) {
                        const divStats = video.querySelector('div.bili-cover-card__stats');
                        if (!searchKeyword && !divStats) {
                            disabled = true;
                        }
                        if (searchKeyword && video.querySelector('img').getAttribute('src').includes('be27fd62c99036dce67efface486fb0a88ffed06')) {
                            disabled = true;
                            if (divStats) {
                                divStats.remove();
                            } else {
                                if (debug) {
                                    addMessage('divStats已移除', false, 'red');
                                    console.warn('divStats已移除');
                                }
                            }
                        }

                    } else {
                        if (video.classList.contains('disabled')) {
                            disabled = true;
                        }
                    }

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

                    as = video.querySelectorAll('a');

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

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

                    if (newFreshSpace) {
                        const getBVFromURLMatch = as[0].getAttribute('href').match(getBVFromURLRegex);
                        if (!getBVFromURLMatch) {
                            throw ['无法获取该视频的BV号, 请检查是否有其他脚本或插件修改了该视频封面和标题的链接地址, 并将其关闭'];
                        }
                        AVBVTitle.BV = getBVFromURLMatch[1];
                    } else {
                        AVBVTitle.BV = video.getAttribute('data-aid');
                    }

                    AVBVTitle.title = as[1].innerText;

                    if (debug) {
                        console.log('========video========');
                        console.log(`收藏夹fid: ${fid}`);
                        console.log(`位置: 第${pageNumber}页的第${index + 1}个`);
                        consoleAVBVTitle('log', AVBVTitle);
                    }

                    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.textContent = '投稿于:';
                        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');
                        spanFavTime.textContent = metaText.replace(': ', ':');
                        spanFavTime.style.width = 'auto';
                        spanFavTime.style.lineHeight = '16px';
                        divMetaPubdate.appendChild(spanFavTime);

                        divTCABJX = divMetaPubdate;
                    }

                    const spanX = document.createElement('span');
                    spanX.classList.add('backup-spanTCABJX' + classAppendNewFreshSpace);
                    spanX.textContent = 'X';
                    spanX.style.opacity = settings.TCABJXOpacity / 100;
                    if (!newFreshSpace) {
                        // spanX.style.marginRight = '11px';
                        spanX.style.marginRight = '9px';
                        spanX.style.lineHeight = '16px';
                    }
                    spanX.addEventListener('click', () => {
                        try {
                            GM_openInTab(`https://${settings.getFromXbeibeixURL}/video/${AVBVTitle.BV}`, { active: true, insert: false, setParent: true });
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanX);

                    const spanJ = document.createElement('span');
                    spanJ.classList.add('backup-spanTCABJX' + classAppendNewFreshSpace);
                    spanJ.textContent = 'J';
                    spanJ.style.opacity = settings.TCABJXOpacity / 100;
                    spanJ.style.marginRight = '3px';
                    if (!newFreshSpace) {
                        spanJ.style.lineHeight = '16px';
                    }
                    spanJ.addEventListener('click', () => {
                        try {
                            GM_openInTab(`https://${settings.getFromJijidownURL}/video/${AVBVTitle.BV}`, { active: true, insert: false, setParent: true });
                            if (debug) {
                                GM_openInTab(`https://${settings.getFromJijidownURL}/api/v1/video_bv/get_info?id=${AVBVTitle.BV.slice(2)}`, { insert: false, setParent: true });
                                GM_openInTab(`https://${settings.getFromJijidownURL}/api/v1/video_bv/get_download_info?id=${AVBVTitle.BV.slice(2)}`, { insert: false, setParent: true });
                            }
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanJ);

                    const spanB = document.createElement('span');
                    spanB.classList.add('backup-spanTCABJX' + classAppendNewFreshSpace);
                    spanB.textContent = 'B';
                    spanB.style.opacity = settings.TCABJXOpacity / 100;
                    spanB.style.marginRight = '3px';
                    if (!newFreshSpace) {
                        spanB.style.lineHeight = '16px';
                    }
                    spanB.addEventListener('click', () => {
                        try {
                            GM_openInTab(`https://www.biliplus.com/video/${AVBVTitle.BV}`, { active: true, insert: false, setParent: true });
                            if (debug) {
                                if (AVBVTitle.AV) {
                                    GM_openInTab(`https://www.biliplus.com/api/view?id=${AVBVTitle.AV}`, { insert: false, setParent: true });
                                } else {
                                    GM_openInTab(`https://www.biliplus.com/api/view?id=${AVBVTitle.BV}`, { insert: false, setParent: true });
                                }
                            }
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanB);

                    const spanA = document.createElement('span');
                    spanA.classList.add('backup-spanTCABJX' + classAppendNewFreshSpace);
                    spanA.textContent = 'A';
                    spanA.style.opacity = settings.TCABJXOpacity / 100;
                    spanA.style.marginRight = '3px';
                    if (!newFreshSpace) {
                        spanA.style.lineHeight = '16px';
                    }
                    spanA.addEventListener('click', async () => {
                        try {
                            GM_openInTab(`https://api.bilibili.com/x/web-interface/view?bvid=${AVBVTitle.BV}`, { active: true, insert: false, setParent: true });
                            if (debug) {
                                GM_openInTab(`https://api.bilibili.com/x/web-interface/archive/desc?bvid=${AVBVTitle.BV}`, { insert: false, setParent: true });
                                if (AVBVTitle.AV) {
                                    GM_openInTab(`https://api.bilibili.com/x/v3/fav/resource/infos?resources=${AVBVTitle.AV}%3A2&platform=web&folder_id=${fid}`, { insert: false, setParent: true });
                                }
                                GM_openInTab(await appendParamsForGetFromApi(fid, ((pageNumber - 1) * pageSize + index + 1), 1), { insert: false, setParent: true });
                            }
                        } catch (error) {
                            catchUnknownError(error);
                        }
                    });
                    divTCABJX.appendChild(spanA);

                    const backup = GM_getValue(AVBVTitle.BV, {
                        BV: null,
                        AV: null,
                        title: null,
                        intro: null,
                        cover: null,
                        upperUID: null,
                        upperName: null,
                        upperAvatar: null,
                        timeUpload: null,
                        timePublish: null,
                        timeFavorite: null,
                        dynamic: null,
                        pages: null,
                        api: null,
                        apiExtra: null,
                        biliplus: null,
                        biliplusExtra: null,
                        jijidown: null,
                        jijidownExtra: null,
                        xbeibeix: null,
                    });

                    if (!backup.BV) {
                        backup.BV = AVBVTitle.BV;
                        GM_setValue(AVBVTitle.BV, backup);
                    }

                    AVBVTitle.AV = backup.AV;

                    formatBackup(backup, true, AVBVTitle.BV);

                    if (!searchType && updateTimeFavoriteInBackup(backup, undefined, fid)) {
                        GM_setValue(AVBVTitle.BV, backup);
                    }

                    const functions = [];

                    try {

                        if (newFreshSpace) {
                            if (backup.timePublish) {
                                spanFavTime.textContent = `投稿于:${formatTsTimePublish(1000 * backup.timePublish)}`;
                                spanFavTime.setAttribute('title', new Date(1000 * backup.timePublish).toLocaleString());
                            }

                        } else {
                            if (backup.timeFavorite) {
                                const target = backup.timeFavorite.find(el => el.fid === fid);
                                if (target && target.value) {
                                    spanFavTime.textContent = `收藏于:${formatTsTimeFavorite(new Date(1000 * target.value))}`;
                                    spanFavTime.setAttribute('title', new Date(1000 * target.value).toLocaleString());
                                }
                            }
                        }

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

                        const getFromApiExtraNeeded = { value: false };

                        if (settings.getFromApi) {
                            if (!backup.api || (settings.getFromApiUpdate && getCurrentTs() - backup.api.ts > 3600 * settings.getFromApiUpdateInterval)) {
                                if (await getFromApi(AVBVTitle, backup, spanA, apiDetails, fid, pageNumber, disabled, spanFavTime, searchType, getFromApiExtraNeeded)) {
                                    continue;
                                }

                            } else {
                                spanA.style.color = backup.api.value ? '#00ff00' : '#ff0000';
                                if (!searchType && apiDetails.value) {
                                    const apiDetail = apiDetails.value.find(el => el.bvid === AVBVTitle.BV);
                                    if (apiDetail) {
                                        if (updateTimeFavoriteInBackup(backup, apiDetail.fav_time, fid)) {
                                            GM_setValue(AVBVTitle.BV, backup);
                                        }
                                    }
                                }
                            }
                        }

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

                        if (settings.getFromApiExtra) {
                            if (!backup.apiExtra) {
                                await getFromApiExtra(AVBVTitle, backup, spanA, disabled);
                            } else if (settings.getFromApiExtraUpdate && settings.getFromApiExtraUpdateAll && getCurrentTs() - backup.apiExtra.ts > 3600 * 24 * settings.getFromApiExtraUpdateInterval) {
                                await getFromApiExtra(AVBVTitle, backup, spanA, disabled);
                            } else if (settings.getFromApiExtraUpdate && !settings.getFromApiExtraUpdateAll && getFromApiExtraNeeded.value) {
                                await getFromApiExtra(AVBVTitle, backup, spanA, disabled);
                            } else {
                                spanA.style.color = backup.apiExtra.value ? '#008000' : '#800000';
                            }
                        }

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

                        if (settings.getFromApiExtraThumbnails && !disabled && backup.AV && backup.pages) {
                            await getFromApiExtraThumbnails(AVBVTitle, backup, as, controller);
                        }

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

                        if (settings.getFromJijidown) {
                            if (!backup.jijidown || (settings.getFromJijidownUpdate && getCurrentTs() - backup.jijidown.ts > 3600 * 24 * 7 * settings.getFromJijidownUpdateInterval)) {
                                functions.push(getFromJijidown(AVBVTitle, backup, spanJ));

                            } else {
                                if (settings.getFromJijidownExtra) {
                                    if (!backup.jijidownExtra || (settings.getFromJijidownExtraUpdate && getCurrentTs() - backup.jijidownExtra.ts > 3600 * 24 * 7 * settings.getFromJijidownExtraUpdateInterval)) {
                                        functions.push(getFromJijidown(AVBVTitle, backup, spanJ));
                                    } else {
                                        spanJ.style.color = backup.jijidownExtra.value ? '#008000' : '#800000';
                                    }
                                } else {
                                    spanJ.style.color = backup.jijidown.value ? '#00ff00' : '#ff0000';
                                }
                            }
                        }

                        if (settings.getFromXbeibeix) {
                            if (!backup.xbeibeix || (settings.getFromXbeibeixUpdate && getCurrentTs() - backup.xbeibeix.ts > 3600 * 24 * 7 * settings.getFromXbeibeixUpdateInterval)) {
                                functions.push(getFromXbeibeix(AVBVTitle, backup, spanX));

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

                        if (settings.getFromBiliplus) {
                            if (!backup.biliplus || (settings.getFromBiliplusUpdate && getCurrentTs() - backup.biliplus.ts > 3600 * 24 * 7 * settings.getFromBiliplusUpdateInterval)) {
                                functions.push(getFromBiliplus(AVBVTitle, backup, spanB));

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

                        if (functions.length) {
                            if (controller.signal.aborted) {
                                throw new DOMException('', 'AbortError');
                            }
                            await Promise.all(functions);
                            const sortedBackup = {};
                            for (const sortedKey of sortedKeys) {
                                sortedBackup[sortedKey] = backup[sortedKey];
                            }
                            GM_setValue(AVBVTitle.BV, sortedBackup);
                            if (debug) {
                                console.log('保存第三方网站的数据至本地');
                                consoleAVBVTitle('debug', AVBVTitle);
                                console.debug(sortedBackup);
                            }
                        }

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

                        if (settings.getFromBiliplusExtra) {
                            if (!backup.biliplusExtra || (settings.getFromBiliplusExtraUpdate && getCurrentTs() - backup.biliplusExtra.ts > 3600 * 24 * 7 * settings.getFromBiliplusExtraUpdateInterval)) {
                                await getFromBiliplusExtra(AVBVTitle, backup, spanB, as, controller);

                            } else {
                                spanB.style.color = backup.biliplusExtra.value ? '#008000' : '#800000';
                            }
                        }

                    } catch (error) {
                        if (error instanceof Error) {
                            if (error.name === 'AbortError') {
                                throw error;
                            }

                            addMessage('发生未知错误, 请反馈该问题', false, 'red');
                            addMessage(`收藏夹fid: ${fid}`, true);
                            addMessage(`位置: 第${pageNumber}页的第${index + 1}个`, true);
                            addMessageAVBVTitle(AVBVTitle);
                            addMessage(error.stack, true);

                            console.error(`收藏夹fid: ${fid}`);
                            console.error(`位置: 第${pageNumber}页的第${index + 1}个`);
                            consoleAVBVTitle('error', AVBVTitle);
                            console.error(error);
                            if (as[1]) {
                                as[1].style.color = '#ff0000';
                            }

                        } else {
                            addMessage(error[0], false, 'red');
                            for (let i = 1; i < error.length; i++) {
                                addMessage(error[i], true);
                            }
                            addMessage(`收藏夹fid: ${fid}`, true);
                            addMessage(`位置: 第${pageNumber}页的第${index + 1}个`, true);
                            addMessageAVBVTitle(AVBVTitle);
                            if (as[1]) {
                                as[1].style.color = '#ff0000';
                            }
                        }
                    }

                    let picture;
                    let sourceAvif;
                    let sourceWebp;
                    let img;

                    if (newFreshSpace) {
                        img = video.querySelector('img');
                        if (debug) {
                            if (!img.getAttribute('src').endsWith('@672w_378h_1c.avif')) {
                                addMessage('封面avif已更改', false, 'red');
                                console.warn('封面avif已更改');
                            }
                            if (disabled && !searchKeyword && !img.getAttribute('src').includes('be27fd62c99036dce67efface486fb0a88ffed06')) {
                                addMessage('失效视频封面已更改', false, 'red');
                                console.warn('失效视频封面已更改');
                            }
                        }
                    } else {
                        picture = video.querySelector('picture');
                        sourceAvif = picture.querySelector('source[type="image/avif"]');
                        sourceWebp = picture.querySelector('source[type="image/webp"]');
                        img = picture.querySelector('img');
                    }

                    if (disabled) {
                        // video.style.opacity = '0.7';

                        if (newFreshSpace) {
                            as[2].style.textDecoration = 'line-through';
                            as[2].style.opacity = '0.7';

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

                        } else {
                            video.classList.remove('disabled');
                            as[0].classList.remove('disabled');

                            as[0].setAttribute('href', `//www.bilibili.com/video/${AVBVTitle.BV}/`);
                            as[0].setAttribute('target', '_blank');
                            as[1].setAttribute('target', '_blank');
                            as[1].setAttribute('href', `//www.bilibili.com/video/${AVBVTitle.BV}/`);

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

                        spanFavTime.style.textDecoration = 'line-through';
                        spanFavTime.style.opacity = '0.7';

                        as[1].style.textDecoration = 'line-through';
                        as[1].style.opacity = '0.5';

                        if (backup.title) {
                            as[1].textContent = backup.title[backup.title.length - 1].value;
                            img.setAttribute('alt', 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);
                            }
                        }
                    }

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

                        const spanC = document.createElement('span');
                        spanC.classList.add('backup-spanTCABJX' + classAppendNewFreshSpace);
                        spanC.textContent = 'C';
                        spanC.style.opacity = settings.TCABJXOpacity / 100;
                        spanC.style.marginRight = '3px';
                        spanC.style.color = '#000000';
                        if (!newFreshSpace) {
                            spanC.style.lineHeight = '16px';
                        }
                        let i = backup.cover.length - 2;
                        spanC.addEventListener('click', () => {
                            try {
                                if (i < 0) {
                                    i = backup.cover.length - 1;
                                }

                                if (newFreshSpace) {
                                    img.setAttribute('src', `//${backup.cover[i].value.slice(8)}@672w_378h_1c.avif`);
                                } else {
                                    sourceAvif.setAttribute('srcset', `//${backup.cover[i].value.slice(8)}@320w_200h_1c_!web-space-favlist-video.avif`);
                                    sourceWebp.setAttribute('srcset', `//${backup.cover[i].value.slice(8)}@320w_200h_1c_!web-space-favlist-video.webp`);
                                    img.setAttribute('src', `//${backup.cover[i].value.slice(8)}@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.classList.add('backup-spanTCABJX' + classAppendNewFreshSpace);
                        spanT.textContent = 'T';
                        spanT.style.opacity = settings.TCABJXOpacity / 100;
                        spanT.style.marginRight = '3px';
                        spanT.style.color = '#000000';
                        if (!newFreshSpace) {
                            spanT.style.lineHeight = '16px';
                        }
                        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 (controller.signal.aborted) {
                        throw new DOMException('', 'AbortError');
                    }

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

                } catch (error) {
                    if (error instanceof Error) {
                        if (error.name === 'AbortError') {
                            throw error;
                        }

                        addMessage('发生未知错误, 请反馈该问题', false, 'red');
                        addMessage(`收藏夹fid: ${fid}`, true);
                        addMessage(`位置: 第${pageNumber}页的第${index + 1}个`, true);
                        addMessageAVBVTitle(AVBVTitle);
                        addMessage(error.stack, true);

                        console.error(`收藏夹fid: ${fid}`);
                        console.error(`位置: 第${pageNumber}页的第${index + 1}个`);
                        consoleAVBVTitle('error', AVBVTitle);
                        console.error(error);
                        if (as[1]) {
                            as[1].style.color = '#ff0000';
                        }

                    } else {
                        addMessage(error[0], false, 'red');
                        for (let i = 1; i < error.length; i++) {
                            addMessage(error[i], true);
                        }
                        addMessage(`收藏夹fid: ${fid}`, true);
                        addMessage(`位置: 第${pageNumber}页的第${index + 1}个`, true);
                        addMessageAVBVTitle(AVBVTitle);
                        if (as[1]) {
                            as[1].style.color = '#ff0000';
                        }
                    }
                }
            }

            if (autoNextPage) {
                if (newFreshSpace) {
                    const pager = Array.from(document.querySelectorAll('button.vui_pagenation--btn-side')).find(el => el.innerText === '下一页');
                    if (pager && !pager.classList.contains('vui_button--disabled')) {
                        await delay(1000 * settings.autoNextPageInterval);
                        if (controller.signal.aborted) {
                            throw new DOMException('', 'AbortError');
                        }
                        if (autoNextPage) {
                            pager.click();
                        }

                    } else if (autoNextFavlist) {
                        if (!currentFavlist.parentNode.getAttribute('id')) {
                            if (document.querySelector('div.fav-sortable-list').childElementCount) {
                                await delay(1000 * settings.autoNextPageInterval);
                                if (controller.signal.aborted) {
                                    throw new DOMException('', 'AbortError');
                                }
                                if (autoNextFavlist) {
                                    document.querySelector('div.fav-sortable-list').firstElementChild.querySelector('div').click();
                                }
                            }

                        } else {
                            const nextFavlist = currentFavlist.parentNode.nextElementSibling;
                            if (nextFavlist) {
                                await delay(1000 * settings.autoNextPageInterval);
                                if (controller.signal.aborted) {
                                    throw new DOMException('', 'AbortError');
                                }
                                if (autoNextFavlist) {
                                    nextFavlist.querySelector('div').click();
                                }
                            }
                        }
                    }

                } else {
                    const pager = document.querySelector('li.be-pager-next');
                    if (pager && !pager.classList.contains('be-pager-disabled')) {
                        await delay(1000 * settings.autoNextPageInterval);
                        if (controller.signal.aborted) {
                            throw new DOMException('', 'AbortError');
                        }
                        if (autoNextPage) {
                            pager.click();
                        }

                    } else if (autoNextFavlist) {
                        if (currentFavlist.nodeName === 'DIV') {
                            if (document.querySelector('ul.fav-list').childElementCount) {
                                await delay(1000 * settings.autoNextPageInterval);
                                if (controller.signal.aborted) {
                                    throw new DOMException('', 'AbortError');
                                }
                                if (autoNextFavlist) {
                                    document.querySelector('ul.fav-list').firstElementChild.querySelector('a').click();
                                }
                            }

                        } else {
                            const nextFavlist = currentFavlist.nextElementSibling;
                            if (nextFavlist) {
                                await delay(1000 * settings.autoNextPageInterval);
                                if (controller.signal.aborted) {
                                    throw new DOMException('', 'AbortError');
                                }
                                if (autoNextFavlist) {
                                    nextFavlist.querySelector('a').click();
                                }
                            }
                        }
                    }
                }
            }

        } catch (error) {
            if (error instanceof Error) {
                if (error.name === 'AbortError') {
                    return;
                }
                catchUnknownError(error);

            } else {
                addMessage(error[0], false, 'red');
                for (let i = 1; i < error.length; i++) {
                    addMessage(error[i], true);
                }
            }

        } finally {
            activeControllers.delete(controller);
        }
    }

    function initControls() {

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

        const style = document.createElement('style');
        style.textContent = `
            .backup-spanTCABJX, .backup-spanTCABJX-newFreshSpace {
                float: right;
                font-weight: bold;
                cursor: pointer;
            }
            .backup-div-first {
                padding: 2px;
            }
            .backup-div-first-newFreshSpace {
                padding: 2px 0;
            }
            .backup-div-second {
                padding: 2px 0 2px 16px;
            }
            .backup-div-second-newFreshSpace {
                padding: 2px 0 2px 16px;
            }
            .backup-label, .backup-label-newFreshSpace {
                line-height: 1;
            }
            .backup-inputCheckbox, .backup-inputCheckbox-newFreshSpace {
                margin-left: 0;
            }
            .backup-inputCheckbox {
                margin-right: 1px;
            }
            .backup-inputCheckbox-newFreshSpace {
                margin-right: 3px;
            }
            .backup-disabled, .backup-disabled-newFreshSpace {
                opacity: 0.5;
                pointer-events: none;
            }
            .backup-inputText, .backup-inputText-newFreshSpace {
                box-sizing: content-box;
                border: 1px solid #cccccc;
                padding: 1px 2px;
                line-height: 1;
            }
            .backup-inputText {
                width: 28px;
                height: 14px;
                border-radius: 2px;
                font-size: 14px;
            }
            .backup-inputText-newFreshSpace {
                width: 32px;
                height: 16px;
                border-radius: 3px;
                font-size: 16px;
            }
            .backup-inputRadio, .backup-inputRadio-newFreshSpace {
                margin-left: 0;
            }
            .backup-inputRadio {
                margin-right: 1px;
            }
            .backup-inputRadio-newFreshSpace {
                margin-right: 3px;
            }
            .backup-hidden, .backup-hidden-newFreshSpace {
                display: none;
            }
            .backup-button, .backup-button-newFreshSpace {
                border: 1px solid #cccccc;
                line-height: 1;
                cursor: pointer;
            }
            .backup-button {
                border-radius: 2px;
                padding: 2px;
                font-size: 14px;
            }
            .backup-button-newFreshSpace {
                border-radius: 3px;
                padding: 3px;
                font-size: 16px;
            }
            .backup-divMessage, .backup-divMessage-newFreshSpace {
                overflow-y: auto;
                background-color: #eeeeee;
                line-height: 1.5;
                scrollbar-width: none;
            }
            .backup-divMessage {
                margin: 2px;
            }
            .backup-divMessage-heightFixed {
                height: 280px;
            }
            .backup-divMessage::-webkit-scrollbar {
                display: none;
            }
            .backup-divMessage-newFreshSpace {
                margin: 2px 0;
            }
            .backup-divMessage-heightFixed-newFreshSpace {
                height: 320px;
            }
            .backup-divMessage-newFreshSpace::-webkit-scrollbar {
                display: none;
            }
        `;
        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('backup-div-first' + classAppendNewFreshSpace);
        if (!newFreshSpace) {
            divControls.style.borderTop = '1px solid #e4e9f0';
        }
        divSide.appendChild(divControls);

        const divLabelProcessNormal = document.createElement('div');
        divLabelProcessNormal.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divLabelProcessNormal.setAttribute('title',
            '默认: 开启\n' +
            '由于新视频的BV号随机生成, 各个第三方网站无法自动地爬取新视频的信息。\n' +
            '开启该选项的同时开启下面的从第三方网站获取数据, 脚本将代替您访问相应的第三方网站。\n' +
            '如果处理的视频第三方网站还没有备份, 这将使其备份该视频当前版本的信息。\n' +
            '如果处理的视频之前有人备份过了, 这将获取到该视频的信息备份到第三方网站时的版本。');
        divControls.appendChild(divLabelProcessNormal);

        const labelProcessNormal = document.createElement('label');
        labelProcessNormal.classList.add('backup-label' + classAppendNewFreshSpace);
        labelProcessNormal.textContent = '处理正常视频';
        divLabelProcessNormal.appendChild(labelProcessNormal);

        const checkboxProcessNormal = document.createElement('input');
        checkboxProcessNormal.type = 'checkbox';
        checkboxProcessNormal.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxProcessNormal.checked = settings.processNormal;
        checkboxProcessNormal.addEventListener('change', () => {
            try {
                settings.processNormal = checkboxProcessNormal.checked;
                if (!settings.processNormal && !settings.processDisabled) {
                    divLabelGetFromApi.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraThumbnails.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraDelay.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdatePart.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateAll.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplus.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidown.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL1.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL2.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeix.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL1.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL2.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL3.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromApi.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApi) {
                        divLabelGetFromApiUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiUpdate) {
                            divLabelGetFromApiUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        divLabelGetFromApiExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiExtra) {
                            divLabelGetFromApiExtraThumbnails.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            divLabelGetFromApiExtraDelay.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            divLabelGetFromApiExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromApiExtraUpdate) {
                                divLabelGetFromApiExtraUpdatePart.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                divLabelGetFromApiExtraUpdateAll.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                if (settings.getFromApiExtraUpdateAll) {
                                    divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                }
                            }
                        }
                    }
                    divLabelGetFromBiliplus.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromBiliplus) {
                        divLabelGetFromBiliplusUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromBiliplusUpdate) {
                            divLabelGetFromBiliplusUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        if (settings.getFromApi) {
                            divLabelGetFromBiliplusExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromBiliplusExtra) {
                                divLabelGetFromBiliplusExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                if (settings.getFromBiliplusExtraUpdate) {
                                    divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                }
                            }
                        }
                    }
                    divLabelGetFromJijidown.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromJijidown) {
                        divLabelGetFromJijidownUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromJijidownUpdate) {
                            divLabelGetFromJijidownUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        divLabelGetFromJijidownExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromJijidownExtra) {
                            divLabelGetFromJijidownExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromJijidownExtraUpdate) {
                                divLabelGetFromJijidownExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            }
                        }
                        divLabelGetFromJijidownURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromJijidownURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                    divLabelGetFromXbeibeix.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromXbeibeix) {
                        divLabelGetFromXbeibeixUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromXbeibeixUpdate) {
                            divLabelGetFromXbeibeixUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        divLabelGetFromXbeibeixURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromXbeibeixURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromXbeibeixURL3.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelProcessNormal.insertAdjacentElement('afterbegin', checkboxProcessNormal);

        const divLabelProcessDisabled = document.createElement('div');
        divLabelProcessDisabled.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divLabelProcessDisabled.setAttribute('title',
            '默认: 开启\n' +
            '开启该选项的同时开启下面的从第三方网站获取数据, 脚本将尝试从相应的第三方网站获取失效视频的信息。');
        divControls.appendChild(divLabelProcessDisabled);

        const labelProcessDisabled = document.createElement('label');
        labelProcessDisabled.classList.add('backup-label' + classAppendNewFreshSpace);
        labelProcessDisabled.textContent = '处理失效视频';
        divLabelProcessDisabled.appendChild(labelProcessDisabled);

        const checkboxProcessDisabled = document.createElement('input');
        checkboxProcessDisabled.type = 'checkbox';
        checkboxProcessDisabled.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxProcessDisabled.checked = settings.processDisabled;
        checkboxProcessDisabled.addEventListener('change', () => {
            try {
                settings.processDisabled = checkboxProcessDisabled.checked;
                if (!settings.processNormal && !settings.processDisabled) {
                    divLabelGetFromApi.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraThumbnails.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraDelay.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdatePart.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateAll.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplus.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidown.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL1.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL2.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeix.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL1.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL2.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL3.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromApi.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApi) {
                        divLabelGetFromApiUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiUpdate) {
                            divLabelGetFromApiUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        divLabelGetFromApiExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiExtra) {
                            divLabelGetFromApiExtraThumbnails.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            divLabelGetFromApiExtraDelay.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            divLabelGetFromApiExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromApiExtraUpdate) {
                                divLabelGetFromApiExtraUpdatePart.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                divLabelGetFromApiExtraUpdateAll.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                if (settings.getFromApiExtraUpdateAll) {
                                    divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                }
                            }
                        }
                    }
                    divLabelGetFromBiliplus.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromBiliplus) {
                        divLabelGetFromBiliplusUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromBiliplusUpdate) {
                            divLabelGetFromBiliplusUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        if (settings.getFromApi) {
                            divLabelGetFromBiliplusExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromBiliplusExtra) {
                                divLabelGetFromBiliplusExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                if (settings.getFromBiliplusExtraUpdate) {
                                    divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                                }
                            }
                        }
                    }
                    divLabelGetFromJijidown.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromJijidown) {
                        divLabelGetFromJijidownUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromJijidownUpdate) {
                            divLabelGetFromJijidownUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        divLabelGetFromJijidownExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromJijidownExtra) {
                            divLabelGetFromJijidownExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromJijidownExtraUpdate) {
                                divLabelGetFromJijidownExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            }
                        }
                        divLabelGetFromJijidownURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromJijidownURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                    divLabelGetFromXbeibeix.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromXbeibeix) {
                        divLabelGetFromXbeibeixUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromXbeibeixUpdate) {
                            divLabelGetFromXbeibeixUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                        divLabelGetFromXbeibeixURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromXbeibeixURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromXbeibeixURL3.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelProcessDisabled.insertAdjacentElement('afterbegin', checkboxProcessDisabled);

        const divLabelGetFromApi = document.createElement('div');
        divLabelGetFromApi.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApi.setAttribute('title',
            '默认: 开启\n' +
            '地址: https://api.bilibili.com/x/v3/fav/resource/list?media_id={收藏夹fid}&pn={页码}&ps={每页展示视频数量}\n' +
            '数据: AV号, 标题 (失效视频无法获取), 简介 (仅能获取前255个字符), 封面地址 (失效视频无法获取), UP主UID, UP主昵称, UP主头像地址, 上传时间, 发布时间, 添加到当前收藏夹的时间, 第1个分集的cid (均为最新版本)\n' +
            '上述接口一次请求即可获取当前页所有视频的数据。\n' +
            '地址: https://api.bilibili.com/x/web-interface/archive/desc?bvid={BV号}\n' +
            '数据: 完整简介 (最新版本, 非必要不会调用该接口)');
        divControls.appendChild(divLabelGetFromApi);

        const labelGetFromApi = document.createElement('label');
        labelGetFromApi.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApi.textContent = '从B站接口获取数据';
        divLabelGetFromApi.appendChild(labelGetFromApi);

        const checkboxGetFromApi = document.createElement('input');
        checkboxGetFromApi.type = 'checkbox';
        checkboxGetFromApi.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromApi.checked = settings.getFromApi;
        checkboxGetFromApi.addEventListener('change', () => {
            try {
                settings.getFromApi = checkboxGetFromApi.checked;
                if (!settings.getFromApi) {
                    settings.getFromApiExtra = false;
                    checkboxGetFromApiExtra.checked = false;
                    settings.getFromApiExtraThumbnails = false;
                    checkboxGetFromApiExtraThumbnails.checked = false;
                    settings.getFromBiliplusExtra = false;
                    checkboxGetFromBiliplusExtra.checked = false;
                    divLabelGetFromApiUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraThumbnails.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraDelay.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdatePart.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateAll.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromApiUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApiUpdate) {
                        divLabelGetFromApiUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                    divLabelGetFromApiExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApiExtra) {
                        divLabelGetFromApiExtraThumbnails.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromApiExtraDelay.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromApiExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiExtraUpdate) {
                            divLabelGetFromApiExtraUpdatePart.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            divLabelGetFromApiExtraUpdateAll.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromApiExtraUpdateAll) {
                                divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            }
                        }
                    }
                    if (settings.getFromBiliplus) {
                        divLabelGetFromBiliplusExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromBiliplusExtra) {
                            divLabelGetFromBiliplusExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromBiliplusExtraUpdate) {
                                divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            }
                        }
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApi.insertAdjacentElement('afterbegin', checkboxGetFromApi);

        const divLabelGetFromApiUpdate = document.createElement('div');
        divLabelGetFromApiUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiUpdate.setAttribute('title',
            '默认: 开启\n' +
            '关闭后每个视频从B站接口只会获取一次数据。\n' +
            '不建议关闭, 因为某些视频的信息可能会经常更新。');
        divControls.appendChild(divLabelGetFromApiUpdate);

        const labelGetFromApiUpdate = document.createElement('label');
        labelGetFromApiUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiUpdate.textContent = '启用更新';
        divLabelGetFromApiUpdate.appendChild(labelGetFromApiUpdate);

        const checkboxGetFromApiUpdate = document.createElement('input');
        checkboxGetFromApiUpdate.type = 'checkbox';
        checkboxGetFromApiUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromApiUpdate.checked = settings.getFromApiUpdate;
        checkboxGetFromApiUpdate.addEventListener('change', () => {
            try {
                settings.getFromApiUpdate = checkboxGetFromApiUpdate.checked;
                if (!settings.getFromApiUpdate) {
                    divLabelGetFromApiUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromApiUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApiUpdate.insertAdjacentElement('afterbegin', checkboxGetFromApiUpdate);

        const divLabelGetFromApiUpdateInterval = document.createElement('div');
        divLabelGetFromApiUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiUpdateInterval.setAttribute('title',
            '默认: 5小时\n' +
            '脚本处理某个视频时, 如果发现距离上次从B站接口获取到该视频的数据已经超过了设定的时间间隔, 则会再次从B站接口获取该视频的数据。');
        divControls.appendChild(divLabelGetFromApiUpdateInterval);

        const labelGetFromApiUpdateInterval = document.createElement('label');
        labelGetFromApiUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromApiUpdateInterval.appendChild(labelGetFromApiUpdateInterval);

        const inputTextGetFromApiUpdateInterval = document.createElement('input');
        inputTextGetFromApiUpdateInterval.type = 'text';
        inputTextGetFromApiUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromApiUpdateInterval.value = settings.getFromApiUpdateInterval;
        inputTextGetFromApiUpdateInterval.setAttribute('backup-def', 5);
        inputTextGetFromApiUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromApiUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromApiUpdateInterval.setAttribute('backup-setting', 'getFromApiUpdateInterval');
        inputTextGetFromApiUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromApiUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromApiUpdateInterval.appendChild(inputTextGetFromApiUpdateInterval);
        labelGetFromApiUpdateInterval.appendChild(document.createTextNode('小时'));

        const divLabelGetFromApiExtra = document.createElement('div');
        divLabelGetFromApiExtra.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtra.setAttribute('title',
            '默认: 关闭\n' +
            '地址: https://api.bilibili.com/x/web-interface/view?bvid={BV号}\n' +
            '数据: 完整简介, 视频发布动态内容, 每个分集的标题, 第1帧截图地址 (旧视频无法获取), cid (均为最新版本, 失效视频均无法获取)\n' +
            '目前接口 "api.bilibili.com/x/v3/fav/resource/list" 已经设置调用频率限制, 每次请求之间需要至少间隔300毫秒左右。在未来接口 "api.bilibili.com/x/web-interface/view" 也可能会设置类似的限制, 届时频繁从B站接口获取额外数据将会出现问题。');
        divControls.appendChild(divLabelGetFromApiExtra);

        const labelGetFromApiExtra = document.createElement('label');
        labelGetFromApiExtra.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiExtra.textContent = '从B站接口获取额外数据(可能频率受限)';
        divLabelGetFromApiExtra.appendChild(labelGetFromApiExtra);

        const checkboxGetFromApiExtra = document.createElement('input');
        checkboxGetFromApiExtra.type = 'checkbox';
        checkboxGetFromApiExtra.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromApiExtra.checked = settings.getFromApiExtra;
        checkboxGetFromApiExtra.addEventListener('change', () => {
            try {
                settings.getFromApiExtra = checkboxGetFromApiExtra.checked;
                if (!settings.getFromApiExtra) {
                    settings.getFromApiExtraThumbnails = false;
                    checkboxGetFromApiExtraThumbnails.checked = false;
                    divLabelGetFromApiExtraThumbnails.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraDelay.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdatePart.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateAll.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromApiExtraThumbnails.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraDelay.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApiExtraUpdate) {
                        divLabelGetFromApiExtraUpdatePart.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromApiExtraUpdateAll.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiExtraUpdateAll) {
                            divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApiExtra.insertAdjacentElement('afterbegin', checkboxGetFromApiExtra);

        const divLabelGetFromApiExtraThumbnails = document.createElement('div');
        divLabelGetFromApiExtraThumbnails.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraThumbnails.setAttribute('title',
            '默认: 关闭\n' +
            '地址: https://api.bilibili.com/x/player/videoshot?aid={AV号}&cid={分集cid}\n' +
            '数据: 进度条缩略图地址 (失效视频以及部分旧视频无法获取)\n' +
            '如果UP主对某个视频进行了换源, 只要该视频的本地备份数据中保存了旧源的cid, 就可以尝试获取旧源的进度条缩略图地址。\n' +
            '一次请求只能获取一个分集的进度条缩略图地址, 如果某个视频的分集较多, 则需要等待一段时间。');
        divControls.appendChild(divLabelGetFromApiExtraThumbnails);

        const labelGetFromApiExtraThumbnails = document.createElement('label');
        labelGetFromApiExtraThumbnails.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiExtraThumbnails.textContent = '获取进度条缩略图地址';
        divLabelGetFromApiExtraThumbnails.appendChild(labelGetFromApiExtraThumbnails);

        const checkboxGetFromApiExtraThumbnails = document.createElement('input');
        checkboxGetFromApiExtraThumbnails.type = 'checkbox';
        checkboxGetFromApiExtraThumbnails.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromApiExtraThumbnails.checked = settings.getFromApiExtraThumbnails;
        checkboxGetFromApiExtraThumbnails.addEventListener('change', () => {
            try {
                settings.getFromApiExtraThumbnails = checkboxGetFromApiExtraThumbnails.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApiExtraThumbnails.insertAdjacentElement('afterbegin', checkboxGetFromApiExtraThumbnails);

        const divLabelGetFromApiExtraDelay = document.createElement('div');
        divLabelGetFromApiExtraDelay.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraDelay.setAttribute('title',
            '默认: 开启');
        if (debug) divControls.appendChild(divLabelGetFromApiExtraDelay);

        const labelGetFromApiExtraDelay = document.createElement('label');
        labelGetFromApiExtraDelay.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiExtraDelay.textContent = '停留500毫秒';
        if (debug) divLabelGetFromApiExtraDelay.appendChild(labelGetFromApiExtraDelay);

        const checkboxGetFromApiExtraDelay = document.createElement('input');
        checkboxGetFromApiExtraDelay.type = 'checkbox';
        checkboxGetFromApiExtraDelay.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromApiExtraDelay.checked = getFromApiExtraDelay;
        checkboxGetFromApiExtraDelay.addEventListener('change', () => {
            try {
                getFromApiExtraDelay = checkboxGetFromApiExtraDelay.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        if (debug) labelGetFromApiExtraDelay.insertAdjacentElement('afterbegin', checkboxGetFromApiExtraDelay);

        const divLabelGetFromApiExtraUpdate = document.createElement('div');
        divLabelGetFromApiExtraUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraUpdate.setAttribute('title',
            '默认: 开启');
        divControls.appendChild(divLabelGetFromApiExtraUpdate);

        const labelGetFromApiExtraUpdate = document.createElement('label');
        labelGetFromApiExtraUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiExtraUpdate.textContent = '启用更新';
        divLabelGetFromApiExtraUpdate.appendChild(labelGetFromApiExtraUpdate);

        const checkboxGetFromApiExtraUpdate = document.createElement('input');
        checkboxGetFromApiExtraUpdate.type = 'checkbox';
        checkboxGetFromApiExtraUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromApiExtraUpdate.checked = settings.getFromApiExtraUpdate;
        checkboxGetFromApiExtraUpdate.addEventListener('change', () => {
            try {
                settings.getFromApiExtraUpdate = checkboxGetFromApiExtraUpdate.checked;
                if (!settings.getFromApiExtraUpdate) {
                    divLabelGetFromApiExtraUpdatePart.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateAll.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromApiExtraUpdatePart.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdateAll.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApiExtraUpdateAll) {
                        divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApiExtraUpdate.insertAdjacentElement('afterbegin', checkboxGetFromApiExtraUpdate);

        const divLabelGetFromApiExtraUpdatePart = document.createElement('div');
        divLabelGetFromApiExtraUpdatePart.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraUpdatePart.setAttribute('title',
            '默认: 只更新可能换源的视频\n' +
            '只更新可能换源的视频: 脚本处理某个视频时, 从接口 "api.bilibili.com/x/v3/fav/resource/list" 中可以获取到该视频的分集数量以及第1个分集的cid, 如果发现该视频可能进行了换源 (获取的第1个分集的cid在本地备份数据中找不到, 或者获取的分集数量大于1), 才会再次从接口 "api.bilibili.com/x/web-interface/view" 获取该视频的数据。\n' +
            '总是更新所有视频: 脚本处理某个视频时, 如果发现距离上次从接口 "api.bilibili.com/x/web-interface/view" 获取到该视频的数据已经超过了设定的时间间隔, 则会再次从该接口获取该视频的数据。一次请求只能获取一个视频的数据, 频繁调用该接口不仅耗费时间, 如果该接口设置了调用频率限制还可能出现问题。');
        divControls.appendChild(divLabelGetFromApiExtraUpdatePart);

        const labelGetFromApiExtraUpdatePart = document.createElement('label');
        labelGetFromApiExtraUpdatePart.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiExtraUpdatePart.textContent = '只更新可能换源的视频';
        divLabelGetFromApiExtraUpdatePart.appendChild(labelGetFromApiExtraUpdatePart);

        const radioGetFromApiExtraUpdatePart = document.createElement('input');
        radioGetFromApiExtraUpdatePart.type = 'radio';
        radioGetFromApiExtraUpdatePart.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromApiExtraUpdatePart.name = 'getFromApiExtraUpdateAll';
        radioGetFromApiExtraUpdatePart.value = false;
        radioGetFromApiExtraUpdatePart.checked = !settings.getFromApiExtraUpdateAll;
        radioGetFromApiExtraUpdatePart.addEventListener('change', () => {
            try {
                settings.getFromApiExtraUpdateAll = false;
                divLabelGetFromApiExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApiExtraUpdatePart.insertAdjacentElement('afterbegin', radioGetFromApiExtraUpdatePart);

        const divLabelGetFromApiExtraUpdateAll = document.createElement('div');
        divLabelGetFromApiExtraUpdateAll.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraUpdateAll.setAttribute('title',
            '默认: 只更新可能换源的视频\n' +
            '只更新可能换源的视频: 脚本处理某个视频时, 从接口 "api.bilibili.com/x/v3/fav/resource/list" 中可以获取到该视频的分集数量以及第1个分集的cid, 如果发现该视频可能进行了换源 (获取的第1个分集的cid在本地备份数据中找不到, 或者获取的分集数量大于1), 才会再次从接口 "api.bilibili.com/x/web-interface/view" 获取该视频的数据。\n' +
            '总是更新所有视频: 脚本处理某个视频时, 如果发现距离上次从接口 "api.bilibili.com/x/web-interface/view" 获取到该视频的数据已经超过了设定的时间间隔, 则会再次从该接口获取该视频的数据。一次请求只能获取一个视频的数据, 频繁调用该接口不仅耗费时间, 如果该接口设置了调用频率限制还可能出现问题。');
        divControls.appendChild(divLabelGetFromApiExtraUpdateAll);

        const labelGetFromApiExtraUpdateAll = document.createElement('label');
        labelGetFromApiExtraUpdateAll.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromApiExtraUpdateAll.textContent = '总是更新所有视频';
        divLabelGetFromApiExtraUpdateAll.appendChild(labelGetFromApiExtraUpdateAll);

        const radioGetFromApiExtraUpdateAll = document.createElement('input');
        radioGetFromApiExtraUpdateAll.type = 'radio';
        radioGetFromApiExtraUpdateAll.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromApiExtraUpdateAll.name = 'getFromApiExtraUpdateAll';
        radioGetFromApiExtraUpdateAll.value = true;
        radioGetFromApiExtraUpdateAll.checked = settings.getFromApiExtraUpdateAll;
        radioGetFromApiExtraUpdateAll.addEventListener('change', () => {
            try {
                settings.getFromApiExtraUpdateAll = true;
                divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromApiExtraUpdateAll.insertAdjacentElement('afterbegin', radioGetFromApiExtraUpdateAll);

        const divLabelGetFromApiExtraUpdateInterval = document.createElement('div');
        divLabelGetFromApiExtraUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraUpdateInterval.setAttribute('title',
            '默认: 5天');
        divControls.appendChild(divLabelGetFromApiExtraUpdateInterval);

        const labelGetFromApiExtraUpdateInterval = document.createElement('label');
        labelGetFromApiExtraUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromApiExtraUpdateInterval.appendChild(labelGetFromApiExtraUpdateInterval);

        const inputTextGetFromApiExtraUpdateInterval = document.createElement('input');
        inputTextGetFromApiExtraUpdateInterval.type = 'text';
        inputTextGetFromApiExtraUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromApiExtraUpdateInterval.value = settings.getFromApiExtraUpdateInterval;
        inputTextGetFromApiExtraUpdateInterval.setAttribute('backup-def', 5);
        inputTextGetFromApiExtraUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromApiExtraUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromApiExtraUpdateInterval.setAttribute('backup-setting', 'getFromApiExtraUpdateInterval');
        inputTextGetFromApiExtraUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromApiExtraUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromApiExtraUpdateInterval.appendChild(inputTextGetFromApiExtraUpdateInterval);
        labelGetFromApiExtraUpdateInterval.appendChild(document.createTextNode('天'));

        const divLabelGetFromBiliplus = document.createElement('div');
        divLabelGetFromBiliplus.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromBiliplus.setAttribute('title',
            '默认: 关闭\n' +
            '从BiliPlus获取数据时可能会出现问题。\n' +
            '地址: https://www.biliplus.com/video/{BV号}\n' +
            '数据: 标题, 简介, 封面地址, UP主昵称, 每个分集的标题, cid (均为备份时的版本)');
        divControls.appendChild(divLabelGetFromBiliplus);

        const labelGetFromBiliplus = document.createElement('label');
        labelGetFromBiliplus.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromBiliplus.textContent = '从BiliPlus获取数据(不稳定)';
        divLabelGetFromBiliplus.appendChild(labelGetFromBiliplus);

        const checkboxGetFromBiliplus = document.createElement('input');
        checkboxGetFromBiliplus.type = 'checkbox';
        checkboxGetFromBiliplus.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromBiliplus.checked = settings.getFromBiliplus;
        checkboxGetFromBiliplus.addEventListener('change', () => {
            try {
                settings.getFromBiliplus = checkboxGetFromBiliplus.checked;
                if (!settings.getFromBiliplus) {
                    settings.getFromBiliplusExtra = false;
                    checkboxGetFromBiliplusExtra.checked = false;
                    divLabelGetFromBiliplusUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromBiliplusUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromBiliplusUpdate) {
                        divLabelGetFromBiliplusUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                    if (settings.getFromApi) {
                        divLabelGetFromBiliplusExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromBiliplusExtra) {
                            divLabelGetFromBiliplusExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            if (settings.getFromBiliplusExtraUpdate) {
                                divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                            }
                        }
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromBiliplus.insertAdjacentElement('afterbegin', checkboxGetFromBiliplus);

        const divLabelGetFromBiliplusUpdate = document.createElement('div');
        divLabelGetFromBiliplusUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusUpdate.setAttribute('title',
            '默认: 关闭');
        divControls.appendChild(divLabelGetFromBiliplusUpdate);

        const labelGetFromBiliplusUpdate = document.createElement('label');
        labelGetFromBiliplusUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromBiliplusUpdate.textContent = '启用更新';
        divLabelGetFromBiliplusUpdate.appendChild(labelGetFromBiliplusUpdate);

        const checkboxGetFromBiliplusUpdate = document.createElement('input');
        checkboxGetFromBiliplusUpdate.type = 'checkbox';
        checkboxGetFromBiliplusUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromBiliplusUpdate.checked = settings.getFromBiliplusUpdate;
        checkboxGetFromBiliplusUpdate.addEventListener('change', () => {
            try {
                settings.getFromBiliplusUpdate = checkboxGetFromBiliplusUpdate.checked;
                if (!settings.getFromBiliplusUpdate) {
                    divLabelGetFromBiliplusUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromBiliplusUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromBiliplusUpdate.insertAdjacentElement('afterbegin', checkboxGetFromBiliplusUpdate);

        const divLabelGetFromBiliplusUpdateInterval = document.createElement('div');
        divLabelGetFromBiliplusUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusUpdateInterval.setAttribute('title',
            '默认: 10星期');
        divControls.appendChild(divLabelGetFromBiliplusUpdateInterval);

        const labelGetFromBiliplusUpdateInterval = document.createElement('label');
        labelGetFromBiliplusUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusUpdateInterval.appendChild(labelGetFromBiliplusUpdateInterval);

        const inputTextGetFromBiliplusUpdateInterval = document.createElement('input');
        inputTextGetFromBiliplusUpdateInterval.type = 'text';
        inputTextGetFromBiliplusUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromBiliplusUpdateInterval.value = settings.getFromBiliplusUpdateInterval;
        inputTextGetFromBiliplusUpdateInterval.setAttribute('backup-def', 10);
        inputTextGetFromBiliplusUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromBiliplusUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromBiliplusUpdateInterval.setAttribute('backup-setting', 'getFromBiliplusUpdateInterval');
        inputTextGetFromBiliplusUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromBiliplusUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromBiliplusUpdateInterval.appendChild(inputTextGetFromBiliplusUpdateInterval);
        labelGetFromBiliplusUpdateInterval.appendChild(document.createTextNode('星期'));

        const divLabelGetFromBiliplusExtra = document.createElement('div');
        divLabelGetFromBiliplusExtra.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusExtra.setAttribute('title',
            '默认: 关闭\n' +
            '地址: https://www.biliplus.com/all/video/av{AV号}/\n' +
            '数据: 调用下面的接口所需的验证参数\n' +
            '地址: https://www.biliplus.com/api/view_all?av={AV号}&ts={验证参数1}&sign={验证参数2}\n' +
            '数据: 标题, 简介, 封面地址, UP主昵称 (第一次备份到BiliPlus时的版本); 每个分集的标题, cid (所有曾经备份到BiliPlus版本)\n' +
            'BiliPlus原始页面的底部有一个刷新数据的功能, 该功能会让BiliPlus再次从B站接口获取某个视频的最新信息并保存在其数据库中。\n' +
            'BiliPlus原始页面显示的信息为最后一次备份到BiliPlus时的版本, 而此接口可以获取到之前备份到BiliPlus时的版本。\n' +
            '请注意: 此接口有调用频率限制, 短时间内频繁调用此接口可能会出现请求失败的情况, 之后每次调用此接口需要先等待10秒左右。');
        divControls.appendChild(divLabelGetFromBiliplusExtra);

        const labelGetFromBiliplusExtra = document.createElement('label');
        labelGetFromBiliplusExtra.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromBiliplusExtra.textContent = '从BiliPlus获取额外数据(频率受限)';
        divLabelGetFromBiliplusExtra.appendChild(labelGetFromBiliplusExtra);

        const checkboxGetFromBiliplusExtra = document.createElement('input');
        checkboxGetFromBiliplusExtra.type = 'checkbox';
        checkboxGetFromBiliplusExtra.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromBiliplusExtra.checked = settings.getFromBiliplusExtra;
        checkboxGetFromBiliplusExtra.addEventListener('change', () => {
            try {
                settings.getFromBiliplusExtra = checkboxGetFromBiliplusExtra.checked;
                if (!settings.getFromBiliplusExtra) {
                    divLabelGetFromBiliplusExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromBiliplusExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromBiliplusExtraUpdate) {
                        divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromBiliplusExtra.insertAdjacentElement('afterbegin', checkboxGetFromBiliplusExtra);

        const divLabelGetFromBiliplusExtraUpdate = document.createElement('div');
        divLabelGetFromBiliplusExtraUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusExtraUpdate.setAttribute('title',
            '默认: 关闭');
        divControls.appendChild(divLabelGetFromBiliplusExtraUpdate);

        const labelGetFromBiliplusExtraUpdate = document.createElement('label');
        labelGetFromBiliplusExtraUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromBiliplusExtraUpdate.textContent = '启用更新';
        divLabelGetFromBiliplusExtraUpdate.appendChild(labelGetFromBiliplusExtraUpdate);

        const checkboxGetFromBiliplusExtraUpdate = document.createElement('input');
        checkboxGetFromBiliplusExtraUpdate.type = 'checkbox';
        checkboxGetFromBiliplusExtraUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromBiliplusExtraUpdate.checked = settings.getFromBiliplusExtraUpdate;
        checkboxGetFromBiliplusExtraUpdate.addEventListener('change', () => {
            try {
                settings.getFromBiliplusExtraUpdate = checkboxGetFromBiliplusExtraUpdate.checked;
                if (!settings.getFromBiliplusExtraUpdate) {
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromBiliplusExtraUpdate.insertAdjacentElement('afterbegin', checkboxGetFromBiliplusExtraUpdate);

        const divLabelGetFromBiliplusExtraUpdateInterval = document.createElement('div');
        divLabelGetFromBiliplusExtraUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusExtraUpdateInterval.setAttribute('title',
            '默认: 10星期');
        divControls.appendChild(divLabelGetFromBiliplusExtraUpdateInterval);

        const labelGetFromBiliplusExtraUpdateInterval = document.createElement('label');
        labelGetFromBiliplusExtraUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromBiliplusExtraUpdateInterval.appendChild(labelGetFromBiliplusExtraUpdateInterval);

        const inputTextGetFromBiliplusExtraUpdateInterval = document.createElement('input');
        inputTextGetFromBiliplusExtraUpdateInterval.type = 'text';
        inputTextGetFromBiliplusExtraUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromBiliplusExtraUpdateInterval.value = settings.getFromBiliplusExtraUpdateInterval;
        inputTextGetFromBiliplusExtraUpdateInterval.setAttribute('backup-def', 10);
        inputTextGetFromBiliplusExtraUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromBiliplusExtraUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromBiliplusExtraUpdateInterval.setAttribute('backup-setting', 'getFromBiliplusExtraUpdateInterval');
        inputTextGetFromBiliplusExtraUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromBiliplusExtraUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromBiliplusExtraUpdateInterval.appendChild(inputTextGetFromBiliplusExtraUpdateInterval);
        labelGetFromBiliplusExtraUpdateInterval.appendChild(document.createTextNode('星期'));

        const divLabelGetFromJijidown = document.createElement('div');
        divLabelGetFromJijidown.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidown.setAttribute('title',
            '默认: 关闭\n' +
            '从唧唧获取数据时可能会出现请求超时和请求失败的情况。\n' +
            '地址: https://www.jijidown.com/api/v1/video_bv/get_info?id={BV号后10位}\n' +
            '或 https://www.jiji.moe/api/v1/video_bv/get_info?id={BV号后10位} (取决于下面的单选项)\n' +
            '数据: 标题, 简介, 封面地址, UP主昵称, UP主头像地址 (均为备份时的版本)');
        divControls.appendChild(divLabelGetFromJijidown);

        const labelGetFromJijidown = document.createElement('label');
        labelGetFromJijidown.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromJijidown.textContent = '从唧唧获取数据(不稳定)';
        divLabelGetFromJijidown.appendChild(labelGetFromJijidown);

        const checkboxGetFromJijidown = document.createElement('input');
        checkboxGetFromJijidown.type = 'checkbox';
        checkboxGetFromJijidown.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromJijidown.checked = settings.getFromJijidown;
        checkboxGetFromJijidown.addEventListener('change', () => {
            try {
                settings.getFromJijidown = checkboxGetFromJijidown.checked;
                if (!settings.getFromJijidown) {
                    settings.getFromJijidownExtra = false;
                    checkboxGetFromJijidownExtra.checked = false;
                    divLabelGetFromJijidownUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtra.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL1.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL2.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromJijidownUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromJijidownUpdate) {
                        divLabelGetFromJijidownUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                    divLabelGetFromJijidownExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromJijidownExtra) {
                        divLabelGetFromJijidownExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromJijidownExtraUpdate) {
                            divLabelGetFromJijidownExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                    }
                    divLabelGetFromJijidownURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromJijidown.insertAdjacentElement('afterbegin', checkboxGetFromJijidown);

        const divLabelGetFromJijidownUpdate = document.createElement('div');
        divLabelGetFromJijidownUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownUpdate.setAttribute('title',
            '默认: 关闭');
        divControls.appendChild(divLabelGetFromJijidownUpdate);

        const labelGetFromJijidownUpdate = document.createElement('label');
        labelGetFromJijidownUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromJijidownUpdate.textContent = '启用更新';
        divLabelGetFromJijidownUpdate.appendChild(labelGetFromJijidownUpdate);

        const checkboxGetFromJijidownUpdate = document.createElement('input');
        checkboxGetFromJijidownUpdate.type = 'checkbox';
        checkboxGetFromJijidownUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromJijidownUpdate.checked = settings.getFromJijidownUpdate;
        checkboxGetFromJijidownUpdate.addEventListener('change', () => {
            try {
                settings.getFromJijidownUpdate = checkboxGetFromJijidownUpdate.checked;
                if (!settings.getFromJijidownUpdate) {
                    divLabelGetFromJijidownUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromJijidownUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromJijidownUpdate.insertAdjacentElement('afterbegin', checkboxGetFromJijidownUpdate);

        const divLabelGetFromJijidownUpdateInterval = document.createElement('div');
        divLabelGetFromJijidownUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownUpdateInterval.setAttribute('title',
            '默认: 10星期');
        divControls.appendChild(divLabelGetFromJijidownUpdateInterval);

        const labelGetFromJijidownUpdateInterval = document.createElement('label');
        labelGetFromJijidownUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromJijidownUpdateInterval.appendChild(labelGetFromJijidownUpdateInterval);

        const inputTextGetFromJijidownUpdateInterval = document.createElement('input');
        inputTextGetFromJijidownUpdateInterval.type = 'text';
        inputTextGetFromJijidownUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromJijidownUpdateInterval.value = settings.getFromJijidownUpdateInterval;
        inputTextGetFromJijidownUpdateInterval.setAttribute('backup-def', 10);
        inputTextGetFromJijidownUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromJijidownUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromJijidownUpdateInterval.setAttribute('backup-setting', 'getFromJijidownUpdateInterval');
        inputTextGetFromJijidownUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromJijidownUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromJijidownUpdateInterval.appendChild(inputTextGetFromJijidownUpdateInterval);
        labelGetFromJijidownUpdateInterval.appendChild(document.createTextNode('星期'));

        const divLabelGetFromJijidownExtra = document.createElement('div');
        divLabelGetFromJijidownExtra.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownExtra.setAttribute('title',
            '默认: 关闭\n' +
            '从唧唧获取数据时可能会出现请求超时和请求失败的情况。\n' +
            '地址: https://www.jijidown.com/api/v1/video_bv/get_download_info?id={BV号后10位}\n' +
            '或 https://www.jiji.moe/api/v1/video_bv/get_download_info?id={BV号后10位} (取决于下面的单选项)\n' +
            '数据: 每个分集的标题, cid (均为备份时的版本)');
        divControls.appendChild(divLabelGetFromJijidownExtra);

        const labelGetFromJijidownExtra = document.createElement('label');
        labelGetFromJijidownExtra.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromJijidownExtra.textContent = '从唧唧获取额外数据(不稳定)';
        divLabelGetFromJijidownExtra.appendChild(labelGetFromJijidownExtra);

        const checkboxGetFromJijidownExtra = document.createElement('input');
        checkboxGetFromJijidownExtra.type = 'checkbox';
        checkboxGetFromJijidownExtra.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromJijidownExtra.checked = settings.getFromJijidownExtra;
        checkboxGetFromJijidownExtra.addEventListener('change', () => {
            try {
                settings.getFromJijidownExtra = checkboxGetFromJijidownExtra.checked;
                if (!settings.getFromJijidownExtra) {
                    divLabelGetFromJijidownExtraUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromJijidownExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromJijidownExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromJijidownExtraUpdate) {
                        divLabelGetFromJijidownExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromJijidownExtra.insertAdjacentElement('afterbegin', checkboxGetFromJijidownExtra);

        const divLabelGetFromJijidownExtraUpdate = document.createElement('div');
        divLabelGetFromJijidownExtraUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownExtraUpdate.setAttribute('title',
            '默认: 关闭');
        divControls.appendChild(divLabelGetFromJijidownExtraUpdate);

        const labelGetFromJijidownExtraUpdate = document.createElement('label');
        labelGetFromJijidownExtraUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromJijidownExtraUpdate.textContent = '启用更新';
        divLabelGetFromJijidownExtraUpdate.appendChild(labelGetFromJijidownExtraUpdate);

        const checkboxGetFromJijidownExtraUpdate = document.createElement('input');
        checkboxGetFromJijidownExtraUpdate.type = 'checkbox';
        checkboxGetFromJijidownExtraUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromJijidownExtraUpdate.checked = settings.getFromJijidownExtraUpdate;
        checkboxGetFromJijidownExtraUpdate.addEventListener('change', () => {
            try {
                settings.getFromJijidownExtraUpdate = checkboxGetFromJijidownExtraUpdate.checked;
                if (!settings.getFromJijidownExtraUpdate) {
                    divLabelGetFromJijidownExtraUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromJijidownExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromJijidownExtraUpdate.insertAdjacentElement('afterbegin', checkboxGetFromJijidownExtraUpdate);

        const divLabelGetFromJijidownExtraUpdateInterval = document.createElement('div');
        divLabelGetFromJijidownExtraUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownExtraUpdateInterval.setAttribute('title',
            '默认: 10星期');
        divControls.appendChild(divLabelGetFromJijidownExtraUpdateInterval);

        const labelGetFromJijidownExtraUpdateInterval = document.createElement('label');
        labelGetFromJijidownExtraUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromJijidownExtraUpdateInterval.appendChild(labelGetFromJijidownExtraUpdateInterval);

        const inputTextGetFromJijidownExtraUpdateInterval = document.createElement('input');
        inputTextGetFromJijidownExtraUpdateInterval.type = 'text';
        inputTextGetFromJijidownExtraUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromJijidownExtraUpdateInterval.value = settings.getFromJijidownExtraUpdateInterval;
        inputTextGetFromJijidownExtraUpdateInterval.setAttribute('backup-def', 10);
        inputTextGetFromJijidownExtraUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromJijidownExtraUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromJijidownExtraUpdateInterval.setAttribute('backup-setting', 'getFromJijidownExtraUpdateInterval');
        inputTextGetFromJijidownExtraUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromJijidownExtraUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromJijidownExtraUpdateInterval.appendChild(inputTextGetFromJijidownExtraUpdateInterval);
        labelGetFromJijidownExtraUpdateInterval.appendChild(document.createTextNode('星期'));

        const divLabelGetFromJijidownURL1 = document.createElement('div');
        divLabelGetFromJijidownURL1.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownURL1.setAttribute('title',
            '默认: www.jijidown.com\n' +
            'www.jiji.moe为唧唧的新域名。\n' +
            '如果脚本从唧唧获取数据时反复出现问题, 切换至另一域名可能有帮助。');
        divControls.appendChild(divLabelGetFromJijidownURL1);

        const labelGetFromJijidownURL1 = document.createElement('label');
        labelGetFromJijidownURL1.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromJijidownURL1.textContent = 'www.jijidown.com';
        divLabelGetFromJijidownURL1.appendChild(labelGetFromJijidownURL1);

        const radioGetFromJijidownURL1 = document.createElement('input');
        radioGetFromJijidownURL1.type = 'radio';
        radioGetFromJijidownURL1.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromJijidownURL1.name = 'getFromJijidownURL';
        radioGetFromJijidownURL1.value = 'www.jijidown.com';
        radioGetFromJijidownURL1.checked = settings.getFromJijidownURL === 'www.jijidown.com';
        radioGetFromJijidownURL1.addEventListener('change', () => {
            try {
                settings.getFromJijidownURL = 'www.jijidown.com';
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromJijidownURL1.insertAdjacentElement('afterbegin', radioGetFromJijidownURL1);

        const divLabelGetFromJijidownURL2 = document.createElement('div');
        divLabelGetFromJijidownURL2.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromJijidownURL2.setAttribute('title',
            '默认: www.jijidown.com\n' +
            'www.jiji.moe为唧唧的新域名。\n' +
            '如果脚本从唧唧获取数据时反复出现问题, 切换至另一域名可能有帮助。');
        divControls.appendChild(divLabelGetFromJijidownURL2);

        const labelGetFromJijidownURL2 = document.createElement('label');
        labelGetFromJijidownURL2.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromJijidownURL2.textContent = 'www.jiji.moe';
        divLabelGetFromJijidownURL2.appendChild(labelGetFromJijidownURL2);

        const radioGetFromJijidownURL2 = document.createElement('input');
        radioGetFromJijidownURL2.type = 'radio';
        radioGetFromJijidownURL2.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromJijidownURL2.name = 'getFromJijidownURL';
        radioGetFromJijidownURL2.value = 'www.jiji.moe';
        radioGetFromJijidownURL2.checked = settings.getFromJijidownURL === 'www.jiji.moe';
        radioGetFromJijidownURL2.addEventListener('change', () => {
            try {
                settings.getFromJijidownURL = 'www.jiji.moe';
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromJijidownURL2.insertAdjacentElement('afterbegin', radioGetFromJijidownURL2);

        const divLabelGetFromXbeibeix = document.createElement('div');
        divLabelGetFromXbeibeix.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeix.setAttribute('title',
            '默认: 关闭\n' +
            '从贝贝工具站获取数据时可能会出现请求超时和请求失败的情况。\n' +
            '地址: https://xbeibeix.com/video/{BV号}\n' +
            '或 https://beibeigame.com/video/{BV号}\n' +
            '或 https://bbdownloader.com/video/{BV号} (取决于下面的单选项)\n' +
            '数据: 标题, 简介, 封面地址, UP主昵称 (均为备份时的版本)');
        divControls.appendChild(divLabelGetFromXbeibeix);

        const labelGetFromXbeibeix = document.createElement('label');
        labelGetFromXbeibeix.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromXbeibeix.textContent = '从贝贝工具站获取数据(不稳定)';
        divLabelGetFromXbeibeix.appendChild(labelGetFromXbeibeix);

        const checkboxGetFromXbeibeix = document.createElement('input');
        checkboxGetFromXbeibeix.type = 'checkbox';
        checkboxGetFromXbeibeix.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromXbeibeix.checked = settings.getFromXbeibeix;
        checkboxGetFromXbeibeix.addEventListener('change', () => {
            try {
                settings.getFromXbeibeix = checkboxGetFromXbeibeix.checked;
                if (!settings.getFromXbeibeix) {
                    divLabelGetFromXbeibeixUpdate.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL1.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL2.classList.add('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL3.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromXbeibeixUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromXbeibeixUpdate) {
                        divLabelGetFromXbeibeixUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                    divLabelGetFromXbeibeixURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromXbeibeixURL3.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromXbeibeix.insertAdjacentElement('afterbegin', checkboxGetFromXbeibeix);

        const divLabelGetFromXbeibeixUpdate = document.createElement('div');
        divLabelGetFromXbeibeixUpdate.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeixUpdate.setAttribute('title',
            '默认: 关闭');
        divControls.appendChild(divLabelGetFromXbeibeixUpdate);

        const labelGetFromXbeibeixUpdate = document.createElement('label');
        labelGetFromXbeibeixUpdate.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromXbeibeixUpdate.textContent = '启用更新';
        divLabelGetFromXbeibeixUpdate.appendChild(labelGetFromXbeibeixUpdate);

        const checkboxGetFromXbeibeixUpdate = document.createElement('input');
        checkboxGetFromXbeibeixUpdate.type = 'checkbox';
        checkboxGetFromXbeibeixUpdate.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxGetFromXbeibeixUpdate.checked = settings.getFromXbeibeixUpdate;
        checkboxGetFromXbeibeixUpdate.addEventListener('change', () => {
            try {
                settings.getFromXbeibeixUpdate = checkboxGetFromXbeibeixUpdate.checked;
                if (!settings.getFromXbeibeixUpdate) {
                    divLabelGetFromXbeibeixUpdateInterval.classList.add('backup-disabled' + classAppendNewFreshSpace);
                } else {
                    divLabelGetFromXbeibeixUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromXbeibeixUpdate.insertAdjacentElement('afterbegin', checkboxGetFromXbeibeixUpdate);

        const divLabelGetFromXbeibeixUpdateInterval = document.createElement('div');
        divLabelGetFromXbeibeixUpdateInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced', 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeixUpdateInterval.setAttribute('title',
            '默认: 10星期');
        divControls.appendChild(divLabelGetFromXbeibeixUpdateInterval);

        const labelGetFromXbeibeixUpdateInterval = document.createElement('label');
        labelGetFromXbeibeixUpdateInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeixUpdateInterval.appendChild(labelGetFromXbeibeixUpdateInterval);

        const inputTextGetFromXbeibeixUpdateInterval = document.createElement('input');
        inputTextGetFromXbeibeixUpdateInterval.type = 'text';
        inputTextGetFromXbeibeixUpdateInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextGetFromXbeibeixUpdateInterval.value = settings.getFromXbeibeixUpdateInterval;
        inputTextGetFromXbeibeixUpdateInterval.setAttribute('backup-def', 10);
        inputTextGetFromXbeibeixUpdateInterval.setAttribute('backup-min', 0);
        inputTextGetFromXbeibeixUpdateInterval.setAttribute('backup-max', 100);
        inputTextGetFromXbeibeixUpdateInterval.setAttribute('backup-setting', 'getFromXbeibeixUpdateInterval');
        inputTextGetFromXbeibeixUpdateInterval.addEventListener('blur', validateInputText);

        labelGetFromXbeibeixUpdateInterval.appendChild(document.createTextNode('最小更新间隔'));
        labelGetFromXbeibeixUpdateInterval.appendChild(inputTextGetFromXbeibeixUpdateInterval);
        labelGetFromXbeibeixUpdateInterval.appendChild(document.createTextNode('星期'));

        const divLabelGetFromXbeibeixURL1 = document.createElement('div');
        divLabelGetFromXbeibeixURL1.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeixURL1.setAttribute('title',
            '默认: xbeibeix.com\n' +
            'beibeigame.com和bbdownloader.com为贝贝工具站的新域名。\n' +
            '如果脚本从贝贝工具站获取数据时反复出现问题, 切换至另一域名可能有帮助。');
        divControls.appendChild(divLabelGetFromXbeibeixURL1);

        const labelGetFromXbeibeixURL1 = document.createElement('label');
        labelGetFromXbeibeixURL1.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromXbeibeixURL1.textContent = 'xbeibeix.com';
        divLabelGetFromXbeibeixURL1.appendChild(labelGetFromXbeibeixURL1);

        const radioGetFromXbeibeixURL1 = document.createElement('input');
        radioGetFromXbeibeixURL1.type = 'radio';
        radioGetFromXbeibeixURL1.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromXbeibeixURL1.name = 'getFromXbeibeixURL';
        radioGetFromXbeibeixURL1.value = 'xbeibeix.com';
        radioGetFromXbeibeixURL1.checked = settings.getFromXbeibeixURL === 'xbeibeix.com';
        radioGetFromXbeibeixURL1.addEventListener('change', () => {
            try {
                settings.getFromXbeibeixURL = 'xbeibeix.com';
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromXbeibeixURL1.insertAdjacentElement('afterbegin', radioGetFromXbeibeixURL1);

        const divLabelGetFromXbeibeixURL2 = document.createElement('div');
        divLabelGetFromXbeibeixURL2.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeixURL2.setAttribute('title',
            '默认: xbeibeix.com\n' +
            'beibeigame.com和bbdownloader.com为贝贝工具站的新域名。\n' +
            '如果脚本从贝贝工具站获取数据时反复出现问题, 切换至另一域名可能有帮助。');
        divControls.appendChild(divLabelGetFromXbeibeixURL2);

        const labelGetFromXbeibeixURL2 = document.createElement('label');
        labelGetFromXbeibeixURL2.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromXbeibeixURL2.textContent = 'beibeigame.com';
        divLabelGetFromXbeibeixURL2.appendChild(labelGetFromXbeibeixURL2);

        const radioGetFromXbeibeixURL2 = document.createElement('input');
        radioGetFromXbeibeixURL2.type = 'radio';
        radioGetFromXbeibeixURL2.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromXbeibeixURL2.name = 'getFromXbeibeixURL';
        radioGetFromXbeibeixURL2.value = 'beibeigame.com';
        radioGetFromXbeibeixURL2.checked = settings.getFromXbeibeixURL === 'beibeigame.com';
        radioGetFromXbeibeixURL2.addEventListener('change', () => {
            try {
                settings.getFromXbeibeixURL = 'beibeigame.com';
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromXbeibeixURL2.insertAdjacentElement('afterbegin', radioGetFromXbeibeixURL2);

        const divLabelGetFromXbeibeixURL3 = document.createElement('div');
        divLabelGetFromXbeibeixURL3.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelGetFromXbeibeixURL3.setAttribute('title',
            '默认: xbeibeix.com\n' +
            'beibeigame.com和bbdownloader.com为贝贝工具站的新域名。\n' +
            '如果脚本从贝贝工具站获取数据时反复出现问题, 切换至另一域名可能有帮助。');
        divControls.appendChild(divLabelGetFromXbeibeixURL3);

        const labelGetFromXbeibeixURL3 = document.createElement('label');
        labelGetFromXbeibeixURL3.classList.add('backup-label' + classAppendNewFreshSpace);
        labelGetFromXbeibeixURL3.textContent = 'bbdownloader.com';
        divLabelGetFromXbeibeixURL3.appendChild(labelGetFromXbeibeixURL3);

        const radioGetFromXbeibeixURL3 = document.createElement('input');
        radioGetFromXbeibeixURL3.type = 'radio';
        radioGetFromXbeibeixURL3.classList.add('backup-inputRadio' + classAppendNewFreshSpace);
        radioGetFromXbeibeixURL3.name = 'getFromXbeibeixURL';
        radioGetFromXbeibeixURL3.value = 'bbdownloader.com';
        radioGetFromXbeibeixURL3.checked = settings.getFromXbeibeixURL === 'bbdownloader.com';
        radioGetFromXbeibeixURL3.addEventListener('change', () => {
            try {
                settings.getFromXbeibeixURL = 'bbdownloader.com';
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelGetFromXbeibeixURL3.insertAdjacentElement('afterbegin', radioGetFromXbeibeixURL3);

        const divLabelAutoNextPage = document.createElement('div');
        divLabelAutoNextPage.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divLabelAutoNextPage.setAttribute('title',
            '开启后脚本将在当前页的视频都处理完毕之后点击下一页, 继续处理下一页的视频。');
        divControls.appendChild(divLabelAutoNextPage);

        const labelAutoNextPage = document.createElement('label');
        labelAutoNextPage.classList.add('backup-label' + classAppendNewFreshSpace);
        labelAutoNextPage.textContent = '自动点击下一页';
        divLabelAutoNextPage.appendChild(labelAutoNextPage);

        const checkboxAutoNextPage = document.createElement('input');
        checkboxAutoNextPage.type = 'checkbox';
        checkboxAutoNextPage.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxAutoNextPage.checked = autoNextPage;
        checkboxAutoNextPage.addEventListener('change', async () => {
            try {
                autoNextPage = checkboxAutoNextPage.checked;
                if (autoNextPage) {
                    divLabelAutoNextFavlist.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (!activeControllers.size) {
                        if (newFreshSpace) {
                            if (!document.querySelector('div.fav-collapse').contains(document.querySelector('div.vui_sidebar-item--active'))) {
                                return;
                            }
                            const pager = Array.from(document.querySelectorAll('button.vui_pagenation--btn-side')).find(el => el.innerText === '下一页');
                            if (pager && !pager.classList.contains('vui_button--disabled')) {
                                await delay(500);
                                if (autoNextPage) {
                                    pager.click();
                                }
                            }

                        } else {
                            if (!document.querySelector('div.nav-container').contains(document.querySelector('.fav-item.cur'))) {
                                return;
                            }
                            const pager = document.querySelector('li.be-pager-next');
                            if (pager && !pager.classList.contains('be-pager-disabled')) {
                                await delay(500);
                                if (autoNextPage) {
                                    pager.click();
                                }
                            }
                        }
                    }

                } else {
                    autoNextFavlist = false;
                    checkboxAutoNextFavlist.checked = false;
                    divLabelAutoNextFavlist.classList.add('backup-disabled' + classAppendNewFreshSpace);
                }
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelAutoNextPage.insertAdjacentElement('afterbegin', checkboxAutoNextPage);

        const divLabelAutoNextFavlist = document.createElement('div');
        divLabelAutoNextFavlist.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-disabled' + classAppendNewFreshSpace);
        divLabelAutoNextFavlist.setAttribute('title',
            '开启后脚本将在当前收藏夹的视频都处理完毕之后点击下一收藏夹, 继续处理下一收藏夹的视频。');
        divControls.appendChild(divLabelAutoNextFavlist);

        const labelAutoNextFavlist = document.createElement('label');
        labelAutoNextFavlist.classList.add('backup-label' + classAppendNewFreshSpace);
        labelAutoNextFavlist.textContent = '自动点击下一收藏夹';
        divLabelAutoNextFavlist.appendChild(labelAutoNextFavlist);

        const checkboxAutoNextFavlist = document.createElement('input');
        checkboxAutoNextFavlist.type = 'checkbox';
        checkboxAutoNextFavlist.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxAutoNextFavlist.checked = autoNextFavlist;
        checkboxAutoNextFavlist.addEventListener('change', async () => {
            try {
                autoNextFavlist = checkboxAutoNextFavlist.checked;
                if (autoNextFavlist && !activeControllers.size) {
                    let currentFavlist;
                    if (newFreshSpace) {
                        currentFavlist = document.querySelector('div.vui_sidebar-item--active');
                        if (!document.querySelector('div.fav-collapse').contains(currentFavlist)) {
                            return;
                        }
                        const pager = Array.from(document.querySelectorAll('button.vui_pagenation--btn-side')).find(el => el.innerText === '下一页');
                        if (pager && !pager.classList.contains('vui_button--disabled')) {

                        } else {
                            if (!currentFavlist.parentNode.getAttribute('id')) {
                                if (document.querySelector('div.fav-sortable-list').childElementCount) {
                                    await delay(500);
                                    if (autoNextFavlist) {
                                        document.querySelector('div.fav-sortable-list').firstElementChild.querySelector('div').click();
                                    }
                                }

                            } else {
                                const nextFavlist = currentFavlist.parentNode.nextElementSibling;
                                if (nextFavlist) {
                                    await delay(500);
                                    if (autoNextFavlist) {
                                        nextFavlist.querySelector('div').click();
                                    }
                                }
                            }
                        }

                    } else {
                        currentFavlist = document.querySelector('.fav-item.cur');
                        if (!document.querySelector('div.nav-container').contains(currentFavlist)) {
                            return;
                        }
                        const pager = document.querySelector('li.be-pager-next');
                        if (pager && !pager.classList.contains('be-pager-disabled')) {

                        } else {
                            if (currentFavlist.nodeName === 'DIV') {
                                if (document.querySelector('ul.fav-list').childElementCount) {
                                    await delay(500);
                                    if (autoNextFavlist) {
                                        document.querySelector('ul.fav-list').firstElementChild.querySelector('a').click();
                                    }
                                }

                            } else {
                                const nextFavlist = currentFavlist.nextElementSibling;
                                if (nextFavlist) {
                                    await delay(500);
                                    if (autoNextFavlist) {
                                        nextFavlist.querySelector('a').click();
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelAutoNextFavlist.insertAdjacentElement('afterbegin', checkboxAutoNextFavlist);

        const divLabelAutoNextPageInterval = document.createElement('div');
        divLabelAutoNextPageInterval.classList.add('backup-div-second' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelAutoNextPageInterval.setAttribute('title',
            '默认: 5秒\n' +
            '每次点击下一页或下一收藏夹之前停留在当前页的时间。');
        divControls.appendChild(divLabelAutoNextPageInterval);

        const labelAutoNextPageInterval = document.createElement('label');
        labelAutoNextPageInterval.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelAutoNextPageInterval.appendChild(labelAutoNextPageInterval);

        const inputTextAutoNextPageInterval = document.createElement('input');
        inputTextAutoNextPageInterval.type = 'text';
        inputTextAutoNextPageInterval.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextAutoNextPageInterval.value = settings.autoNextPageInterval;
        inputTextAutoNextPageInterval.setAttribute('backup-def', 5);
        inputTextAutoNextPageInterval.setAttribute('backup-min', 1);
        inputTextAutoNextPageInterval.setAttribute('backup-max', 500);
        inputTextAutoNextPageInterval.setAttribute('backup-setting', 'autoNextPageInterval');
        inputTextAutoNextPageInterval.addEventListener('blur', validateInputText);

        labelAutoNextPageInterval.appendChild(document.createTextNode('停留间隔'));
        labelAutoNextPageInterval.appendChild(inputTextAutoNextPageInterval);
        labelAutoNextPageInterval.appendChild(document.createTextNode('秒'));

        const divLabelRequestTimeout = document.createElement('div');
        divLabelRequestTimeout.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelRequestTimeout.setAttribute('title',
            '默认: 10秒\n' +
            '脚本获取数据时, 如果等待了设定的时长后仍未得到响应数据, 则暂时跳过处理当前视频。');
        divControls.appendChild(divLabelRequestTimeout);

        const labelRequestTimeout = document.createElement('label');
        labelRequestTimeout.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelRequestTimeout.appendChild(labelRequestTimeout);

        const inputTextRequestTimeout = document.createElement('input');
        inputTextRequestTimeout.type = 'text';
        inputTextRequestTimeout.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextRequestTimeout.value = settings.requestTimeout;
        inputTextRequestTimeout.setAttribute('backup-def', 10);
        inputTextRequestTimeout.setAttribute('backup-min', 1);
        inputTextRequestTimeout.setAttribute('backup-max', 100);
        inputTextRequestTimeout.setAttribute('backup-setting', 'requestTimeout');
        inputTextRequestTimeout.addEventListener('blur', validateInputText);

        labelRequestTimeout.appendChild(document.createTextNode('请求超时时间'));
        labelRequestTimeout.appendChild(inputTextRequestTimeout);
        labelRequestTimeout.appendChild(document.createTextNode('秒'));

        const divLabelDelayBeforeMain = document.createElement('div');
        divLabelDelayBeforeMain.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelDelayBeforeMain.setAttribute('title',
            '默认: 300毫秒\n' +
            '如果您使用的是新版个人空间, 并且遇到了视频标题下方的信息展示出来后又突然消失的情况, 请将这个时长调大一些。\n' +
            '如果您想将其设置为0, 则需要避免取消收藏, 指定排序方式或筛选条件等操作, 否则可能会出现问题。');
        divControls.appendChild(divLabelDelayBeforeMain);

        const labelDelayBeforeMain = document.createElement('label');
        labelDelayBeforeMain.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelDelayBeforeMain.appendChild(labelDelayBeforeMain);

        const inputTextDelayBeforeMain = document.createElement('input');
        inputTextDelayBeforeMain.type = 'text';
        inputTextDelayBeforeMain.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextDelayBeforeMain.value = settings.delayBeforeMain;
        inputTextDelayBeforeMain.setAttribute('backup-def', 300);
        inputTextDelayBeforeMain.setAttribute('backup-min', 0);
        inputTextDelayBeforeMain.setAttribute('backup-max', 1000);
        inputTextDelayBeforeMain.setAttribute('backup-setting', 'delayBeforeMain');
        inputTextDelayBeforeMain.addEventListener('blur', validateInputText);

        labelDelayBeforeMain.appendChild(document.createTextNode('开始处理视频前等待'));
        labelDelayBeforeMain.appendChild(inputTextDelayBeforeMain);
        labelDelayBeforeMain.appendChild(document.createTextNode('毫秒'));

        const divLabelAppendDropdownCover = document.createElement('div');
        divLabelAppendDropdownCover.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelAppendDropdownCover.setAttribute('title',
            '默认: 开启\n' +
            '控制是否在各个视频右下角的下拉列表中添加查看封面原图的功能。');
        divControls.appendChild(divLabelAppendDropdownCover);

        const labelAppendDropdownCover = document.createElement('label');
        labelAppendDropdownCover.classList.add('backup-label' + classAppendNewFreshSpace);
        labelAppendDropdownCover.textContent = '下拉列表: 封面原图';
        divLabelAppendDropdownCover.appendChild(labelAppendDropdownCover);

        const checkboxAppendDropdownCover = document.createElement('input');
        checkboxAppendDropdownCover.type = 'checkbox';
        checkboxAppendDropdownCover.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxAppendDropdownCover.checked = settings.appendDropdownCover;
        checkboxAppendDropdownCover.addEventListener('change', () => {
            try {
                settings.appendDropdownCover = checkboxAppendDropdownCover.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelAppendDropdownCover.insertAdjacentElement('afterbegin', checkboxAppendDropdownCover);

        const divLabelAppendDropdownLocal = document.createElement('div');
        divLabelAppendDropdownLocal.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelAppendDropdownLocal.setAttribute('title',
            '默认: 开启');
        divControls.appendChild(divLabelAppendDropdownLocal);

        const labelAppendDropdownLocal = document.createElement('label');
        labelAppendDropdownLocal.classList.add('backup-label' + classAppendNewFreshSpace);
        labelAppendDropdownLocal.textContent = '下拉列表: 本地备份数据';
        divLabelAppendDropdownLocal.appendChild(labelAppendDropdownLocal);

        const checkboxAppendDropdownLocal = document.createElement('input');
        checkboxAppendDropdownLocal.type = 'checkbox';
        checkboxAppendDropdownLocal.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxAppendDropdownLocal.checked = settings.appendDropdownLocal;
        checkboxAppendDropdownLocal.addEventListener('change', () => {
            try {
                settings.appendDropdownLocal = checkboxAppendDropdownLocal.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelAppendDropdownLocal.insertAdjacentElement('afterbegin', checkboxAppendDropdownLocal);

        const divLabelAppendDropdownJump = document.createElement('div');
        divLabelAppendDropdownJump.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelAppendDropdownJump.setAttribute('title',
            '默认: 开启');
        divControls.appendChild(divLabelAppendDropdownJump);

        const labelAppendDropdownJump = document.createElement('label');
        labelAppendDropdownJump.classList.add('backup-label' + classAppendNewFreshSpace);
        labelAppendDropdownJump.textContent = '下拉列表: 跳转至BJX';
        divLabelAppendDropdownJump.appendChild(labelAppendDropdownJump);

        const checkboxAppendDropdownJump = document.createElement('input');
        checkboxAppendDropdownJump.type = 'checkbox';
        checkboxAppendDropdownJump.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxAppendDropdownJump.checked = settings.appendDropdownJump;
        checkboxAppendDropdownJump.addEventListener('change', () => {
            try {
                settings.appendDropdownJump = checkboxAppendDropdownJump.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelAppendDropdownJump.insertAdjacentElement('afterbegin', checkboxAppendDropdownJump);

        const divLabelAppendDropdownReset = document.createElement('div');
        divLabelAppendDropdownReset.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelAppendDropdownReset.setAttribute('title',
            '默认: 开启');
        divControls.appendChild(divLabelAppendDropdownReset);

        const labelAppendDropdownReset = document.createElement('label');
        labelAppendDropdownReset.classList.add('backup-label' + classAppendNewFreshSpace);
        labelAppendDropdownReset.textContent = '下拉列表: 重置备份数据';
        divLabelAppendDropdownReset.appendChild(labelAppendDropdownReset);

        const checkboxAppendDropdownReset = document.createElement('input');
        checkboxAppendDropdownReset.type = 'checkbox';
        checkboxAppendDropdownReset.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxAppendDropdownReset.checked = settings.appendDropdownReset;
        checkboxAppendDropdownReset.addEventListener('change', () => {
            try {
                settings.appendDropdownReset = checkboxAppendDropdownReset.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelAppendDropdownReset.insertAdjacentElement('afterbegin', checkboxAppendDropdownReset);

        const divLabelTCABJXOpacity = document.createElement('div');
        divLabelTCABJXOpacity.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelTCABJXOpacity.setAttribute('title',
            '默认: 100%\n' +
            '如果您觉得视频标题下方的TCABJX太显眼, 可以将其不透明度调低一些。');
        divControls.appendChild(divLabelTCABJXOpacity);

        const labelTCABJXOpacity = document.createElement('label');
        labelTCABJXOpacity.classList.add('backup-label' + classAppendNewFreshSpace);
        divLabelTCABJXOpacity.appendChild(labelTCABJXOpacity);

        const inputTextTCABJXOpacity = document.createElement('input');
        inputTextTCABJXOpacity.type = 'text';
        inputTextTCABJXOpacity.classList.add('backup-inputText' + classAppendNewFreshSpace);
        inputTextTCABJXOpacity.value = settings.TCABJXOpacity;
        inputTextTCABJXOpacity.setAttribute('backup-def', 100);
        inputTextTCABJXOpacity.setAttribute('backup-min', 0);
        inputTextTCABJXOpacity.setAttribute('backup-max', 100);
        inputTextTCABJXOpacity.setAttribute('backup-setting', 'TCABJXOpacity');
        inputTextTCABJXOpacity.addEventListener('blur', validateInputText);

        labelTCABJXOpacity.appendChild(document.createTextNode('TCABJX不透明度'));
        labelTCABJXOpacity.appendChild(inputTextTCABJXOpacity);
        labelTCABJXOpacity.appendChild(document.createTextNode('%'));

        const divLabelDisplayAdvancedControls = document.createElement('div');
        divLabelDisplayAdvancedControls.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divControls.appendChild(divLabelDisplayAdvancedControls);

        const labelDisplayAdvancedControls = document.createElement('label');
        labelDisplayAdvancedControls.classList.add('backup-label' + classAppendNewFreshSpace);
        labelDisplayAdvancedControls.textContent = '显示高级选项和功能';
        divLabelDisplayAdvancedControls.appendChild(labelDisplayAdvancedControls);

        const checkboxDisplayAdvancedControls = document.createElement('input');
        checkboxDisplayAdvancedControls.type = 'checkbox';
        checkboxDisplayAdvancedControls.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxDisplayAdvancedControls.checked = settings.displayAdvancedControls;
        checkboxDisplayAdvancedControls.addEventListener('change', () => {
            try {
                settings.displayAdvancedControls = checkboxDisplayAdvancedControls.checked;
                if (!settings.displayAdvancedControls) {
                    divControls.querySelectorAll('.backup-advanced').forEach(el => { el.classList.add('backup-hidden' + classAppendNewFreshSpace) });
                } else {
                    divControls.querySelectorAll('.backup-advanced').forEach(el => { el.classList.remove('backup-hidden' + classAppendNewFreshSpace) });
                }
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelDisplayAdvancedControls.insertAdjacentElement('afterbegin', checkboxDisplayAdvancedControls);

        const divButtonExportBackup = document.createElement('div');
        divButtonExportBackup.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divButtonExportBackup.setAttribute('title',
            '脚本内已有的备份数据会与导入的备份数据合并在一起。\n' +
            '请不要随意修改导出的备份数据文件中的内容, 否则导入时可能会出错。');
        divControls.appendChild(divButtonExportBackup);

        const buttonExportBackup = document.createElement('button');
        buttonExportBackup.type = 'button';
        buttonExportBackup.classList.add('backup-button' + classAppendNewFreshSpace);
        buttonExportBackup.textContent = '导出本地备份数据';
        buttonExportBackup.addEventListener('click', () => {
            try {
                // const BVs = GM_listValues();
                // const backupsToExport = {};
                // BVs.forEach(BV => {
                //     backupsToExport[BV] = GM_getValue(BV, null);
                // });

                const backupsToExport = GM_getValues(GM_listValues().sort());
                delete backupsToExport.settings;
                const currentTs = getCurrentTs();

                // const backupData = JSON.stringify(backupToExport);
                const backupsData = JSON.stringify(backupsToExport, null, 4);
                const blob = new Blob([backupsData], { type: 'application/json' });
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = `${formatTsYYMMDD_HHMMSS(currentTs)}.json`;
                link.click();
                URL.revokeObjectURL(link.href);

                if (settings.exportBackupWithoutTsFrom) {
                    removeTsFromInBackup(backupsToExport);
                    // const backupData = JSON.stringify(backupToExport);
                    const backupsData = JSON.stringify(backupsToExport, null, 4);
                    const blob = new Blob([backupsData], { type: 'application/json' });
                    const link = document.createElement('a');
                    link.href = URL.createObjectURL(blob);
                    link.download = `${formatTsYYMMDD_HHMMSS(currentTs)}_without_ts_from.json`;
                    link.click();
                    URL.revokeObjectURL(link.href);
                }

            } catch (error) {
                catchUnknownError(error);
            }
        });
        divButtonExportBackup.appendChild(buttonExportBackup);

        const divLabelExportBackupWithoutTsFrom = document.createElement('div');
        divLabelExportBackupWithoutTsFrom.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divLabelExportBackupWithoutTsFrom.setAttribute('title',
            '默认: 关闭\n' +
            '本地备份数据中各个信息的ts代表上一次从B站接口获取到该信息的时间戳或第三方网站备份该信息的时间戳, from代表该信息的获取来源。\n' +
            '如果您想要对比两个备份数据文件内容的差异, 对比两个文件去除ts和from的副本会更容易一些。\n' +
            '导入本地备份数据时请导入原始文件, 不要导入去除ts和from的副本。');
        divControls.appendChild(divLabelExportBackupWithoutTsFrom);

        const labelExportBackupWithoutTsFrom = document.createElement('label');
        labelExportBackupWithoutTsFrom.classList.add('backup-label' + classAppendNewFreshSpace);
        labelExportBackupWithoutTsFrom.textContent = '同时导出去除ts和from的副本';
        divLabelExportBackupWithoutTsFrom.appendChild(labelExportBackupWithoutTsFrom);

        const checkboxExportBackupWithoutTsFrom = document.createElement('input');
        checkboxExportBackupWithoutTsFrom.type = 'checkbox';
        checkboxExportBackupWithoutTsFrom.classList.add('backup-inputCheckbox' + classAppendNewFreshSpace);
        checkboxExportBackupWithoutTsFrom.checked = settings.exportBackupWithoutTsFrom;
        checkboxExportBackupWithoutTsFrom.addEventListener('change', () => {
            try {
                settings.exportBackupWithoutTsFrom = checkboxExportBackupWithoutTsFrom.checked;
                GM_setValue('settings', settings);
            } catch (error) {
                catchUnknownError(error);
            }
        });
        labelExportBackupWithoutTsFrom.insertAdjacentElement('afterbegin', checkboxExportBackupWithoutTsFrom);

        const divButtonImportBackup = document.createElement('div');
        divButtonImportBackup.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divButtonImportBackup.setAttribute('title',
            '脚本内已有的备份数据会与导入的备份数据合并在一起。\n' +
            '请不要随意修改导出的备份数据文件中的内容, 否则导入时可能会出错。');
        divControls.appendChild(divButtonImportBackup);

        const buttonImportBackup = document.createElement('button');
        buttonImportBackup.type = 'button';
        buttonImportBackup.classList.add('backup-button' + classAppendNewFreshSpace);
        buttonImportBackup.textContent = '导入本地备份数据';
        divButtonImportBackup.appendChild(buttonImportBackup);

        const divInputFile = document.createElement('div');
        divInputFile.style.display = 'none';
        divControls.appendChild(divInputFile);

        const inputFile = document.createElement('input');
        inputFile.type = 'file';
        inputFile.accept = '.json';
        divInputFile.appendChild(inputFile);

        buttonImportBackup.addEventListener('click', () => {
            try {
                inputFile.click();
            } catch (error) {
                catchUnknownError(error);
            }
        });

        inputFile.addEventListener('change', (event) => {
            try {
                const file = event.target.files[0];
                if (!file) {
                    return;
                }

                const reader = new FileReader();
                reader.onload = (e) => {
                    try {
                        const backupsToImport = JSON.parse(e.target.result);
                        if (typeof backupsToImport !== 'object') {
                            addMessage('文件内容有误, 无法导入', false, 'red');
                            return;
                        }

                        for (const BVOfBackupToImport in backupsToImport) {
                            const backupToImport = backupsToImport[BVOfBackupToImport];

                            try {

                                formatBackup(backupToImport, false, undefined);

                                const backup = GM_getValue(BVOfBackupToImport, {
                                    BV: null,
                                    AV: null,
                                    title: null,
                                    intro: null,
                                    cover: null,
                                    upperUID: null,
                                    upperName: null,
                                    upperAvatar: null,
                                    timeUpload: null,
                                    timePublish: null,
                                    timeFavorite: null,
                                    dynamic: null,
                                    pages: null,
                                    api: null,
                                    apiExtra: null,
                                    biliplus: null,
                                    biliplusExtra: null,
                                    jijidown: null,
                                    jijidownExtra: null,
                                    xbeibeix: null,
                                });

                                formatBackup(backup, false, undefined);

                                if (backupToImport.AV) {
                                    backup.AV = backupToImport.AV;
                                }
                                if (backupToImport.BV) {
                                    backup.BV = backupToImport.BV;
                                }
                                if (backupToImport.title) {
                                    backupToImport.title.forEach(el => {
                                        updateArrayDataInBackup(backup, 'title', el.value, el.ts, el.from);
                                    });
                                }
                                if (backupToImport.intro) {
                                    backupToImport.intro.forEach(el => {
                                        updateArrayDataInBackup(backup, 'intro', el.value, el.ts, el.from);
                                    });
                                }
                                if (backupToImport.cover) {
                                    backupToImport.cover.forEach(el => {
                                        updateArrayDataInBackup(backup, 'cover', el.value, el.ts, el.from);
                                    });
                                }
                                if (backupToImport.upperUID) {
                                    backup.upperUID = backupToImport.upperUID;
                                }
                                if (backupToImport.upperName) {
                                    backupToImport.upperName.forEach(el => {
                                        updateArrayDataInBackup(backup, 'upperName', el.value, el.ts, el.from);
                                    });
                                }
                                if (backupToImport.upperAvatar) {
                                    backupToImport.upperAvatar.forEach(el => {
                                        updateArrayDataInBackup(backup, 'upperAvatar', el.value, el.ts, el.from);
                                    });
                                }
                                if (backupToImport.timeUpload) {
                                    backup.timeUpload = backupToImport.timeUpload;
                                }
                                if (backupToImport.timePublish) {
                                    backup.timePublish = backupToImport.timePublish;
                                }
                                if (backupToImport.timeFavorite) {
                                    backupToImport.timeFavorite.forEach(el => {
                                        updateTimeFavoriteInBackup(backup, el.value, el.fid);
                                    });
                                }
                                if (backupToImport.dynamic) {
                                    backupToImport.dynamic.forEach(el => {
                                        updateArrayDataInBackup(backup, 'dynamic', el.value, el.ts, el.from);
                                    });
                                }
                                if (backupToImport.pages) {
                                    backupToImport.pages.forEach(el => {
                                        updatePagesInBackup(backup, el.index, el.title, el.firstFrame, el.thumbnails, el.cid, el.ts, el.from);
                                    });
                                }
                                ['api', 'apiExtra', 'biliplus', 'biliplusExtra', 'jijidown', 'jijidownExtra', 'xbeibeix'].forEach(key => {
                                    if (backupToImport[key]) {
                                        if (!backup[key]) {
                                            backup[key] = { value: backupToImport[key].value, ts: backupToImport[key].ts };
                                        } else {
                                            if (backup[key].ts < backupToImport[key].ts) {
                                                backup[key].value = backupToImport[key].value;
                                                backup[key].ts = backupToImport[key].ts;
                                            }
                                        }
                                    }
                                });
                                const sortedBackup = {};
                                for (const sortedKey of sortedKeys) {
                                    sortedBackup[sortedKey] = backup[sortedKey];
                                }
                                GM_setValue(BVOfBackupToImport, sortedBackup);

                            } catch (error) {
                                addMessage('导入该视频失败', false, 'red');
                                addMessage(`BV号: ${BVOfBackupToImport}`, true);
                                if (error instanceof Error) {
                                    addMessage(error.stack, true);
                                    console.error(error);
                                } else {
                                    addMessage(error[0], true);
                                    for (let i = 1; i < error.length; i++) {
                                        addMessage(error[i], true);
                                    }
                                }
                                console.error('需要导入的数据');
                                console.error(backupToImport);
                                console.error('本地已有的数据');
                                console.error(GM_getValue(BVOfBackupToImport, {}));
                            }
                        }

                        addMessage('导入完成', false, 'green');

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

                reader.readAsText(file);

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

        const divButtonGetFromApiLegacy = document.createElement('div');
        divButtonGetFromApiLegacy.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
        divButtonGetFromApiLegacy.setAttribute('title',
            '地址: https://api.bilibili.com/medialist/gateway/base/spaceDetail?media_id={收藏夹fid}&pn={页码}&ps={每页展示视频数量}\n' +
            '数据: AV号, 标题 (失效视频无法获取), 简介 (仅能获取前255个字符), 封面地址 (失效视频无法获取), UP主UID, UP主昵称, UP主头像地址, 上传时间, 发布时间, 添加到当前收藏夹的时间, 每个分集的标题, cid (均为最新版本)\n' +
            '此功能将会从旧版B站接口获取当前收藏夹内所有视频的数据。与目前的B站接口相比, 旧版接口可以获取到每个分集的标题和cid, 无论视频是否失效。\n' +
            '您需要将当前收藏夹设置为公开后才能获取到数据。如果收藏夹内第一个视频不是失效视频, 修改可见性会导致收藏夹的封面被固定为该视频的封面, 建议修改可见性之前先复制一个失效视频到当前收藏夹的首位。');
        divControls.appendChild(divButtonGetFromApiLegacy);

        const buttonGetFromApiLegacy = document.createElement('button');
        buttonGetFromApiLegacy.type = 'button';
        buttonGetFromApiLegacy.classList.add('backup-button' + classAppendNewFreshSpace);
        buttonGetFromApiLegacy.textContent = '从旧版B站接口获取数据';
        buttonGetFromApiLegacy.addEventListener('click', async () => {
            try {
                clearMessage();

                let currentFavlist;
                if (newFreshSpace) {
                    currentFavlist = document.querySelector('div.vui_sidebar-item--active');
                    if (!document.querySelector('div.fav-collapse').contains(currentFavlist)) {
                        throw ['不支持处理特殊收藏夹'];
                    }
                } else {
                    currentFavlist = document.querySelector('.fav-item.cur');
                    if (!document.querySelector('div.nav-container').contains(currentFavlist)) {
                        throw ['不支持处理特殊收藏夹'];
                    }
                }

                let fid;
                if (newFreshSpace) {
                    const getFidFromURLMatch = location.href.match(getFidFromURLRegex);
                    if (getFidFromURLMatch) {
                        fid = parseInt(getFidFromURLMatch[1], 10);
                    } else if (settings.defaultFavlistFid) {
                        fid = settings.defaultFavlistFid;
                    } else {
                        throw ['无法获取当前收藏夹的fid, 刷新页面可能有帮助'];
                    }

                } else {
                    fid = parseInt(currentFavlist.getAttribute('fid'), 10);
                }

                let pageNumber = 1;
                let count = 0;
                while (true) {
                    const response = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: `https://api.bilibili.com/medialist/gateway/base/spaceDetail?media_id=${fid}&pn=${pageNumber}&ps=20&keyword=&order=mtime&type=0&tid=0&jsonp=jsonp`,
                            timeout: 1000 * settings.requestTimeout,
                            responseType: 'json',
                            onload: (res) => resolve(res),
                            onerror: (res) => reject(['请求失败', 'api.bilibili.com/medialist/gateway/base/spaceDetail', res.error]),
                            ontimeout: () => reject(['请求超时', 'api.bilibili.com/medialist/gateway/base/spaceDetail'])
                        });
                    });

                    if (response.response.code === -403 || response.response.code === 7201004) {
                        throw ['不支持处理私密收藏夹'];
                    } else if (response.response.code) {
                        throw ['发生未知错误, 请反馈该问题', JSON.stringify(response.response)];
                    }

                    if (!response.response.data.medias) {
                        addMessage('已完成', false, 'green');
                        return;
                    }

                    response.response.data.medias.forEach(media => {
                        const backup = GM_getValue(media.bvid, {
                            BV: null,
                            AV: null,
                            title: null,
                            intro: null,
                            cover: null,
                            upperUID: null,
                            upperName: null,
                            upperAvatar: null,
                            timeUpload: null,
                            timePublish: null,
                            timeFavorite: null,
                            dynamic: null,
                            pages: null,
                            api: null,
                            apiExtra: null,
                            biliplus: null,
                            biliplusExtra: null,
                            jijidown: null,
                            jijidownExtra: null,
                            xbeibeix: null,
                        });

                        formatBackup(backup, false, undefined);

                        let disabled = false;
                        if (media.cover.includes('be27fd62c99036dce67efface486fb0a88ffed06')) {
                            disabled = true;
                        }

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

                        if (!disabled) {
                            updateArrayDataInBackup(backup, 'title', media.title, getCurrentTs(), 'api.bilibili.com/medialist/gateway/base/spaceDetail');
                            updateArrayDataInBackup(backup, 'cover', media.cover, getCurrentTs(), 'api.bilibili.com/medialist/gateway/base/spaceDetail');
                        }
                        updateArrayDataInBackup(backup, 'intro', media.intro, getCurrentTs(), 'api.bilibili.com/medialist/gateway/base/spaceDetail');

                        if (!backup.upperUID) {
                            backup.upperUID = media.upper.mid;
                        }
                        updateArrayDataInBackup(backup, 'upperName', media.upper.name, getCurrentTs(), 'api.bilibili.com/medialist/gateway/base/spaceDetail');
                        updateArrayDataInBackup(backup, 'upperAvatar', media.upper.face, getCurrentTs(), 'api.bilibili.com/medialist/gateway/base/spaceDetail');

                        if (!backup.timeUpload) {
                            backup.timeUpload = media.ctime;
                        }
                        if (!backup.timePublish) {
                            backup.timePublish = media.pubtime;
                        }
                        updateTimeFavoriteInBackup(backup, media.fav_time, fid);

                        if (Array.isArray(media.pages)) {
                            media.pages.forEach(el => {
                                updatePagesInBackup(backup, el.page, el.title, undefined, undefined, el.id, getCurrentTs(), 'api.bilibili.com/medialist/gateway/base/spaceDetail');
                            });
                        } else {
                            if (debug) {
                                addMessage('getFromApiLegacy 无法获取pages', false, 'red');
                                addMessage(media.bvid, true);
                                console.warn('getFromApiLegacy 无法获取pages');
                                console.warn(media);
                            }
                        }

                        const sortedBackup = {};
                        for (const sortedKey of sortedKeys) {
                            sortedBackup[sortedKey] = backup[sortedKey];
                        }
                        GM_setValue(media.bvid, sortedBackup);
                        if (debug) {
                            console.log('保存旧版B站接口的数据至本地');
                            console.debug(media.bvid);
                            console.debug(sortedBackup);
                        }
                    });

                    count += response.response.data.medias.length;
                    addMessage(`已处理视频个数: ${count}`, true);
                    pageNumber++;
                }

            } catch (error) {
                if (error instanceof Error) {
                    catchUnknownError(error);
                } else {
                    addMessage(error[0], false, 'red');
                    for (let i = 1; i < error.length; i++) {
                        addMessage(error[i], true);
                    }
                }
            }
        });
        divButtonGetFromApiLegacy.appendChild(buttonGetFromApiLegacy);

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '清空提示信息';
            button.addEventListener('click', () => {
                try {
                    clearMessage();
                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '重置timeFavorite';
            button.addEventListener('click', () => {
                try {
                    const backups = GM_getValues(GM_listValues());
                    delete backups.settings;
                    for (const BVOfBackup in backups) {
                        const backup = backups[BVOfBackup];
                        backup.timeFavorite = null;
                        GM_setValue(BVOfBackup, backup);
                    }
                    addMessage('已完成', false, 'green');

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '删除timeFavorite为null';
            button.addEventListener('click', () => {
                try {
                    const backups = GM_getValues(GM_listValues());
                    delete backups.settings;
                    for (const BVOfBackup in backups) {
                        if (!backups[BVOfBackup].timeFavorite) {
                            GM_deleteValue(BVOfBackup);
                        }
                    }
                    addMessage('已完成', false, 'green');

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '保留timeFavorite为null';
            button.addEventListener('click', () => {
                try {
                    const backups = GM_getValues(GM_listValues());
                    delete backups.settings;
                    for (const BVOfBackup in backups) {
                        if (backups[BVOfBackup].timeFavorite) {
                            GM_deleteValue(BVOfBackup);
                        }
                    }
                    addMessage('已完成', false, 'green');

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '删除指定BV';
            button.addEventListener('click', () => {
                try {
                    const BVs = [
                        "BV17x411w7KC",
                    ];
                    const backups = GM_getValues(GM_listValues());
                    delete backups.settings;
                    for (const BVOfBackup in backups) {
                        if (BVs.includes(BVOfBackup)) {
                            GM_deleteValue(BVOfBackup);
                        }
                    }
                    addMessage('已完成', false, 'green');

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '保留指定BV';
            button.addEventListener('click', () => {
                try {
                    const BVs = [
                        "BV17x411w7KC",
                    ];
                    const backups = GM_getValues(GM_listValues());
                    delete backups.settings;
                    for (const BVOfBackup in backups) {
                        if (!BVs.includes(BVOfBackup)) {
                            GM_deleteValue(BVOfBackup);
                        }
                    }
                    addMessage('已完成', false, 'green');

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        // @grant             GM_cookie
        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '批量移动视频';
            button.addEventListener('click', () => {
                try {
                    GM_cookie.list({ name: 'bili_jct' }, async (cookies, error) => {
                        if (error) {
                            throw ['无法读取cookie, 更新Tampermonkey可能有帮助'];
                        }

                        try {
                            let currentFavlist;
                            if (newFreshSpace) {
                                currentFavlist = document.querySelector('div.vui_sidebar-item--active');
                                if (!document.querySelector('div.fav-collapse').contains(currentFavlist)) {
                                    throw ['不支持处理特殊收藏夹'];
                                }
                            } else {
                                currentFavlist = document.querySelector('.fav-item.cur');
                                if (!document.querySelector('div.nav-container').contains(currentFavlist)) {
                                    throw ['不支持处理特殊收藏夹'];
                                }
                            }

                            let fid;
                            if (newFreshSpace) {
                                const getFidFromURLMatch = location.href.match(getFidFromURLRegex);
                                if (getFidFromURLMatch) {
                                    fid = parseInt(getFidFromURLMatch[1], 10);
                                } else if (settings.defaultFavlistFid) {
                                    fid = settings.defaultFavlistFid;
                                } else {
                                    throw ['无法获取当前收藏夹的fid, 刷新页面可能有帮助'];
                                }

                            } else {
                                fid = parseInt(currentFavlist.getAttribute('fid'), 10);
                            }

                            let searchKeyword = '';
                            const inputKeyword = document.querySelector(newFreshSpace ? 'input.fav-list-header-filter__search' : 'input.search-fav-input');
                            if (inputKeyword) {
                                searchKeyword = inputKeyword.value;
                            }

                            const fidSource = fid;
                            const fidTarget = parseInt(searchKeyword, 10);
                            const order = 'pubtime';

                            let pageNumber = 1;
                            const BVsFavlistVisable = [];
                            while (true) {
                                const response = await new Promise((resolve, reject) => {
                                    GM_xmlhttpRequest({
                                        method: 'GET',
                                        url: `https://api.bilibili.com/x/v3/fav/resource/list?media_id=${fidSource}&pn=${pageNumber}&ps=40&keyword=&order=${order}&type=0&tid=0&platform=web&web_location=333.1387`,
                                        timeout: 1000 * settings.requestTimeout,
                                        responseType: 'json',
                                        onload: (res) => resolve(res),
                                        onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/v3/fav/resource/list', res.error]),
                                        ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/v3/fav/resource/list'])
                                    });
                                });
                                if (response.response.data.medias) {
                                    BVsFavlistVisable.push(...response.response.data.medias.filter(el => el.type === 2).map(el => `${el.id}:2`));
                                }
                                addMessage(`已获取可见视频个数: ${BVsFavlistVisable.length}`, true);
                                if (!response.response.data.has_more) {
                                    break;
                                }
                                pageNumber++;
                            }

                            BVsFavlistVisable.reverse();

                            let start = 0;
                            while (true) {
                                const sl = BVsFavlistVisable.slice(start, start + 40);
                                if (!sl.length) {
                                    addMessage('已完成', false, 'green');
                                    break;
                                }
                                const csrf = cookies[0].value;
                                const data = `resources=${sl.join()}&src_media_id=${fidSource}&tar_media_id=${fidTarget}&mid=${settings.defaultUID}&platform=web&csrf=${csrf}`;
                                const response2 = await new Promise((resolve, reject) => {
                                    GM_xmlhttpRequest({
                                        method: 'POST',
                                        url: 'https://api.bilibili.com/x/v3/fav/resource/move',
                                        data: data,
                                        timeout: 1000 * settings.requestTimeout,
                                        headers: {
                                            'Content-Length': data.length,
                                            'Content-Type': 'application/x-www-form-urlencoded'
                                        },
                                        onload: (res) => resolve(res),
                                        onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/v3/fav/resource/move', res.error]),
                                        ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/v3/fav/resource/move'])
                                    });
                                });
                                addMessage('B站接口响应内容:');
                                addMessage(response2.response, true);

                                start += 40;

                                await delay(1000);

                            }

                        } catch (error) {
                            if (error instanceof Error) {
                                catchUnknownError(error);
                            } else {
                                addMessage(error[0], false, 'red');
                                for (let i = 1; i < error.length; i++) {
                                    addMessage(error[i], true);
                                }
                            }
                        }
                    });

                } catch (error) {
                    catchUnknownError(error);
                }
            });
            divButton.appendChild(button);
        }

        if (debug) {
            const divButton = document.createElement('div');
            divButton.classList.add('backup-div-first' + classAppendNewFreshSpace, 'backup-advanced');
            divControls.appendChild(divButton);

            const button = document.createElement('button');
            button.type = 'button';
            button.classList.add('backup-button' + classAppendNewFreshSpace);
            button.textContent = '手动获取数据';
            button.addEventListener('click', async () => {
                try {
                    const videos = [
                        {
                            "AV": 170001,
                            "BV": "BV17x411w7KC"
                        },
                    ];

                    for (const video of videos) {

                        const AVBVTitle = {
                            AV: null,
                            BV: null,
                            title: null
                        };

                        try {
                            AVBVTitle.AV = video.AV;
                            AVBVTitle.BV = video.BV;
                            AVBVTitle.title = '';

                            const spanX = document.createElement('span');
                            const spanJ = document.createElement('span');
                            const spanB = document.createElement('span');
                            const spanA = document.createElement('span');

                            const backup = GM_getValue(AVBVTitle.BV, {
                                BV: null,
                                AV: null,
                                title: null,
                                intro: null,
                                cover: null,
                                upperUID: null,
                                upperName: null,
                                upperAvatar: null,
                                timeUpload: null,
                                timePublish: null,
                                timeFavorite: null,
                                dynamic: null,
                                pages: null,
                                api: null,
                                apiExtra: null,
                                biliplus: null,
                                biliplusExtra: null,
                                jijidown: null,
                                jijidownExtra: null,
                                xbeibeix: null,
                            });

                            const functions = [];

                            functions.push(getFromJijidown(AVBVTitle, backup, spanJ));
                            functions.push(getFromXbeibeix(AVBVTitle, backup, spanX));
                            functions.push(getFromBiliplus(AVBVTitle, backup, spanB));
                            await Promise.all(functions);

                        } catch (error) {
                            if (error instanceof Error) {
                                if (error.name === 'AbortError') {
                                    throw error;
                                }

                                addMessage('发生未知错误, 请反馈该问题', false, 'red');
                                addMessageAVBVTitle(AVBVTitle);
                                addMessage(error.stack, true);

                                consoleAVBVTitle('error', AVBVTitle);
                                console.error(error);

                            } else {
                                addMessage(error[0], false, 'red');
                                for (let i = 1; i < error.length; i++) {
                                    addMessage(error[i], true);
                                }
                                addMessageAVBVTitle(AVBVTitle);
                            }
                        }
                    }
                    addMessage('已完成', false, 'green');

                } catch (error) {
                    if (error instanceof Error) {
                        if (error.name === 'AbortError') {
                            return;
                        }
                        catchUnknownError(error);

                    } else {
                        addMessage(error[0], false, 'red');
                        for (let i = 1; i < error.length; i++) {
                            addMessage(error[i], true);
                        }
                    }
                }
            });
            divButton.appendChild(button);
        }

        const divButtonStopProcessing = document.createElement('div');
        divButtonStopProcessing.classList.add('backup-div-first' + classAppendNewFreshSpace);
        divButtonStopProcessing.setAttribute('title',
            '此功能适用于脚本处理视频时反复出现问题的情况。');
        divControls.appendChild(divButtonStopProcessing);

        const buttonStopProcessing = document.createElement('button');
        buttonStopProcessing.type = 'button';
        buttonStopProcessing.classList.add('backup-button' + classAppendNewFreshSpace);
        buttonStopProcessing.textContent = '停止处理当前页视频';
        buttonStopProcessing.addEventListener('click', () => {
            try {
                abortActiveControllers();
            } catch (error) {
                catchUnknownError(error);
            }
        });
        divButtonStopProcessing.appendChild(buttonStopProcessing);

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

        // if (displayUpdate) {
        //     setTimeout(() => {
        //         addMessage(updates);
        //     }, 300);
        // }

        if (settings.processNormal || settings.processDisabled) {
            divLabelGetFromApi.classList.remove('backup-disabled' + classAppendNewFreshSpace);
            if (settings.getFromApi) {
                divLabelGetFromApiUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                if (settings.getFromApiUpdate) {
                    divLabelGetFromApiUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                divLabelGetFromApiExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                if (settings.getFromApiExtra) {
                    divLabelGetFromApiExtraThumbnails.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraDelay.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    divLabelGetFromApiExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromApiExtraUpdate) {
                        divLabelGetFromApiExtraUpdatePart.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        divLabelGetFromApiExtraUpdateAll.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromApiExtraUpdateAll) {
                            divLabelGetFromApiExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                    }
                }
            }
            divLabelGetFromBiliplus.classList.remove('backup-disabled' + classAppendNewFreshSpace);
            if (settings.getFromBiliplus) {
                divLabelGetFromBiliplusUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                if (settings.getFromBiliplusUpdate) {
                    divLabelGetFromBiliplusUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                if (settings.getFromApi) {
                    divLabelGetFromBiliplusExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromBiliplusExtra) {
                        divLabelGetFromBiliplusExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        if (settings.getFromBiliplusExtraUpdate) {
                            divLabelGetFromBiliplusExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                        }
                    }
                }
            }
            divLabelGetFromJijidown.classList.remove('backup-disabled' + classAppendNewFreshSpace);
            if (settings.getFromJijidown) {
                divLabelGetFromJijidownUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                if (settings.getFromJijidownUpdate) {
                    divLabelGetFromJijidownUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                divLabelGetFromJijidownExtra.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                if (settings.getFromJijidownExtra) {
                    divLabelGetFromJijidownExtraUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    if (settings.getFromJijidownExtraUpdate) {
                        divLabelGetFromJijidownExtraUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                    }
                }
                divLabelGetFromJijidownURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                divLabelGetFromJijidownURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
            }
            divLabelGetFromXbeibeix.classList.remove('backup-disabled' + classAppendNewFreshSpace);
            if (settings.getFromXbeibeix) {
                divLabelGetFromXbeibeixUpdate.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                if (settings.getFromXbeibeixUpdate) {
                    divLabelGetFromXbeibeixUpdateInterval.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                }
                divLabelGetFromXbeibeixURL1.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                divLabelGetFromXbeibeixURL2.classList.remove('backup-disabled' + classAppendNewFreshSpace);
                divLabelGetFromXbeibeixURL3.classList.remove('backup-disabled' + classAppendNewFreshSpace);
            }
        }

        if (!settings.displayAdvancedControls) {
            divControls.querySelectorAll('.backup-advanced').forEach(el => {
                el.classList.add('backup-hidden' + classAppendNewFreshSpace);
            });
        }
    }

    function appendDropdowns(dropdownContainer, BV) {

        try {
            if (newFreshSpace) {
                if (dropdownContainer.childElementCount === 1) {
                    return;
                }

                const biliCardDropdownVisible = document.querySelectorAll('div.bili-card-dropdown--visible');
                if (biliCardDropdownVisible.length !== 1) {
                    addMessage('下拉列表开关不存在或不唯一, 无法确定下拉列表所对应的视频, 刷新页面可能有帮助', false, 'red');
                    return;
                }

                let divTargetVideo;
                try {
                    divTargetVideo = document.querySelector('div.items__item:has(div.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('无法确定下拉列表所对应的视频, 请反馈该问题', false, 'red');
                    return;
                }

                if (divTargetVideo.querySelector('div.bili-cover-card__tags')) {
                    if (debug) console.log('不处理特殊视频');
                    return;
                }

                if (!settings.processNormal && divTargetVideo.querySelector('div.bili-cover-card__stats')) {
                    return;
                }
                if (!settings.processDisabled && !divTargetVideo.querySelector('div.bili-cover-card__stats')) {
                    return;
                }

                const getBVFromURLMatch = biliCardDropdownVisible[0].parentNode.querySelector('a').getAttribute('href').match(getBVFromURLRegex);
                if (!getBVFromURLMatch) {
                    addMessage('无法获取该视频的BV号, 请检查是否有其他脚本或插件修改了该视频封面和标题的链接地址, 并将其关闭', false, 'red');
                    return;
                }
                BV = getBVFromURLMatch[1];

            } else {
                if (dropdownContainer.lastElementChild.classList.contains('backup')) {
                    return;
                }
                if (settings.appendDropdownCover || settings.appendDropdownLocal || settings.appendDropdownJump || settings.appendDropdownReset) {
                    dropdownContainer.lastElementChild.classList.add('be-dropdown-item-delimiter');
                }
            }

            const backup = GM_getValue(BV, {});

            if (settings.appendDropdownCover && 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.setAttribute('title',
                    '查看该视频的本地备份数据中所有版本的封面原图, 从新到旧');
                dropdownCover.textContent = '封面原图';
                dropdownCover.addEventListener('click', () => {
                    try {
                        if (newFreshSpace) {
                            dropdownContainer.classList.remove('visible');
                        }
                        GM_openInTab(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(backup.cover[i].value, { insert: false, setParent: true });
                        }

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

            if (settings.appendDropdownLocal) {
                const dropdownLocal = document.createElement(newFreshSpace ? 'div' : 'li');
                dropdownLocal.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
                if (!newFreshSpace) {
                    dropdownLocal.classList.add('backup');
                }
                dropdownLocal.setAttribute('title',
                    '查看该视频的本地备份数据');
                dropdownLocal.textContent = '本地备份数据';
                dropdownLocal.addEventListener('click', () => {
                    try {
                        if (newFreshSpace) {
                            dropdownContainer.classList.remove('visible');
                        }
                        const data = GM_getValue(BV, {});
                        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);
            }

            if (settings.appendDropdownJump) {
                const dropdownJump = document.createElement(newFreshSpace ? 'div' : 'li');
                dropdownJump.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
                if (!newFreshSpace) {
                    dropdownJump.classList.add('backup');
                }
                dropdownJump.setAttribute('title',
                    '跳转至该视频在各个第三方网站的原始页面');
                dropdownJump.textContent = '跳转至BJX';
                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://${settings.getFromJijidownURL}/video/${BV}`, { insert: false, setParent: true });
                        GM_openInTab(`https://${settings.getFromXbeibeixURL}/video/${BV}`, { insert: false, setParent: true });

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

            if (settings.appendDropdownReset) {
                const dropdownReset = document.createElement(newFreshSpace ? 'div' : 'li');
                dropdownReset.classList.add(newFreshSpace ? 'bili-card-dropdown-popper__item' : 'be-dropdown-item');
                if (!newFreshSpace) {
                    dropdownReset.classList.add('backup');
                }
                dropdownReset.setAttribute('title',
                    '如果该视频的本地备份数据出现错乱, 请使用此功能将其删除以便重新备份该视频。\n' +
                    '如果您想删除脚本内所有已保存的数据, 请依次点击: Tampermonkey > 管理面板 >\n' +
                    '哔哩哔哩(B站|Bilibili)收藏夹Fix (备份视频信息) > 开发者 > 重置到出厂。');
                dropdownReset.textContent = '重置备份数据';
                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 getFromApi(AVBVTitle, backup, spanA, apiDetails, fid, pageNumber, disabled, spanFavTime, searchType, getFromApiExtraNeeded) {

        if (!apiDetails.value) {
            const urlWithParams = await appendParamsForGetFromApi(fid, pageNumber, pageSize);
            const response = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: urlWithParams,
                    timeout: 1000 * settings.requestTimeout,
                    responseType: 'json',
                    onload: (res) => resolve(res),
                    onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/v3/fav/resource/list', res.error]),
                    ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/v3/fav/resource/list'])
                });
            });
            if (response.status !== 200) {
                throw ['请求失败', 'api.bilibili.com/x/v3/fav/resource/list', `${response.status} ${response.statusText}`];
            }

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

        const apiDetail = apiDetails.value.find(el => el.bvid === AVBVTitle.BV);
        if (!apiDetail) {
            if (debug) {
                addMessage('getFromApi 从B站接口获取的数据中没有该视频', false, 'red');
                addMessage(AVBVTitle.BV, true);
                console.warn('getFromApi 从B站接口获取的数据中没有该视频');
                console.warn(AVBVTitle.BV);
            }
            return true;
        }

        if (!AVBVTitle.AV) {
            AVBVTitle.AV = apiDetail.id;
            backup.AV = apiDetail.id;
        }

        if (debug) {
            console.log('从B站接口获取数据');
            consoleAVBVTitle('debug', AVBVTitle);
            console.debug(apiDetail);
        }

        if (!disabled) {
            updateArrayDataInBackup(backup, 'title', apiDetail.title, getCurrentTs(), 'api.bilibili.com/x/v3/fav/resource/list');
            updateArrayDataInBackup(backup, 'cover', apiDetail.cover, getCurrentTs(), 'api.bilibili.com/x/v3/fav/resource/list');
        } else {
            if (debug) {
                if (apiDetail.title !== '已失效视频') {
                    addMessage('失效视频标题已更改', false, 'red');
                    console.warn('失效视频标题已更改');
                }
                if (!apiDetail.cover.includes('be27fd62c99036dce67efface486fb0a88ffed06')) {
                    addMessage('失效视频封面已更改', false, 'red');
                    console.warn('失效视频封面已更改');
                }
            }
        }
        updateArrayDataInBackup(backup, 'intro', apiDetail.intro, getCurrentTs(), 'api.bilibili.com/x/v3/fav/resource/list');

        if (!backup.upperUID) {
            backup.upperUID = apiDetail.upper.mid;
        }
        updateArrayDataInBackup(backup, 'upperName', apiDetail.upper.name, getCurrentTs(), 'api.bilibili.com/x/v3/fav/resource/list');
        updateArrayDataInBackup(backup, 'upperAvatar', apiDetail.upper.face, getCurrentTs(), 'api.bilibili.com/x/v3/fav/resource/list');

        backup.timeUpload = apiDetail.ctime;
        backup.timePublish = apiDetail.pubtime;
        if (!searchType) {
            updateTimeFavoriteInBackup(backup, apiDetail.fav_time, fid);
        }

        if (apiDetail.ugc && apiDetail.ugc.first_cid) {
            if (updatePagesInBackup(backup, 1, undefined, undefined, undefined, apiDetail.ugc.first_cid, getCurrentTs(), 'api.bilibili.com/x/v3/fav/resource/list')) {
                getFromApiExtraNeeded.value = true;
            }
        } else if (!disabled) {
            if (debug) {
                addMessage('getFromApi 无first_cid', false, 'red');
                addMessage(AVBVTitle.BV, true);
                console.warn('getFromApi 无first_cid');
                console.warn(AVBVTitle.BV);
            }
        }

        if (apiDetail.page) {
            if (apiDetail.page > 1) {
                getFromApiExtraNeeded.value = true;
            }
        } else if (!disabled) {
            if (debug) {
                addMessage('getFromApi 无page', false, 'red');
                addMessage(AVBVTitle.BV, true);
                console.warn('getFromApi 无page');
                console.warn(AVBVTitle.BV);
            }
        }

        if (!disabled && apiDetail.intro.length >= 255) {
            let getIntroNeeded = true;
            if (settings.getFromApiExtra) {
                if (!backup.apiExtra) {
                    getIntroNeeded = false;
                } else if (settings.getFromApiExtraUpdate && settings.getFromApiExtraUpdateAll && getCurrentTs() - backup.apiExtra.ts > 3600 * 24 * settings.getFromApiExtraUpdateInterval) {
                    getIntroNeeded = false;
                } else if (settings.getFromApiExtraUpdate && !settings.getFromApiExtraUpdateAll && getFromApiExtraNeeded.value) {
                    getIntroNeeded = false;
                }
            }

            if (getIntroNeeded) {
                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://api.bilibili.com/x/web-interface/archive/desc?bvid=${AVBVTitle.BV}`,
                        timeout: 1000 * settings.requestTimeout,
                        responseType: 'json',
                        onload: (res) => resolve(res),
                        onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/web-interface/archive/desc', res.error]),
                        ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/web-interface/archive/desc'])
                    });
                });
                if (response.status !== 200) {
                    throw ['请求失败', 'api.bilibili.com/x/web-interface/archive/desc', `${response.status} ${response.statusText}`];
                }

                updateArrayDataInBackup(backup, 'intro', response.response.data, getCurrentTs(), 'api.bilibili.com/x/web-interface/archive/desc');
            }
        }

        if (newFreshSpace) {
            spanFavTime.textContent = `投稿于:${formatTsTimePublish(1000 * apiDetail.pubtime)}`;
            spanFavTime.setAttribute('title', new Date(1000 * apiDetail.pubtime).toLocaleString());
        } else {
            spanFavTime.textContent = `收藏于:${formatTsTimeFavorite(new Date(1000 * apiDetail.fav_time))}`;
            spanFavTime.setAttribute('title', new Date(1000 * apiDetail.fav_time).toLocaleString());
        }

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

        const sortedBackup = {};
        for (const sortedKey of sortedKeys) {
            sortedBackup[sortedKey] = backup[sortedKey];
        }
        GM_setValue(AVBVTitle.BV, sortedBackup);
        if (debug) {
            console.log('保存B站接口的数据至本地');
            consoleAVBVTitle('debug', AVBVTitle);
            console.debug(sortedBackup);
        }
    }

    async function getFromApiExtra(AVBVTitle, backup, spanA, disabled) {

        if (disabled) {
            backup.apiExtra = { value: false, ts: getCurrentTs() };
            spanA.style.color = '#800000';

        } else {
            if (getFromApiExtraDelay) {
                await delay(500);
            }

            const response = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `https://api.bilibili.com/x/web-interface/view?bvid=${AVBVTitle.BV}`,
                    timeout: 1000 * settings.requestTimeout,
                    responseType: 'json',
                    onload: (res) => resolve(res),
                    onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/web-interface/view', res.error]),
                    ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/web-interface/view'])
                });
            });
            if (response.status !== 200) {
                throw ['请求失败', 'api.bilibili.com/x/web-interface/view', `${response.status} ${response.statusText}`];
            }

            const currentTs = getCurrentTs();

            updateArrayDataInBackup(backup, 'intro', response.response.data.desc, currentTs, 'api.bilibili.com/x/web-interface/view');

            if (response.response.data.desc_v2) {
                let introWithUID = '';
                for (const [index, desc] of response.response.data.desc_v2.entries()) {
                    if (desc.type === 1) {
                        introWithUID = introWithUID + desc.raw_text;
                    } else if (desc.type === 2) {
                        introWithUID = introWithUID + `@${desc.raw_text}{{UID:${desc.biz_id}}}` + (index === response.response.data.desc_v2.length - 1 ? '' : ' ');
                    } else {
                        if (debug) {
                            addMessage(`getFromApiExtra desc_v2未知type: ${desc.type}`, false, 'red');
                            addMessage(AVBVTitle.BV, true);
                            console.warn(`getFromApiExtra desc_v2未知type: ${desc.type}`);
                            console.warn(AVBVTitle.BV);
                        }
                    }
                }
                updateArrayDataInBackup(backup, 'intro', introWithUID, currentTs, 'api.bilibili.com/x/web-interface/view');
            }

            updateArrayDataInBackup(backup, 'dynamic', response.response.data.dynamic, getCurrentTs(), 'api.bilibili.com/x/web-interface/view');

            response.response.data.pages.forEach(el => {
                updatePagesInBackup(backup, el.page, el.part, el.first_frame, undefined, el.cid, getCurrentTs(), 'api.bilibili.com/x/web-interface/view');
            });

            backup.apiExtra = { value: true, ts: getCurrentTs() };
            spanA.style.color = '#008000';
        }

        const sortedBackup = {};
        for (const sortedKey of sortedKeys) {
            sortedBackup[sortedKey] = backup[sortedKey];
        }
        GM_setValue(AVBVTitle.BV, sortedBackup);
        if (debug) {
            console.log('保存B站接口的额外数据至本地');
            consoleAVBVTitle('debug', AVBVTitle);
            console.debug(sortedBackup);
        }
    }

    async function getFromApiExtraThumbnails(AVBVTitle, backup, as, controller) {
        let setValueNeeded = false;
        for (const [index, page] of backup.pages.entries()) {
            if (controller.signal.aborted) {
                throw new DOMException('', 'AbortError');
            }

            if (!page.cid) {
                continue;
            }

            let getThumbnailsNeeded = true;
            if (page.thumbnails) {
                // for (const thumbnail of page.thumbnails) {
                //     if (thumbnail.includes('videoshotpvhdboss')) {
                //         getThumbnailsNeeded = false;
                //         break;
                //     }
                // }
                getThumbnailsNeeded = false;
            }

            if (getThumbnailsNeeded) {
                if (backup.pages.length > 10) {
                    as[1].textContent = `获取缩略图${index + 1}/${backup.pages.length} | ${AVBVTitle.title}`;
                }

                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://api.bilibili.com/x/player/videoshot?aid=${backup.AV}&cid=${page.cid}`,
                        timeout: 1000 * settings.requestTimeout,
                        responseType: 'json',
                        onload: (res) => resolve(res),
                        onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/player/videoshot', res.error]),
                        ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/player/videoshot'])
                    });
                });
                if (response.status !== 200) {
                    throw ['请求失败', 'api.bilibili.com/x/player/videoshot', `${response.status} ${response.statusText}`];
                }

                if (!response.response.data.image || !response.response.data.image.length) {
                    if (debug) {
                        addMessage('getFromApiExtra 无法获取thumbnails', false, 'red');
                        addMessage(JSON.stringify(response.response), true);
                        addMessage(`https://api.bilibili.com/x/player/videoshot?aid=${backup.AV}&cid=${page.cid}`, true);
                        console.warn('getFromApiExtra 无法获取thumbnails');
                        console.warn(response.response.message);
                        console.warn(`https://api.bilibili.com/x/player/videoshot?aid=${backup.AV}&cid=${page.cid}`);
                    }
                    continue;
                }

                response.response.data.image.forEach(el => {
                    updatePagesInBackup(backup, undefined, undefined, undefined, el, page.cid, undefined, undefined);
                });

                setValueNeeded = true;
            }
        }

        if (backup.pages.length > 10) {
            as[1].textContent = AVBVTitle.title;
        }

        if (setValueNeeded) {
            const sortedBackup = {};
            for (const sortedKey of sortedKeys) {
                sortedBackup[sortedKey] = backup[sortedKey];
            }
            GM_setValue(AVBVTitle.BV, sortedBackup);
            if (debug) {
                console.log('保存进度条缩略图地址至本地');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(sortedBackup);
            }
        }
    }

    async function getFromBiliplus(AVBVTitle, backup, spanB) {

        const response = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://www.biliplus.com/video/${AVBVTitle.BV}`,
                timeout: 1000 * settings.requestTimeout,
                onload: (res) => resolve(res),
                onerror: (res) => reject(['请求失败', 'www.biliplus.com/video', res.error]),
                ontimeout: () => reject(['请求超时', 'www.biliplus.com/video'])
            });
        });

        let json;
        const getJsonFromBiliplusMatch = response.response.match(getJsonFromBiliplusRegex);
        if (getJsonFromBiliplusMatch) {
            json = JSON.parse(getJsonFromBiliplusMatch[1]);
        }

        if (!json) {
            if (debug) {
                addMessage('getFromBiliplus 获取数据为空', false, 'red');
                addMessage(AVBVTitle.BV, true);
                console.warn('getFromBiliplus 获取数据为空');
                console.warn(AVBVTitle.BV);
            }
        }

        if (json && json.title) {
            if (debug) {
                console.log('从BiliPlus获取有效数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(json);
            }

            if (!json.lastupdatets) {
                if (!json.lastupdate) {
                    throw ['从BiliPlus获取的数据中无备份时间, 请反馈该问题'];
                }
                if (!localeTimeStringRegex.test(json.lastupdate)) {
                    throw ['从BiliPlus获取的数据中备份时间不符合规范, 请反馈该问题'];
                }
                if (isNaN(new Date(json.lastupdate).getTime())) {
                    throw ['从BiliPlus获取的数据中备份时间不符合规范, 请反馈该问题'];
                }
                json.lastupdatets = new Date(json.lastupdate).getTime() / 1000;
            }

            updateArrayDataInBackup(backup, 'title', json.title, json.lastupdatets, 'www.biliplus.com/video');
            updateArrayDataInBackup(backup, 'intro', json.description, json.lastupdatets, 'www.biliplus.com/video');
            updateArrayDataInBackup(backup, 'cover', json.pic, json.lastupdatets, 'www.biliplus.com/video');
            updateArrayDataInBackup(backup, 'upperName', json.author, json.lastupdatets, 'www.biliplus.com/video');

            if (Array.isArray(json.list)) {
                json.list.forEach(el => {
                    updatePagesInBackup(backup, el.page, el.part, undefined, undefined, el.cid, json.lastupdatets, 'www.biliplus.com/video');
                });
            }

            if (json.v2_app_api) {
                updateArrayDataInBackup(backup, 'title', json.v2_app_api.title, json.lastupdatets, 'www.biliplus.com/video');
                updateArrayDataInBackup(backup, 'intro', json.v2_app_api.desc, json.lastupdatets, 'www.biliplus.com/video');
                updateArrayDataInBackup(backup, 'cover', json.v2_app_api.pic, json.lastupdatets, 'www.biliplus.com/video');
                updateArrayDataInBackup(backup, 'upperName', json.v2_app_api.owner?.name, json.lastupdatets, 'www.biliplus.com/video');
                // updateArrayDataInBackup(backup, 'upperAvatar', json.v2_app_api.owner.face, json.lastupdatets, 'www.biliplus.com/video');

                // if (debug) {
                //     if (json.v2_app_api.cid && json.v2_app_api.cid !== json.list[0].cid) {
                //         addMessage('getFromBiliplus cid 不一致1', false, 'red');
                //         addMessage(json.v2_app_api.cid, true);
                //         addMessage(json.list[0].cid, true);
                //         console.warn('getFromBiliplus cid 不一致1');
                //         console.warn(json.v2_app_api.cid);
                //         console.warn(json.list[0].cid);
                //     }
                //     if (json.v2_app_api.cid && json.v2_app_api.cid !== json.v2_app_api.pages[0].cid) {
                //         addMessage('getFromBiliplus cid 不一致2', false, 'red');
                //         addMessage(json.v2_app_api.cid, true);
                //         addMessage(json.v2_app_api.pages[0].cid, true);
                //         console.warn('getFromBiliplus cid 不一致2');
                //         console.warn(json.v2_app_api.cid);
                //         console.warn(json.v2_app_api.pages[0].cid);
                //     }
                //     if (json.list[0].cid !== json.v2_app_api.pages[0].cid) {
                //         addMessage('getFromBiliplus cid 不一致3', false, 'red');
                //         addMessage(json.list[0].cid, true);
                //         addMessage(json.v2_app_api.pages[0].cid, true);
                //         console.warn('getFromBiliplus cid 不一致3');
                //         console.warn(json.list[0].cid);
                //         console.warn(json.v2_app_api.pages[0].cid);
                //     }
                // }

                // if (json.v2_app_api.first_frame !== json.v2_app_api.pages[0].first_frame) {
                //     if (debug) {
                //         addMessage('getFromBiliplus firstFrame 不一致', false, 'red');
                //         addMessage(json.v2_app_api.first_frame, true);
                //         addMessage(json.v2_app_api.pages[0].first_frame, true);
                //         console.warn('getFromBiliplus firstFrame 不一致');
                //         console.warn(json.v2_app_api.first_frame);
                //         console.warn(json.v2_app_api.pages[0].first_frame);
                //     }
                //     updatePagesInBackup(backup, 0, undefined, json.v2_app_api.first_frame, undefined, 0, json.lastupdatets, 'www.biliplus.com/video');
                // }

                updateArrayDataInBackup(backup, 'dynamic', json.v2_app_api.dynamic, json.lastupdatets, 'www.biliplus.com/video');
                // json.v2_app_api.pages.forEach(el => {
                //     updatePagesInBackup(backup, el.page, el.part, el.first_frame, undefined, el.cid, json.lastupdatets, 'www.biliplus.com/video');
                // });
            }

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

        } else {
            if (debug) {
                console.log('从BiliPlus获取无效数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(json);
            }
            backup.biliplus = { value: false, ts: getCurrentTs() };
            spanB.style.color = '#ff0000';
        }
    }

    async function getFromBiliplusExtra(AVBVTitle, backup, spanB, as, controller) {

        if (getFromBiliplusExtraCounter > 350) {
            as[1].textContent = `等待${getFromBiliplusExtraCounter - 350}秒 | ${AVBVTitle.title}`;
            await new Promise((resolve, reject) => {
                const timer = setInterval(() => {
                    if (controller.signal.aborted) {
                        clearInterval(timer);
                        reject(new DOMException('', 'AbortError'));
                        return;
                    }
                    if (getFromBiliplusExtraCounter <= 350) {
                        clearInterval(timer);
                        resolve();
                        return;
                    }
                    as[1].textContent = `等待${getFromBiliplusExtraCounter - 350}秒 | ${AVBVTitle.title}`;
                }, 1000);
            });
            as[1].textContent = AVBVTitle.title;
        }

        const response1 = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://www.biliplus.com/all/video/av${AVBVTitle.AV}/`,
                timeout: 1000 * settings.requestTimeout,
                onload: (res) => resolve(res),
                onerror: (res) => reject(['请求失败', 'www.biliplus.com/all/video', res.error]),
                ontimeout: () => reject(['请求超时', 'www.biliplus.com/all/video'])
            });
        });

        const paramsWithSign = response1.response.match(getParamsWithSignFromBiliplusRegex)[1];

        const response2 = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://www.biliplus.com/api/view_all${paramsWithSign}`,
                timeout: 1000 * settings.requestTimeout,
                responseType: 'json',
                onload: (res) => resolve(res),
                onerror: (res) => reject(['请求失败', 'www.biliplus.com/api/view_all', res.error]),
                ontimeout: () => reject(['请求超时', 'www.biliplus.com/api/view_all'])
            });
        });

        if (response2.response.code === -503) {
            getFromBiliplusExtraCounter = 372;
            throw ['从BiliPlus获取额外数据的频率过快'];
        }

        getFromBiliplusExtraCounter += 12;

        if (response2.response.code === 0) {
            if (debug) {
                console.log('从BiliPlus获取有效额外数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(response2.response);

                if (!backup.biliplus.value) {
                    addMessage('getFromBiliplusExtra 无法获取数据但可获取额外数据', false, 'red');
                    addMessage(AVBVTitle.BV, true);
                    console.warn('getFromBiliplusExtra 无法获取数据但可获取额外数据');
                    console.warn(AVBVTitle.BV);
                }
            }

            updateArrayDataInBackup(backup, 'title', response2.response.data.info.title, undefined, 'www.biliplus.com/api/view_all');
            updateArrayDataInBackup(backup, 'intro', response2.response.data.info.description, undefined, 'www.biliplus.com/api/view_all');
            updateArrayDataInBackup(backup, 'cover', response2.response.data.info.pic, undefined, 'www.biliplus.com/api/view_all');
            updateArrayDataInBackup(backup, 'upperName', response2.response.data.info.author, undefined, 'www.biliplus.com/api/view_all');

            response2.response.data.parts.forEach(el => {
                updatePagesInBackup(backup, el.page, el.part, undefined, undefined, el.cid, undefined, 'www.biliplus.com/api/view_all');
            });

            backup.biliplusExtra = { value: true, ts: getCurrentTs() };
            spanB.style.color = '#008000';

        } else if (response2.response.code === -404) {
            // if (debug) {
            //     addMessage('getFromBiliplusExtra 无法获取额外数据', false, 'red');
            //     addMessage(AVBVTitle.BV, true);
            //     console.warn('getFromBiliplusExtra 无法获取额外数据');
            //     console.warn(AVBVTitle.BV);
            // }

            backup.biliplusExtra = { value: false, ts: getCurrentTs() };
            spanB.style.color = '#800000';

        } else {
            if (debug) {
                addMessage('getFromBiliplusExtra 未知错误', false, 'red');
                addMessage(JSON.stringify(response2.response), true);
                addMessage(AVBVTitle.BV, true);
                console.warn('getFromBiliplusExtra 未知错误');
                console.warn(response2.response);
                console.warn(AVBVTitle.BV);
            }

            backup.biliplusExtra = { value: false, ts: getCurrentTs() };
            spanB.style.color = '#800000';
        }

        const sortedBackup = {};
        for (const sortedKey of sortedKeys) {
            sortedBackup[sortedKey] = backup[sortedKey];
        }
        GM_setValue(AVBVTitle.BV, sortedBackup);
        if (debug) {
            console.log('保存BiliPlus的额外数据至本地');
            consoleAVBVTitle('debug', AVBVTitle);
            console.debug(sortedBackup);
        }
    }

    async function getFromJijidown(AVBVTitle, backup, spanJ) {

        let retryCount = 0;
        while (true) {
            const response = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `https://${settings.getFromJijidownURL}/api/v1/video_bv/get_info?id=${AVBVTitle.BV.slice(2)}`,
                    timeout: 1000 * settings.requestTimeout,
                    responseType: 'json',
                    onload: (res) => resolve(res),
                    onerror: (res) => reject(['请求失败', `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`, res.error]),
                    ontimeout: () => reject(['请求超时', `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`])
                });
            });

            if (debug) console.debug(response);
            if (response.status !== 200) {
                throw ['请求失败', `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`, `${response.status} ${response.statusText}`];
            }

            if (response.response.upid > 0) {
                if (debug) {
                    console.log('从唧唧获取有效数据');
                    consoleAVBVTitle('debug', AVBVTitle);
                    console.debug(response.response);
                }

                updateArrayDataInBackup(backup, 'title', response.response.title, response.response.ltime, `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`);
                if (response.response.desc) {
                    updateArrayDataInBackup(backup, 'intro', decodeHTMLEntities(response.response.desc.replaceAll('<br/>', '\n').replaceAll('\r', '\\r')).replaceAll('\\r', '\r'), response.response.ltime, `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`);
                }
                updateArrayDataInBackup(backup, 'cover', response.response.img, response.response.ltime, `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`);
                if (response.response.up.id > 0) {
                    updateArrayDataInBackup(backup, 'upperName', response.response.up.author, response.response.ltime, `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`);
                    updateArrayDataInBackup(backup, 'upperAvatar', response.response.up.avatar, response.response.ltime, `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`);
                }

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

                if (settings.getFromJijidownExtra && (!backup.jijidownExtra || (settings.getFromJijidownExtraUpdate && getCurrentTs() - backup.jijidownExtra.ts > 3600 * 24 * 7 * settings.getFromJijidownExtraUpdateInterval))) {
                    await getFromJijidownExtra(AVBVTitle, backup, spanJ, response.response.ltime);
                }

                return;

            } else if (response.response.msg === 'loading') {
                retryCount++;
                if (debug) {
                    console.log('从唧唧获取无效数据');
                    consoleAVBVTitle('debug', AVBVTitle);
                    console.log(`请求重试次数: ${retryCount}`);
                    console.debug(response.response);
                }

                if (retryCount > 4) {
                    throw ['请求重试次数过多', `${settings.getFromJijidownURL}/api/v1/video_bv/get_info`];
                }

            } else {
                if (debug) {
                    console.log('从唧唧获取无效数据');
                    consoleAVBVTitle('debug', AVBVTitle);
                    console.debug(response.response);
                }
                backup.jijidown = { value: false, ts: getCurrentTs() };
                if (settings.getFromJijidownExtra && (!backup.jijidownExtra || (settings.getFromJijidownExtraUpdate && getCurrentTs() - backup.jijidownExtra.ts > 3600 * 24 * 7 * settings.getFromJijidownExtraUpdateInterval))) {
                    backup.jijidownExtra = { value: false, ts: getCurrentTs() };
                    spanJ.style.color = '#800000';
                } else {
                    spanJ.style.color = '#ff0000';
                }
                return;
            }

            await delay(600);
        }
    }

    async function getFromJijidownExtra(AVBVTitle, backup, spanJ, ts) {

        const response = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://${settings.getFromJijidownURL}/api/v1/video_bv/get_download_info?id=${AVBVTitle.BV.slice(2)}`,
                timeout: 1000 * settings.requestTimeout,
                responseType: 'json',
                onload: (res) => resolve(res),
                onerror: (res) => reject(['请求失败', `${settings.getFromJijidownURL}/api/v1/video_bv/get_download_info`, res.error]),
                ontimeout: () => reject(['请求超时', `${settings.getFromJijidownURL}/api/v1/video_bv/get_download_info`])
            });
        });

        if (debug) console.debug(response);
        if (response.status !== 200) {
            throw ['请求失败', `${settings.getFromJijidownURL}/api/v1/video_bv/get_download_info`, `${response.status} ${response.statusText}`];
        }

        if (response.response.res && response.response.res.length) {
            if (debug) {
                console.log('从唧唧获取有效额外数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(response.response);
            }

            response.response.res.forEach(el => {
                updatePagesInBackup(backup, 0, el.part, undefined, undefined, el.cid, ts, `${settings.getFromJijidownURL}/api/v1/video_bv/get_download_info`);
            });

            backup.jijidownExtra = { value: true, ts: getCurrentTs() };
            spanJ.style.color = '#008000';

        } else {
            if (debug) {
                console.log('从唧唧获取无效额外数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(response.response);
            }
            backup.jijidownExtra = { value: false, ts: getCurrentTs() };
            spanJ.style.color = '#800000';
        }
    }

    async function getFromXbeibeix(AVBVTitle, backup, spanX) {

        const response = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://${settings.getFromXbeibeixURL}/video/${AVBVTitle.BV}`,
                timeout: 1000 * settings.requestTimeout,
                onload: (res) => resolve(res),
                onerror: (res) => reject(['请求失败', `${settings.getFromXbeibeixURL}/video`, res.error]),
                ontimeout: () => reject(['请求超时', `${settings.getFromXbeibeixURL}/video`])
            });
        });

        if (debug) console.debug(response);
        if (response.status !== 200) {
            throw ['请求失败', `${settings.getFromXbeibeixURL}/video`, `${response.status} ${response.statusText}`];
        }

        if (response.finalUrl !== `https://${settings.getFromXbeibeixURL}/`) {
            if (debug) {
                console.log('从贝贝工具站获取有效数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(response);
            }

            if (!response.responseXML) {
                throw ['请您使用版本为 5.3.0 及以上的 <a href="https://www.tampermonkey.net/" target="_blank" style="text-decoration-line: underline;">Tampermonkey</a> 运行此脚本'];
            }

            updateArrayDataInBackup(backup, 'title', response.responseXML.querySelector('h5.fw-bold').innerText, undefined, `${settings.getFromXbeibeixURL}/video`);
            if (response.responseXML.querySelector('div.col-8 > textarea').value) {
                updateArrayDataInBackup(backup, 'intro', decodeHTMLEntities(response.responseXML.querySelector('div.col-8 > textarea').value), undefined, `${settings.getFromXbeibeixURL}/video`);
            }
            updateArrayDataInBackup(backup, 'cover', response.responseXML.querySelector('div.col-4 > img').getAttribute('src'), undefined, `${settings.getFromXbeibeixURL}/video`);
            updateArrayDataInBackup(backup, 'upperName', response.responseXML.querySelector('div.input-group.mb-2 > input').value, undefined, `${settings.getFromXbeibeixURL}/video`);

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

        } else {
            if (debug) {
                console.log('从贝贝工具站获取无效数据');
                consoleAVBVTitle('debug', AVBVTitle);
                console.debug(response);
            }
            backup.xbeibeix = { value: false, ts: getCurrentTs() };
            spanX.style.color = '#ff0000';
        }
    }

    function addMessage(msg, smallFontSize, border) {
        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`;
        if (border === 'red') {
            p.style.borderTop = '1px solid #800000';
        } else if (border === 'green') {
            p.style.borderTop = '1px solid #008000';
        }
        divMessage.appendChild(p);

        if (divMessageHeightFixed) {
            divMessage.scrollTop = divMessage.scrollHeight;
        } else {
            if (newFreshSpace) {
                if (divMessage.scrollHeight > 320) {
                    divMessage.classList.add('backup-divMessage-heightFixed-newFreshSpace');
                    divMessageHeightFixed = true;
                    divMessage.scrollTop = divMessage.scrollHeight;
                }
            } else {
                if (divMessage.scrollHeight > 280) {
                    divMessage.classList.add('backup-divMessage-heightFixed');
                    divMessageHeightFixed = true;
                    divMessage.scrollTop = divMessage.scrollHeight;
                }
            }
        }

        divMessage.scrollIntoView({ behavior: 'instant', block: 'nearest' });
    }

    function clearMessage() {
        while (divMessage.firstChild) {
            divMessage.removeChild(divMessage.firstChild);
        }
        divMessage.classList.remove('backup-divMessage-heightFixed' + classAppendNewFreshSpace);
        divMessageHeightFixed = false;
    }

    function addMessageAVBVTitle(AVBVTitle) {
        addMessage(`AV号: ${AVBVTitle.AV}`, true);
        addMessage(`BV号: ${AVBVTitle.BV}`, true);
        if (AVBVTitle.title) {
            addMessage(`标题: ${AVBVTitle.title.slice(0, 13)}`, true);
        }
    }

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

    function catchUnknownError(error) {
        addMessage('发生未知错误, 请反馈该问题', false, 'red');
        addMessage(error.stack, true);
        console.error(error);
    }

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

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

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

    function formatTsTimeFavorite(t) {
        const e = new Date();
        const n = e.getTime();
        const r = t.getTime();
        const 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 formatTsYYMMDD_HHMMSS(ts) {
        const date = new Date(1000 * ts);
        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 formatTsTimePublish(e) {
        const n = new Date(e);
        const u = Date.now();
        if (u - e <= 6e4) {
            return '刚刚';
        }
        if (u - e < 36e5) {
            return Math.floor((u - e) / 6e4) + '分钟前';
        }
        if (u - e < 864e5) {
            return Math.floor((u - e) / 36e5) + '小时前';
        }
        if (new Date().setHours(0, 0, 0, 0) - e < 864e5) {
            return '昨天';
        }
        const l = n.getFullYear();
        const c = '0'.concat(n.getMonth() + 1).slice(-2);
        const f = '0'.concat(n.getDate()).slice(-2);
        return l === new Date().getFullYear() ? ''.concat(c, '-').concat(f) : ''.concat(l, '-').concat(c, '-').concat(f);
    }

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

    function updateArrayDataInBackup(backup, key, value, ts, from) {
        if (!value) {
            return;
        }
        if (!ts) {
            ts = 0;
        }
        if (key === 'cover' || key === 'upperAvatar') {
            value = 'https://' + value.replace(getHttpsFromURLRegex, '').replace(getAvifFromURLRegex, '');
        }

        if (!backup[key]) {
            backup[key] = [];
            const data = { value, ts, from };
            backup[key].push(data);
            return;
        }

        let target;
        target = backup[key].find(el => el.value === value);
        if (target) {
            if (target.ts <= ts) {
                target.ts = ts;
                target.from = from;
                backup[key].sort((a, b) => a.ts - b.ts);
            }
            return;
        }

        if (key === 'cover' || key === 'upperAvatar') {
            target = backup[key].find(el => el.value.match(getFilenameFromURLRegex)[0] === value.match(getFilenameFromURLRegex)[0]);
            if (target) {
                if (target.ts <= ts) {
                    target.value = value;
                    target.ts = ts;
                    target.from = from;
                    backup[key].sort((a, b) => a.ts - b.ts);
                }
                return;
            }

        } else if (key === 'intro') {
            target = backup.intro.find(el => el.value.replaceAll('\r', '') === value);
            if (target) {
                return;
            }

            target = backup.intro.find(el => el.value === value.replaceAll('\r', ''));
            if (target) {
                target.value = value;
                target.ts = ts;
                target.from = from;
                backup.intro.sort((a, b) => a.ts - b.ts);
                return;
            }

            if (value.length >= 255 || ((from.includes('xbeibeix') || from.includes('bbdownloader') || from.includes('beibeigame')) && value.length >= 200)) {
                target = backup.intro.find(el => el.value.replaceAll('\r', '').startsWith(value.replaceAll('\r', '')));
                if (target) {
                    return;
                }

                target = backup.intro.find(el => value.replaceAll('\r', '').startsWith(el.value.replaceAll('\r', '')));
                if (target) {
                    target.value = value;
                    target.ts = ts;
                    target.from = from;
                    backup.intro.sort((a, b) => a.ts - b.ts);
                    return;
                }
            }
        }

        const data = { value, ts, from };
        backup[key].push(data);
        backup[key].sort((a, b) => a.ts - b.ts);
    }

    function updateTimeFavoriteInBackup(backup, value, fid) {
        if (!value) {
            value = 0;
        }
        if (!backup.timeFavorite) {
            backup.timeFavorite = [];
            const data = { value, fid };
            backup.timeFavorite.push(data);
            return true;

        } else {
            const target = backup.timeFavorite.find(el => el.fid === fid);
            if (target) {
                if (target.value < value) {
                    target.value = value;
                    backup.timeFavorite.sort((a, b) => a.value - b.value);
                    return true;
                }
            } else {
                const data = { value, fid };
                backup.timeFavorite.push(data);
                backup.timeFavorite.sort((a, b) => a.value - b.value);
                return true;
            }
        }
    }

    function updatePagesInBackup(backup, index, title, firstFrame, thumbnails, cid, ts, from) {
        if (!index && index !== 0) {
            index = null;
        }
        if (title) {
            if (!Array.isArray(title)) {
                title = [title];
            }
        } else {
            title = null;
        }
        if (firstFrame) {
            firstFrame = 'https://' + firstFrame.replace(getHttpsFromURLRegex, '').replace(getAvifFromURLRegex, '');
        } else {
            firstFrame = null;
        }
        if (thumbnails) {
            if (!Array.isArray(thumbnails)) {
                thumbnails = ['https://' + thumbnails.replace(getHttpsFromURLRegex, '').replace(getAvifFromURLRegex, '')];
            } else {
                thumbnails = thumbnails.map(el => 'https://' + el.replace(getHttpsFromURLRegex, '').replace(getAvifFromURLRegex, ''));
            }
        } else {
            thumbnails = null;
        }
        if (!cid && cid !== 0) {
            cid = null;
        }
        if (!ts) {
            ts = 0;
        }
        if (!from) {
            from = null;
        }

        if (!backup.pages) {
            backup.pages = [];
            const data = { index, title, firstFrame, thumbnails, cid, ts, from };
            backup.pages.push(data);
            return true;

        } else {
            const target = backup.pages.find(el => el.cid === cid);
            if (target) {
                let modified = false;
                if (index) {
                    if (!target.index) {
                        target.index = index;
                        modified = true;
                    } else if (target.index !== index && target.ts <= ts) {
                        target.index = index;
                        modified = true;
                    }
                }
                if (title) {
                    if (!target.title) {
                        target.title = title;
                        modified = true;
                    } else {
                        title.forEach(el => {
                            if (!target.title.find(ele => ele === el)) {
                                target.title.push(el);
                                modified = true;
                            }
                        });
                    }
                }
                if (firstFrame) {
                    if (!target.firstFrame) {
                        target.firstFrame = firstFrame;
                        modified = true;
                    } else if (target.firstFrame !== firstFrame) {
                        if (debug) {
                            addMessage('updatePagesInBackup firstFrame 不一致', false, 'red');
                            addMessage(`旧: ${target.firstFrame}`, true);
                            addMessage(`新: ${firstFrame}`, true);
                            console.warn('updatePagesInBackup firstFrame 不一致');
                            console.warn(`旧: ${target.firstFrame}`);
                            console.warn(`新: ${firstFrame}`);
                        }
                        // target.firstFrame = firstFrame;
                        // modified = true;
                    }
                }
                if (thumbnails) {
                    if (!target.thumbnails) {
                        target.thumbnails = thumbnails;
                        modified = true;
                    } else {
                        thumbnails.forEach(el => {
                            if (!target.thumbnails.find(ele => ele === el)) {
                                target.thumbnails.push(el);
                                modified = true;
                            }
                        });
                    }
                }
                if (target.ts <= ts) {
                    target.ts = ts;
                    modified = true;
                }
                if (modified) {
                    if (from) {
                        target.from = from;
                    }
                    backup.pages.sort((a, b) => {
                        if (a.index !== b.index) {
                            return a.index - b.index;
                        } else if (a.ts !== b.ts) {
                            return a.ts - b.ts;
                        } else {
                            return a.cid - b.cid;
                        }
                    });
                }

            } else {
                const data = { index, title, firstFrame, thumbnails, cid, ts, from };
                backup.pages.push(data);
                backup.pages.sort((a, b) => {
                    if (a.index !== b.index) {
                        return a.index - b.index;
                    } else if (a.ts !== b.ts) {
                        return a.ts - b.ts;
                    } else {
                        return a.cid - b.cid;
                    }
                });
                return true;
            }
        }
    }

    async function appendParamsForGetFromApi(fid, pageNumber, pageSize) {
        const inputKeyword = document.querySelector(newFreshSpace ? 'input.fav-list-header-filter__search' : 'input.search-fav-input');
        let keyword = '';
        if (inputKeyword) {
            keyword = encodeURIComponent(inputKeyword.value);
        }
        if (debug) {
            console.log(`keyword: ${decodeURIComponent(keyword)}`);
            console.log(keyword);
        }

        let divFilterOrder;
        let divTid;

        if (!newFreshSpace) {
            const divDropdownFilterItems = document.querySelectorAll('div.fav-filters > div.be-dropdown.filter-item');
            if (divDropdownFilterItems.length === 2) {
                divFilterOrder = divDropdownFilterItems[1].querySelector('span');
                divTid = divDropdownFilterItems[0].querySelector('span');
            } else if (divDropdownFilterItems.length === 1) {
                divFilterOrder = divDropdownFilterItems[0].querySelector('span');
                divTid = null;
            } else {
                divFilterOrder = null;
                divTid = null;
            }
        }

        if (!newFreshSpace) {
            let orderText = '收藏';
            if (divFilterOrder) {
                orderText = divFilterOrder.innerText;
            }
            if (orderText.includes('收藏')) {
                order = 'mtime';
            } else if (orderText.includes('播放')) {
                order = 'view';
            } else if (orderText.includes('投稿')) {
                order = 'pubtime';
            } else {
                throw ['无法确定各个视频的排序方式, 请反馈该问题'];
            }
        }
        if (debug) console.log(`order: ${order}`);

        const divType = document.querySelector(newFreshSpace ? 'div.vui_input__prepend' : 'div.search-types');
        let typeText = '当前';
        if (divType) {
            typeText = divType.innerText;
        }
        if (!keyword) {
            typeText = '当前';
        }
        if (debug) console.log(`typeText: ${typeText}`);
        let type;
        if (typeText.includes('当前')) {
            type = 0;
        } else if (typeText.includes('全部')) {
            type = 1;
        } else {
            throw ['无法确定搜索的范围为当前收藏夹还是全部收藏夹, 请反馈该问题'];
        }
        if (debug) console.log(`type: ${type}`);

        if (newFreshSpace) {
            divTid = document.querySelector('div.fav-list-header-collapse div.radio-filter__item--active');
        }
        let tidText = '全部分区';
        if (divTid) {
            tidText = divTid.innerText;
        }
        if (debug) console.log(`tidText: ${tidText}`);
        let tid;
        if (tidText.includes('全部')) {
            tid = 0;
        } else {
            const UID = parseInt(location.href.match(getUIDFromURLRegex)[1], 10);
            const response = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `https://api.bilibili.com/x/v3/fav/resource/partition?up_mid=${UID}&media_id=${fid}` + (newFreshSpace ? '&web_location=333.1387' : ''),
                    timeout: 1000 * settings.requestTimeout,
                    responseType: 'json',
                    onload: (res) => resolve(res),
                    onerror: (res) => reject(['请求失败', 'api.bilibili.com/x/v3/fav/resource/partition', res.error]),
                    ontimeout: () => reject(['请求超时', 'api.bilibili.com/x/v3/fav/resource/partition'])
                });
            });
            if (response.status !== 200) {
                throw ['请求失败', 'api.bilibili.com/x/v3/fav/resource/partition', `${response.status} ${response.statusText}`];
            }

            const target = response.response.data.find(el => tidText.includes(el.name));
            if (target) {
                tid = target.tid;
            } else {
                throw ['无法确定选择的分区, 请反馈该问题'];
            }
        }
        if (debug) console.log(`tid: ${tid}`);

        if (debug) console.log(`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${fid}&pn=${pageNumber}&ps=${pageSize}&keyword=${keyword}&order=${order}&type=${type}&tid=${tid}&platform=web` + (newFreshSpace ? '&web_location=333.1387' : ''));
        return (`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${fid}&pn=${pageNumber}&ps=${pageSize}&keyword=${keyword}&order=${order}&type=${type}&tid=${tid}&platform=web` + (newFreshSpace ? '&web_location=333.1387' : ''));
    }

    function validateInputText(event) {
        try {
            const inputText = event.target;
            let value = inputText.value.trim();
            const def = parseInt(inputText.getAttribute('backup-def'), 10);
            if (!value || isNaN(value)) {
                value = def;
            } else {
                value = parseInt(value, 10);
                const min = parseInt(inputText.getAttribute('backup-min'), 10);
                const max = parseInt(inputText.getAttribute('backup-max'), 10);
                if (value < min) {
                    value = min;
                } else if (value > max) {
                    value = max;
                }
            }
            inputText.value = value;
            const key = inputText.getAttribute('backup-setting');
            settings[key] = value;
            GM_setValue('settings', settings);
        } catch (error) {
            catchUnknownError(error);
        }
    }

    function removeTsFromInBackup(obj) {
        if (Array.isArray(obj)) {
            obj.forEach(el => removeTsFromInBackup(el));
        } else if (obj && typeof obj === 'object') {
            for (const key in obj) {
                if (key === 'ts' || key === 'from') {
                    delete obj[key];
                } else {
                    removeTsFromInBackup(obj[key]);
                }
            }
        }
    }

    function formatBackup(backup, setValue, BV) {
        let modified = false;
        // v9
        if (backup.timeFavorite) {
            backup.timeFavorite.forEach(el => {
                if (typeof el.fid === 'string') {
                    el.fid = parseInt(el.fid, 10);
                    modified = true;
                }
            });
        }
        // v9
        if (Array.isArray(backup.timeUpload)) {
            if (backup.timeUpload.length === 1) {
                backup.timeUpload = backup.timeUpload[0].value;
            } else {
                backup.timeUpload = null;
            }
            modified = true;
        }
        // v9
        if (Array.isArray(backup.timePublish)) {
            if (backup.timePublish.length === 1) {
                backup.timePublish = backup.timePublish[0].value;
            } else {
                backup.timePublish = null;
            }
            modified = true;
        }
        // v10
        if (backup.hasOwnProperty('jiji')) {
            backup.jijidown = backup.jiji;
            delete backup.jiji;
            modified = true;
        }
        // v10
        if (backup.hasOwnProperty('bbdownloader')) {
            backup.xbeibeix = backup.bbdownloader;
            delete backup.bbdownloader;
            modified = true;
        }
        // v14
        if (backup.hasOwnProperty('firstFrame')) {
            delete backup.firstFrame;
            modified = true;
        }
        // v16
        if (backup.intro) {
            const tempBackup = { intro: null };
            backup.intro.forEach(el => {
                updateArrayDataInBackup(tempBackup, 'intro', el.value, el.ts, el.from);
            });
            backup.intro = tempBackup.intro;
            modified = true;
        }
        // v21
        ['cover', 'upperAvatar'].forEach(key => {
            if (backup[key]) {
                const tempBackup = {};
                backup[key].forEach(el => {
                    updateArrayDataInBackup(tempBackup, key, el.value, el.ts, el.from);
                });
                backup[key] = tempBackup[key];
                modified = true;
            }
        });
        // v21
        if (backup.pages) {
            const tempBackup = {};
            backup.pages.forEach(el => {
                updatePagesInBackup(tempBackup, el.index, el.title, el.firstFrame, el.thumbnails, el.cid, el.ts, el.from);
            });
            backup.pages = tempBackup.pages;
            modified = true;
        }
        if (setValue && modified) {
            GM_setValue(BV, backup);
        }
    }
})();

QingJ © 2025

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