Greasy Fork is available in English.

哔哩哔哩自定义视频信息条

完全自定义你的视频标题下方信息栏,排序,增加,删除!

// ==UserScript==
// @name         哔哩哔哩自定义视频信息条
// @namespace    ckylin-bilibili-display-video-id
// @version      1.19.3
// @description  完全自定义你的视频标题下方信息栏,排序,增加,删除!
// @author       CKylinMC
// @match        https://www.bilibili.com/video*
// @match        https://www.bilibili.com/medialist/play/*
// @resource     cktools https://greasyfork.org/scripts/429720-cktools/code/CKTools.js?version=1034581
// @resource     popjs https://cdn.jsdelivr.net/gh/CKylinMC/PopNotify.js@master/PopNotify.js
// @resource     popcss https://cdn.jsdelivr.net/gh/CKylinMC/PopNotify.js@master/PopNotify.css
// @resource     timeago https://unpkg.com/timeago.js@4.0.2/dist/timeago.min.js
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceText
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @license      GPL-3.0-only
// ==/UserScript==

(function () {
    //======[Apply all resources]
    const resourceList = [
        { name: 'cktools', type: 'js' },
        { name: 'timeago', type: 'js' },
        { name: 'popjs', type: 'js' },
        { name: 'popcss', type: 'css' },
        { name: 'popcsspatch', type: 'rawcss', content: "div.popNotifyUnitFrame{z-index:110000!important;}.CKTOOLS-modal-content{color: #616161!important;max-height: 80vh;overflow: auto;}" },
    ]
    function applyResource() {
        resloop: for (let res of resourceList) {
            if (!document.querySelector("#" + res.name)) {
                let el;
                switch (res.type) {
                    case 'js':
                    case 'rawjs':
                        el = document.createElement("script");
                        break;
                    case 'css':
                    case 'rawcss':
                        el = document.createElement("style");
                        break;
                    default:
                        console.log('Err:unknown type', res);
                        continue resloop;
                }
                el.id = res.name;
                el.innerHTML = res.type.startsWith('raw') ? res.content : GM_getResourceText(res.name);
                document.head.appendChild(el);
            }
        }
    }
    applyResource();
    //======
    const wait = (t) => new Promise(r => setTimeout(r, t));
    const waitForPageVisible = async () => {
        return document.hidden && new Promise(r => document.addEventListener("visibilitychange", r))
    }
    const log = (...m) => console.log('[ShowAV]', ...m);
    const getAPI = (bvid) => fetch('https://api.bilibili.com/x/web-interface/view?bvid=' + bvid).then(raw => raw.json());
    const getAidAPI = (aid) => fetch('https://api.bilibili.com/x/web-interface/view?aid=' + aid).then(raw => raw.json());
    const config = {
        defaultAv: true,
        hideTime: true,
        firstTimeLoad: true,
        defaultTextTime: true,
        foldedWarningTip: true,
        forceRemoveAllItem: true,
        showInNewLine: false,
        forceGap: false,
        nobreakline: false,
        jssafetyWarning: true,
        pnmaxlength: 18,
        orders: ['openGUI', 'showArgue', 'showPic', 'showAv', 'showPn'],
        all: ['showAv', 'showSAv', 'showSBv', 'showPn', 'showCid', 'showCate', 'openGUI', 'showPic', 'showSize', 'showMore', 'showCTime', 'showViews', 'showDmk', 'showTop', 'showArgue'],
        copyitems: ['currTime', 'short', 'shareTime', 'vid'],
        copyitemsAll: ['curr', 'currTime', 'short', 'share', 'shareTime', 'md', 'bb', 'html', 'vid'],
        customcopyitems: {},
        customComponents: {},
        vduration: 0,
        running: {}
    };
    const ignoredConfigKeys = ["all", "vduration", "firstTimeLoad", "running"];
    const menuId = {
        defaultAv: -1,
        foldedWarningTip: -1,
        showInNewLine: -1,
    };
    const txtCn = {
        showAv: "可切换视频编号和高级复制",
        showSAv: "视频AV号和高级复制",
        showSBv: "视频BV号和高级复制",
        showPn: "视频分P名",
        showCid: "视频CID编号",
        showCate: "视频所在分区",
        showPic: "视频封面",
        showSize: "视频分辨率",
        showMore: "更多信息",
        showCTime: "视频投稿时间",
        showViews: "替换视频播放量",
        showDmk: "替换视频弹幕量",
        showTop: "替换全站排名提示",
        showArgue: "显示危险提示",
        curr: "当前视频地址",
        currTime: "当前视频地址(含视频进度)",
        short: "短地址",
        share: "快速分享",
        shareTime: "快速分享(含视频进度)",
        md: "Markdown 格式",
        bb: "BBCode 格式",
        html: "HTML 格式",
        vid: "视频编号",
        openGUI: "设置选项"
    };
    const descCn = {
        showAv: "展示视频号(AV号/BV号右键单击可切换),左键单击快速复制(包含当前播放时间),左键长按打开更多格式复制窗口",
        showSAv: "展示视频AV号,左键单击快速复制(包含当前播放时间),左键长按打开更多格式复制窗口",
        showSBv: "展示视频BV号,左键单击快速复制(包含当前播放时间),左键长按打开更多格式复制窗口",
        showPn: "展示视频分P信息以及缓存名(分P名)。可能较长,建议放在最下面,并调整最大长度。",
        showCid: "展示视频资源CID编号,通常不需要展示。",
        showCate: "展示视频所在的子分区。",
        showPic: "提供按钮一键查看封面,长按可以在新标签页打开大图。",
        showSize: "展示视频当前分辨率(宽高信息)。",
        showMore: "查看视频更多信息。",
        showCTime: "用文字方式描述投稿时间,如:一周前",
        showViews: "替换展示视频播放量(由于内容相同,将自动隐藏原版播放量信息)",
        showDmk: "替换展示视频弹幕量(由于内容相同,将自动隐藏原版弹幕量信息)",
        showTop: "替换原版全站排名信息",
        showArgue: "如果视频有危险提示,则显示危险提示",
        curr: "提供当前视频纯净地址",
        currTime: "提供当前视频地址,并在播放时提供含跳转时间的地址(可以直接跳转到当前进度)。",
        short: "提供当前视频的b23.tv短地址",
        share: "提供当前视频的标题和地址组合文本。",
        shareTime: "提供当前视频的标题和地址组合文本,在播放时提供含跳转时间的地址(可以直接跳转到当前进度)。",
        md: "提供Markdown特殊语法的快速复制。",
        bb: "提供BBCode特殊语法的快速复制。",
        html: "提供HTML格式的快速复制。",
        vid: "提供当前视频av号/BV号/CID号。请注意此项目不支持快速复制。",
        openGUI: "提供按钮快速进入设置选项。"
    };
    const idTn = {
        showAv: 2,
        showSAv: 2,
        showSBv: 2,
        showPn: 5,
        showCid: 2,
        showCate: 3,
        showPic: 1,
        showSize: 2,
        showMore: 1,
        showCTime: -4,
        showViews: -2,
        showDmk: -2,
        showTop: 0,
        showArgue: 1,
        openGUI: 1
    };
    let globalinfos = {};
    // https://stackoverflow.com/questions/10726638
    String.prototype.mapReplace = function (map) {
        var regex = [];
        for (var key in map)
            regex.push(key.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));
        return this.replace(new RegExp(regex.join('|'), "g"), function (word) {
            return map[word];
        });
    };

    // CSDN https://blog.csdn.net/namechenfl/article/details/91968396
    function numberFormat(value) {
        let param = {};
        let k = 10000,
            sizes = ['', '万', '亿', '万亿'],
            i;
        if (value < k) {
            param.value = value
            param.unit = ''
        } else {
            i = Math.floor(Math.log(value) / Math.log(k));
            param.value = ((value / Math.pow(k, i))).toFixed(2);
            param.unit = sizes[i];
        }
        return param;
    }

    function exec(code,binding=this){
        return (async function(){
            eval(code);
        }).bind(binding);
    }

    async function saveAllConfig() {
        for (let configKey of Object.keys(config)) {
            if (ignoredConfigKeys.includes(configKey)) continue;
            await GM_setValue(configKey, config[configKey]);
        }
        popNotify.success("配置保存成功");
    }

    async function initScript(flag = false) {
        for (let menuitem of Object.keys(menuId)) {
            if (menuId[menuitem] != -1) GM_unregisterMenuCommand(menuId[menuitem]);
        }
        for (let configKey of Object.keys(config)) {
            if (ignoredConfigKeys.includes(configKey)) continue;
            if (typeof (await GM_getValue(configKey)) === 'undefined') {
                await GM_setValue(configKey, config[configKey]);
            } else {
                config[configKey] = await GM_getValue(configKey);
            }
        }
        GM_registerMenuCommand("打开设置", async () => {
            await GUISettings();
        });
        CKTools.addStyle(`
            #bilibiliShowPN{
                max-width: ${config.pnmaxlength}em!important;
            }
        `, "showav_pnlen", "update", document.head);
        tryInject(flag);
    }

    async function playerReady() {
        let i = 150;
        while (--i > 0) {
            await wait(100);
            console.log('ShowAV waiting for player ready - loop')
            if (unsafeWindow.player?.isInitialized()??false) break;
        }
        console.log('ShowAV waiting for player ready - loop end')
        if (i < 0) return false;
        console.log('ShowAV waiting for player ready - wait visible')
        await waitForPageVisible();
        while (1) {
            await wait(200);
            console.log('ShowAV waiting for player controlls')
            if (document.querySelector(".bilibili-player-video-control-wrap, .bpx-player-control-wrap")) return true;
        }
    }

    async function waitForDom(q) {
        let i = 50;
        let dom;
        while (--i >= 0) {
            if (dom = document.querySelector(q)) break;
            await wait(100);
        }
        return dom;
    }

    function getUrlParam(key) {
        return (new URL(location.href)).searchParams.get(key);
    }

    function getOrNew(id, parent,) {
        let target = document.querySelector("#" + id);
        if (!target) {
            target = document.createElement("span");
            target.id = id;
            parent.appendChild(target);
        }
        return target;
    }

    async function getPlayerSeeks() {
        const video = await waitForDom(".bilibili-player-video video");
        let seconds = 0;
        if (video) {
            seconds = Math.floor(video.currentTime);
        }
        if (seconds == 0) {
            let fromParam = getUrlParam("t") || 0;
            return fromParam;
        } else return seconds;
    }

    function getHEVC(){
        return document.querySelector(".bilibili-player-video bwp-video")
    }

    async function registerVideoChangeHandler() {
        const video = await waitForDom(".bilibili-player-video video");
        const HEVCplayer = getHEVC();// must behind video loaded(no more waitfordom)
        let target = HEVCplayer || video;
        if(!target) return;
        const observer = new MutationObserver(async e => {
            if (e[0].target.src) {
                tryInject(true);
            }
        });
        observer.observe(target, { attribute: true, attributes: true, childList: false });
    }

    function getPageFromCid(cid, infos) {
        if (!cid || !infos || !infos.pages) return 1;
        let pages = infos.pages;
        if (pages.length == 1) return 1;
        let page;
        for (page of pages) {
            if (!page) continue;
            if (page.cid == cid) return page.page;
        }
        return 1;
    }

    async function feat_showCate() {
        const { av_root, infos } = this;
        const cate_span = getOrNew("bilibiliShowCate", av_root);
        //if (config.showCate) {
        cate_span.style.textOverflow = "ellipsis";
        cate_span.style.whiteSpace = "nowarp";
        cate_span.style.overflow = "hidden";
        cate_span.title = "分区: " + infos.tname;
        cate_span.innerText = "分区: " + infos.tname;
        //} else cate_span.remove();
    }

    async function feat_showStaticAv() {
        const func = feat_showAv.bind(this);
        func(true);
    }

    async function feat_showStaticBv() {
        const func = feat_showAv.bind(this);
        func(true, 'bv');
    }

    async function prepareData(infos,el=null){
        const defaultVid = el?el.innerText:'av'+infos.aid;
        const currpage = new URL(location.href);
        let part = infos.p==currpage.searchParams.get("p")
            ? infos.p
            : (currpage.searchParams.get("p") ? currpage.searchParams.get("p") : infos.p);
        let url = new URL(location.protocol + "//" + location.hostname + "/video/" + defaultVid);
        part == 1 || url.searchParams.append("p", part);
        let vidurl = new URL(url);
        let shorturl = new URL(location.protocol + "//b23.tv/" + defaultVid);
        let t = await getPlayerSeeks();
        if (t && t != "0" && t != ("" + config.vduration)) url.searchParams.append("t", t);  
        try {
            partinfo = infos.pages[infos.p - 1];
        } catch (e) {
            partinfo = infos.pages[0];
        }
        return {currpage,partinfo,url,vidurl,shorturl,part,t,infos}
    }
    
    async function getCopyItem(copyitem,infos,av_span=null){
        const {partinfo,url,vidurl,shorturl,part,t} = await prepareData(infos,av_span);
        switch (copyitem) {
            case "curr":
                return {
                    title: "当前地址",
                    content: vidurl,
                    type: "copiable"
                };
            case "currTime":
                return {
                    title: "含视频进度地址(仅在播放时提供)",
                    content: url,
                    type: "copiable"
                };
            case "short":
                return {
                    title: "短地址格式",
                    content: shorturl,
                    type: "copiable"
                };
            case "share":
                return {
                    title: "快速分享",
                    content: `${infos.title}_地址:${shorturl}`,
                    type: "copiable"
                };
            case "shareTime":
                return {
                    title: "快速分享(含视频进度)",
                    content: `${infos.title}_地址:${url}`,
                    type: "copiable"
                };
            case "md":
                return {
                    title: "MarkDown格式",
                    content: `[${infos.title}](${vidurl})`,
                    type: "copiable"
                };
            case "bb":
                return {
                    title: "BBCode格式",
                    content: `[url=${vidurl}]${infos.title}[/url]`,
                    type: "copiable"
                };
            case "html":
                return {
                    title: "HTML格式",
                    content: `<a href="${vidurl}">${infos.title}</a>`,
                    type: "copiable"
                };
            case "vid":
                return {
                    title: "视频编号",
                    content: `<div class="shoav_expandinfo">
                    <div>
                    AV号
                    <input class="shortinput" readonly value="av${infos.aid}" onclick="showav_fastcopy(this);" />
                    </div>
                    <div>
                    BV号
                    <input class="shortinput" readonly value="${infos.bvid}" onclick="showav_fastcopy(this);" />
                    </div>
                    <div>
                    资源CID
                    <input class="shortinput" readonly value="${infos.cid}" onclick="showav_fastcopy(this);" />
                    </div>
                </div>
                `,
                    type: "component",
                    copyaction: function(){
                        copy(this.av_span.innerText);
                        popNotify.success("已复制到剪贴板",this.av_span.innerText);
                    }
                };
            default:
                if (Object.keys(config.customcopyitems).includes(copyitem)) {
                    let ccopyitem = config.customcopyitems[copyitem];
                    let pat = ccopyitem.content ? ccopyitem.content : "无效内容";
                    pat = apiBasedVariablesReplacement(pat.mapReplace({
                        "%timeurl%": url,
                        "%vidurl%": vidurl,
                        "%shorturl%": shorturl,
                        "%seek%": t,
                        "%title%": infos.title,
                        "%av%": infos.aid,
                        "%bv%": infos.bvid,
                        "%cid%": infos.cid,
                        "%p%": part,
                        "%pname%": partinfo.part,
                        "'": "\'"
                    }));
                    return {
                        title: `(自定义) ${ccopyitem.title}`,
                        content: pat,
                        type: "copiable"
                    }
                }else return null;
        }
    };

    function apiBasedVariablesReplacement(txt='',infos=globalinfos){
        log("apiBasedVariablesReplacement",infos);
        const vars = [...txt.matchAll(/\%[a-zA-Z.]+\%/g)].map(k=>k[0]);
        if(vars.length==0) return txt;
        const map = {};
        for(let replaceVar of vars){
            const varName = replaceVar.substring(1,replaceVar.length-1);
            const apiResult = recursiveApiResolver(varName,infos);
            if(!apiResult) continue;
            map[replaceVar] = apiResult;
        }
        return txt.mapReplace(map);
    }

    function recursiveApiResolver(name,infos=globalinfos){
        const pargs = name.split(".").filter(arr=>arr.length);
        if([pargs[0],pargs[pargs.length-1]].includes("")) return null;
        let target = infos;
        for(let parg of pargs){
            if(target.hasOwnProperty(parg)){
                target = target[parg];
            }else return null;
        }
        return target.toString();
    }

    async function feat_showAv(force = false, mode = 'av'/* 'bv' */) {
        const { av_root, infos } = this;
        const av_span = getOrNew("bilibiliShowAV" + (force ? mode : ''), av_root);
        //if (config.showAv) {
        if (force) {
            if (mode == 'bv') {
                av_span.innerText = infos.bvid;
            } else {
                av_span.innerText = 'av' + infos.aid;
            }
        } else if (config.defaultAv)
            av_span.innerText = 'av' + infos.aid;
        else
            av_span.innerText = infos.bvid;
        av_span.style.overflow = "hidden";
        const video = await waitForDom("video");
        if (video) {
            config.vduration = Math.floor(video.duration);
        }
        let title = `左键单击复制,${force?'右键单击切换显示,':''}长按打开窗口`;
        if(config.copyitems.length){
            const firstCopyItem = config.copyitems[0];
            const firstInfo = await getCopyItem(firstCopyItem,globalinfos,av_span);
            if(firstInfo!==null){
                if(firstInfo.type=="copiable"||firstInfo.type=="component"){
                    av_span.setAttribute('title',title + '\n默认复制: '+firstInfo.title);
                }
            }else av_span.setAttribute('title',title + '\n没有默认复制行为');
        }else{
            av_span.setAttribute('title',title + '\n没有默认复制行为');
        }
        if (av_span.getAttribute("setup") != globalinfos.cid) {
            if (!force)
                av_span.oncontextmenu = e => {
                    if (e.target.innerText.startsWith('av')) e.target.innerText = infos.bvid;
                    else av_span.innerText = 'av' + infos.aid;
                    e.preventDefault();
                };
            let runningCfg = config.running['avspanHC'+(force ? mode : '')];
            if(runningCfg) runningCfg.uninstall();
            runningCfg = new CKTools.HoldClick(av_span);
            runningCfg.onclick(async e => {
                for (let copyitem of config.copyitems) {
                    const copyiteminfo = await getCopyItem(copyitem,globalinfos,av_span);
                    if(copyiteminfo===null) {
                        log(`[ADVCOPY] warning: unknown custom copy item id "${copyitem}", maybe should clean settings up.`);
                        continue;
                    }
                    if(copyiteminfo.type=="copiable"){
                        copy(copyiteminfo.content);
                        popNotify.success(copyiteminfo.title+"复制成功", copyiteminfo.content);
                        return;
                    }
                    else if(copyiteminfo.type=="component"){
                        if(typeof(copyiteminfo.copyaction)==="function"){
                            try{
                                const func = copyiteminfo.copyaction.bind({av_span});
                                func();
                            }catch(e){log(copyiteminfo,e);}
                        }else{
                            copy(copyiteminfo.copyaction.toString());
                            popNotify.success("已复制到剪贴板",copyiteminfo.copyaction.toString())
                        }
                        return;
                    }
                    else continue;
                }
                popNotify.error("快速复制失败","没有任何已启用的可用快速复制设定");
            });
            runningCfg.onhold(async e => {
                let modalcontent = `
                <style scoped>
                input:not(.shortinput){
                    width:100%;
                    display:block;
                }
                .shoav_expandinfo>div {
                    text-align: center;
                    flex: 1;
                }
                input.shortinput {
                    width: 7.8em;
                    text-align: center;
                }
                .CKTOOLS-modal-content>div>div{
                    width: 440px!important;
                }
                .shoav_expandinfo{
                    display: flex;
                    flex-direction: row;
                    flex-wrap: nowrap;
                    align-content: center;
                    justify-content: space-around;
                    align-items: stretch;
                }
                </style>
                <b>点击输入框可以快速复制</b><br>`;
                for (let copyitem of config.copyitems) {
                    const copyiteminfo = await getCopyItem(copyitem,globalinfos,av_span);
                    if(copyiteminfo.type=="copiable"){
                        modalcontent+=`<span class="copyitem-title">${copyiteminfo.title}</span><input readonly value="${copyiteminfo.content}" onclick="showav_fastcopy(this);" />`
                    }
                    else{
                        modalcontent+=copyiteminfo.content;
                    }
                }
                modalcontent += `<br><hr><a href="javascript:void(0)" onclick="showav_guisettings_advcopy()">⚙ 复制设置</a><br>
                <a href="https://github.com/CKylinMC/UserJS/issues/new?assignees=CKylinMC&labels=&template=feature-request.yaml&title=%5BIDEA%5D+ShowAV%E8%84%9A%E6%9C%AC%E9%A2%84%E8%AE%BE%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B7%E6%B1%82&target=[%E8%84%9A%E6%9C%AC%EF%BC%9A%E8%A7%86%E9%A2%91%E9%A1%B5%E9%9D%A2%E5%B8%B8%E9%A9%BB%E6%98%BE%E7%A4%BAAV/BV%E5%8F%B7]&desp=%E6%88%91%E5%B8%8C%E6%9C%9B%E6%B7%BB%E5%8A%A0%E6%96%B0%E7%9A%84%E9%A2%84%E8%AE%BE%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%EF%BC%8C%E5%A6%82%E4%B8%8B...">缺少你需要的格式?反馈来添加...</a>
                `;
                modalcontent+= closeButton().outerHTML;
                CKTools.modal.alertModal("高级复制", modalcontent, "关闭");
            });
            av_span.setAttribute("setup", globalinfos.cid);
            config.running['avspanHC'+(force ? mode : '')] = runningCfg;
        }
        //} else av_span.remove();
    }

    async function feat_showMore() {
        const { av_root } = this;
        const more_span = getOrNew("bilibiliShowMore", av_root);
        more_span.innerHTML = '⋯';
        more_span.title = "展示更多信息";
        more_span.style.cursor = "pointer";
        if (more_span.getAttribute("setup") != globalinfos.cid) {
            more_span.addEventListener('click', async e => {
                let part, videoData = globalinfos;
                try {
                    part = videoData.pages[globalinfos.p - 1];
                } catch (e) {
                    part = videoData.pages[0];
                }
                let currentPageName = part.part.length ? part.part : '';
                let currentPageNum;
                if (videoData.videos != 1) {
                    currentPageNum = `P ${globalinfos.p}/${videoData.videos}`;
                } else {
                    currentPageNum = "P 1/1";
                }
                CKTools.modal.alertModal("视频信息", `
            <style scoped>
                li{
                    line-height: 2em;
                }
            </style>
            <li>
                <b>AV号: </b>av${globalinfos.aid}
            </li>
            <li>
                <b>BV号: </b>${globalinfos.bvid}
            </li>
            <li>
                <b>CID: </b>${globalinfos.cid}
            </li>
            <li>
                <b>分P: </b>${currentPageNum}
            </li>
            <li>
                <b>P名: </b>${currentPageName}
            </li>
            <li>
                <b>长度: </b>${globalinfos.duration}s
            </li>
            <li>
                <b>投稿: </b>${timeago.format(globalinfos.ctime * 1000, 'zh_CN')}
            </li>
            <li>
                <b>分区: </b>${globalinfos.tname}
            </li>
            <li>
                <b>大小: </b>${globalinfos.dimension.width}x${globalinfos.dimension.height}
            </li>
            <li>
                <b>封面: </b><a href="${globalinfos.pic}" target="_blank">点击查看</a>
            </li>
            `, "确定");
            })
            more_span.setAttribute("setup", globalinfos.cid);
        }
    }

    async function feat_showCTime() {
        const { av_root, infos } = this;
        const ct_span = getOrNew("bilibiliShowCTime", av_root);
        ct_span.style.textOverflow = "ellipsis";
        ct_span.style.whiteSpace = "nowarp";
        ct_span.style.overflow = "hidden";
        const d = new Date(infos.pubdate * 1000);
        let txttime = timeago.format(infos.pubdate * 1000, 'zh_CN');
        let rawtime = `${d.getFullYear()}-${(d.getMonth() + 1) < 10 ? '0' + (d.getMonth() + 1) : d.getMonth() + 1}-${d.getDate() < 10 ? '0' + d.getDate() : d.getDate()} ${d.getHours() < 10 ? '0' + d.getHours() : d.getHours()}:${d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes()}:${d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds()}`;

        ct_span.title = "投稿时间 " + (config.defaultTextTime ? rawtime : txttime);
        ct_span.innerHTML = config.defaultTextTime ? txttime : rawtime
        if (config.hideTime) ct_span.innerHTML += `
        <style>
        .video-info-detail span:nth-child(3),.video-info-detail span.pupdate{
            display:none!important;
        }
        </style>`;
    }

    async function feat_showViews() {
        const { av_root, infos } = this;
        const v_span = getOrNew("bilibiliShowViews", av_root);
        v_span.style.textOverflow = "ellipsis";
        v_span.style.whiteSpace = "nowarp";
        v_span.style.overflow = "hidden";
        v_span.title = `播放量 ${infos.stat.view}`;
        v_span.innerHTML = (() => {
            const res = numberFormat(infos.stat.view);
            return `${res.value}${res.unit}播放`;
        })();
        v_span.innerHTML += `
        <style>
        .video-info-detail span.view{
            display:none!important;
        }
        </style>`;
    }

    async function feat_showDmk() {
        const { av_root, infos } = this;
        const dmk_span = getOrNew("bilibiliShowDmk", av_root);
        dmk_span.style.textOverflow = "ellipsis";
        dmk_span.style.whiteSpace = "nowarp";
        dmk_span.style.overflow = "hidden";
        dmk_span.title = `${infos.stat.danmaku}条弹幕`;
        dmk_span.innerHTML = (() => {
            const res = numberFormat(infos.stat.danmaku);
            return `${res.value}${res.unit}条弹幕`;
        })();
        dmk_span.innerHTML += `
        <style>
        .video-info-detail span.dm{
            display:none!important;
        }
        </style>`;
    }

    async function feat_showTop() {
        const { av_root, infos } = this;
        const top_span = getOrNew("bilibiliShowTop", av_root);
        top_span.style.textOverflow = "ellipsis";
        top_span.style.whiteSpace = "nowarp";
        top_span.style.overflow = "hidden";
        top_span.title = `全站最高排行第${infos.stat.his_rank}名`;
        top_span.innerHTML = ''
        top_span.innerHTML += `
        <style>
        .video-info-detail span.rank,.video-info-detail a.honor{
            display:none!important;
        }
        </style>`;
        if (infos.stat.his_rank === 0) {
            top_span.style.display = "none";
            setTimeout(() => {
                if (top_span.nextElementSibling) {
                    top_span.nextElementSibling.style.marginLeft = 0;
                }
            }, 100);
        } else {
            top_span.innerHTML += '📊 ' + infos.stat.his_rank;
        }
    }

    async function feat_showPic() {
        const { av_root, infos } = this;
        const pic_span = getOrNew("bilibiliShowPic", av_root);
        pic_span.style.textOverflow = "ellipsis";
        pic_span.style.whiteSpace = "nowarp";
        pic_span.style.overflow = "hidden";
        pic_span.title = "查看封面";
        pic_span.innerHTML = "🖼️";
        pic_span.style.cursor = "pointer";
        if (pic_span.getAttribute("setup") != globalinfos.cid) {
            config.running.picHC && config.running.picHC.uninstall();
            config.running.picHC = new CKTools.HoldClick(pic_span);
            config.running.picHC.onclick(() => {
                CKTools.modal.alertModal("封面", `
            <img src="${globalinfos.pic}" style="width:100%" onload="this.parentElement.style.width='100%'" />
            `, "关闭");
            });
            config.running.picHC.onhold(() => {
                open(globalinfos.pic);
            });
            pic_span.setAttribute("setup", globalinfos.cid);
        }
    }

    async function feat_showCid() {
        const { av_root, infos } = this;
        const cid_span = getOrNew("bilibiliShowCID", av_root);
        //if (config.showCid) {
        cid_span.style.textOverflow = "ellipsis";
        cid_span.style.whiteSpace = "nowarp";
        cid_span.style.overflow = "hidden";
        cid_span.title = "CID:" + infos.cid;
        cid_span.innerText = "CID:" + infos.cid;
        if (cid_span.getAttribute("setup") != globalinfos.cid) {
            config.running.cidspanHC && config.running.cidspanHC.uninstall();
            config.running.cidspanHC = new CKTools.HoldClick(cid_span);
            config.running.cidspanHC.onclick(() => {
                copy(currentPageName);
                popNotify.success("CID复制成功", globalinfos.cid);
            });
            config.running.cidspanHC.onhold(() => {
                CKTools.modal.alertModal("CID信息", `
                <input readonly style="width:440px" value="${globalinfos.cid}" />
                `, "关闭");
            });
            cid_span.setAttribute("setup", globalinfos.cid);
        }
        //} else cid_span.remove();
    }

    async function feat_showSize() {
        const { av_root, infos } = this;
        const size_span = getOrNew("bilibiliShowSize", av_root);
        //if (config.showCid) {
        size_span.style.textOverflow = "ellipsis";
        size_span.style.whiteSpace = "nowarp";
        size_span.style.overflow = "hidden";
        size_span.title = `${infos.dimension.width}x${infos.dimension.height}`;
        size_span.innerText = `${infos.dimension.width}x${infos.dimension.height}`;
        //} else cid_span.remove();
    }

    async function feat_openGUI() {
        const { av_root, infos } = this;
        const gui_span = getOrNew("bilibiliShowGUISettings", av_root);
        gui_span.innerHTML = "⚙";
        gui_span.title = "ShowAV 设置";
        gui_span.style.overflow = "hidden";
        gui_span.style.cursor = "pointer";
        gui_span.onclick = e => GUISettings();
    }

    async function feat_showArgue() {
        const { av_root, infos } = this;
        const argue_span = getOrNew("bilibiliShowArgue", av_root);
        const original = document.querySelector(".argue.item");
        if(!original) argue_span.style.display = "none";
        else argue_span.style.display = "block";
        argue_span.style.color = "rgb(255, 170, 44)";
        argue_span.innerHTML = "<i class='van-icon-info_warning'></i>";
        argue_span.title = (original&&original.title)||"警告";
        argue_span.style.overflow = "hidden";
    }

    async function feat_showPn() {
        const { av_root, infos } = this;
        const pn_span = getOrNew("bilibiliShowPN", av_root);
        //if (config.showPn) {
        const videoData = infos;
        if (!videoData) return;
        let part = {
            part: 'P' + infos.p
        }
        try {
            part = videoData.pages[infos.p - 1];
        } catch (e) {
            part = videoData.pages[0];
        }
        let currentPageName = part.part.length ? part.part : '';
        let currentPageNum;
        let delimiters;
        if (videoData.videos != 1) {
            currentPageNum = `P ${infos.p}/${videoData.videos}`;
            delimiters = ["\n", " "];
        } else {
            currentPageNum = "";
            delimiters = ["", ""];
        }
        pn_span.style.textOverflow = "ellipsis";
        pn_span.style.whiteSpace = "nowarp";
        pn_span.style.overflow = "hidden";
        pn_span.title = currentPageNum + delimiters[0] + currentPageName
        pn_span.innerText = currentPageNum + delimiters[1] + currentPageName;

        if (pn_span.getAttribute("setup") != globalinfos.cid) {
            config.running.pnspanHC && config.running.pnspanHC.uninstall();
            config.running.pnspanHC = new CKTools.HoldClick(pn_span);
            config.running.pnspanHC.onclick(() => {
                copy(currentPageName);
                popNotify.success("分P标题复制成功", currentPageName);
            });
            config.running.pnspanHC.onhold(() => {
                CKTools.modal.alertModal("分P标题", `
                <input readonly style="width:440px" value="${currentPageName}" />
                `, "关闭");
            });
            pn_span.setAttribute("setup", globalinfos.cid);
        }
        //} else pn_span.remove();
    }

    async function feat_custom(itemid){
        const { av_root, infos } = this;
        const that = this;
        that.window = unsafeWindow;
        const custom_span = getOrNew("bilibili_"+itemid, av_root);
        const {partinfo,url,vidurl,shorturl,part,t} = await prepareData(infos);
        const parseTxt = txt=>apiBasedVariablesReplacement(txt.mapReplace({
            "%timeurl%": url,
            "%vidurl%": vidurl,
            "%shorturl%": shorturl,
            "%seek%": t,
            "%title%": infos.title,
            "%av%": infos.aid,
            "%bv%": infos.bvid,
            "%cid%": infos.cid,
            "%p%": part,
            "%pname%": partinfo.part,
            "'": "\'"
        }));
        if(Object.keys(config.customComponents).includes(itemid)){
            const item = config.customComponents[itemid];
            let content = item.content;
            if(item.content.startsWith("js:")){
                content = item.content.replace("js:","");
            }
            else content = parseTxt(item.content);
            custom_span.style.overflow = "hidden";
            try{
                if(item.title.startsWith("js:")){
                    let itemtitle = item.title.substr(3);
                    custom_span.innerHTML = eval(parseTxt(itemtitle));
                }else 
                custom_span.innerHTML = parseTxt(item.title);
            }catch(e){
                custom_span.innerHTML = parseTxt(item.title);
            }
            custom_span.title = `自定义组件: ${item.title}\n长按管理自定义组件`;
            if(custom_span.getAttribute("setup")!=globalinfos.cid){
                custom_span.setAttribute("setup",globalinfos.cid);
                config.running[itemid] && config.running[itemid].uninstall();
                config.running[itemid] = new CKTools.HoldClick(custom_span);
                config.running[itemid].onclick(e => {
                    console.log(item.content)
                    if(item.content.startsWith("js:")){
                        log("executing:",content);
                        exec(content,that)();
                    }else{
                        copy(content);
                        popNotify.success("已复制"+item.title,content);
                    }
                });
                config.running[itemid].onhold(e=>{
                    GUISettings_customcomponents();
                })
            }
        }else{
            log("Errored while handling custom components:",k,"not found");
            custom_span.remove();
        }
    } 

    function getSideloadModules(){
        if(!unsafeWindow.ShowAVModules) return {};
        const mods = {};
        for(const modName of Object.keys(unsafeWindow.ShowAVModules)){
            const mod = unsafeWindow.ShowAVModules[modName];
            if(mod&&(typeof(mod.name)==='string')&&(typeof(mod.onload)==='function')&&(typeof(mod.onclick)==='function')&&(typeof(mod.onhold)==='function')){
                mods[modName] = mod;
            }
        }
        return mods;
    }

    function mappedSideloadModules(){
        const sideloads = getSideloadModules();
        const mods = {};
        for(const modName of Object.keys(sideloads)){
            mods['sideload_'+modName] = sideloads[modName];
        }
        return mods;
    }

    async function runSideloadModule(module,moduleInternalID = (Math.floor(Math.random()*10000))){
        let slm_span = null;
        try{
            const { av_root }=this;
            const onloadFn = module.onload.bind(this);
            const onclickFn = module.onclick.bind(this);
            const onholdFn = module.onhold.bind(this);
            const name = "showav_slm_" + moduleInternalID;
            slm_span = getOrNew(name, av_root);
            slm_span.innerHTML = '';
            slm_span.style.textOverflow = "ellipsis";
            slm_span.style.whiteSpace = "nowarp";
            slm_span.style.overflow = "hidden";
            slm_span.title = "模块:" + module.name;
            if(module.tip){
                if(typeof(module.tip)=='function') slm_span.title+='\n'+module.tip.bind(this)();
                else slm_span.title+='\n'+module.tip;
            }else if(module.description){
                slm_span.title+='\n'+module.description;
            }
            slm_span.appendChild(await onloadFn(slm_span));
            if (slm_span.getAttribute("setup") != globalinfos.cid) {
                config.running[name] && config.running[name].uninstall();
                config.running[name] = new CKTools.HoldClick(slm_span);
                config.running[name].onclick(onclickFn);
                config.running[name].onhold(onholdFn);
                slm_span.setAttribute("setup", globalinfos.cid);
            }
        }catch(e){
            log('[ERR]',module.name,e);
            (slm_span&&(slm_span instanceof HTMLElement)&&slm_span.remove());
        }
    }

    async function tryInject(flag) {
        console.log('ShowAV waiting for player ready')
        if (flag && config.orders.length === 0) return log('Terminated because no option is enabled.');
        if (!(await playerReady())) return log('Can not load player in time.');

        if (config.firstTimeLoad) {
            registerVideoChangeHandler();
            config.firstTimeLoad = false;
        }
        console.log('ShowAV start inject')
        
        CKTools.addStyle(`.video-container-v1 .copyright.item{display:none!important;}.video-container-v1 .video-info-detail{flex-wrap: wrap!important;}`,"showav_patchNewPlayer","update",document.head);

        if (config.forceGap) {
            CKTools.addStyle(`#bilibiliShowInfos{margin-left: 12px!important;}`,"showav_forceGapCss","update",document.head);
        }else{
            CKTools.addStyle(``,"showav_forceGapCss","update",document.head);
        }
        
        if(config.forceRemoveAllItem){
            CKTools.addStyle(`.video-container-v1 .video-info-detail>.item{display:none!important}.video-info-detail>span:not(#bilibiliShowInfos){display:none!important}`,"showav_hideall", "update", document.head);
        }else{
            CKTools.addStyle(``,"showav_hideall", "update", document.head);
        }
        
        if(config.nobreakline){
            CKTools.addStyle(`#bilibiliShowInfos{max-width: 100%;flex-wrap: nowrap!important;}`,"showav_nobreak", "update", document.head);
        }else{
            CKTools.addStyle(``,"showav_nobreak", "update", document.head);
        }

        if (location.pathname.startsWith("/medialist")) {
            let aid = unsafeWindow.aid;
            if (!aid) {
                log("Variable 'aid' is not available from unsafeWindow.");
                let activeVideo = await waitForDom(".player-auxiliary-playlist-item-active");
                aid = activeVideo.getAttribute("data-aid");
            }
            let apidata = await getAidAPI(aid);
            globalinfos = apidata.data;
        } else {
            if (flag)
            globalinfos = (await getAPI(unsafeWindow.vd?.bvid)).data;
            else globalinfos = unsafeWindow.vd;
        }
        globalinfos.p = getUrlParam("p") || getPageFromCid(unsafeWindow.cid, globalinfos);

        //const av_infobar = await waitForDom(".video-data");
        const av_infobar = await waitForDom(".video-info-detail");
        if (!av_infobar) return log('Can not load info-bar in time.');
        let av_root;
        if (config.showInNewLine) {
            av_root = getOrNew("bilibiliShowInfos", av_infobar.parentElement);
        } else {
            let rootel = document.querySelector("#bilibiliShowInfos");
            if (!rootel) {
                rootel = document.createElement("span");
                rootel.id = "bilibiliShowInfos";
                av_infobar.appendChild(rootel);
            }
            av_root = rootel;
        }
        //const av_root = getOrNew("bilibiliShowInfos",av_infobar);
        //const av_root = av_infobar;

        av_root.style.textOverflow = "ellipsis";
        av_root.style.whiteSpace = "nowrap!important";
        // av_root.style.overflow = "hidden";
        
        const that = {
            av_root, config, av_infobar, infos : globalinfos, CKTools, popNotify, tools:{
                copy, wait, waitForPageVisible, log, getPlayerSeeks, getHEVC, waitForDom, getOrNew, playerReady, variablesReplace:apiBasedVariablesReplacement
            },
        };

        const functions = {
            showAv: feat_showAv.bind(that),
            showSAv: feat_showStaticAv.bind(that),
            showSBv: feat_showStaticBv.bind(that),
            showCate: feat_showCate.bind(that),
            showCid: feat_showCid.bind(that),
            showPn: feat_showPn.bind(that),
            showPic: feat_showPic.bind(that),
            showSize: feat_showSize.bind(that),
            showMore: feat_showMore.bind(that),
            showCTime: feat_showCTime.bind(that),
            showDmk: feat_showDmk.bind(that),
            showViews: feat_showViews.bind(that),
            showTop: feat_showTop.bind(that),
            showArgue: feat_showArgue.bind(that),
            openGUI: feat_openGUI.bind(that),
            customDriver: feat_custom.bind(that)
        }

        const sideloads = mappedSideloadModules();

        config.orders.forEach(async k => {
            if(Object.keys(functions).includes(k)) await functions[k]();
            else if(Object.keys(sideloads).includes(k)) await runSideloadModule.bind(that)(sideloads[k], k);
            else{
                try{
                    await functions.customDriver(k);
                }catch(e){
                    log(`Custom component "${k}" throwed an error:`,e)
                };
            }
        });
        
        const titleobj = document.querySelector("span.tit");
        if(titleobj&&!titleobj.getAttribute("data-copy-action-registered")){
            titleobj.onclick = e => {
                let content = e.target.innerText;
                let tip = "已复制视频标题";
                if(unsafeWindow.getSelection().toString().length){
                    content = unsafeWindow.getSelection().toString();
                    tip = "已复制视频标题选中部分";
                }
                copy(content);
                popNotify.success(tip,content);
            }
            titleobj.setAttribute("data-copy-action-registered",true);
        }

        setupWarningAutoFolding();
    }

    function setupWarningAutoFolding(){
        //if(config.foldedWarningTip)
            /*CKTools.addStyle(
                "span.argue{margin-right: 10px !important;margin-left: 0 !important;overflow: hidden !important;width: 15px !important;text-overflow: clip !important;padding: 3px 4px !important}span.argue>i{margin-right: 5px!important}",
                "showav_foldWarningTip","update");*/
            CKTools.addStyle(
                "span.argue{display:none!important}",
                "showav_foldWarningTip","update");
        /*else
        CKTools.addStyle(
            "span.argue{margin-right: 10px !important;margin-left: 0 !important;}",
            "showav_foldWarningTip","update");*/
    }

    function closeButton(){
        const closebtn = document.createElement("div");
        closebtn.innerHTML = " × ";
        closebtn.style.position = "absolute";
        closebtn.style.top = "10px";
        closebtn.style.right = "10px";
        closebtn.style.cursor = "pointer";
        closebtn.style.fontWeight = 900;
        closebtn.style.fontSize = "larger";
        closebtn.setAttribute("onclick","CKTools.modal.hideModal()");
        return closebtn;
    }

    async function GUISettings() {
        if (CKTools.modal.isModalShowing()) {
            CKTools.modal.hideModal();
            await wait(300);
        }
        CKTools.modal.openModal("ShowAV / 设置", await CKTools.domHelper("div", async container => {
            container.style.alignItems = "stretch";
            container.style.minWidth = "300px";
            [
                closeButton(),
                await CKTools.domHelper("div", async tip => {
                    tip.style.lineHeight = "2em";
                    tip.style.fontSize = "small";
                    tip.style.fontStyle = "italic";
                    tip.style.width = "100%";
                    tip.innerText = "修改设置后记得点击保存哦";
                }),
                await CKTools.domHelper("li", async list => {
                    list.classList.add("showav_menuitem");
                    list.onclick = e => GUISettings_options();
                    [
                        await CKTools.domHelper("label", label => {
                            label.innerHTML = "功能选项";
                        }),
                        await CKTools.domHelper("span", label => {
                            label.innerHTML = "调整每个功能模块的单独选项";
                            label.style.marginLeft = "6px";
                        }),
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.classList.add("showav_menuitem");
                    list.onclick = e => GUISettings_components();
                    [
                        await CKTools.domHelper("label", label => {
                            label.innerHTML = "组件设置";
                        }),
                        await CKTools.domHelper("span", label => {
                            label.innerHTML = "启用/排序/自定义功能组件";
                            label.style.marginLeft = "6px";
                        }),
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.classList.add("showav_menuitem");
                    list.onclick = e => GUISettings_customcomponents(()=>GUISettings());
                    [
                        await CKTools.domHelper("label", label => {
                            label.innerHTML = "自定义组件设置";
                        }),
                        await CKTools.domHelper("span", label => {
                            label.innerHTML = "添加或删除自定义的信息栏组件";
                            label.style.marginLeft = "6px";
                        }),
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.classList.add("showav_menuitem");
                    list.onclick = e => GUISettings_advcopy(()=>GUISettings());
                    [
                        await CKTools.domHelper("label", label => {
                            label.innerHTML = "高级复制设置";
                        }),
                        await CKTools.domHelper("span", label => {
                            label.innerHTML = "自定义复制弹窗和默认动作";
                            label.style.marginLeft = "6px";
                        }),
                    ].forEach(e => list.appendChild(e));
                }),
            ].forEach(e => container.appendChild(e));
        }));
    }

    async function GUISettings_options() {
        if (CKTools.modal.isModalShowing()) {
            CKTools.modal.hideModal();
            await wait(300);
        }
        CKTools.modal.openModal("ShowAV / 设置 / 功能选项", await CKTools.domHelper("div", async container => {
            container.style.alignItems = "stretch";
            [
                closeButton(),
                await CKTools.domHelper("li", sectiontitle=>{
                    sectiontitle.innerText = "信息栏";
                    sectiontitle.className = "showav_settings_sectiontitle";
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_forcegap";
                            input.name = "showav_forcegap";
                            input.style.display = "none";
                            input.checked = config.forceGap;
                            input.addEventListener("change",e=>{
                                const label = document.querySelector("#showav_forcegaptip");
                                if(!label) return;
                                if(input.checked){
                                    label.innerHTML = "在第一个组件前<b>强制添加</b>间隔(点击切换)"
                                }else{
                                    label.innerHTML = "在第一个组件前<b>保持默认</b>间隔(点击切换)"
                                }
                            })
                        }),
                        await CKTools.domHelper("label", label => {
                            label.id = "showav_forcegaptip";
                            label.setAttribute('for', "showav_forcegap");
                            if(config.forceGap){
                                label.innerHTML = "在第一个组件前<b>强制添加</b>间隔(点击切换)"
                            }else{
                                label.innerHTML = "在第一个组件前<b>保持默认</b>间隔(点击切换)"
                            }
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `可选扩展信息栏和原版信息栏之间强制添加一个间隔,或保持默认`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_newline";
                            input.style.display = "none";
                            input.name = "showav_newline";
                            input.checked = config.showInNewLine;
                            input.addEventListener("change",e=>{
                                const label = document.querySelector("#showav_showinnewlinetip");
                                if(!label) return;
                                if(input.checked){
                                    label.innerHTML = "在<b>新的一行中</b>显示扩展信息栏(点击切换)"
                                }else{
                                    label.innerHTML = "在<b>当前位置后</b>显示扩展信息栏(点击切换)"
                                }
                            })
                        }),
                        await CKTools.domHelper("label", label => {
                            label.id = "showav_showinnewlinetip";
                            label.setAttribute('for', "showav_newline");
                            if(config.showInNewLine){
                                label.innerHTML = "在<b>新的一行中</b>显示扩展信息栏(点击切换)"
                            }else{
                                label.innerHTML = "在<b>当前位置后</b>显示扩展信息栏(点击切换)"
                            }
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `可选将扩展信息栏显示在下一行,尽量减少对原信息栏的修改`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.id = "showav_nobreakline_tip";
                            label.setAttribute('for', "showav_nobreakline");
                            if (config.nobreakline)
                                label.innerHTML = "默认 <b>禁止</b> 信息栏换行(点击切换)";
                            else
                                label.innerHTML = "默认 <b>允许</b> 信息栏换行";
                        }),
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_nobreakline";
                            input.name = "showav_nobreakline";
                            input.style.display = "none";
                            input.checked = config.nobreakline;
                            input.addEventListener('change', e => {
                                const label = document.querySelector("#showav_nobreakline_tip");
                                if (!label) return;
                                if (input.checked)
                                    label.innerHTML = "默认 <b>禁止</b> 信息栏换行(点击切换)";
                                else
                                    label.innerHTML = "默认 <b>允许</b> 信息栏换行(点击切换)";
                            })
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `是否要求信息栏尽量不换行,可能其中的文本会被截断。`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.id = "showav_foldvidwarn_tip";
                            label.setAttribute('for', "showav_foldvidwarn");
                            //if (config.foldedWarningTip)
                                //label.innerHTML = "默认 <b>隐藏</b> 视频警告文字(点击切换)";
                            //else
                                label.innerHTML = "默认 <b>隐藏</b> 视频警告文字";
                        }),
                        /*await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_foldvidwarn";
                            input.name = "showav_foldvidwarn";
                            input.style.display = "none";
                            input.checked = config.foldedWarningTip;
                            input.addEventListener('change', e => {
                                const label = document.querySelector("#showav_foldvidwarn_tip");
                                if (!label) return;
                                if (input.checked)
                                    label.innerHTML = "默认 <b>折叠</b> 视频警告文字(点击切换)";
                                else
                                    label.innerHTML = "默认 <b>展示</b> 视频警告文字(点击切换)";
                            })
                        }),*/
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `将视频警告(如 含有危险行为)折叠为图标,防止占用过多信息栏空间。<br>由于新版本播放器适配问题,默认隐藏原版提示。<br>请前往组件管理开启或关闭组件中的警告提示。`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.id = "showav_forceRemoveAllItem_tip";
                            label.setAttribute('for', "showav_forceRemoveAllItem");
                            if (config.forceRemoveAllItem)
                                label.innerHTML = "默认 <b>隐藏</b> 原版所有组件(点击切换)";
                            else
                                label.innerHTML = "默认 <b>不隐藏</b> 原版所有组件";
                        }),
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_forceRemoveAllItem";
                            input.name = "showav_forceRemoveAllItem";
                            input.style.display = "none";
                            input.checked = config.forceRemoveAllItem;
                            input.addEventListener('change', e => {
                                const label = document.querySelector("#showav_forceRemoveAllItem_tip");
                                if (!label) return;
                                if (input.checked)
                                    label.innerHTML = "默认 <b>隐藏</b> 原版所有组件(点击切换)";
                                else
                                    label.innerHTML = "默认 <b>不隐藏</b> 原版所有组件(点击切换)";
                            })
                        }),
                        // await CKTools.domHelper("div", div => {
                        //     div.style.paddingLeft = "20px";
                        //     div.style.color = "#919191";
                        //     div.innerHTML = `是否尽量隐藏B站原本信息条中的组件。仅对新版本播放器生效。`;
                        // })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", sectiontitle=>{
                    sectiontitle.innerText = "组件: 显示视频分P信息";
                    sectiontitle.className = "showav_settings_sectiontitle";
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.setAttribute('for', "showav_pnwid");
                            label.innerHTML = "字数限制";
                        }),
                        await CKTools.domHelper("input", input => {
                            input.type = "number";
                            input.id = "showav_pnwid";
                            input.name = "showav_pnwid";
                            input.setAttribute('min', 5);
                            input.setAttribute('max', 100);
                            input.style.width = "3em";
                            input.style.textAlign = "center";
                            input.style.marginLeft = "1em";
                            input.style.lineHeight = "1em";
                            input.value = config.pnmaxlength;
                            const updatePreview = () =>
                                wait(2).then(() => CKTools.addStyle(`
                                #showav_lengthpreview{
                                    max-width: ${input.value}em !important;
                                }
                                `, "showav_lengthpreviewcss", "update"));
                            input.addEventListener("input", updatePreview);
                            wait(300).then(updatePreview);
                        }),
                        await CKTools.domHelper("span", span => {
                            span.id = "showav_lengthpreview";
                            span.innerText = "这里是一条长度预览,你可以在这里查看长度限制的效果。好吧,我承认,后面这几个字只是为了凑个字数而已的。等等,你还要更长???相信我,你不会想要这么长的。";
                            span.style.maxWidth = "0em";
                            span.style.marginLeft = "30px";
                            span.style.textOverflow = "ellipsis";
                            span.style.whiteSpace = "nowarp";
                            span.style.overflow = "hidden";
                            span.style.whiteSpace = "nowrap";
                            span.style.display = "block";
                            span.style.fontSize = "12px";
                            span.style.transition = "all .5s";
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `限制分P信息显示时的最大长度`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", sectiontitle=>{
                    sectiontitle.innerText = "组件: 显示视频编号和高级复制";
                    sectiontitle.className = "showav_settings_sectiontitle";
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.id = "showav_defaultav_tip";
                            label.setAttribute('for', "showav_defaultav");
                            if (config.defaultAv)
                                label.innerHTML = "默认展示 <b>视频AV号</b> (点击切换)";
                            else
                                label.innerHTML = "默认展示 <b>视频BV号</b> (点击切换)";
                        }),
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_defaultav";
                            input.name = "showav_defaultav";
                            input.style.display = "none";
                            input.checked = config.defaultAv;
                            input.addEventListener('change', e => {
                                const label = document.querySelector("#showav_defaultav_tip");
                                if (!label) return;
                                if (input.checked)
                                    label.innerHTML = "默认展示 <b>视频AV号</b> (点击切换)";
                                else
                                    label.innerHTML = "默认展示 <b>视频BV号</b> (点击切换)";

                            })
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `仅对<b>可切换视频编号和高级复制</b>功能起效。<br>
                            可切换视频编号和高级复制组件可以使用右键临时切换显示内容。<br>
                            高级复制和快速复制默认读取对应组件显示内容,因此此处设置也会影响可切换视频编号组件的默认复制内容。`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", sectiontitle=>{
                    sectiontitle.innerText = "组件: 显示视频投稿时间";
                    sectiontitle.className = "showav_settings_sectiontitle";
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.id = "showav_hidetime_tip";
                            label.setAttribute('for', "showav_hidetime");
                            if (config.hideTime)
                                label.innerHTML = "<b>隐藏</b>原版发布时间 (点击切换)";
                            else
                                label.innerHTML = "<b>显示</b>原版发布时间 (点击切换)";
                        }),
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_hidetime";
                            input.name = "showav_hidetime";
                            input.style.display = "none";
                            input.checked = config.hideTime;
                            input.addEventListener('change', e => {
                                const label = document.querySelector("#showav_hidetime_tip");
                                if (!label) return;
                                if (input.checked)
                                    label.innerHTML = "<b>隐藏</b>原版发布时间 (点击切换)";
                                else
                                    label.innerHTML = "<b>显示</b>原版发布时间 (点击切换)";
                            })
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `仅在开启<b>视频投稿时间</b>功能时起效。<br>
                            插件添加的视频投稿时间可以选择显示两种时间格式,并且可排序。`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("li", async list => {
                    list.style.lineHeight = "2em";
                    [
                        await CKTools.domHelper("label", label => {
                            label.style.paddingLeft = "3px";
                            label.id = "showav_deftxttime_tip";
                            label.setAttribute('for', "showav_deftxttime");
                            if (config.defaultTextTime)
                                label.innerHTML = "显示<b>相对时间</b> (点击切换)";
                            else
                                label.innerHTML = "显示<b>完整时间戳</b> (点击切换)";
                        }),
                        await CKTools.domHelper("input", input => {
                            input.type = "checkbox";
                            input.id = "showav_deftxttime";
                            input.name = "showav_deftxttime";
                            input.style.display = "none";
                            input.checked = config.defaultTextTime;
                            input.addEventListener('change', e => {
                                const label = document.querySelector("#showav_deftxttime_tip");
                                if (!label) return;
                                if (input.checked)
                                    label.innerHTML = "显示<b>相对时间</b> (点击切换)";
                                else
                                    label.innerHTML = "显示<b>完整时间戳</b> (点击切换)";
                            })
                        }),
                        await CKTools.domHelper("div", div => {
                            div.style.paddingLeft = "20px";
                            div.style.color = "#919191";
                            div.innerHTML = `<b>相对时间格式:</b> 如  1周前<br><b>完整时间戳格式:</b> 如  2021-09-10 11:21:03<br>仅对<b>视频投稿时间</b>功能起效。`;
                        })
                    ].forEach(e => list.appendChild(e));
                }),
                await CKTools.domHelper("div", async btns => {
                    btns.style.display = "flex";
                    btns.style.alignItems = "flex-end";
                    btns.appendChild(await CKTools.domHelper("button", btn => {
                        btn.className = "CKTOOLS-toolbar-btns";
                        btn.innerHTML = "保存并返回";
                        btn.onclick = e => {
                            config.defaultAv = document.querySelector("#showav_defaultav").checked;
                            config.forceGap = document.querySelector("#showav_forcegap").checked;
                            config.hideTime = document.querySelector("#showav_hidetime").checked;
                            config.defaultTextTime = document.querySelector("#showav_deftxttime").checked;
                            config.forceRemoveAllItem = document.querySelector("#showav_forceRemoveAllItem").checked;
                            config.nobreakline = document.querySelector("#showav_nobreakline").checked;
                            config.pnmaxlength = parseInt(document.querySelector("#showav_pnwid").value);
                            config.showInNewLine = document.querySelector("#showav_newline").checked;
                            saveAllConfig();
                            CKTools.addStyle(``, "showav_lengthpreviewcss", "update");
                            CKTools.modal.hideModal();
                            let old = document.querySelector("#bilibiliShowInfos")
                            if (old) old.remove();
                            initScript(true);
                            wait(300).then(()=>GUISettings());
                        }
                    }))
                    btns.appendChild(await CKTools.domHelper("button", btn => {
                        btn.className = "CKTOOLS-toolbar-btns";
                        btn.innerHTML = "返回";
                        btn.style.background = "#ececec";
                        btn.style.color = "black";
                        btn.onclick = e => {
                            CKTools.addStyle(``, "showav_lengthpreviewcss", "update");
                            CKTools.modal.hideModal();
                            wait(300).then(()=>GUISettings());
                        }
                    }))
                })
            ].forEach(e => container.appendChild(e));
        }));
    }

    async function GUISettings_components() {
        if (CKTools.modal.isModalShowing()) {
            CKTools.modal.hideModal();
            await wait(300);
        }
        CKTools.modal.openModal("ShowAV / 设置 / 组件", await CKTools.domHelper("div", async container => {
            container.style.alignItems = "stretch";
            [
                closeButton(),
                // dragable code from ytb v=jfYWwQrtzzY
                await CKTools.domHelper("li", async list => {
                    const makeDragable = async id => {
                        return await CKTools.domHelper("div", draggable => {
                            draggable.className = "showav_dragableitem";
                            draggable.setAttribute("draggable", true);
                            draggable.setAttribute("data-id", id);
                            if (id.split("_")[0] === "custom") {
                                draggable.innerHTML = config.customComponents[id].title;
                                const node = document.createElement("div");
                                node.appendChild(document.createTextNode(config.customComponents[id].content));
                                draggable.appendChild(node);
                            }else if (id.split("_")[0] == "sideload") {
                                let ids = id.split("_");
                                ids.shift();
                                const modname = ids.join('_');
                                draggable.innerHTML = getSideloadModules()[modname].name;
                                const node = document.createElement("div");
                                node.appendChild(document.createTextNode(getSideloadModules()[modname].description??'外挂组件'));
                                draggable.appendChild(node);
                            } else {
                                draggable.innerHTML = txtCn[id];
                                draggable.innerHTML += `<div>${descCn[id]}</div>`;
                            }
                            let expanded = false;
                            draggable.addEventListener('dragstart', e => {
                                if (expanded) draggable.classList.remove('showav_expand');
                                draggable.classList.add('showav_dragging');
                                [...document.querySelectorAll('.showav_dragablediv')].forEach(e => e.classList.add('showav_child_dragging'))
                            })
                            draggable.addEventListener('dragend', e => {
                                if (expanded) draggable.classList.add('showav_expand');
                                draggable.classList.remove('showav_dragging');
                                [...document.querySelectorAll('.showav_child_dragging')].forEach(e => e.classList.remove('showav_child_dragging'))
                            })
                            draggable.addEventListener('click', e => {
                                expanded = draggable.classList.toggle('showav_expand');
                            })
                        })
                    };
                    function getClosestItem(container, y) {
                        const draggables = [...container.querySelectorAll(".showav_dragableitem:not(.showav_dragging)")];
                        return draggables.reduce((closest, child) => {
                            const box = child.getBoundingClientRect();
                            const offset = y - box.top - box.height / 2;
                            if (offset < 0 && offset > closest.offset) return { offset, element: child };
                            else return closest;
                        }, { offset: Number.NEGATIVE_INFINITY }).element;
                    }
                    function registerDragEvent(draggablediv) {
                        draggablediv.addEventListener('dragover', e => {
                            e.preventDefault();
                            const closestElement = getClosestItem(draggablediv, e.clientY);
                            const dragging = document.querySelector(".showav_dragging");
                            if (closestElement === null) {
                                draggablediv.appendChild(dragging);
                            } else {
                                draggablediv.insertBefore(dragging, closestElement);
                            }
                        })
                    }
                    [
                        await CKTools.domHelper("div", div => {
                            div.innerHTML = `<b>拖动下面的功能模块进行排序</b>`;
                        }),
                        await CKTools.domHelper("div", async enableddiv => {
                            enableddiv.innerHTML = `<b>启用</b>`;
                            enableddiv.className = "showav_dragablediv showav_enableddiv";
                            config.orders.forEach(async k => {
                                enableddiv.appendChild(await makeDragable(k));
                            });
                            registerDragEvent(enableddiv);
                        }),
                        await CKTools.domHelper("div", async disableddiv => {
                            disableddiv.innerHTML = `<b>禁用</b>`;
                            disableddiv.className = "showav_dragablediv showav_disableddiv";
                            const sideloads = getSideloadModules();
                            const sideloaditems = Object.keys(sideloads).map(k => 'sideload_'+k);
                            [...config.all,...sideloaditems].forEach(async k => {
                                if (config.orders.includes(k)) return;
                                disableddiv.appendChild(await makeDragable(k));
                            });
                            registerDragEvent(disableddiv);
                        }),
                        await CKTools.domHelper("div", async div => {
                            div.style.lineHeight = "2em";
                            div.style.cursor = "pointer";
                            div.style.color = "#1976d2";
                            div.style.fontWeight = "bold";
                            div.innerHTML = `功能设置`;
                            div.onclick = e => GUISettings_options();
                        }),
                        await CKTools.domHelper("div", async div => {
                            div.style.lineHeight = "2em";
                            div.style.cursor = "pointer";
                            div.style.color = "#1976d2";
                            div.style.fontWeight = "bold";
                            div.innerHTML = `管理自定义组件`;
                            div.onclick = e => GUISettings_customcomponents();
                        }),
                        await CKTools.domHelper("div", async div => {
                            div.style.lineHeight = "2em";
                            div.innerHTML = `<a href="https://github.com/CKylinMC/UserJS/issues/new?assignees=CKylinMC&labels=&template=feature-request.yaml&title=%5BIDEA%5D+ShowAV%E8%84%9A%E6%9C%AC%E6%98%BE%E7%A4%BA%E5%8A%9F%E8%83%BD%E8%AF%B7%E6%B1%82&target=[%E8%84%9A%E6%9C%AC%EF%BC%9A%E8%A7%86%E9%A2%91%E9%A1%B5%E9%9D%A2%E5%B8%B8%E9%A9%BB%E6%98%BE%E7%A4%BAAV/BV%E5%8F%B7]&desp=%E6%88%91%E5%B8%8C%E6%9C%9B%E6%B7%BB%E5%8A%A0%E6%96%B0%E7%9A%84%E5%BF%AB%E6%8D%B7%E5%B1%95%E7%A4%BA%E5%8A%9F%E8%83%BD%EF%BC%8C%E5%8A%9F%E8%83%BD%E7%9A%84%E4%BD%9C%E7%94%A8%E5%92%8C%E6%95%88%E6%9E%9C%E5%A6%82%E4%B8%8B...">需要添加其他的显示或快捷功能?反馈来添加...</a>`
                        }),
                        await CKTools.domHelper("div", async div => {
                            div.appendChild(await CKTools.domHelper("div", async btns => {
                                btns.style.display = "flex";
                                btns.appendChild(await CKTools.domHelper("button", btn => {
                                    btn.className = "CKTOOLS-toolbar-btns";
                                    btn.innerHTML = "保存并返回";
                                    btn.onclick = e => {
                                        const enableddiv = document.querySelector(".showav_enableddiv");
                                        const elements = enableddiv.querySelectorAll(".showav_dragableitem");
                                        let enabledArray = [];
                                        for (let element of [...elements]) {
                                            enabledArray.push(element.getAttribute('data-id'));
                                        }
                                        config.orders = enabledArray;
                                        saveAllConfig();
                                        CKTools.modal.hideModal();
                                        let old = document.querySelector("#bilibiliShowInfos")
                                        if (old) old.remove();
                                        initScript(true);
                                        wait(310).then(()=>GUISettings());
                                    }
                                }))
                                btns.appendChild(await CKTools.domHelper("button", btn => {
                                    btn.className = "CKTOOLS-toolbar-btns";
                                    btn.innerHTML = "返回";
                                    btn.style.background = "#ececec";
                                    btn.style.color = "black";
                                    btn.onclick = e => {
                                        CKTools.modal.hideModal();
                                        wait(310).then(()=>GUISettings());
                                    }
                                }))
                            }))
                        }),
                    ].forEach(e => list.appendChild(e));
                })
            ].forEach(e => container.appendChild(e));
        }));
    }

    async function GUISettings_advcopy(back=null) {
        if (CKTools.modal.isModalShowing()) {
            CKTools.modal.hideModal();
            await wait(300);
        }
        CKTools.modal.openModal("ShowAV / 设置 / 快速复制设置", await CKTools.domHelper("div", async container => {
            container.style.alignItems = "stretch";
            [
                closeButton(),
                // dragable code from ytb v=jfYWwQrtzzY
                await CKTools.domHelper("li", async list => {
                    const makeDragable = async id => {
                        return await CKTools.domHelper("div", draggable => {
                            draggable.className = "showav_dragableitem copyitem";
                            draggable.setAttribute("draggable", true);
                            draggable.setAttribute("data-id", id);
                            if (id.split("_")[0] === "custom") {
                                draggable.innerHTML = config.customcopyitems[id].title;
                                const node = document.createElement("div");
                                node.appendChild(document.createTextNode(config.customcopyitems[id].content));
                                draggable.appendChild(node);
                            } else {
                                draggable.innerHTML = txtCn[id];
                                draggable.innerHTML += `<div>${descCn[id]}</div>`;
                            }
                            draggable.removeItem = draggable.remove;
                            let expanded = false;
                            draggable.addEventListener('dragstart', e => {
                                if (expanded) draggable.classList.remove('showav_expand');
                                draggable.classList.add('showav_dragging');
                                [...document.querySelectorAll('.showav_dragablediv')].forEach(e => e.classList.add('showav_child_dragging'))
                            })
                            draggable.addEventListener('dragend', e => {
                                if (expanded) draggable.classList.add('showav_expand');
                                draggable.classList.remove('showav_dragging');
                                [...document.querySelectorAll('.showav_child_dragging')].forEach(e => e.classList.remove('showav_child_dragging'))
                            })
                            draggable.addEventListener('click', e => {
                                expanded = draggable.classList.toggle('showav_expand');
                            })
                        })
                    };
                    function getClosestItem(container, y) {
                        const draggables = [...container.querySelectorAll(".showav_dragableitem:not(.showav_dragging)")];
                        return draggables.reduce((closest, child) => {
                            const box = child.getBoundingClientRect();
                            const offset = y - box.top - box.height / 2;
                            if (offset < 0 && offset > closest.offset) return { offset, element: child };
                            else return closest;
                        }, { offset: Number.NEGATIVE_INFINITY }).element;
                    }
                    function registerDragEvent(draggablediv) {
                        draggablediv.addEventListener('dragover', e => {
                            e.preventDefault();
                            const closestElement = getClosestItem(draggablediv, e.clientY);
                            const dragging = document.querySelector(".showav_dragging");
                            if (closestElement === null) {
                                draggablediv.appendChild(dragging);
                            } else {
                                draggablediv.insertBefore(dragging, closestElement);
                            }
                        })
                    }
                    [
                        await CKTools.domHelper("div", div => {
                            div.innerHTML = `<b>拖动下面的功能模块进行排序</b>,第一个单项将成为默认快速复制项目。`;
                        }),
                        await CKTools.domHelper("div", async enableddiv => {
                            enableddiv.innerHTML = `<b>启用</b>`;
                            enableddiv.className = "showav_dragablediv showav_enableddiv";
                            config.copyitems.forEach(async k => {
                                enableddiv.appendChild(await makeDragable(k));
                            });
                            registerDragEvent(enableddiv);
                        }),
                        await CKTools.domHelper("div", async disableddiv => {
                            disableddiv.innerHTML = `<b>禁用</b>`;
                            disableddiv.className = "showav_dragablediv showav_disableddiv";
                            config.copyitemsAll.forEach(async k => {
                                if (config.copyitems.includes(k)) return;
                                disableddiv.appendChild(await makeDragable(k));
                            });
                            registerDragEvent(disableddiv);
                        }),
                        await CKTools.domHelper("li", async list => {
                            const makeItem = (copyitemid,focus=false) => {
                                const item = config.customcopyitems[copyitemid];
                                const node = document.createElement("li");
                                node.className = "copyitem";
                                if(focus){
                                    node.classList.add("actionpending");
                                    setTimeout(() => {
                                        node.classList.remove("actionpending");
                                        node.scrollIntoView();
                                    },20);
                                }
                                node.setAttribute("data-id", copyitemid);
                                node.innerHTML = `${item.title}<br>`;
                                node.style.borderRadius = "3px";
                                node.style.border = "solid 2px grey";
                                node.style.padding = "3px";
                                node.style.margin = "1px";
                                const smallp = document.createElement("p");
                                smallp.style.fontSize = "small";
                                smallp.style.color = "grey";
                                smallp.style.overflow = "hidden";
                                smallp.style.wordWrap = "nowarp";
                                smallp.appendChild(document.createTextNode(item.content));
                                node.appendChild(smallp);
                                node.removeItem = ()=>{
                                    node.classList.add("actionpending");
                                    setTimeout(()=>node.remove(),350);
                                };
                                node.onclick = async e => {
                                    if(node.classList.contains("preremove")){
                                        if (config.copyitems.includes(copyitemid)) {
                                            config.copyitems.splice(config.copyitems.indexOf(copyitemid), 1);
                                        }
                                        if (config.copyitemsAll.includes(copyitemid)) {
                                            config.copyitemsAll.splice(config.copyitemsAll.indexOf(copyitemid), 1);
                                        }
                                        delete config.customcopyitems[copyitemid];
                                        saveAllConfig();
                                        [...document.querySelectorAll(`.copyitem[data-id="${copyitemid}"]`)].forEach(e => e.removeItem());
                                    }else{
                                        [...document.querySelectorAll("li.copyitem.preremove")].forEach(e=>{
                                            e.classList.remove("preremove");
                                            try{if(e.clearTimer){
                                                clearTimeout(e.clearTimer);
                                            }}catch(e){};
                                        });
                                        node.classList.add("preremove");
                                        node.clearTimer = setTimeout(() => {
                                            node.classList.remove("preremove");
                                            node.clearTimer = null;
                                        },5000);
                                    }
                                }
                                return node;
                            };
                            [
                                await CKTools.domHelper("label", label => {
                                    label.style.paddingLeft = "3px";
                                    label.style.fontWeight = "bold";
                                    label.innerHTML = "添加自定义复制项目";
                                }),
                                await CKTools.domHelper("div", async div => {
                                    div.style.paddingLeft = "20px";
                                    [
                                        await CKTools.domHelper("input", async input => {
                                            input.id = "showav_customcopytitle";
                                            input.setAttribute("type", "text");
                                            input.style.width = "60%";
                                            input.style.margin = "6px 0 0 0";
                                            input.style.padding = "6px";
                                            input.style.borderRadius = "6px";
                                            input.style.border = "solid 2px grey";
                                            input.setAttribute("placeholder", "自定义标题");
                                        }),
                                        await CKTools.domHelper("input", async input => {
                                            input.id = "showav_customcopycontent";
                                            input.setAttribute("type", "text");
                                            input.style.width = "60%";
                                            input.style.margin = "6px 0 0 0";
                                            input.style.padding = "6px";
                                            input.style.borderRadius = "6px";
                                            input.style.border = "solid 2px grey";
                                            input.setAttribute("placeholder", "自定义内容");
                                        }),
                                        await CKTools.domHelper("div", div => {
                                            div.style.paddingLeft = "20px";
                                            div.style.color = "#919191";
                                            div.innerHTML = `变量提示<br><ul>
                                            <li>%timeurl% => 包含时间的完整地址</li>
                                            <li>%vidurl% => 视频纯净地址</li>
                                            <li>%shorturl% => 短地址</li>
                                            <li>%seek% => 当前视频播放秒数</li>
                                            <li>%title% => 视频标题</li>
                                            <li>%av% => av号</li>
                                            <li>%bv% => BV号</li>
                                            <li>%cid% => CID号</li>
                                            <li>%p% => 分P</li>
                                            <li>%pname% => 分P名</li>
                                            <li>%tname% => 分区名</li>
                                            </ul>`;
                                            div.style.maxHeight = '2rem';
                                            div.style.overflow = 'hidden';
                                            div.style.transition = 'all .3s';
                                            let expanded = false;
                                            div.onclick = e => {
                                                expanded = !expanded;
                                                if (expanded) {
                                                    div.style.maxHeight = "30rem";
                                                } else {
                                                    div.style.maxHeight = '2rem';
                                                }
                                            }
                                        }),
                                        await CKTools.domHelper("button", btn => {
                                            btn.className = "CKTOOLS-toolbar-btns";
                                            btn.innerHTML = "添加";
                                            btn.style.background = "#ececec";
                                            btn.style.color = "black";
                                            btn.onclick = async e => {
                                                const ccid = "custom_" + Math.random().toString(36).replace('.', '');
                                                const title = document.querySelector("#showav_customcopytitle").value;
                                                const content = document.querySelector("#showav_customcopycontent").value;
                                                if (title.trim().length < 1 || content.trim().length < 1) {
                                                    popNotify.warn("无法添加自定义项目", "标题或内容为空");
                                                    return;
                                                }
                                                config.customcopyitems[ccid] = { title, content };
                                                if (!config.copyitemsAll.includes(ccid)) config.copyitemsAll.push(ccid);
                                                saveAllConfig();
                                                const disablediv = document.querySelector(".showav_disableddiv");
                                                disablediv && disablediv.appendChild(await makeDragable(ccid));
                                                const customlist = document.querySelector("#showav_customitems");
                                                customlist && customlist.appendChild(makeItem(ccid,true));
                                                document.querySelector("#showav_customcopytitle").value = "";
                                                document.querySelector("#showav_customcopycontent").value = "";
                                            }
                                        })
                                    ].forEach(e => div.appendChild(e));
                                }),
                                await CKTools.domHelper("label", label => {
                                    label.style.paddingLeft = "3px";
                                    label.style.fontWeight = "bold";
                                    label.innerHTML = "已有自定义复制项目 <small>(点击移除)</small>";
                                }),
                                await CKTools.domHelper("ul", ul => {
                                    ul.style.paddingLeft = "3px";
                                    ul.id = "showav_customitems";
                                    for (let copyitemid of Object.keys(config.customcopyitems)) {
                                        ul.appendChild(makeItem(copyitemid));
                                    }
                                }),
                            ].forEach(e => list.appendChild(e));
                        }),
                        await CKTools.domHelper("div", async div => {
                            div.appendChild(await CKTools.domHelper("div", async btns => {
                                btns.style.display = "flex";
                                btns.appendChild(await CKTools.domHelper("button", btn => {
                                    btn.className = "CKTOOLS-toolbar-btns";
                                    btn.innerHTML = "保存并关闭";
                                    if(back!=null)
                                        btn.innerHTML = "保存并返回";
                                    btn.onclick = e => {
                                        const enableddiv = document.querySelector(".showav_enableddiv");
                                        const elements = enableddiv.querySelectorAll(".showav_dragableitem");
                                        let enabledArray = [];
                                        for (let element of [...elements]) {
                                            enabledArray.push(element.getAttribute('data-id'));
                                        }
                                        config.copyitems = enabledArray;
                                        saveAllConfig();
                                        initScript(true);
                                        if(back!=null) back();
                                        else CKTools.modal.hideModal();
                                    }
                                }))
                                btns.appendChild(await CKTools.domHelper("button", btn => {
                                    btn.className = "CKTOOLS-toolbar-btns";
                                    btn.innerHTML = "关闭";
                                    if(back!=null)
                                        btn.innerHTML = "返回";
                                    btn.onclick = e => {
                                        if(back!=null) back();
                                        else CKTools.modal.hideModal();
                                    }
                                }))
                            }))
                        }),
                    ].forEach(e => list.appendChild(e));
                })
            ].forEach(e => container.appendChild(e));
        }));
    }

    async function GUISettings_customcomponents(back=GUISettings_components) {
        if (CKTools.modal.isModalShowing()) {
            CKTools.modal.hideModal();
            await wait(300);
        }
        CKTools.modal.openModal("ShowAV / 设置 / 组件 / 自定义组件", await CKTools.domHelper("div", async container => {
            container.style.alignItems = "stretch";
            [
                closeButton(),
                // dragable code from ytb v=jfYWwQrtzzY
                await CKTools.domHelper("li", async list => {
                    [
                        await CKTools.domHelper("li", async list => {
                            const makeItem = (customitemid,focus=false) => {
                                const item = config.customComponents[customitemid];
                                const node = document.createElement("li");
                                node.className = "copyitem";
                                if(focus){
                                    node.classList.add("actionpending");
                                    setTimeout(() => {
                                        node.classList.remove("actionpending");
                                        node.scrollIntoView();
                                    },20);
                                }
                                node.setAttribute("data-id", customitemid);
                                node.innerHTML = `${item.title}<br>`;
                                node.style.borderRadius = "3px";
                                node.style.border = "solid 2px grey";
                                node.style.padding = "3px";
                                node.style.margin = "1px";
                                const smallp = document.createElement("p");
                                smallp.style.fontSize = "small";
                                smallp.style.color = "grey";
                                smallp.style.overflow = "hidden";
                                smallp.style.wordWrap = "nowarp";
                                smallp.appendChild(document.createTextNode(item.content));
                                node.appendChild(smallp);
                                node.removeItem = ()=>{
                                    node.classList.add("actionpending");
                                    setTimeout(()=>node.remove(),350);
                                };
                                node.onclick = async e => {
                                    if(node.classList.contains("preremove")){
                                        if (config.orders.includes(customitemid)) {
                                            config.orders.splice(config.orders.indexOf(customitemid), 1);
                                        }
                                        if (config.all.includes(customitemid)) {
                                            config.all.splice(config.all.indexOf(customitemid), 1);
                                        }
                                        delete config.customComponents[customitemid];
                                        saveAllConfig();
                                        [...document.querySelectorAll(`.copyitem[data-id="${customitemid}"]`)].forEach(e => e.removeItem());
                                    }else{
                                        [...document.querySelectorAll("li.copyitem.preremove")].forEach(e=>{
                                            e.classList.remove("preremove");
                                            try{if(e.clearTimer){
                                                clearTimeout(e.clearTimer);
                                            }}catch(e){};
                                        });
                                        node.classList.add("preremove");
                                        node.clearTimer = setTimeout(() => {
                                            node.classList.remove("preremove");
                                            node.clearTimer = null;
                                        },5000);
                                    }
                                }
                                return node;
                            };
                            [
                                await CKTools.domHelper("label", label => {
                                    label.style.paddingLeft = "3px";
                                    label.style.fontWeight = "bold";
                                    label.innerHTML = "添加组件";
                                }),
                                await CKTools.domHelper("div", async div => {
                                    div.style.paddingLeft = "20px";
                                    [
                                        await CKTools.domHelper("input", async input => {
                                            input.id = "showav_customcopntitle";
                                            input.setAttribute("type", "text");
                                            input.style.width = "60%";
                                            input.style.margin = "6px 0 0 0";
                                            input.style.padding = "6px";
                                            input.style.borderRadius = "6px";
                                            input.style.border = "solid 2px grey";
                                            input.setAttribute("placeholder", "自定义显示文本");
                                            input.addEventListener("keydown",e=>{
                                                const contentel = document.querySelector("#showav_customcopncontent");
                                                if(!contentel) return;
                                                if(contentel.getAttribute("data-sync")!=="1") return;
                                                setTimeout(()=>contentel.value = input.value,10);
                                            })
                                        }),
                                        await CKTools.domHelper("input", async input => {
                                            input.id = "showav_customcopncontent";
                                            input.setAttribute("type", "text");
                                            input.style.width = "60%";
                                            input.style.margin = "6px 0 0 0";
                                            input.style.padding = "6px";
                                            input.style.borderRadius = "6px";
                                            input.style.border = "solid 2px grey";
                                            input.title = `默认与自定义显示文本同步\n使用"js:"开头时将在点击时执行脚本`;
                                            input.setAttribute("data-sync","1");
                                            input.setAttribute("placeholder", "自定义复制内容或脚本");
                                            input.addEventListener("keydown",e=>input.setAttribute("data-sync","0"));
                                            input.addEventListener("keydown",async e=>{
                                                await wait(1);
                                                if(input.value.startsWith("js:")){
                                                    if(config.jssafetyWarning){
                                                        config.jssafetyWarning = !confirm(`安全性警告:\n\n"js:"开头的内容将作为JS脚本执行。\n\nJS脚本拥有您在当前页面的所有权限,请勿复制和执行未知来源的脚本!\n请仅在了解你输入的内容情况下使用此功能!\n\n如果不点击确定,则每次输入"js:"时都会弹出此消息。\n\n继续输入吗?`);
                                                        if(config.jssafetyWarning){
                                                            saveAllConfig();
                                                        }else{
                                                            input.value = input.value.replace("js:","");
                                                        }
                                                    }else{
                                                        document.querySelector("#showav_custom_txttip").style.display = "none";
                                                        document.querySelector("#showav_custom_jstip").style.display = "block";
                                                    }
                                                }else{
                                                    document.querySelector("#showav_custom_jstip").style.display = "none";
                                                    document.querySelector("#showav_custom_txttip").style.display = "block";
                                                }
                                            })
                                        }),
                                        await CKTools.domHelper("div", div => {
                                            div.style.paddingLeft = "20px";
                                            div.id = "showav_custom_txttip";
                                            div.style.color = "#919191";
                                            div.innerHTML = `变量提示<br><ul>
                                            <li>%timeurl% => 包含时间的完整地址</li>
                                            <li>%vidurl% => 视频纯净地址</li>
                                            <li>%shorturl% => 短地址</li>
                                            <li>%seek% => 当前视频播放秒数</li>
                                            <li>%title% => 视频标题</li>
                                            <li>%av% => av号</li>
                                            <li>%bv% => BV号</li>
                                            <li>%cid% => CID号</li>
                                            <li>%p% => 分P</li>
                                            <li>%pname% => 分P名</li>
                                            <li>%tname% => 分区名</li>
                                            </ul>`;
                                            div.style.maxHeight = '2rem';
                                            div.style.overflow = 'hidden';
                                            div.style.transition = 'all .3s';
                                            let expanded = false;
                                            div.onclick = e => {
                                                expanded = !expanded;
                                                if (expanded) {
                                                    div.style.maxHeight = "30rem";
                                                } else {
                                                    div.style.maxHeight = '2rem';
                                                }
                                            }
                                        }),
                                        await CKTools.domHelper("div", div => {
                                            div.style.paddingLeft = "20px";
                                            div.id = "showav_custom_jstip";
                                            div.style.display = "none";
                                            div.style.color = "#919191";
                                            div.innerHTML = `脚本提示<br><ul>
                                            <li>变量 infos => 视频信息</li>
                                            <li>方法 parseTxt("string") => 解析文本</li>
                                            <li>方法 copy("string") => 复制文字</li>
                                            </ul>`;
                                            div.style.maxHeight = '2rem';
                                            div.style.overflow = 'hidden';
                                            div.style.transition = 'all .3s';
                                            let expanded = false;
                                            div.onclick = e => {
                                                expanded = !expanded;
                                                if (expanded) {
                                                    div.style.maxHeight = "30rem";
                                                } else {
                                                    div.style.maxHeight = '2rem';
                                                }
                                            }
                                        }),
                                        await CKTools.domHelper("button", btn => {
                                            btn.className = "CKTOOLS-toolbar-btns";
                                            btn.innerHTML = "添加";
                                            btn.style.background = "#ececec";
                                            btn.style.color = "black";
                                            btn.onclick = async e => {
                                                const ccid = "custom_" + Math.random().toString(36).replace('.', '');
                                                const title = document.querySelector("#showav_customcopntitle").value;
                                                const content = document.querySelector("#showav_customcopncontent").value;
                                                if (title.trim().length < 1 || content.trim().length < 1) {
                                                    popNotify.warn("无法添加自定义组件", "标题或内容为空");
                                                    return;
                                                }
                                                config.customComponents[ccid] = { title, content };
                                                if (!config.all.includes(ccid)) config.all.push(ccid);
                                                saveAllConfig();
                                                const customlist = document.querySelector("#showav_customitems");
                                                customlist && customlist.appendChild(makeItem(ccid,true));
                                                document.querySelector("#showav_customcopntitle").value = "";
                                                document.querySelector("#showav_customcopncontent").value = "";
                                            }
                                        })
                                    ].forEach(e => div.appendChild(e));
                                }),
                                await CKTools.domHelper("label", label => {
                                    label.style.paddingLeft = "3px";
                                    label.style.fontWeight = "bold";
                                    label.innerHTML = "已有自定义组件 <small>(点击移除)</small>";
                                }),
                                await CKTools.domHelper("ul", ul => {
                                    ul.style.paddingLeft = "3px";
                                    ul.id = "showav_customitems";
                                    for (let itemid of Object.keys(config.customComponents)) {
                                        ul.appendChild(makeItem(itemid));
                                    }
                                }),
                            ].forEach(e => list.appendChild(e));
                        }),
                        await CKTools.domHelper("label", label => {
                            label.style.width = "100%";
                            label.style.display = "block";
                            label.style.textAlign = "center";
                            label.innerHTML = "此页面内容自动保存";
                        }),
                        await CKTools.domHelper("div", async div => {
                            div.appendChild(await CKTools.domHelper("div", async btns => {
                                btns.style.display = "flex";
                                btns.appendChild(await CKTools.domHelper("button", btn => {
                                    btn.className = "CKTOOLS-toolbar-btns";
                                    btn.innerHTML = "返回";
                                    btn.onclick = e => {
                                        saveAllConfig();
                                        back();
                                    }
                                }))
                                btns.appendChild(await CKTools.domHelper("button", btn => {
                                    btn.className = "CKTOOLS-toolbar-btns";
                                    btn.innerHTML = "关闭";
                                    btn.onclick = e => {
                                        saveAllConfig();
                                        CKTools.modal.hideModal();
                                    }
                                }))
                            }))
                        }),
                    ].forEach(e => list.appendChild(e));
                })
            ].forEach(e => container.appendChild(e));
        }));
    }

    const copy = function copy(text) {
        if (!navigator.clipboard) {
            prompt('请手动复制', text);
            return;
        }
        navigator.clipboard.writeText(text).then(function () {
            log('Copy OK');
        }, function (err) {
            log('Auto Copy Failed:', err);
            prompt('请手动复制', text);
        });
    }

    unsafeWindow.showav_fastcopy = (el) => {
        copy(el.value);
        popNotify.success("复制成功", el.value);
    }

    unsafeWindow.showav_guisettings = GUISettings;
    unsafeWindow.showav_guisettings_advcopy = GUISettings_advcopy;
    unsafeWindow.showav_guisettings_customcomponents = GUISettings_customcomponents;

    CKTools.modal.initModal();
    CKTools.modal.hideModal();
    const blockwin = CKTools.get("#CKTOOLS-blockWindow");
    blockwin&&(blockwin.onclick = CKTools.modal.hideModal);
    CKTools.addStyle(`
    #CKTOOLS-modal{
        width: fit-content!important;
        max-width: 80%!important;
    }
    .CKTOOLS-modal-content li label b {
        color: #1976d2!important;
    }
    .showav_menuitem{
        line-height: 2em;
        width: 100%;
        transition: all .3s;
        cursor: pointer;
    }
    .showav_menuitem:hover{
        transform: translateX(6px);
    }
    .showav_menuitem>label{
        color: #1976d2;
        font-weight: bold;
        font-size: large;
        display: block;
    }
    .showav_dragablediv {
        width: 400px;
        max-width: 80%;
        max-width: 400px;
        min-height: 60px;
        border: dotted;
        border-radius: 8px;
        padding: 12px;
        margin: 5px;
        position: relative;
        margin: 3px auto;
    }
    .showav_dragableitem {
        background: white;
        margin: 3px;
        padding: 3px;
        border-radius: 4px;
        border: solid #bdbdbd 2px;
        color: black;
        transition: all .3s;
        max-height: 2rem;
    }
    .showav_dragableitem.showav_expand {
        max-height: 8rem;
    }
    .showav_dragableitem>div {
        color: #adadad;
        margin: 0 6px;
        opacity: 0;
        transition: all .3s ease-in-out;
        transform: translateX(-10px);
        font-size: small;
        overflow: hidden;
        max-height: 0;
    }
    .showav_dragableitem.showav_expand>div{
        transform: translateX(0px);
        max-height: 8rem;
        opacity: 1;
    }
    .showav_dragableitem::before {
        content: "⋮⋮";
        float: right;
        font-size: xx-small;
        padding: 3px;
        color: #bbbbbb !important;
    }
    .showav_dragging {
        background: grey;
        color: white;
        border: solid #515050 2px;
        transform: scale(1.1);
        transition: all .3s;
    }
    .showav_dragablediv:not(.showav_child_dragging) .showav_dragableitem:hover:not(.showav_dragging) {
        background: grey;
        color: white;
        border: solid #515050 2px;
        transform: scale(1.03);
        transition: all .3s;
    }
    .showav_dragablediv>b {
        position: absolute;
        left: -4rem;
    }
    .showav_disableddiv .showav_dragableitem {
        color: #a9a8a8;
    }
    .showav_enableddiv{
        background: #dcedc8;
    }
    .showav_disableddiv{
        background: #ffcdd2;
    }
    .showav_settings_sectiontitle{
        display: block;
        width: 100%;
        font-weight: bold;
        color: #1976d2;
        border-bottom: 2px solid #1976d2;
        margin: 18px 0 3px 0;
    }
    .showav_settings_sectiontitle:first-of-type{
        margin-top: 0!important;
    }
    #showav_newlinetip{
        font-size: small;
        display: inline-block;
        padding: 0 2px;
        line-height: 1.5em;
        border-radius: 3px;
        background: #ff5722;
        color: white;
        overflow: hidden;
        transition: all .3s;
        opacity: 0;
    }
    #showav_newlinetip.showav_newlinetip_ok{
        background: #0288d1!important;
    }
    #showav_newlinetip.showav_newlinetip{
        opacity: 1;
    }
    ul#showav_customitems{
        min-height: 60px;
    }
    ul#showav_customitems::after{
        content:"目前没有自定义项目。当添加了自定义项目时,可以在这里删除。";
        padding: 6px;
        display: block;
        opacity: 0;
        transition: all.3s;
        overflow: hidden;
        height: 0px;
    }
    ul#showav_customitems:empty::after{
        opacity: 1;
        height: 4rem!important;
    }
    li.copyitem{
        transition: all 0.3s;
        opacity: 1;
        max-height: 8em;
    }
    li.copyitem.preremove{
        color: red!important;
        border-color: red!important;
    }
    li.copyitem::after{
        transition: all 0.3s;
        line-height: 0px!important;
        content:"再次点击以移除";
        display: block;
        overflow: hidden;
        color: red!important;
        opacity: 0;
        max-height: 8em;
    }
    li.copyitem.actionpending{
        transition: all 0.5s;
        padding: 0px!important;
        border-width: 0px;
        margin-top: 0px!important;
        margin-bottom: 0px!important;
        max-height: 0em!important;
        opacity: 0;
    }
    li.copyitem.preremove::after{
        line-height: 2rem!important;
        opacity: 1;
    }
    #bilibiliShowInfos {
        display: flex;
        column-gap: 12px;
        flex-wrap: wrap;
    }
    `, 'showav_dragablecss', "unique", document.head);

    CKTools.addStyle(`
    .video-info-detail-list{
        display: none!important;
    }
    #bilibiliShowInfos{
        white-space: nowrap !important;
    }
    #CKTOOLS-modal li, #CKTOOLS-modal ul{
        list-style: none !important;
    }
    `,'showav_css_patch', 'unique', document.head);

    console.log('ShowAV loaded')

    initScript(false);
})();