[Bilibili] 关注管理器

快速排序和筛选你的关注列表,一键取关不再关注的UP等

当前为 2023-11-07 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         [Bilibili] 关注管理器
// @namespace    ckylin-bilibili-foman
// @version      0.2.20
// @description  快速排序和筛选你的关注列表,一键取关不再关注的UP等
// @author       CKylinMC
// @supportURL   https://github.com/CKylinMC/UserJS
// @require      https://greasyfork.org/scripts/429720-cktools/code/CKTools.js?version=1034581
// @include      http://space.bilibili.com/*
// @include      https://space.bilibili.com/*
// @connect      api.bilibili.com
// @grant        GM_registerMenuCommand
// @grant        GM_getResourceText
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_removeValue
// @grant        unsafeWindow
// @license      GPL-3.0-only
// @compatible   chrome 80+
// @compatible   firefox 74+
// ==/UserScript==
(function () {
    'use strict';
    const s = {
        get(key, def) {
            const val = GM_getValue('autoExtendInfo');
            if (typeof (val) == 'undefined' || val === null) return def;
            return val;
        },
        set(key, val) { 
            GM_setValue('autoExtendInfo', val);
        },
        del(key) {
            if (typeof (GM_removeValue) == 'function') GM_removeValue(key);
            else GM_setValue(key, undefined);
        }
    };
    const datas = {
        status: 0,
        total: 0,
        fetched: 0,
        pages: 0,
        followings: [],
        mappings: {},
        dommappings: {},
        checked: [],
        tags: {},
        self: 0,
        isSelf: false,
        currUid: 0,
        fetchstat: "OK",
        currInfo: {
            black: -1,
            follower: -1,
            following: -1,
            mid: -1,
            whisper: -1,
        },
        preventUserCard: false,
        settings: {
            get autoExtendInfo() {
                return s.get('autoExtendInfo', true);
            },
            set autoExtendInfo(val) {
                s.set('autoExtendInfo', val);
            },
            get lazyRenderForList() {
                return s.get('lazyRenderForList', true);
            },
            set lazyRenderForList(val) {
                s.set('lazyRenderForList', val);
            },
            get batchOperationDelay() {
                return s.get('batchOperationDelay', .5);
            },
            set batchOperationDelay(val) {
                s.set('batchOperationDelay', val);
            },
        }
    };
    const cfg = {
        debug: false,
        retrial: 3,
        enableNewModules: false,
        VERSION: "0.2.18 Beta",
        infobarTemplate: ()=>`共读取 ${datas.fetched} 条关注`,
        titleTemplate: () => `<h1>关注管理器 FoMan <small>v${cfg.VERSION} ${cfg.debug ? "debug" : ""}</small></h1>`,

        // Turn this on will abort all alerts.
        I_KNOW_WHAT_IM_DOING: false
    }
    const get = q => document.querySelector(q);
    const getAll = q => document.querySelectorAll(q);
    const wait = t => new Promise(r => setTimeout(r, t));
    const batchDelay = async () => await wait(datas.settings.batchOperationDelay*1000);
    const log = (...m) => cfg.debug && console.log('[FoMan]', ...m);
    const mdi = (name, asHTML=true, px = '10', extras = []) => {
        const i = CKTools.domHelper('i', {
            classnames: ['mdi',`mdi-${name}`, `mdi-${px}px`, ...extras],
            text:' '
        });
        return asHTML ? i.outerHTML : i;
    };
    const getSelfId = async () => {
        let stat = unsafeWindow.UserStatus;
        let retrial = 20;
        while (stat === null || stat === undefined) {
            if (--retrial < 0) return 0;
            log("Waiting for userstatus...")
            await wait(200);
        }
        if (!stat.userInfo.isLogin) {
            log("NOT LOGIN");
            return -1
        }
        log("User:", stat.userInfo.mid, stat.userInfo);
        return stat.userInfo.mid;
    };
    async function copy(txt = '') {
        try {
            await navigator.clipboard.writeText(txt);
            return true;
        } catch (e) {
            return false;
        }
    }
    function download(filename, text) {
        var element = document.createElement('a');
        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);
      
        element.style.display = 'none';
        document.body.appendChild(element);
      
        element.click();
      
        document.body.removeChild(element);
      }
    const makeDom = async (domname, func = () => {
    }) => {
        if(CKTools.domHelper) return CKTools.domHelper(domname, func);
        const d = document.createElement(domname);
        if(typeof(func)=='function') func.constructor.name=='AsyncFunction' ? await func(d) : func(d);
        return d;
    };
    const isHardCoreMember = d => d.is_senior_member ===1;
    const isFans = d => d.attribute === 6;
    const isWhisper = d => d.attribute === 1;
    const isNearly = d => {
        const nearly = (new Date).getTime() - (60 * 60 * 24 * 7 * 4 * 3 * 1000);
        return parseInt(d + "000") > nearly;
    }
    const isLongAgo = (d) => {
        const loneAgo = (new Date).getTime() - (60 * 60 * 24 * 7 * 4 * 12 * 2 * 1000);
        return parseInt(d + "000") < loneAgo;
    }
    /* StackOverflow 10730362 */
    const getCookie = (name) => {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
    }
    const getCSRFToken = () => getCookie("bili_jct");
    const getBgColor = () => {/*兼容blbl进化的夜间模式*/
        try {
            let color = getComputedStyle(document.body).backgroundColor;
            if (color === "rgba(0, 0, 0, 0)") return "white";
            else return color;
        } catch (e) {
            return "white"
        }
    }

    const getCurrentUid = async () => {
        setInfoBar("正在查询当前用户UID");
        let paths = location.pathname.split('/');
        if (paths.length > 1) {
            return paths[1];
        } else throw "Failed to get current ID";
    };
    const getHeaders = () => {
        return {
            "user-agent": unsafeWindow.navigator.userAgent,
            "cookie": unsafeWindow.document.cookie.split('; ').map(it=>it.split("=")).map(it=>it.map(i=>i.match(/[^\x00-\x7F]/gm)?encodeURIComponent(i):i)).map(it=>it.join("=")).join(", "),
            "origin": "space.bilibili.com",
            "referer": "https://www.bilibili.com/"
        }
    };
    const getUInfoURL = uid => `https://api.bilibili.com/x/space/acc/info?mid=${uid}`;
    const getGroupURL = () => `https://api.bilibili.com/x/relation/tags`;
    const getWhispersURL = (pn,ps=50) => `https://api.bilibili.com/x/relation/whispers?pn=${pn}&ps=${ps}&order=desc&order_type=attention`;
    const getFetchURL = (uid, pn) => `https://api.bilibili.com/x/relation/followings?vmid=${uid}&pn=${pn}&ps=50&order=desc&order_type=attention`;
    const getUnfolURL = () => `https://api.bilibili.com/x/relation/modify`;
    const getFollowURL = () => `https://api.bilibili.com/x/relation/batch/modify`;
    const getLatestVidURL = uid => `https://api.bilibili.com/x/space/arc/search?mid=${uid}&ps=1&pn=1`
    const getSubInfoURL = uid => `https://api.bilibili.com/x/relation/stat?vmid=${uid}`;
    const getCreateGroupURL = ()=> `https://api.bilibili.com/x/relation/tag/create`;
    const getRenameGroupURL = ()=> `https://api.bilibili.com/x/relation/tag/update`;
    const getRemoveGroupURL = ()=> `https://api.bilibili.com/x/relation/tag/del`;
    const getMoveToGroupURL = ()=> `https://api.bilibili.com/x/relation/tags/addUsers`;
    const getCopyToGroupURL = ()=> `https://api.bilibili.com/x/relation/tags/copyUsers`;
    const getDynamicURL = (selfid,hostid)=>`https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?visitor_uid=${selfid}&host_uid=${hostid}&offset_dynamic_id=0&need_top=1&platform=web`;
    const getRequest = path => new Request(path, {
        method: 'GET',
        headers: getHeaders(),
        credentials: "include"
    });
    const getPostRequest = (path, body = null) => new Request(path, {
        method: 'POST',
        headers: getHeaders(),
        credentials: "include",
        body
    });
    const cacheGroupList = async () => {
        setInfoBar("正在获取分组信息...");
        try {
            const jsonData = await (await fetch(getRequest(getGroupURL()))).json();
            if (jsonData && jsonData.code === 0) {
                datas.tags = [];
                for (let tag of jsonData.data) {
                    datas.tags[tag.tagid] = tag;
                }
                return true;
            } else {
                log(jsonData);
                return false;
            }
        } catch (err) {
            log(err);
            return false;
        }
    };
    const createGroup = async (tagname) => {
        setInfoBar(`正在创建新的分组"${tagname}"...`);
        try {
            const jsonData = await (await fetch(
                getPostRequest(getCreateGroupURL(),
                new URLSearchParams({
                    tag: tagname,
                    csrf: getCSRFToken()
            }))));
            if (jsonData.code === 0) return true;
            else throw new Error(jsonData.message);
        }catch(err){
            log(err);
            return false;
        } finally {
            await cacheGroupList();
            CacheManager.save();
        }
    }
    const renameGroup = async (tagid, tagname) => {
        setInfoBar(`正在修改分组为"${tagname}"...`);
        try {
            const jsonData = await (await fetch(
                getPostRequest(getRenameGroupURL(),
                new URLSearchParams({
                    tagid,
                    name: tagname,
                    csrf: getCSRFToken()
            }))));
            if (jsonData.code === 0) return true;
            else throw new Error(jsonData.message);
        }catch(err){
            log(err);
            return false;
        } finally {
            await cacheGroupList();
            CacheManager.save();
            await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
            resetInfoBar();
        }
    }
    const removeGroup = async (tagid) => {
        setInfoBar(`正在移除分组"${tagid}"...`);
        try {
            const jsonData = await (await fetch(
                getPostRequest(getRemoveGroupURL(),
                new URLSearchParams({
                    tagid,
                    csrf: getCSRFToken()
            }))));
            if (jsonData.code === 0) return true;
            else throw new Error(jsonData.message);
        }catch(err){
            log(err);
            return false;
        } finally {
            await cacheGroupList();
            CacheManager.save();
            await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
            resetInfoBar();
        }
    }
    const moveUserToDefaultGroup = uids => moveUserToGroup(uids, [0]);//unused
    const moveUserToGroup = async (uids, tagids) => {
        setInfoBar(`正在移动用户分组...`);
        try {
            const jsonData = await (await fetch(
                getPostRequest(getMoveToGroupURL(),
                new URLSearchParams({
                    fids: uids.join(','),
                    tagids: tagids.join(','),
                    csrf: getCSRFToken()
            }))).json());
            if (jsonData.code === 0) {
                for (let uid of uids) {
                    const u = parseInt(uid);
                    let targetUser;
                    if (datas.mappings.includes(u)) {
                        targetUser = datas.mappings[u];
                    } else if (datas.mappings.includes(uid)) {
                        targetUser = datas.mappings[uid];
                    } else {
                        //TODO: need reload
                    }
                    targetUser.tag = tagids.map(i=>parseInt(i))
                }
                return true;
            }
            else throw new Error(jsonData.message);
        }catch(err){
            log(err);
            return false;
        }
    }
    const copyUserToGroup = async (uids, tagids) => {
        setInfoBar(`正在添加用户分组...`);
        try {
            const jsonData = await (await fetch(
                getPostRequest(getCopyToGroupURL(),
                new URLSearchParams({
                    fids: uids.join(','),
                    tagids: tagids.join(','),
                    csrf: getCSRFToken()
            }))).json());
            log(jsonData,jsonData.code,jsonData.code===0);//TODO:BUG
            if (jsonData.code == 0) {
                for (let uid of uids) {
                    const u = parseInt(uid);
                    let targetUser;
                    if (datas.mappings.includes(u)) {
                        targetUser = datas.mappings[u];
                    } else if (datas.mappings.includes(uid)) {
                        targetUser = datas.mappings[uid];
                    } else {
                        //TODO: need reload
                    }
                    targetUser.tag = (function () {
                        const tag = [];
                        for (const tid of [...targetUser.tag, ...tagids]) {
                            const ntid = parseInt(tid);
                            if(!tag.includes(ntid)) tag.push(ntid)
                        }
                        return tag;
                    })()
                }
                return true;
            }
            else throw new Error(jsonData.message);
        }catch(err){
            log(err);
            return false;
        }
    }
    const getCurrSubStat = async uid => {
        try {
            const jsonData = await (await fetch(getRequest(getSubInfoURL(uid)))).json();
            if (jsonData && jsonData.code === 0) {
                return jsonData.data;
            } else {
                log("Failed fetch self info: unexpected response", jsonData);
                return null;
            }
        } catch (e) {
            log("Failed fetch self info: error found", e);
            return null;
        }
    }
    const getLatestVideoPublishDate = async uid => {
        try {
            const jsonData = await (await fetch(getRequest(getLatestVidURL(uid)))).json();
            if (jsonData && jsonData.code === 0) {
                if (
                    jsonData.data
                    && jsonData.data.list
                ) {
                    let mostCates = "";
                    if (jsonData.data.list.tlist.length !== 0) {
                        let max = 0, name="";
                        for(let itemname of Object.keys(jsonData.data.list.tlist)){
                            const item = jsonData.data.list.tlist[itemname];
                            if(item.count>max){
                                max = item.count;
                                name= item.name;
                            }else if(item.count===max){
                                name+= "、"+item.name;
                            }
                        }
                        mostCates = name;
                    }
                    if (jsonData.data.list.vlist.length === 0) {
                        return {ok: false,mostCates:mostCates}
                    }
                    const vid = jsonData.data.list.vlist[0];
                    return {ok: true, value: vid.created,vinfo: {aid:vid.aid,title:vid.title,pic:vid.pic,play:vid.play},mostCates:mostCates}
                } else {
                    return {ok: false}
                }
            } else {
                return {ok: false}
            }
        } catch (e) {
            log(uid,e)
            return {ok: false}
        }
    };
    const getTypeNameFromDynamicTypeID = (id,fallback='?') => {
        switch (+id) {
            case 1:
                return mdi('share')+"转发动态";
            case 2:
                return mdi('image-multiple')+"相册图片";
            case 4:
                return mdi('text');//文字动态
            case 8:
                return mdi('youtube')+"视频投稿";
            case 16:
                return mdi('video-box')+"小视频";
            case 64: 
                return mdi('newspaper-variant-outline')+"专栏文章";
            case 128: 
                return fallback;
            case 256: 
                return mdi('playlist-music')+"音频投稿";
            case 512: 
                return mdi('filmstrip-box-multiple')+"番剧更新";
            case 1024:
                return fallback; 
            case 2048: 
                return mdi('playlist-play')+"歌单分享";
            case 4300:
                return mdi('playlist-star')+"收藏夹";
            default: return fallback;
        }
    }
    const getContentFromDynamic = (card) => { 
        if (!card) return '无法解析内容(空内容)';
        if (card.item?.content) return card.item.content;
        if (card.aid) return 'av'+card.aid+' | <b>'+card.title + "</b><br>简介: " + card.desc;
        if (card.item?.pictures) return card.item.pictures_count + '张图片';
        if (card.origin) return `转发自${card?.user?.uname}: ${card.item?.content}`;
        if (card.item?.description) return card.item.description;
        if(card.summary) return `cv${card.id} | <b>${card.title}</b><br>简介: ${card.summary}`
        return '无法解析内容(未知特征)';
    }
    const parseDynamic = (d)=>{
        const dynamic = {
            id: d.desc.dynamic_id_str,
            sender:d.desc.user_profile,
            like: d.desc.like,
            comment: d.desc.comment,
            repost: d.desc.repost,
            status: d.desc.status,
            timestamp: d.desc.timestamp,
            type: d.desc.type,
            content: getContentFromDynamic(d.card),
            origin: (d.desc.orig_dy_id && d.desc.orig_dy_id !== 0) ? (
                d.card.origin = JSON.parse(d.card.origin),
                getContentFromDynamic(d.card.origin)
            ):null,
            istop: d.extra.is_space_top===1,
            isrepost: d.desc.orig_dy_id&&d.desc.orig_dy_id!==0,
            publisher: d.desc.orig_dy_id ? (d.desc.orig_dy_id === 0 ? d.card.user : d.card.origin_user.info) : d.card.user,
            prefix: getTypeNameFromDynamicTypeID(d.desc.type),
            origprefix: getTypeNameFromDynamicTypeID(d.desc.orig_type)
        };
        return dynamic;
    }
    const getDynamic = async uid => {
        try {
            const jsonData = await (await fetch(getRequest(getDynamicURL(datas.self,uid)))).json();
            if (jsonData && jsonData.code === 0) {
                const data = jsonData.data.cards;
                const dynamics = {
                    top:null,
                    next:null,
                }
                if(!data || data.length === 0) {
                    return dynamics;
                }
                let d = data.shift();
                d.card = JSON.parse(d.card);
                let obj = parseDynamic(d);
                if(obj.istop){
                    dynamics.top = obj;
                    let nd = data.shift();
                    nd.card = JSON.parse(nd.card);
                    let nobj = parseDynamic(nd);
                    dynamics.next = nobj;
                }else{
                    dynamics.next = obj;
                }
                return dynamics;
            } else {
                log("Failed fetch self info: unexpected response", jsonData);
                return null;
            }
        } catch (e) {
            log("Failed fetch self info: error found", e);
            return null;
        }
    }
    const getUserStats = async (uid, withraw=false) => {
        try {
            const jsonData = await (await fetch(getRequest(getUInfoURL(uid)))).json();
            if (jsonData && jsonData.code === 0) {
                const udata = jsonData.data;
                const parsedData = {
                    ok: true,
                    level: udata.level,
                    banned: udata.silence === 1,
                    RIP: udata.sys_notice === 20,
                    disputed: udata.sys_notice === 8,
                    notice: udata.sys_notice,
                    sign: udata.sign,
                    cates: udata.tags,
                    lives: udata.live_room,
                    official_verify: udata.official_verify??udata.official,
                };
                if(withraw){
                    return Object.assign({},udata,parsedData);
                }
                return parsedData
            }
        } catch (e) {

        }
        return {ok: false}
    }
    const fillUserStatus = async (uid, refresh=false) => {
        setInfoBar(`正在为${uid}填充用户信息`)
        uid = parseInt(uid);
        if(datas.mappings[uid]&&datas.mappings[uid].filled){
            log(uid,"already filled")
            resetInfoBar();
            return datas.mappings[uid];
        }
        const userinfo = await getUserStats(uid,refresh);
        if (userinfo.ok) {
            if(refresh) datas.mappings[uid] = userinfo;
            datas.mappings[uid].level = userinfo.level;
            datas.mappings[uid].banned = userinfo.banned;
            datas.mappings[uid].RIP = userinfo.RIP;
            datas.mappings[uid].disputed = userinfo.disputed;
            datas.mappings[uid].notice = userinfo.notice;
            datas.mappings[uid].sign = userinfo.sign;
            datas.mappings[uid].cates = userinfo.cates;
            datas.mappings[uid].lives = userinfo.lives;
            datas.mappings[uid].filled = true;
            if (!userinfo.banned && !userinfo.RIP) {
                const lastUpdate = await getLatestVideoPublishDate(uid);
                log(uid,lastUpdate)
                if (lastUpdate.ok) {
                    datas.mappings[uid].lastUpdate = lastUpdate.value;
                    datas.mappings[uid].lastUpdateInfo = lastUpdate.vinfo;
                }
                if(lastUpdate.mostCates) datas.mappings[uid].mostCates = lastUpdate.mostCates;
            }
            log(uid, datas.mappings[uid]);
        } else {
            log(uid, "fetch space info failed");
        }
        resetInfoBar();
        return datas.mappings[uid];
    }
    const RELE_ACTION = {
        FOLLOW:1,
        UNFOLLOW:2,
        WHISPER:3,
        UNWHISPER:4,
        BLOCK:5,
        UNBLOCK:6,
        KICKFANS:7
    }
    const batchOperateUser = async (uids = [], actCode) => {
        if (uids.length === 0) return {ok: false, res: "UIDS is empty"};
        if(!Object.values(RELE_ACTION).includes(actCode)){
            if(Object.keys(RELE_ACTION).includes(actCode)){
                actCode = RELE_ACTION[actCode];
            }else{
                return {ok: false, res: "Unknown action code"};
            }
        }
        const act = actCode;
        log("Batch Operating with Action Code",act);
        const operate = async(_uids,_act)=>{
            try {
                const jsonData = await (await fetch(getPostRequest(getFollowURL(), new URLSearchParams(`fids=${_uids.join(',')}&act=${_act}&re_src=11&jsonp=jsonp&csrf=${getCSRFToken()}`)))).json()
                if (jsonData && jsonData.code === 0) return {ok: true, uids, res: ""};
                return {ok: false, uids, res: jsonData.message, data: jsonData.data};
            } catch (e) {
                return {ok: false, uids, res: e.message};
            }
        }
        const list = [...uids];
        const results = {ok:true,uids,res:"",data:{failed_fids:[],failed_results:[]}};//failed_fids
        if(list.length>50) log("WARNING: Operating with more than 50 items, it may cause some issues.");
        while(list.length){
            const currents = list.splice(0,50);
            const result = await operate(currents,act);
            if(!result.ok){
                results.ok = false;
                results.res="部分请求出现错误";
                results.data.failed_fids.concat(result.data.failed_fids);
                results.data.failed_results.push(result);
            }
        }
        log("Results:",results);
        return results;
    }
    const convertToWhisper = async (uids)=>{
        log("Unfollowing",uids);
        let unfo = uids.length===1?await operateUser(uids[0],RELE_ACTION.UNFOLLOW):await batchOperateUser(uids,RELE_ACTION.UNFOLLOW);
        log("Unfollowed:",unfo);
        if(!unfo.ok) return unfo;
        log("Whispering",uids);
        let whis = uids.length===1?await operateUser(uids[0],RELE_ACTION.WHISPER):await batchOperateUser(uids,RELE_ACTION.WHISPER);
        log("Whispered:",whis);
        return whis;
    }
    const convertToFollow = async (uids)=>{
        log("Unwhispering",uids);
        let unwh = uids.length===1?await operateUser(uids[0],RELE_ACTION.UNWHISPER):await batchOperateUser(uids,RELE_ACTION.UNWHISPER);
        log("Unwhispered:",unwh);
        if(!unwh.ok) return unwh;
        log("Following",uids);
        let foll = uids.length===1?await operateUser(uids[0],RELE_ACTION.FOLLOW):await batchOperateUser(uids,RELE_ACTION.FOLLOW);
        log("Followed:",foll);
        return foll;
    }
    // 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;
    }
    const operateUser = async (uid, actCode) => {
        if(!Object.values(RELE_ACTION).includes(actCode)){
            if(Object.keys(RELE_ACTION).includes(actCode)){
                actCode = RELE_ACTION[actCode];
            }else{
                return {ok: false, res: "Unknown action code"};
            }
        }
        const act = actCode;
        log("Operating with Action Code",act);
        try {
            const jsonData = await (await fetch(getPostRequest(getUnfolURL(), new URLSearchParams(`fid=${uid}&act=${act}&re_src=11&jsonp=jsonp&csrf=${getCSRFToken()}`)))).json()
            if (jsonData && jsonData.code === 0) return {ok: true, uid, res: ""};
            return {ok: false, uid, res: jsonData.message};
        } catch (e) {
            return {ok: false, uid, res: e.message};
        }
    }
    const unfollowUser = async (uid,iswhisper=false) => {
        try {
            if(datas.isSelf){
                iswhisper = datas.mappings[uid].attribute===1 || datas.mappings[uid].isWhisper;
            }
            return operateUser(uid,iswhisper?RELE_ACTION.UNWHISPER:RELE_ACTION.UNFOLLOW);
        } catch (e) {
            return {ok: false, uid, res: e.message};
        }
    }
    const unfollowUsers = async uids => {
        let okgroup = [];
        let errgroup = [];
        for (let uid of uids) {
            setInfoBar(`正在取关 ${uid} ...`)
            let result = await unfollowUser(uid);
            log(result);
            if (result.ok) {
                okgroup.push(uid);
            } else {
                errgroup.push(uid);
            }
            await batchDelay();
        }
        setInfoBar(`取关完成`)
        return {
            ok: errgroup.length === 0,
            okgroup, errgroup
        }
    }
    const fetchFollowings = async (uid, page = 1) => {
        let retry = cfg.retrial;
        while (retry-- > 0) {
            try {
                const jsonData = await (await fetch(getRequest(getFetchURL(uid, page)))).json();
                if (jsonData) {
                    if (jsonData.code === 0) return jsonData;
                    if (jsonData.code === 22007) {
                        retry = -1;
                        datas.fetchstat = "GUEST-LIMIT";
                        throw "Not the owner of uid " + uid;
                    }
                    if(jsonData.code === 22115) {
                        retry = -1;
                        datas.fetchstat = "PERMS-DENIED";
                        throw "Permission denied.";
                    }
                }
                log("Unexcept fetch result", "retry:", retry, "uid:", uid, "p:", page, "data", jsonData)
            } catch (e) {
                if(datas.fetchstat==="OK")datas.fetchstat = "ERRORED";
                log("Errored while fetching followings", "retry:", retry, "uid:", uid, "p:", page, "e:", e);
            }
        }
        return null;
    }
    const fetchWhisperFollowings = async (uid, page = 1) => {
        if(!datas.isSelf) return null;
        let retry = cfg.retrial;
        while (retry-- > 0) {
            try {
                const jsonData = await (await fetch(getRequest(getWhispersURL(page)))).json();
                if (jsonData) {
                    if (jsonData.code === 0) {
                        for(let item of jsonData.data.list){
                            item.isWhisper = true;
                        }
                        return jsonData;
                    }
                    if (jsonData.code === 22007) {
                        retry = -1;
                        datas.fetchstat = "GUEST-LIMIT";
                        throw "Not the owner of uid " + uid;
                    }
                    if(jsonData.code === 22115) {
                        retry = -1;
                        datas.fetchstat = "PERMS-DENIED";
                        throw "Permission denied.";
                    }
                }
                log("Unexcept fetch result", "retry:", retry, "uid:", uid, "p:", page, "data", jsonData)
            } catch (e) {
                if(datas.fetchstat==="OK")datas.fetchstat = "ERRORED";
                log("Errored while fetching followings", "retry:", retry, "uid:", uid, "p:", page, "e:", e);
            }
        }
        return null;
    }
    const getFollowings = async (force = false) => {
        if (datas.status === 1) {
            log("Task canceled due to busy");
            return;
        }
        log("Fetching followings with param force =",force?"true":"false");
        cfg.infobarTemplate = ()=>`共读取 ${datas.fetched} 条关注`;
        datas.status = 1;
        datas.checked = [];
        let currentPageNum = 1;
        const uid = await getCurrentUid();
        const self = await getSelfId();
        datas.currUid = uid;
        datas.self = self;
        if (self === -1) {
            if(!cfg.I_KNOW_WHAT_IM_DOING)alertModal("没有登录", "你没有登录,部分功能可能无法正常工作。", "确定");
            log("Not login");
        } else if (self === 0) {
            if(!cfg.I_KNOW_WHAT_IM_DOING)alertModal("获取当前用户信息失败", "无法得知当前页面是否为你的个人空间,因此部分功能可能无法正常工作。", "确定");
            log("Failed fetch current user");
        } else if (self + "" !== uid) {
            if(!cfg.I_KNOW_WHAT_IM_DOING)alertModal("他人的关注列表", "这不是你的个人空间,因此获取的关注列表也不是你的列表。<br>非本人关注列表最多显示前250个关注。<br>你仍然可以对其进行筛选,但是不能进行操作。", "确定");
            log("Other's space.");
        } else if (self + "" === uid) {
            datas.isSelf = true;
        }
        unsafeWindow.FoMan_CurrentUser = ()=>createUserInfoCardFromOthers(datas.currUid);
        cfg.titleTemplate = ()=>`<h1>关注管理器 <small>v${cfg.VERSION} ${cfg.debug?"debug":""} <span style="color:grey;font-size:x-small;margin-right:12px;float:right">当前展示: UID:${datas.currUid} ${datas.isSelf?"(你)":`(${document.title.replace("的个人空间_哔哩哔哩_bilibili","").replace("的个人空间_哔哩哔哩_Bilibili","")})`} <a href='javascript:void(0)' onclick='FoMan_CurrentUser()'>👁️‍🗨️</a></span></small></h1>`
        setTitle();
        let needreload = force || !CacheManager.load();
        const currInfo = await getCurrSubStat(uid);
        if (datas.currInfo.following !== -1 && currInfo !== null) {
            if (force === false && datas.currInfo.following === currInfo.following && datas.currInfo.whisper === currInfo.whisper) {
                if (datas.fetched > 0)
                    needreload = false;
            } else if(!needreload && (datas.currInfo.following !== currInfo.following || datas.currInfo.whisper !== currInfo.whisper)){
                alertModal("自动重新加载","检测到数据变化,已经自动重新加载。","确定");
                needreload = true;
            }
        }
        datas.currInfo = currInfo;
        if (needreload) {
            datas.followings = [];
            datas.mappings = {};
            datas.fetched = 0;
            const firstPageData = await fetchFollowings(uid, currentPageNum);
            if (!firstPageData) throw "Failed to fetch followings";
            datas.total = firstPageData.data.total;
            datas.pages = Math.floor(datas.total / 50) + (datas.total % 50 ? 1 : 0);
            datas.followings = datas.followings.concat(firstPageData.data.list);
            datas.fetched += firstPageData.data.list.length;
            firstPageData.data.list.forEach(it => {
                datas.mappings[parseInt(it.mid)] = it;
            })
            currentPageNum += 1;
            for (; currentPageNum <= datas.pages; currentPageNum++) {
                const currentData = await fetchFollowings(uid, currentPageNum);
                if (!currentData) break;
                datas.followings = datas.followings.concat(currentData.data.list);
                datas.fetched += currentData.data.list.length;
                currentData.data.list.forEach(it => {
                    datas.mappings[parseInt(it.mid)] = it;
                });
                setInfoBar(`正在查询关注数据:已获取 ${datas.fetched} 条数据`);
            }
            log("isSelf? ",datas.isSelf);
            if(datas.isSelf){
                setInfoBar(`正在查询悄悄关注数据`);
                let whisperPageNum =1;
                let fetched = 0;
                const whisperPages = Math.floor(datas.currInfo.whisper / 50) + (datas.currInfo.whisper % 50 ? 1 : 0);
                for(; whisperPageNum<=whisperPages;whisperPageNum++){
                    const currentData = await fetchWhisperFollowings(whisperPageNum);
                    log(currentData);
                    if (!currentData) break;
                    datas.followings = datas.followings.concat(currentData.data.list);
                    fetched += currentData.data.list.length;
                    currentData.data.list.forEach(it => {
                        datas.mappings[parseInt(it.mid)] = it;
                    });
                    setInfoBar(`正在查询悄悄关注数据:已获取 ${fetched} 条数据`);
                }
            }
            CacheManager.save();
        }else{
            log("Using last result.");
            cfg.infobarTemplate = ()=>`共读取 ${datas.fetched} 条关注(缓存,<a href="javascript:void(0)" onclick="openFollowManager(true)">点此重新加载</a>)`
            setInfoBar("使用上次数据");
        }
        datas.status = 2;
        log("fetch completed.");
        autoCacheCleaner();
    }
    const autoCacheCleaner = (force=false) => {
        let size = CacheManager.getSize();
        if (force || size >= 2) {
            setInfoBar("正在整理缓存空间...");
            alertModal('请稍等', '由于缓存空间到达警戒值,正在自动整理缓存,请稍等...');
            CacheManager.prune();
            let aftersize = CacheManager.getSize();
            if (aftersize >= 2) {
                alertModal('请稍等', '缓存空间仍然处于警戒值以上,整理缓存无效,正在自动清理缓存,请稍等...');
                CacheManager.clean();
            }
            aftersize = CacheManager.getSize();
            alertModal('清理完成', '本次自动清理释放了' + (size - aftersize) + ' MB缓存空间。', "确定");
            resetInfoBar();
        }
    }
    unsafeWindow.FoManCleaner = (force=false)=>autoCacheCleaner(force);
    const CacheProvider = {
        storage: window.localStorage,
        prefix: "Unfollow_",
        expire: 1000*60*60*2,
        getKey:(key)=>CacheProvider.prefix+key,
        valueWrapper: (value='',no=false)=>{
            log(JSON.stringify({
                et: no?(new Date('2999/1/1')).getTime():(new Date()).getTime()+CacheProvider.expire,
                vl: value
            }));
            return JSON.stringify({
                et: no?(new Date('2999/1/1')).getTime():(new Date()).getTime()+CacheProvider.expire,
                vl: value
            });
        },
        getValue: (value="{}",key=null,noprefix=false)=>{
            try{
                const itemArc = JSON.parse(value);
                if(itemArc.hasOwnProperty('et')&&itemArc.et>=(new Date()).getTime()){
                    return itemArc.vl;
                }
                if(key)CacheProvider.del(key,noprefix);
                return null;
            }catch(e){
                if(key)CacheProvider.del(key,noprefix);
                return null;
            }
        },
        list: ()=>Object.keys(CacheProvider.storage).filter(el=>el.startsWith(CacheProvider.prefix)),
        has: (key,noprefix=false)=>{
            if(!noprefix){
                key = CacheProvider.getKey(key);
            }
            return CacheProvider.storage.getItem(key)===null;
        },
        valid: (key,noprefix=false)=>{
            if(!noprefix){
                key = CacheProvider.getKey(key);
            }
            if(CacheProvider.has(key,true)){
                const value = CacheProvider.storage.getItem(key);
                return CacheProvider.getValue(value,key,true)!==null;
            }else return false;
        },
        set: (key,val,noexpire=false,noprefix = false)=>{
            if(!noprefix){
                key = CacheProvider.getKey(key);
            }
            CacheProvider.storage.setItem(key,CacheProvider.valueWrapper(val,noexpire));
        },
        get: (key,fallback=null,noprefix=false)=>{
            if(!noprefix){
                key = CacheProvider.getKey(key);
            }
            const result = CacheProvider.storage.getItem(key);
            log('Cache-get-with-key',key,result);
            if(result===null) return fallback;
            log('Cache-get-parsed-value',key,CacheProvider.getValue(result,key,true));
            return CacheProvider.getValue(result,key,true);
        },
        del: (key,noprefix=false)=>{
            if(!noprefix){
                key = CacheProvider.getKey(key);
            }
            delete CacheProvider.storage[key];
        },
        prune: ()=>{
            const count = {
                valid:0,expired:0
            };
            CacheProvider.list().forEach(it=>{
                if(!it) return;
                if(CacheProvider.valid(it,true)){
                    count.valid++;
                }else{
                    count.expired++;
                }
            })
            return;
        },
        getSize: (filter = (key) => key.startsWith(CacheProvider.prefix)) => {
            const sum = (...args)=>args.reduce((a,b)=>a+b,0);
            return sum(...Object.keys(CacheProvider.storage).filter(filter).map(it=>CacheProvider.storage.getItem(it).length));
        }
    }
    const CacheManager = {
        version: 1,
        save:(uid=datas.currUid)=>{
            const {total,fetched,pages,followings,tags,currInfo} = datas;
            const tagclone = {};
            for(let tn of Object.keys(tags)){
                tagclone[tn+''] = tags[tn];
            }
            /*log({
                total,fetched,pages,followings,mappings,tagclone,currInfo
            });*/
            CacheProvider.set(`cache_${uid}`,{
                total,fetched,pages,followings,tagclone,currInfo,cacheVersion:CacheManager.version
            });
        },
        load:(uid=datas.currUid)=>{
            if(!datas.isSelf) return false;
            const cached = CacheProvider.get(`cache_${uid}`);
            if(cached===null) return false;
            else{
                const { total, fetched, pages, followings, tagclone, currInfo } = cached;
                if (!cached.cacheVersion || cached.cacheVersion < CacheManager.version) {
                    CacheProvider.del(`cache_${uid}`);
                    return false;
                }
                const tags = {};
                for(let tn of Object.keys(tagclone)){
                    tags[parseInt(tn)] = tagclone[tn];
                }
                const mappings = {};
                for (const follow of followings) {
                    mappings[+follow.mid] = follow;
                }
                const cdata = {total,fetched,pages,followings,mappings,tags,currInfo};
                for(let n of Object.keys(cdata)){
                    datas[n] = cdata[n];
                }
                return true;
            }
        },
        prune: () => {
            CacheProvider.prune();
            try{
                CacheProvider.list().forEach(el => {
                    const value = CacheProvider.get(el, null, true);
                    if (!value.cacheVersion||value.cacheVersion < CacheManager.version) CacheProvider.del(el, true);
                });
                return true;
            }catch(e){
                log(e);
                return false;
            }
        },
        clean:()=>{
            try{
                CacheProvider.list().forEach(el=>CacheProvider.del(el,true));
                return true;
            }catch(e){
                log(e);
                return false;
            }
        },
        getSize: () => {
            return (CacheProvider.getSize()/1024/1024).toFixed(2);
        }
    }
    const clearStyles = (className = "CKFOMAN") => {
        let dom = document.querySelectorAll("style." + className);
        if (dom) [...dom].forEach(e => e.remove());
    }
    const addStyle = (s, className = "CKFOMAN", mode = "append") => {
        switch (mode) {
            default:
            case "append":
                break;
            case "unique":
                if (document.querySelector("style." + className)) return;
                break;
            case "update":
                clearStyles(className);
                break;
        }
        let style = document.createElement("style");
        style.classList.add(className);
        style.innerHTML = s;
        document.body.appendChild(style);
    }
    const setTitle = (val = null)=>{
        const title = get("#CKFOMAN-titledom");
        if(val!=null) title.innerHTML = val;
        else title.innerHTML = cfg.titleTemplate();
    }
    const getFloatWindow = () => {
        addMdiBtnStyle();
        addStyle(`
        #CKFOMAN{
            position: fixed;
            z-index: 99000;
            top: 50%;
            left: 50%;
            width: 800px;
            width: 60vw;
            height: 800px;
            height: 80vh;
            background: white;
            border-radius: 8px;
            padding: 12px;
            transform: translate(-50%,-50%);
            transition: all .3s;
            box-shadow: 0 2px 8px grey;
        }
        #CKFOMAN.hide{
            opacity: 0;
            pointer-events: none;
            transform: translate(-50%,-50%) scale(0.95);
        }
        #CKFOMAN.show{
            transform: translate(-50%,-50%) scale(1);
        }
        #CKFOMAN-container{
            width: 100%;
            /*overflow-y: auto;
            overflow-x: hidden;
            max-height: calc(80vh - 70px);*/
            position: relative;
            display: flex;
            flex-direction: column;
            flex-wrap: nowrap;
            justify-content: flex-start;
            align-items: stretch;
        }
        .CKFOMAN-scroll-list{
            margin: 6px auto;
            overflow-y: auto;
            overflow-x: hidden;
            display: flex;
            flex-wrap: nowrap;
            flex-direction: column;
            max-height: calc(80vh - 80px);
        }
        .CKFOMAN-data-inforow{
            border-radius: 6px;
            flex: 1;
            width: 100%;
            display: flex;
            padding: 6px;
            color: #aaa;
            transition: background .3s;
        }
        .CKFOMAN-data-inforow:hover{
            background: #2196f361;
            transition: background .1s;
        }
        .CKFOMAN-data-inforow-toggle{
            margin: 3px 8px;
        }
        .CKFOMAN-toolbar-btns{
            flex: 1;
            border: none;
            background: #2196f3;
            border-radius: 3px;
            margin: 0 6px;
            padding: 3px;
            color: white;
            box-shadow: 0 2px 3px grey;
            /*box-sizing: border-box;*/
            /*border: 2px solid #00000000;*/
            transition: all .5s;
        }
        .CKFOMAN-toolbar-btns:hover{
            /*filter: brightness(0.85);*/
            background: #00467e!important;
            transition: all .15s;
            /*border-bottom: solid 2px white;*/
        }
        .CKFOMAN-toolbar-btns.red{
            background: #e91e63!important;
        }
        .CKFOMAN-toolbar-btns:hover.red{
            background: #8c002f!important;
        }
        .CKFOMAN-toolbar-btns.green{
            background: #4caf50!important;
        }
        .CKFOMAN-toolbar-btns:hover.green{
            background: #1b5e20!important;
        }
        .CKFOMAN-toolbar-btns.orange{
            background: #e64a19!important;
        }
        .CKFOMAN-toolbar-btns:hover.orange{
            background: #bf360c!important;
        }
        .CKFOMAN-toolbar-btns.grey{
            background: #949494!important;
            color: grey!important;
        }
        .CKFOMAN-toolbar-btns:hover.grey{
            background: #878787!important;
            color: grey!important;
        }
        #CKFOMAN-sortbtns-container>button{
            flex: 1 0 40% !important;
            margin: 4px 4px;
        }
        #CKFOMAN .mdi-close:hover{
            color: #ff5722;
        }
        .CKFOMAN-aliastext{
            font-weight: lighter;
            color: gray;
        }
        `, "CKFOMAN-mainWindowcss", "unique");
        const id = "CKFOMAN";
        let win = document.querySelector("#" + id);
        if (win) return win;
        win = document.createElement("div");
        win.id = id;

        const closebtn = document.createElement("div");
        closebtn.innerHTML = `<i class="mdi mdi-18px mdi-close"></i>`
        closebtn.style.float = "right";
        closebtn.style.color = (getBgColor()==="white")?"black":"white";
        closebtn.onclick = hidePanel;
        win.appendChild(closebtn);

        const titleText = document.createElement("div");
        titleText.id="CKFOMAN-titledom";
        titleText.innerHTML = cfg.titleTemplate();
        win.appendChild(titleText);

        const infoBar = document.createElement("div");
        infoBar.style.height = "30px";
        infoBar.style.lineHeight = "30px";
        infoBar.style.width = "100%";
        infoBar.style.textAlign = "center";
        infoBar.id = id + "-infobar";
        win.appendChild(infoBar);

        const container = document.createElement("div");
        container.id = id + "-container";
        win.appendChild(container);

        win.className = "hide";
        document.body.appendChild(win);
        return win;
    }
    const getContainer = () => {
        return getFloatWindow().querySelector("#CKFOMAN-container");
    }
    const setInfoBar = (content = '') => {
        const bar = getFloatWindow().querySelector("#CKFOMAN-infobar");
        if (bar) bar.innerHTML = content;
        return bar;
    }
    const resetInfoBar = () => {
        wait(50).then(() => {
            let str = cfg.infobarTemplate();
            if (datas.checked.length > 0) {
                str += `,已选中 ${datas.checked.length} 条`;
            }
            setInfoBar(str);
        });
    }
    const divider = () => {
        const div = document.createElement("div");
        div.style.width = "30%";
        div.style.borderBottom = "solid grey 2px";
        div.style.lineHeight = "3px";
        div.style.margin = "0 auto";
        div.style.marginBottom = "3px";
        div.style.height = "6px";
        div.style.overflow = "hidden";
        div.style.padding = "0";
        return div;
    }
    const showPanel = () => {
        const panel = getFloatWindow();
        panel.style.backgroundColor = getBgColor();
        panel.className = "show";
    }
    const hidePanel = () => {
        const panel = getFloatWindow();
        panel.className = "hide";
    }
    const openModal = (title = '', content) => {
        blockWindow();
        let modal = get("#CKFOMAN-modal");
        if (!modal) modal = initModal();
        modal.setTitle(title);
        modal.setContent(content);
        modal.show();
    }
    const isModalShowing = () => {
        let modal = get("#CKFOMAN-modal");
        if (modal) return modal.classList.contains("show");
        else return false;
    }
    const hideModal = () => {
        blockWindow(false);
        let modal = get("#CKFOMAN-modal");
        if (modal) modal.hide();
    }
    const initModal = () => {
        addStyle(`
        #CKFOMAN-modal{
            position: fixed;
            z-index: 99010;
            top: 50%;
            left: 50%;
            width: 300px;
            width: 30vw;
            /*height: 300px;
            height: 50vh;*/
            background: white;
            border-radius: 8px;
            padding: 12px;
            transform: translate(-50%,-50%);
            transition: all .3s;
            box-shadow: 0 2px 8px grey;
            max-height: 95vh;
            overflow-y: auto;
        }
        #CKFOMAN-modal.show{
            opacity: 1;
            transform: translate(-50%,-50%) scale(1);
        }
        #CKFOMAN-modal.hide{
            opacity: 0;
            pointer-events: none;
            transform: translate(-50%,-50%) scale(0.9);
        }
        .CKFOMAN-modal-content>div{
            display: flex;
            margin: 6px 10px;
            flex-wrap: wrap;
            flex-direction: column;
            align-content: space-around;
            justify-content: space-between;
            align-items: stretch;
        }
        .CKFOMAN-modal-content button, 
        .CKFOMAN-modal-content input, 
        .CKFOMAN-modal-content keygen, 
        .CKFOMAN-modal-content optgroup, 
        .CKFOMAN-modal-content select, 
        .CKFOMAN-modal-content textarea
        {
            border-width: 2px;
            border-color: transparent;
            margin: 2px;
            border-radius: 3px;
            transition: all .3s;
        }
        .CKFOMAN-modal-content button:hover, 
        .CKFOMAN-modal-content input:hover, 
        .CKFOMAN-modal-content keygen:hover, 
        .CKFOMAN-modal-content optgroup:hover, 
        .CKFOMAN-modal-content select:hover, 
        .CKFOMAN-modal-content textarea:hover
        {
            border-color: grey;
        }

        .CKFOMAN-toolbar-btns>i.mdi {
            float: right;
        }
        `, "CKFOMAN-modal-css", "unique");
        const modal = document.createElement("div");
        modal.id = "CKFOMAN-modal";
        modal.className = "hide";

        const header = document.createElement("h2");
        header.className = "CKFOMAN-modal-title"
        modal.appendChild(header);

        modal.setTitle = (t = '') => {
            header.innerHTML = t;
        }

        const contents = document.createElement("div");
        contents.className = "CKFOMAN-modal-content";
        modal.appendChild(contents);

        modal.setContent = async (c) => {
            let ct = c;
            if (ct instanceof Function) {
                ct = await ct();
            }
            if (ct instanceof HTMLElement) {
                contents.innerHTML = '';
                contents.appendChild(ct);
                return;
            }
            if (ct instanceof String) {
                contents.innerHTML = ct;
                return;
            }
            log("unknown: ", ct);
        }
        modal.addContent = async (c) => {
            let ct = c;
            if (ct instanceof Function) {
                ct = await ct();
            }
            if (ct instanceof HTMLElement) {
                contents.appendChild(ct);
                return;
            }
            if (ct instanceof String) {
                contents.innerHTML += ct;
                return;
            }
            log("unknown: ", ct);
        }

        modal.close = closeModal;
        modal.open = openModal;
        modal.show = () => {
            modal.style.backgroundColor = getBgColor();
            modal.className = "show";
        }
        modal.hide = () => {
            modal.className = "hide";
        }

        document.body.appendChild(modal);
        return modal;
    }
    const closeModal = () => {
        blockWindow(false);
        let modal = get("#CKFOMAN-modal");
        if (modal) modal.remove();
    }
    const addMdiBtnStyle = () => {
        if (document.querySelector("#CKFOMAN-MDICSS")) return;
        document.head.innerHTML += `<link id="CKFOMAN-MDICSS" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css"/>`;
    }
    const refreshChecked = () => {
        setInfoBar(`正在刷新后台数据...`);
        const all = getAll("#CKFOMAN .CKFOMAN-data-inforow-toggle");
        if (!all) return;
        for (let it of all) {
            const mid = it.getAttribute("data-targetmid");
            if (it.checked) {
                if (!datas.checked.includes(mid) && !datas.checked.includes(parseInt(mid))) {
                    datas.checked.push(mid);
                }
            } else {
                if (datas.checked.includes(mid)) {
                    datas.checked.splice(datas.checked.indexOf(mid), 1);
                } else if (datas.checked.includes(parseInt(mid))) {
                    datas.checked.splice(datas.checked.indexOf(parseInt(mid)), 1);
                }
            }
        }
        resetInfoBar();
        return datas.checked;
    }
    const toggleSwitch = (mid, status = false, operateDom = true) => {
        setToggleStatus(mid, status, operateDom);
        //unsafeWindow.postMessage(`CKFOMANSTATUSCHANGES|${mid}|${status ? 1 : 0}`)
    }
    const isAliasPluginInstalled = () => unsafeWindow.FoManPlugins?.UpAlias ?? false;
    const getAliasPlugin = ()=>unsafeWindow.FoManPlugins.UpAlias;
    const upinfoline = async data => {
        let invalid = isInvalid(data);
        let info = datas.mappings[parseInt(data.mid)] || {};
        return await makeDom("li", async item => {
            item.className = "CKFOMAN-data-inforow";
            if(datas.settings.lazyRenderForList)item.style.contentVisibility = "auto";
            item.onclick = e => {
                if (e.target.classList.contains("CKFOMAN-data-inforow-name")) {
                    //open("https://space.bilibili.com/" + data.mid);
                    createUserInfoCard(info);
                } else if (e.target.tagName !== "INPUT") {
                    const toggle = item.querySelector("input");
                    toggleSwitch(data.mid, !toggle.checked);
                    //toggle.checked = !toggle.checked;
                } else {
                    const toggle = item.querySelector("input");
                    wait(50).then(() => toggleSwitch(data.mid, toggle.checked, false))
                }
                //resetInfoBar();
            }
            item.setAttribute("data-special", data.special);
            item.setAttribute("data-invalid", data.invalid ? "1" : "0");
            item.setAttribute("data-fans", data.attribute === 6 ? "1" : "0");
            item.setAttribute("data-vip", data.vip.vipType !== 0 ? "1" : "0");
            item.setAttribute("data-official", data.official_verify.type === 1 ? "1" : "0");
            let title = data.mid + "";
            item.appendChild(await makeDom("input", toggle => {
                toggle.className = "CKFOMAN-data-inforow-toggle";
                toggle.type = "checkbox";
                toggle.checked = datas.checked.includes(data.mid + "") || datas.checked.includes(parseInt(data.mid));
                toggle.setAttribute("data-targetmid", data.mid);
                toggle.onchange = e => {
                    toggleSwitch(data.mid, toggle.checked);
                }
                // toggle.onchange = e =>{
                //     if(toggle.checked){
                //         if(!datas.checked.includes(data.mid)){
                //             datas.checked.push(data.mid);
                //         }
                //     }else{
                //         if(datas.checked.includes(data.mid)){
                //             datas.checked.splice(datas.checked.indexOf(data.mid),1);
                //         }
                //     }
                // }
            }));
            item.appendChild(await makeDom("img", avatar => {
                avatar.src = data.face;
                avatar.style.flex = "1";
                avatar.style.height = "18px";
                avatar.style.maxWidth = "18px";
                avatar.style.borderRadius = "50%";
                avatar.style.verticalAlign = "middle";
                avatar.style.marginRight = "18px";
                avatar.loading = "lazy";
            }));
            item.appendChild(await makeDom("span", name => {
                name.className = "CKFOMAN-data-inforow-name";
                name.innerText = data.uname;
                name.style.flex = "1";
                if (isAliasPluginInstalled()) {
                    name.innerHTML += ` <span class="CKFOMAN-aliastext alias-content-${data.mid}">${getAliasPlugin().provider.getAlias(+data.mid, "")}</span>`
                }
                if (invalid) {
                    name.style.textDecoration = "line-through 3px red";
                } else {
                    name.style.fontWeight = "bold";
                    if (data.isWhisper === true || data.attribute=== 1) {
                        name.innerHTML = `<i class="mdi mdi-18px mdi-eye-off" style="vertical-align: middle;color:gray!important" title="悄悄关注"></i>` + name.innerHTML;
                        title += " | 悄悄关注";
                    }
                    if (data.special === 1) {
                        name.innerHTML = `<i class="mdi mdi-18px mdi-heart" style="vertical-align: middle;color:orangered!important" title="特别关注"></i>` + name.innerHTML;
                        title += " | 特别关注";
                    }
                    if (data.attribute === 6) {
                        name.innerHTML = `<i class="mdi mdi-18px mdi-swap-horizontal" style="vertical-align: middle;color:orangered!important" title="互相关注"></i>` + name.innerHTML;
                        title += " | 互相关注";
                    }
                    if (data.vip.vipType !== 0) {
                        name.style.color = "#e91e63";
                    }
                    if (data.official_verify.type === 1) {
                        name.style.textDecoration = "underline";
                        name.style.color = "#c67927";
                        title += " | 认证账号";
                    }
                    if (info.banned) {
                        name.style.color = "grey";
                        name.innerHTML = `<i class="mdi mdi-18px mdi-cancel" style="vertical-align: middle;color:red!important" title="账号已封禁"></i>` + name.innerHTML;
                        title += " | 账号已封禁";
                    }
                    if (info.RIP) {
                        name.innerHTML = `<i class="mdi mdi-18px mdi-candle" style="vertical-align: middle;color:black!important" title="纪念账号"></i>` + name.innerHTML;
                        title += " | 纪念账号";
                    }
                    if (info.disputed) {
                        name.innerHTML = name.innerHTML + `<i class="mdi mdi-18px mdi-frequently-asked-questions" style="vertical-align: middle;color:orangered!important" title="账号有争议"></i>`;
                        title += " | 账号有争议";
                    }
                    if (info.notice && info.notice.content && !info.banned && !info.RIP && !info.disputed) {
                        name.innerHTML = name.innerHTML + `<i class="mdi mdi-18px mdi-information" style="vertical-align: middle;color:grey!important" title="${info.notice.toString()}"></i>`;
                        title += " | " + (info.notice.content ? info.notice.content : "账号状态未知");
                    }
                }
            }));
            item.appendChild(await makeDom("span", subtime => {
                subtime.style.flex = "1";
                subtime.innerHTML = "关注于" + (new Intl.DateTimeFormat('zh-CN').format(data.mtime + "000"));
                if (isNearly(data.mtime)) {
                    title += " | 最近关注";
                } else if (isLongAgo(data.mtime)) {
                    title += " | 很久前关注";
                }
            }));
            item.appendChild(await makeDom("span", tagsdom => {
                tagsdom.style.flex = "1";
                if (data.tag === null || data.tag.length === 0 || ["[0]", "[-10]"].includes(JSON.stringify(data.tag)))
                    tagsdom.innerHTML = "";
                else {
                    let name = "";
                    for (let gid of data.tag) {
                        if (gid === 0 || gid === -10) continue;
                        if (name !== "") name += ",";
                        if (gid in datas.tags) {
                            name += datas.tags[gid].name;
                        } else {
                            name += "?";
                        }
                    }
                    tagsdom.innerHTML = name;
                }
            }));
            item.appendChild(await makeDom("span", mark => {
                mark.style.flex = "1";
                if (invalid) {
                    title += " | 账号已注销";
                } else {
                    if (data.special === 1) {
                        mark.innerHTML = "特别关注&nbsp;&nbsp;";
                    }
                    if (data.official_verify.type === 1) {
                        mark.innerText = data.official_verify.desc.substring(0, 15);
                    } else if (data.vip.vipType !== 0) {
                        mark.innerText = data.vip.vipType === 1 ? "大会员" : "年费大会员"
                        title += " | " + mark.innerText;
                    }
                    if (info.lastUpdate) {
                        if (isLongAgo(info.lastUpdate)) {
                            title += " | 很久没有发布视频";
                        }
                        if (isNearly(info.lastUpdate)) {
                            title += " | 最近有发布视频";
                        }
                    }
                }
            }));
            log(info, title);
            item.setAttribute("title", title);
        });
    }
    const taginfoline = (data,clickCallback=()=>{},selected = false,showExtras = true,hideOptions = false) => {
        return makeDom("li", async item => {
            let couldRename = true;
            item.className = "CKFOMAN-data-inforow";
            item.onclick = e => {
                if(e.path.filter(el=>el.tagName==="BUTTON"||el.tagName==="INPUT").length){
                    return;
                }else{
                    clickCallback(e,data);
                }
            }
            item.setAttribute("data-id", data.tagid);
            item.setAttribute("data-name", data.name);
            item.setAttribute("data-count", data.count);
            item.setAttribute("data-tip", data.tip);
            if(!hideOptions)item.appendChild(await makeDom("input", toggle => {
                toggle.className = "CKFOMAN-data-inforow-toggle";
                toggle.type = "checkbox";
                toggle.checked = selected;
                toggle.setAttribute("data-tagid", data.tagid);
            }));
            item.appendChild(await makeDom("span", name => {
                name.className = "CKFOMAN-data-inforow-name";
                switch(data.tagid){
                    case 0:
                    case '0':
                        couldRename = false;
                        name.innerHTML = `默认分类`.italics();
                        item.setAttribute("title", "默认的关注分类,包含全部未分组的关注项目。\n不可删除");
                        break;
                    case -10:
                    case '-10':
                        couldRename = false;
                        name.innerHTML = `特别关注`.italics();
                        item.setAttribute("title", "默认的特别关注分类,包含全部特别关注的关注项目。\n不可删除");
                        break;
                    default:
                        name.innerText = `${data.name}`;
                        item.setAttribute("title", `用户创建的分组 "${data.name}"\n删除后用户将被移动到默认分类`);
                }
                name.style.flex = "1";
            }));
            item.appendChild(await makeDom("span", subtime => {
                subtime.style.flex = "1";
                subtime.innerHTML = `${data.tagid}`;
            }));
            item.appendChild(await makeDom("span", subtime => {
                subtime.style.flex = "1";
                subtime.innerHTML = `包含 ${data.count} 个内容`;
            }));
            if(showExtras)item.appendChild(await makeDom("button", renamebtn => {
                renamebtn.style.flex = ".4";
                renamebtn.innerHTML = `更名`;
                renamebtn.style.height = "23px";
                renamebtn.style.margin = "0";
                renamebtn.style.padding = "2px";
                renamebtn.classList.add("CKFOMAN-toolbar-btns");
                if(!couldRename){
                    renamebtn.setAttribute("disabled",true);
                    renamebtn.classList.add("grey");
                }
                renamebtn.onclick = async ()=>{
                    let newname = prompt("请输入新的分类名字",data.name).trim();
                    if(newname.length!==0){
                        if(newname!=data.name){
                            const result = await renameGroup(data.tagid,newname);
                            if(result){
                                await alertModal("分组重命名","分组重命名成功,重新打开窗口以显示修改后的数据。","确定");
                            }else{
                                await alertModal("分组重命名","分组重命名完成,但是不能确定结果。请刷新页面,然后查看是否生效。","确定");
                            }
                        }
                    }
                };
            }));
        });
    }
    const doUnfollowChecked = async () => {
        const checked = datas.checked;
        if (!checked || checked.length === 0) return alertModal("无法操作", "实际选中数量为0,没有任何人被选中取关。", "");
        await alertModal("正在取消关注...", `正在取关${checked.length}个用户,请耐心等候~`);
        const result = await unfollowUsers(checked);
        if (result.ok) {
            await alertModal("操作结束", `已取关 ${result.okgroup.length} 个用户。`, "继续");
        } else {
            await alertModal("操作结束", `已取关 ${result.okgroup.length} 个用户,但有另外 ${result.errgroup.length} 个用户取关失败。`, "继续");
        }
        datas.checked = [];
        log("取关结果", result);
        createMainWindow();
    }
    const isInvalid = data => {
        return (data.face === "http://i0.hdslb.com/bfs/face/member/noface.jpg"
            || data.face === "https://i0.hdslb.com/bfs/face/member/noface.jpg")
            && data.uname === "账号已注销";
    }
    const alertModal = async (title = "", content = "", okbtn = "hidden") => {
        if (isModalShowing()) {
            hideModal();
            await wait(200);
        }
        openModal(title, await makeDom("div", async container => {
            container.appendChild(await makeDom("div", tip => {
                tip.innerHTML = content;
            }))
            if (okbtn !== "hidden")
                container.appendChild(await makeDom("div", async btns => {
                    btns.style.display = "flex";
                    btns.appendChild(await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.innerHTML = okbtn;
                        btn.onclick = e => hideModal();
                    }))
                }))
        }))
        await wait(300);
    }
    const showCacheQuotaModal = async () => {
        hideModal();
        await wait(300);
        const size = CacheManager.getSize();
        let content = "本地缓存空间已占用 " + size + " MB。";
        if(size < 1.8){
            content += "无需处理。定期整理缓存可以减少空间占用。";
        }else if (size < 2.5) {
            content += "<b>建议整理缓存。</b>";
        } else {
            content += "<b>建议整理或清理缓存以避免缓存空间超出配额。</b>";
        }
        content += "<br><br>FoMan使用本地存储空间保存缓存,本地存储在不同浏览器中有不同的限额,最小的为2.5MB(Opera),最大的为10MB(Chromium)。请注意此空间非FoMan独占,B站自身和其他插件也会占用此空间,因此建议经常进行整理。";
        content += "<br><br><b>整理缓存</b>仅会清理过期和过时的缓存。<br><br>默认情况下,FoMan存储的缓存有效期为2小时,超过2小时的缓存和数据总数发生变化时都会触发强制放弃缓存重新加载。"
        content += "<br><br><b>清空缓存</b>会清理所有由FoMan产生的缓存。若配额达到上限,或经常查看其他人关注列表,则建议使用此功能。"
        alertModal("缓存使用说明",content,"确定");
    }
    const createUserInfoCardFromOthers = async(uid)=>{
        if(!uid) return;
        const i = await fillUserStatus(uid, true).catch(err => log(err));
        await createUserInfoCard(i, false, true);
    };
    const createUserInfoCard = async (info, refilldata = true, noactions = false)=>{
        if(datas.preventUserCard) return;
        log(info);
        if(datas.settings.autoExtendInfo){
            alertModal("请稍后...");
            if(refilldata) await fillUserStatus(info.mid).catch(err => log(err));
            info.dynamics = await getDynamic(info.mid).catch(err => log(err));
            info['stats'] = await getCurrSubStat(info.mid);
        }
        hideModal();
        await wait(300);
        openModal("", await makeDom("div", async container => {
            const infocard = await makeDom("div", async card => {
                card.style.display = "flex";
                card.style.flexDirection = "row";
                card.style.minHeight = "100px";
                card.style.minWidth = "400px";
                [
                    await makeDom("img", async img => {
                        img.style.flex = "1";
                        img.style.maxWidth = "70px";
                        img.setAttribute("loading", "lazy");
                        img.src = info.face;
                        img.style.width = "70px";
                        img.style.height = "70px";
                        img.style.borderRadius = "50%";
                        img.style.margin = "0 30px";
                    }),
                    await makeDom("div", async upinfo => {
                        upinfo.style.flex = "1";
                        upinfo.style.maxWidth = "300px";
                        upinfo.innerHTML = `<b style="color:${info.vip['nickname_color']};font-size: large">${info.uname ?? info.name ?? '未知昵称'}</b> <span style="display:inline-block;transform: translateY(-5px);font-size:xx-small;line-height:1.2;padding:1px 3px;border-radius:6px;background: ${info.vip.vipType > 0 ? (info.vip.label['bg_color'] || "#f06292") : "rgba(0,0,0,0)"};color: ${info.vip.label['text_color'] || "white"}">${info.vip.vipType > 1 ? info.vip.label.text : info.vip.vipType > 0 ? "大会员" : ""}</span>`;
                        if (info.level) {
                            upinfo.innerHTML += `<div style="display: inline-block;border-radius:3px;line-height: 1.2;padding: 1px 3px;background:#f06292;margin-left: 12px;color:white">LV${info.level}${isHardCoreMember(info) ? " ⚡ (硬核)" : ""}</div>`;
                        }
                        upinfo.innerHTML += `<div style="color:gray;border-left: 2px solid gray;padding-left: 2px;font-style: italic;">${info.sign}</div>`;
                        if (info.official_verify.type !== -1) {
                            let color = "gray";
                            switch (info.official_verify.type) {
                                case 0:
                                    color = "goldenrod";
                                    break;
                                case 1:
                                    color = "#FB7299";
                                    break;
                                case 2:
                                    color = "dodgerblue";
                                    break;
                            }
                            upinfo.innerHTML += `<div style="color:${color}">${info.official_verify.desc}</div>`;
                        }
                        if (isAliasPluginInstalled()) {
                            document.querySelector("#CKFOMAN-showalias")?.remove();
                            upinfo.innerHTML += `<div id="CKFOMAN-showalias" style="color:gray"></div>`;
                            const refreshAlias = async () => {
                                const aliasdom = document.querySelector("#CKFOMAN-showalias");
                                aliasdom.innerHTML = '';
                                if (!aliasdom) return;
                                [...document.querySelectorAll(`.alias-content-${info.mid}`)].map(el=>el.innerText=getAliasPlugin().provider.getAlias(+info.mid, ""))
                                if (getAliasPlugin().provider.hasAlias(+info.mid)) {
                                    aliasdom.innerHTML += `别名: <span class="alias-content-${info.mid}">` + getAliasPlugin().provider.getAlias(+info.mid, "无别名") + "</span> ";
                                    aliasdom.appendChild(await makeDom("a", async a => {
                                        a.innerText = "修改";
                                        a.onclick = async () => {
                                            await getAliasPlugin().actions.setFor(info.mid, info.uname);
                                            refreshAlias();
                                        }
                                    }))
                                    aliasdom.appendChild(document.createTextNode(" / "));
                                    aliasdom.appendChild(await makeDom("a", async a => {
                                        a.innerText = "删除";
                                        a.onclick = async () => {
                                            await getAliasPlugin().actions.removeFor(info.mid, info.uname);
                                            refreshAlias();
                                        }
                                    }))
                                } else {
                                    aliasdom.appendChild(await makeDom("a", async a => {
                                        a.innerText = "设置别名";
                                        a.onclick = async () => {
                                            await getAliasPlugin().actions.setFor(info.mid, info.uname);
                                            refreshAlias();
                                        }
                                    }))
                                }
                            }
                            setTimeout(()=>refreshAlias(), 20);
                        }
                        if(info.stats){
                            const { follower, following }=info.stats;
                            const [fans,subs] = [numberFormat(follower), numberFormat(following)];
                            upinfo.innerHTML+= `<div style="color:gray">${fans.value}${fans.unit}粉丝 / ${subs.value}${subs.unit}关注</div>`;
                        }
                        if(info.tag){
                            let folders = "分类:";
                            for(let t of info.tag){
                                if(t in datas.tags){
                                    folders +=" "+datas.tags[t].name;
                                }
                            }
                            upinfo.innerHTML+= `<div style="color:gray;font-weight:bold">${folders}</div>`;
                        }
                        let subinfo = "";
                        if(info.special===1){
                            subinfo+= `<span style="color:deeppink;margin-right:6px;">特别关注</span>`;
                        }
                        if(info.attribute===6){
                            subinfo+= `<span style="color:indianred;margin-right:6px;">互相关注</span>`;
                        }
                        if(info.isWhisper === true || info.attribute=== 1){
                            subinfo+= `<span style="color:yellowgreen;margin-right:6px;">悄悄关注</span>`;
                        }
                        if(subinfo.length){
                            upinfo.innerHTML+= `<div>${subinfo}</div>`
                        }
                        if(info.notice && info.notice.id){
                            upinfo.innerHTML+= `<div style="border-radius:6px;padding:3px;background:${info.notice.bg_color};color:${info.notice.text_color};"><a href="${info.notice.url}">${info.notice.content}</a></div>`;
                        }
                        if(info.banned){
                            upinfo.innerHTML+= `<div style="border-radius:6px;padding:3px;background:black;color:white;">账号已封禁</div>`;
                        }
                        if(info.cates && info.cates.length){
                            upinfo.innerHTML+= `<div style="color:gray">标签: ${info.cates.join(", ")}</div>`;
                        }
                        if(info.mostCates && info.mostCates.length){
                            upinfo.innerHTML+= `<div style="color:gray">主要投稿分区: ${info.mostCates}</div>`;
                        }
                        if(info.mid){
                            upinfo.innerHTML+= `<div style="color:gray">UID: ${info.mid}</div>`;
                        }
                        if(info.mtime){
                            const regdate = new Date(info.mtime*1000);
                            upinfo.innerHTML+= `<div style="color:gray">关注于 ${regdate.getFullYear()}年${regdate.getMonth()+1}月${regdate.getDate()}日</div>`;
                        }
                    })
                ].forEach(el=>card.appendChild(el));
            })
            container.appendChild(infocard);
            if(unsafeWindow.FoManPlugins&&unsafeWindow.FoManPlugins.RememberFollows){
                const followinfo = unsafeWindow.FoManPlugins.RememberFollows.get(+info.mid);
                if(followinfo){
                    const fodate = new Date(followinfo.timestamp);
                    [
                        divider(),
                        await makeDom("div",async post=>{
                            post.innerHTML = "<h3 style='padding: 6px 0;'>关注记录</h3>";
                            post.appendChild(await makeDom("div",async vidcard=>{
                                vidcard.style.display = "flex";
                                vidcard.style.flexDirection = "row";
                                vidcard.style.minHeight = "80px";
                                vidcard.style.minWidth = "400px";
                                [
                                    await makeDom("div",async vidinfo=>{
                                        vidinfo.innerHTML = `<div style="font-weight:bold;font-size:larger;color:grey">${followinfo.videoName}</div>`;
                                        vidinfo.innerHTML+= `<div style="color:grey">${fodate.getFullYear()}年${fodate.getMonth()+1}月${fodate.getDate()}日 · 当时UP名: <a href="https://space.bilibili.com/${followinfo.mid}">${followinfo.upName}</a></div>`;
                                    })
                                ].forEach(el=>vidcard.appendChild(el));
                                vidcard.onclick = ()=>open(`https://www.bilibili.com/video/${followinfo.videoId}`)
                            }))
                        })
                    ].forEach(el=>container.appendChild(el));
                }
            }
            if(info.dynamics){
                if(info.dynamics.top){
                    let dynamic = info.dynamics.top;
                    let content = (()=>{
                        if(!dynamic.content || dynamic.content.length===0) return "无内容";
                        let short = dynamic.content.substring(0,300);
                        short = short.split("\n").slice(0,4).join("\n");
                        if(short!=dynamic.content) short+="...";

                        return short.replaceAll("\n","<br>");
                    })();
                    const pushdate = new Date(dynamic.timestamp*1000);
                    [
                        divider(),
                        await makeDom("div",async post=>{
                            post.innerHTML = "<h3 style='padding: 6px 0;'>置顶动态</h3>";
                            post.appendChild(await makeDom("div",async vidcard=>{
                                vidcard.style.display = "flex";
                                vidcard.style.flexDirection = "row";
                                vidcard.style.minHeight = "80px";
                                vidcard.style.minWidth = "400px";
                                [
                                    await makeDom("div",async vidinfo=>{
                                        vidinfo.innerHTML = `<div style="font-weight:normal;font-size:smaller;color:#858585">[${dynamic.prefix}] ${content}</div>`;
                                        vidinfo.innerHTML+= `<div style="color:grey"><i class="mdi mdi-10px mdi-chevron-double-right"></i> ${pushdate.getFullYear()}年${pushdate.getMonth()+1}月${pushdate.getDate()}日 - ${dynamic.like??'?'}点赞 ${dynamic.repost??'?'}转发 ${dynamic.comment??'?'}评论</div>`;
                                        if(dynamic.isrepost){
                                            vidinfo.innerHTML+= `<div style="color:grey"><i class="mdi mdi-10px mdi-share"></i> 转发自<b onclick="open('https://space.bilibili.com/${dynamic.publisher.uid}')">${dynamic.publisher.uname}</b> 的 [${dynamic.origprefix}]:<div style='border-left: 2px solid gray;padding-left:6px'>${dynamic.origin.substr(0,100)}...</div></div>`;
                                        }
                                    })
                                ].forEach(el=>vidcard.appendChild(el));
                                vidcard.onclick = ()=>open(`https://t.bilibili.com/${dynamic.id}?tab=2`)
                            }))
                        })
                    ].forEach(el=>container.appendChild(el));
                }
                if(info.dynamics.next){
                    let dynamic = info.dynamics.next;
                    let content = (()=>{
                        if(!dynamic.content || dynamic.content.length===0) return "无内容";
                        let short = dynamic.content.substring(0,300);
                        short = short.split("\n").slice(0,4).join("\n");
                        if(short!=dynamic.content) short+="...";
                        return short.replaceAll("\n","<br>");
                    })();
                    const pushdate = new Date(dynamic.timestamp*1000);
                    [
                        divider(),
                        await makeDom("div",async post=>{
                            post.innerHTML = "<h3 style='padding: 6px 0;'>最新动态</h3>";
                            post.appendChild(await makeDom("div",async vidcard=>{
                                vidcard.style.display = "flex";
                                vidcard.style.flexDirection = "row";
                                vidcard.style.minHeight = "80px";
                                vidcard.style.minWidth = "400px";
                                [
                                    await makeDom("div",async vidinfo=>{
                                        vidinfo.innerHTML = `<div style="font-weight:normal;font-size:smaller;color:#858585">[${dynamic.prefix}] ${content}</div>`;
                                        vidinfo.innerHTML+= `<div style="color:grey"><i class="mdi mdi-10px mdi-chevron-double-right"></i> ${pushdate.getFullYear()}年${pushdate.getMonth()+1}月${pushdate.getDate()}日 - ${dynamic.like??'?'}点赞 ${dynamic.repost??'?'}转发 ${dynamic.comment??'?'}评论</div>`;
                                        if(dynamic.isrepost){
                                            vidinfo.innerHTML+= `<div style="color:grey"><i class="mdi mdi-10px mdi-share"></i> 转发自<b onclick="open('https://space.bilibili.com/${dynamic.publisher.uid}')">${dynamic.publisher.uname}</b> 的 [${dynamic.origprefix}]:<div style='border-left: 2px solid gray;padding-left:6px'>${dynamic.origin.substr(0,100)}...</div></div>`;
                                        }
                                    })
                                ].forEach(el=>vidcard.appendChild(el));
                                vidcard.onclick = ()=>open(`https://t.bilibili.com/${dynamic.id}?tab=2`)
                            }))
                        })
                    ].forEach(el=>container.appendChild(el));
                }
            }
            if(info.lastUpdate && info.lastUpdateInfo){
                const pushdate = new Date(info.lastUpdate*1000);
                [
                    divider(),
                    await makeDom("div",async post=>{
                        post.innerHTML = "<h3 style='padding: 6px 0;'>最新投稿</h3>";
                        post.appendChild(await makeDom("div",async vidcard=>{
                            vidcard.style.display = "flex";
                            vidcard.style.flexDirection = "row";
                            vidcard.style.minHeight = "80px";
                            vidcard.style.minWidth = "400px";
                            [
                                await makeDom("img", img=>{
                                    img.style.flex = "1";
                                    img.style.maxWidth = "80px";
                                    img.style.height = "50px";
                                    img.setAttribute("loading","lazy");
                                    img.src = info.lastUpdateInfo.pic;
                                    img.style.borderRadius = "6px";
                                    img.style.margin = "0px 12px 0px 10px";
                                }),
                                await makeDom("div",async vidinfo=>{
                                    vidinfo.innerHTML = `<div style="font-weight:bold;font-size:larger;color:grey">${info.lastUpdateInfo.title}</div>`;
                                    vidinfo.innerHTML+= `<div style="color:grey">${pushdate.getFullYear()}年${pushdate.getMonth()+1}月${pushdate.getDate()}日</div>`;
                                })
                            ].forEach(el=>vidcard.appendChild(el));
                            vidcard.onclick = ()=>open(`https://www.bilibili.com/av${info.lastUpdateInfo.aid}`)
                        }))
                    })
                ].forEach(el=>container.appendChild(el));
            }
            if(info.lives && info.lives.liveStatus!==0){
                [
                    divider(),
                    await makeDom("div",async post=>{
                        post.innerHTML = "<h3 style='padding: 6px 0;'>直播间</h3>";
                        post.appendChild(await makeDom("div",async vidcard=>{
                            vidcard.style.display = "flex";
                            vidcard.style.flexDirection = "row";
                            vidcard.style.minHeight = "80px";
                            vidcard.style.minWidth = "400px";
                            [
                                await makeDom("img", img=>{
                                    img.style.flex = "1";
                                    img.style.maxWidth = "80px";
                                    img.style.height = "50px";
                                    img.setAttribute("loading","lazy");
                                    img.src = info.lives.cover;
                                    img.style.borderRadius = "6px";
                                    img.style.margin = "0px 12px 0px 10px";
                                }),
                                await makeDom("div",async vidinfo=>{
                                    vidinfo.innerHTML = `<div style="font-weight:bold;font-size:larger;color:grey">${info.lives.title}</div>`;
                                    vidinfo.innerHTML+= `<div style="color:grey">正在${info.lives.liveStatus===2?'轮':'直'}播 - 房间号: ${info.lives.roomid}</div>`;
                                })
                            ].forEach(el=>vidcard.appendChild(el));
                            vidcard.onclick = ()=>open(`https://live.bilibili.com/${info.lives.roomid}`)
                        }))
                    })
                ].forEach(el=>container.appendChild(el));
            }
            async function addBtn(info,container){
                container.style.display="flex";
                container.style.flexDirection = "column";
                container.style.position = "sticky";
                container.style.bottom = 0;
                container.style.background = getBgColor();
                container.innerHTML = "";
                if(!noactions){
                    if(info.attribute===0){
                        container.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns red";
                            btn.style.margin = "4px 0";
                            btn.innerHTML = "立刻关注";
                            btn.onclick = async e => {
                                btn.innerHTML = "正在关注...";
                                btn.setAttribute("disabled",true)
                                btn.classList.add("grey");
                                const res = await batchOperateUser([info.mid],RELE_ACTION.FOLLOW);
                                if(!res.ok){
                                    log(res)
                                    btn.innerHTML = "关注失败";
                                    btn.removeAttribute("disabled")
                                    btn.classList.remove("grey");
                                }else{
                                    datas.mappings[info.mid].attribute = 1;
                                    btn.remove();
                                    addBtn(datas.mappings[info.mid],container);
                                }
                            }
                        }))
                        container.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns blue";
                            btn.style.margin = "4px 0";
                            btn.innerHTML = "悄悄关注";
                            btn.onclick = async e => {
                                btn.innerHTML = "正在关注...";
                                btn.setAttribute("disabled",true)
                                btn.classList.add("grey");
                                const res = await batchOperateUser([info.mid],RELE_ACTION.WHISPER);
                                if(!res.ok){
                                    log(res)
                                    btn.innerHTML = "关注失败";
                                    btn.removeAttribute("disabled")
                                    btn.classList.remove("grey");
                                }else{
                                    datas.mappings[info.mid].attribute = 1;
                                    btn.remove();
                                    addBtn(datas.mappings[info.mid],container);
                                }
                            }
                        }))
                    }else{
                        container.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns red";
                            btn.style.margin = "4px 0";
                            btn.innerHTML = "立刻取关(谨慎)";
                            btn.onclick = async e => {
                                btn.innerHTML = "正在取关...";
                                btn.setAttribute("disabled",true)
                                btn.classList.add("grey");
                                const res = await unfollowUser(info.mid);
                                if(!res.ok){
                                    log(res);
                                    btn.innerHTML = "取关失败";
                                    btn.removeAttribute("disabled")
                                    btn.classList.remove("grey");
                                }else{
                                    datas.mappings[info.mid].attribute = 0;
                                    btn.remove();
                                    addBtn(datas.mappings[info.mid],container);
                                }
                            }
                        }))
                        if(info.attribute===1){
                            container.appendChild(await makeDom("button", btn => {
                                btn.className = "CKFOMAN-toolbar-btns blue";
                                btn.style.margin = "4px 0";
                                btn.innerHTML = "转为普通关注(不保留关注时间)";
                                btn.onclick = async e => {
                                    btn.innerHTML = "正在转换...";
                                    btn.setAttribute("disabled",true)
                                    btn.classList.add("grey");
                                    const res = await convertToFollow([info.mid]);
                                    if(!res.ok){
                                        log(res)
                                        btn.innerHTML = "关注失败";
                                        btn.removeAttribute("disabled")
                                        btn.classList.remove("grey");
                                    }else{
                                        datas.mappings[info.mid].attribute = 1;
                                        datas.mappings[info.mid].isWhisper = false;
                                        btn.remove();
                                        if(datas.dommappings[info.mid+""]&& datas.dommappings[info.mid+""] instanceof HTMLElement){
                                            datas.dommappings[info.mid+""].replaceWith(await upinfoline(datas.mappings[info.mid]));
                                        }
                                        //addBtn(datas.mappings[info.mid],container);
                                        hideModal();
                                    }
                                }
                            }))
                        }else{
                            container.appendChild(await makeDom("button", btn => {
                                btn.className = "CKFOMAN-toolbar-btns blue";
                                btn.style.margin = "4px 0";
                                btn.innerHTML = "转为悄悄关注(不保留关注时间)";
                                btn.onclick = async e => {
                                    btn.innerHTML = "正在悄悄关注...";
                                    btn.setAttribute("disabled",true)
                                    btn.classList.add("grey");
                                    const res = await convertToWhisper([info.mid]);
                                    if(!res.ok){
                                        log(res)
                                        btn.innerHTML = "关注失败";
                                        btn.removeAttribute("disabled")
                                        btn.classList.remove("grey");
                                    }else{
                                        datas.mappings[info.mid].attribute = 2;
                                        datas.mappings[info.mid].isWhisper = true;
                                        btn.remove();
                                        if(datas.dommappings[info.mid+""]&& datas.dommappings[info.mid+""] instanceof HTMLElement){
                                            datas.dommappings[info.mid+""].replaceWith(await upinfoline(datas.mappings[info.mid]));
                                        }
                                        //addBtn(datas.mappings[info.mid],container);
                                        hideModal();
                                    }
                                }
                            }))
                        }
                    }
                }
                container.appendChild(await makeDom("button", btn => {
                    btn.className = "CKFOMAN-toolbar-btns";
                    btn.style.margin = "4px 0";
                    btn.innerHTML = "个人主页";
                    btn.onclick = () => open(`https://space.bilibili.com/${info.mid}`)
                }))
                container.appendChild(await makeDom("button", btn => {
                    btn.className = "CKFOMAN-toolbar-btns";
                    btn.style.margin = "4px 0";
                    btn.innerHTML = "隐藏";
                    btn.onclick = () => hideModal();
                }))
            }
            const btns = document.createElement("div");
            await addBtn(info,btns);
            container.appendChild(btns);
        }));
    }
    const createGroupInfoModal = async () => {
        hideModal();
        await wait(300);
        openModal("分组管理", await makeDom("div", async container=>{
            container.appendChild(await makeDom("div", tip => {
                tip.style.fontWeight = "bold";
                tip.innerHTML = `若修改过分组信息,建议刷新页面再进行其他操作。`;
            }))
            container.appendChild(divider());
            const taglistdom = document.createElement('div');
            taglistdom.className = "CKFOMAN-scroll-list";
            taglistdom.style.width = "100%";
            taglistdom.style.maxHeight = "calc(50vh - 100px)";
            const refreshList = async ()=>renderTagListTo(taglistdom,[],async (e,data)=>{
                if(e.target.tagName==="INPUT") return;
                if(['0','-10'].includes(data.tagid+'')) return;
                let dom = e.path.filter(it=>it['classList']&&it.classList.contains('CKFOMAN-data-inforow'))[0];
                if(!dom) return log('no target');
                if(dom.hasAttribute('data-del-pending')){
                    if(dom.removePendingTimer) clearTimeout(dom.removePendingTimer);
                    removeGroup(data.tagid).then(()=>refreshList());
                    //cfg.infobarTemplate = `共读取 ${datas.fetched} 条关注 (已修改分组,<a href="javascript:void(0)" onclick="openFollowManager(true)">点此重新加载</a>)`;
                    await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                    resetInfoBar();
                }else{
                    dom.setAttribute('data-del-pending','waiting');
                    let namedom = dom.querySelector('.CKFOMAN-data-inforow-name');
                    if(!namedom) return;
                    let text = namedom.innerHTML;
                    namedom.innerHTML = '再次点击以移除'.fontcolor('red');
                    dom.removePendingTimer = setTimeout(()=>{
                        if(dom.hasAttribute('data-del-pending')) dom.removeAttribute('data-del-pending');
                        if(dom.removePendingTimer) clearTimeout(dom.removePendingTimer);
                        namedom.innerHTML = text;
                    },5000);
                }
            },true);
            container.appendChild(taglistdom);
            container.appendChild(await makeDom("div", async btns => {
                btns.style.display = "flex";
                [
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.innerHTML = "添加分组";
                        btn.style.height = "30px";
                        btn.onclick = async () => {
                            const tagname = prompt("请输入新分组的标题");
                            if(!tagname) return;
                            createGroup(tagname).then(()=>refreshList());
                        };
                    }),
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.style.height = "30px";
                        btn.innerHTML = "关闭";
                        btn.onclick = () => hideModal();
                    }),
                ].forEach(el => btns.appendChild(el));
            }))
            refreshList();
        }))
    }
    const createGroupChangeModal = async (mode='copy'/*move*/) => {
        hideModal();
        await wait(300);
        refreshChecked();
        let uids = datas.checked;
        let users = [];
        let groups = [];
        let act = mode==='copy'?'复制':'移动';
        for(let uid of uids){
            users.push(datas.mappings[uid]);
            let tags = datas.mappings[uid].tag;
            tags && tags.forEach(t=>groups.includes(t)||groups.push(t))
        }
        log(users,groups);
        openModal("分组修改:"+act, await makeDom("div", async container=>{
            container.appendChild(await makeDom("div", tip => {
                tip.style.fontWeight = "bold";
                tip.innerHTML = `若修改过分组信息,建议刷新页面再进行其他操作。`;
            }))
            container.appendChild(divider());
            const taglistdom = document.createElement('div');
            taglistdom.className = "CKFOMAN-scroll-list";
            taglistdom.style.width = "100%";
            taglistdom.style.maxHeight = "calc(50vh - 100px)";
            const refreshList = async ()=>renderTagListTo(taglistdom,mode==='copy'?[]:groups,async (e,data)=>{
                const row = e.path.filter(el=>el.classList?.contains('CKFOMAN-data-inforow'));
                if(row.length){
                    const cb = row[0].querySelector("input[type='checkbox']");
                    if(cb) cb.checked = !cb.checked
                }
            },false);
            container.appendChild(taglistdom);
            container.appendChild(await makeDom("div", async btns => {
                btns.style.display = "flex";
                [
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.style.height = "30px";
                        btn.innerHTML = "管理分组 (Beta)";
                        btn.onclick = async () => createGroupInfoModal();
                    }),
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.style.height = "30px";
                        btn.innerHTML = "取消";
                        btn.onclick = () => hideModal();
                    }),
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.style.height = "30px";
                        btn.innerHTML = "确定";
                        btn.onclick = async () => {
                            const allOptions = [...document.querySelectorAll('.CKFOMAN-data-inforow-toggle[data-tagid]')]
                            const selections = allOptions.map((option)=>{
                                return {tagid:parseInt(option.getAttribute('data-tagid')),checked:option.checked}
                            })
                            const checked = selections.filter((selection) => selection.checked)
                            await alertModal("正在处理...", `正在${act}成员到新分组,请稍候`);
                            if(checked.length===0) checked.push({tagid:0,checked:true});
                            switch(mode){
                                case 'copy':
                                    copyUserToGroup(uids,checked.map(c=>c.tagid));
                                    break;
                                case 'move':
                                    moveUserToGroup(uids,checked.map(c=>c.tagid));
                                    break;
                                // default:
                                //     moveUserToDefaultGroup(uids);
                            }
                            await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                            hideModal();
                            cfg.infobarTemplate = ()=>`共读取 ${datas.fetched} 条关注 (已修改分组,<a href="javascript:void(0)" onclick="openFollowManager(true)">点此重新加载</a>)`;
                            resetInfoBar();
                        }
                    }),
                ].forEach(el => btns.appendChild(el));
            }))
            refreshList();
        }))
    }
    const makeButtons = (btns = []) => {
        const dom = CKTools.domHelper;
        return dom('div', {
            css: {
                'display': 'flex',
                'flex-direction': 'column'
            },
            init: el => {
                for (const btncfg of btns) {
                    let opt = Object.assign({
                        text: '按钮',
                        extras: '',
                        init: () => { },
                        onclick: () => { }
                    }, btncfg);
                    dom('button', {
                        classnames: ['CKFOMAN-toolbar-btns', ...opt.extras],
                        text: opt.text, init: opt.init, listeners: { click: opt.onclick },
                        append: el
                    })
                }
            }
        });
    }
    const openNewFilterGuideScreen = async () => {
        const dom = CKTools.domHelper;
        hideModal();
        await wait(300);
        let newFilterModuleInstalled = unsafeWindow.FoManPlugins && unsafeWindow.FoManPlugins.FilterReborn;
        openModal("全新的筛选模块", dom('div', {
            childs: [
                dom('p', {
                    text: "新的筛选模块支持更多、更灵活的筛选方式,配置方式更加直观,选择器组合方式更加自由,可以实现更高级的批量筛选。"
                }),
                dom('p', {
                    text: "但是新版本选择器并非完美,目前还在初步测试中,不能保证稳定性。"
                }),
                dom('p', {
                    text:"正在检测...",
                    init: el => {
                        if (newFilterModuleInstalled) {
                            el.innerText = "你已安装新模块,点击启动立刻打开使用新模块";
                        } else {
                            el.innerText = "新模块是可选附加模块之一,你需要前往页面点击安装,然后刷新页面生效。点击前往安装立刻打开安装页面。";
                        }
                    }
                }),
                makeButtons([
                    {
                        text: newFilterModuleInstalled?"启动":"前往安装",
                        onclick: async e => {
                            hideModal();
                            if (newFilterModuleInstalled) {
                                hideModal();
                                await wait(300);
                                unsafeWindow.FoManPlugins.FilterReborn({
                                    datas,
                                    info: cfg,
                                    domHelper:CKTools.domHelper,
                                    mdi,
                                    get, getAll, wait, log,
                                    addStyle, clearStyles,
                                    alertModal, hideModal,
                                    checkers: {
                                        isNearly, isLongAgo, isInvalid,
                                        isFans, isWhisper, isHardCoreMember,
                                        isSpecial: d=>d.special === 1,
                                        isVerified: d=>d.official_verify.type>0,
                                        isVIP: d => d.vip.vipType === 0,
                                        isNormalVIP: d => d.vip.vipType === 1,
                                        isYearVIP: d=>d.vip.vipType!==1&&d.vip.vipType!==0,
                                    },
                                    getGroups: () => [...datas.tags],
                                    select: (uid, status = true) => toggleSwitch(uid, status),
                                    getSelected: () => refreshChecked().filter(id => !isNaN(id)).map(id => datas.followings[+id]).filter(el => !!el),
                                    clearSelected: () => {
                                        datas.checked = [];
                                        [...getAll(`input.CKFOMAN-data-inforow-toggle[checked]`)].map(el => el.checked = false);
                                    }
                                }).catch(e => {
                                    alertModal("模块加载失败", "出现了一个错误,不能加载模块。", "确定");
                                    console.error(e);
                                });
                            } else {
                                // open('about:blank');
                                if(!cfg.I_KNOW_WHAT_IM_DOING)alertModal("很抱歉","此模块尚未发布,请等待下个版本更新。","确定");
                            }
                        }
                    },
                    {
                        text: "取消",
                        onclick: e => hideModal()
                    }
                ])
            ]
        }))
    }
    const createBlockOrFollowModal = async (isBlock = true) => {
        hideModal();
        await wait(300);
        refreshChecked();
        if (datas.checked.length === 0) {
            if(!cfg.I_KNOW_WHAT_IM_DOING)alertModal("无法继续", "你没有选中任何项,请选中一些项然后再进行操作。", "确认");
            return;
        }
        const ui = {
            action: isBlock ? "拉黑" : "关注",
            title: isBlock ? "批量拉黑" : "批量关注",
            desc: isBlock ? "确认要拉黑的用户列表。<br>他们不会从你的关注中消失。" : "确认要关注的用户列表。<br>重复关注可能导致你变成新粉丝。",
        }
        openModal(ui.title, await makeDom("div", async container => {
            container.appendChild(await makeDom("div", tip => {
                tip.style.fontWeight = "bold";
                tip.innerHTML = ui.desc;
            }))
            container.appendChild(divider());
            container.appendChild(await makeDom("div", async checkedlistdom => {
                checkedlistdom.className = "CKFOMAN-scroll-list";
                checkedlistdom.style.width = "100%";
                checkedlistdom.style.maxHeight = "calc(50vh - 100px)";
                const checkedList = [];
                for (let uid of datas.checked) {
                    if (uid in datas.mappings)
                        checkedList.push(datas.mappings[uid])
                }
                await renderListTo(checkedlistdom, checkedList, false);
            }))
            container.appendChild(await makeDom("div", async btns => {
                btns.style.display = "flex";
                [
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns red";
                        btn.innerHTML = "确认";
                        btn.onclick = async e => {
                            if (datas.checked.length === 0)
                            if(!cfg.I_KNOW_WHAT_IM_DOING)return alertModal("无需继续", "你没有选中任何项。", "确定");
                            const finalList = datas.checked;
                            await alertModal("正在" + ui.action, `正在${ui.action}${finalList.length}个关注...`);
                            const result = await batchOperateUser(finalList, isBlock?RELE_ACTION.BLOCK:RELE_ACTION.FOLLOW);
                            if (result.ok) {
                                await alertModal(ui.action + "完成", `${finalList.length}个关注全部${ui.action}成功!`, "确定");
                                return createMainWindow(true);
                            } else {
                                if ("data" in result) {
                                    if (result.data !== null && "failed_fids" in result.data)
                                        await alertModal(ui.action + "完成,但部分失败", `尝试${ui.action}了${finalList.length}个关注,但是有${result.data.failed_fids.length}个${ui.action}失败:
                                                                                <br>
                                                                                <textarea readonly onclick="this.select()">${result.data.failed_fids.join(',')}</textarea>`, "确定");
                                    else
                                        await alertModal(ui.action + "失败", `尝试${ui.action}了${finalList.length}个关注但失败了,原因:<br><pre>${result.res}</pre>`, "确定");
                                    return createMainWindow(true);
                                } else {
                                    await alertModal(ui.action + "失败", `尝试${ui.action}了${finalList.length}个关注但失败了,原因:<br><pre>${result.res}</pre>`, "确定");
                                    return createMainWindow(true);
                                }
                            }
                        }
                    }),
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.innerHTML = "取消";
                        btn.onclick = e => hideModal();
                    }),
                ].forEach(el => btns.appendChild(el));
            }))
        }))
    }
    const createOtherSpaceAlert = () => cfg.I_KNOW_WHAT_IM_DOING||alertModal("无法执行操作", "此功能只能在你的个人空间使用,当前是在别人的空间。", "确定");
    const createUnfollowModal = async () => {
        refreshChecked();
        if (datas.checked.length === 0) {
            if(!cfg.I_KNOW_WHAT_IM_DOING)alertModal("取消关注", `你没有勾选任何人,所以无法取关。请勾选后再点击取关按钮。`, "知道了")
        } else
            hideModal();
        await wait(300);
        openModal("取关这些Up?", await makeDom("div", async container => {
            container.appendChild(await makeDom("div", tip => {
                tip.style.color = "red";
                tip.style.fontWeight = "bold";
                tip.innerHTML = `请注意,一旦你确认这个操作,没有任何方法可以撤销!<br>就算你重新关注,也算是新粉丝的哦!`;
            }))
            container.appendChild(await makeDom("div", delaySettings => {
                delaySettings.style.color = "blue";
                delaySettings.style.fontWeight = "bold";
                delaySettings.innerHTML = `操作间隔:<input id="CKFOMAN-form-delay" type="number" step="0.01" value="${datas.settings.batchOperationDelay}" />`;
            }))
            container.appendChild(divider());
            container.appendChild(await makeDom("div", async unfolistdom => {
                unfolistdom.className = "CKFOMAN-scroll-list";
                unfolistdom.style.width = "100%";
                unfolistdom.style.maxHeight = "calc(50vh - 100px)";
                const unfolist = [];
                for (let unfoid of datas.checked) {
                    if (unfoid in datas.mappings)
                        unfolist.push(datas.mappings[unfoid])
                }
                await renderListTo(unfolistdom, unfolist, false);
            }))
            container.appendChild(await makeDom("div", async btns => {
                btns.style.display = "flex";
                [
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns red";
                        btn.innerHTML = "确认";
                        btn.onclick = e => {
                            const delayDom = get("#CKFOMAN-form-delay");
                            if(delayDom) {
                                try{
                                    let delay = parseFloat(delayDom.value);
                                    datas.settings.batchOperationDelay = Math.max(delay,0);
                                }catch{}
                            }
                            doUnfollowChecked()
                        }
                    }),
                    await makeDom("button", btn => {
                        btn.className = "CKFOMAN-toolbar-btns";
                        btn.innerHTML = "取消";
                        btn.onclick = e => hideModal();
                    }),
                ].forEach(el => btns.appendChild(el));
            }))
        }))
    }
    const applyFilters = async config => {// TODO: pending a code refactor
        setInfoBar(`正在处理 ...`);
        await alertModal("请稍等", "正在应用选择的筛选器...");
        const cfg = {
            clear: config.clear || "0",
            invalid: config.invalid || "-2",
            vip: config.vip || "-2",
            official: config.official || "-2",
            fans: config.fans || "-2",
            groups: config.groups || "-4",
            special: config.special || "-2",
            beforetime: {
                enabled: config.beforetime.enabled || false,
                before: config.beforetime.before || (new Date).getTime()
            },
            aftertime: {
                enabled: config.aftertime.enabled || false,
                after: config.aftertime.after || 0
            }
        };
        if (
            cfg.clear === "0"
            && cfg.invalid === "-2"
            && cfg.vip === "-2"
            && cfg.official === "-2"
            && cfg.fans === "-2"
            && cfg.groups === "-4"
            && cfg.special === "-2"
            && cfg.beforetime.enabled === false
            && cfg.aftertime.enabled === false
        ) {
            resetInfoBar();
            return;
        }
        if (cfg.clear === "0") {
            datas.checked = [];
        }
        const filters = {};
        if (cfg.invalid !== "-2") {
            filters.invalid = cfg.invalid === "1";
        }
        if (cfg.vip !== "-2") {
            filters.vip = cfg.vip;
        }
        if (cfg.official !== "-2") {
            filters.official = cfg.official;
        }
        if (cfg.fans !== "-2") {
            filters.fans = cfg.fans;
        }
        if (cfg.special !== "-2") {
            filters.special = cfg.special;
        }
        if (cfg.groups !== "-4") {
            filters.groups = cfg.groups;
        }
        if (cfg.beforetime.enabled) {
            filters.beforetime = parseInt(cfg.beforetime.before);
        }
        if (cfg.aftertime.enabled) {
            filters.aftertime = parseInt(cfg.aftertime.after);
        }
        let checked = [];
        try {
            userloop: for (let mid in datas.mappings) {
                const uid = parseInt(mid);
                const user = datas.mappings[mid];
                log(uid, user);
                for (let filter in filters) {
                    const value = filters[filter];
                    switch (filter) {
                        case "invalid":
                            if (isInvalid(user) !== value) continue userloop;
                            break;
                        case "vip":
                            if (user.vip.vipType != value) continue userloop;
                            break;
                        case "official":
                            if (user.official_verify.type != value) continue userloop;
                            break;
                        case "fans":
                            if (user.attribute == value) continue userloop;
                            break;
                        case "special":
                            if (user.special != value) continue userloop;
                            break;
                        case "groups":
                            switch (value) {
                                case "-3":
                                    if (user.tag !== null) continue userloop;
                                    break;
                                case "-2":
                                    if (user.tag === null) continue userloop;
                                    break;
                                default:
                                    if (!((user.tag instanceof Array) && user.tag.includes(parseInt(value)))) continue userloop;
                            }
                            break;
                        case "beforetime":
                            if (parseInt(user.mtime + "000") > value) continue userloop;
                            break;
                        case "aftertime":
                            if (parseInt(user.mtime + "000") < value) continue userloop;
                            break;
                    }
                }
                checked.push(uid);
                if (!datas.checked.includes(uid) && !datas.checked.includes(uid + "")) {
                    datas.checked.push(uid);
                }
            }
            setInfoBar("正在将筛选应用到列表...");
            await wait(1);
            datas.followings.forEach(it=>toggleSwitch(it.mid,datas.checked.includes(parseInt(it.mid))));
            setInfoBar("正在按已选中优先排序...");
            await wait(1);
            datas.followings.sort((x, y) => {
                const xint = (datas.checked.includes(x.mid + "") || datas.checked.includes(parseInt(x.mid))) ? 1 : 0;
                const yint = (datas.checked.includes(y.mid + "") || datas.checked.includes(parseInt(y.mid))) ? 1 : 0;
                return yint - xint;
            })
            await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
            hideModal();
        } catch (e) {
            alertModal("抱歉", "筛选时出现错误,未能完成筛选。");
            log(e);
        }
        resetInfoBar();
        return checked;
    }
    const createMainWindow = async (forceRefetch = false) => {
        showPanel();
        setInfoBar("正在准备获取关注数据...");
        await createScreen(await makeDom("div", dom => {
            dom.style.position = "fixed";
            dom.style.left = "50%";
            dom.style.top = "50%";
            dom.style.transform = "translate(-50%,-50%)";
            dom.style.textAlign = "center";
            dom.innerHTML = `<h2><i class="mdi mdi-account-search-outline" style="color:cornflowerblue"></i><br>正在获取数据</h2>请稍等片刻,不要关闭窗口。`;
        }));
        if (!(await cacheGroupList())) alertModal("警告", "分组数据获取失败。", "确定");
        return getFollowings(forceRefetch)
            .then(async () => {
                return createScreen(await makeDom("div", async screen => {
                    const toolbar = await makeDom("div", async toolbar => {
                        toolbar.style.display = "flex";
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '批量操作 <i class="mdi mdi-18px mdi-chevron-down"></i>';
                            //btn.style.background = "#e91e63";
                            btn.onclick = async e => {
                                await openModal("批量操作", await makeDom("div", async container => {
                                    container.style.alignContent = "stretch";
                                    [
                                        datas.isSelf?await makeDom("button", async btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = '取关选中';
                                            btn.onclick = () => createUnfollowModal();
                                        }):null,
                                        datas.isSelf?await makeDom("button", async btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = '复制到分组';
                                            btn.onclick = () => createGroupChangeModal('copy');
                                        }):null,
                                        datas.isSelf?await makeDom("button", async btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = '修改分组';
                                            btn.onclick = () => createGroupChangeModal('move');
                                        }):null,
                                        await makeDom("button", async btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = '批量拉黑(测试)';
                                            btn.onclick = () => createBlockOrFollowModal(true);
                                        }),
                                        (() => {
                                            if (!datas.isSelf) {
                                                return makeDom("button", async btn => {
                                                    btn.className = "CKFOMAN-toolbar-btns";
                                                    btn.style.margin = "4px 0";
                                                    btn.innerHTML = '批量关注(测试)';
                                                    btn.onclick = () => createBlockOrFollowModal(false);
                                                })
                                            } else return null;
                                        })(),
                                        divider(),
                                        await makeDom("button", async btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.innerHTML = '返回';
                                            btn.onclick = () => hideModal();
                                        }),
                                    ].forEach(el => el && container.appendChild(el));
                                }));
                            };
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '全选';
                            btn.onclick = e => {
                                setInfoBar("正在处理全选...");
                                const all = getAll(".CKFOMAN-data-inforow-toggle");
                                if (all) {
                                    [...all].forEach(it => {
                                        it.checked = true;
                                        it.onchange();
                                    });
                                }
                                refreshChecked();
                                resetInfoBar();
                            }
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '反选';
                            btn.onclick = e => {
                                setInfoBar("正在处理反选...");
                                const all = getAll(".CKFOMAN-data-inforow-toggle");
                                if (all) {
                                    [...all].forEach(it => {
                                        it.checked = !it.checked;
                                        it.onchange();
                                    });
                                }
                                refreshChecked();
                                resetInfoBar();
                            }
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '全不选';
                            btn.onclick = e => {
                                setInfoBar("正在处理取选...");
                                const all = getAll(".CKFOMAN-data-inforow-toggle");
                                if (all) {
                                    [...all].forEach(it => {
                                        it.checked = false;
                                        it.onchange();
                                    });
                                }
                                refreshChecked();
                                resetInfoBar();
                            }
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '间选';
                            btn.onclick = e => {
                                setInfoBar("正在处理间选...");
                                const all = getAll(".CKFOMAN-data-inforow-toggle");
                                if (all) {
                                    let shouldCheck = false;
                                    for (let el of [...all]) {
                                        if (el.checked === true) {
                                            shouldCheck = !shouldCheck;
                                        } else {
                                            if (shouldCheck) setToggleStatus(el.getAttribute("data-targetmid"), true);
                                        }
                                    }
                                }
                                resetInfoBar();
                            }
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '筛选 <i class="mdi mdi-18px mdi-chevron-down"></i>';
                            btn.onclick = async e => {
                                //alertModal("施工中", "此功能尚未实现!", "返回");
                                openModal("筛选", await makeDom("div", async container => {
                                    const filtersid = "CKFOMAN-filters";
                                    [
                                        await makeDom("div", async tip => {
                                            tip.innerHTML = "勾选要生效的筛选器"
                                        }),
                                        cfg.enableNewModules?await makeDom("div", async tip => {
                                            tip.innerHTML = "👉尝鲜新版筛选器";
                                            tip.style.color = "#00a0e9";
                                            tip.onclick = () => openNewFilterGuideScreen();
                                        }):null,
                                        divider(),
                                        await makeDom("form", async filters => {
                                            filters.id = filtersid;
                                            filters.style.display = "flex";
                                            filters.style.textAlign = "center";
                                            filters.style.flexDirection = "column";
                                            filters.style.flexWrap = "nowrap";
                                            filters.style.alignContent = "center";
                                            filters.style.justifyContent = "space-between";
                                            filters.style.alignItems = "stretch";
                                            [
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-clear";
                                                    select.name = "val-clear";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "0";
                                                            opt.innerHTML = "应用筛选器时取消已选择项目"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "1";
                                                            opt.innerHTML = "应用筛选器时保留已选择项目"
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                }),
                                                await makeDom("div", div => div.innerHTML = "+"),
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-invalid";
                                                    select.name = "val-invalid";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "-2";
                                                            opt.innerHTML = "不使用注销账户选择器"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "0";
                                                            opt.innerHTML = "正常账户"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "1";
                                                            opt.innerHTML = "已注销账户"
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                }),
                                                await makeDom("div", div => div.innerHTML = "+"),
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-special";
                                                    select.name = "val-special";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "-2";
                                                            opt.innerHTML = "不使用特别关注选择器"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "0";
                                                            opt.innerHTML = "非特别关注"
                                                            if(!datas.isSelf) opt.disabled = true;
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "1";
                                                            opt.innerHTML = "特别关注"
                                                            if(!datas.isSelf) opt.disabled = true;
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                }),
                                                await makeDom("div", div => div.innerHTML = "+"),
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-vip";
                                                    select.name = "val-vip";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "-2";
                                                            opt.innerHTML = "不使用会员选择器"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "0";
                                                            opt.innerHTML = "没有大会员的用户"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "1";
                                                            opt.innerHTML = "月度大会员用户"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "6";
                                                            opt.innerHTML = "年度大会员用户"
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                }),
                                                await makeDom("div", div => div.innerHTML = "+"),
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-official";
                                                    select.name = "val-official";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "-2";
                                                            opt.innerHTML = "不使用认证账户选择器"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "0";
                                                            opt.innerHTML = "没有认证的用户"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "1";
                                                            opt.innerHTML = "认证用户"
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                }),
                                                await makeDom("div", div => div.innerHTML = "+"),
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-fans";
                                                    select.name = "val-fans";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "-2";
                                                            opt.innerHTML = "不使用互粉选择器"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "2";
                                                            opt.innerHTML = "单项关注的用户"
                                                            if(!datas.isSelf) opt.disabled = true;
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "6";
                                                            opt.innerHTML = "互粉用户"
                                                            if(!datas.isSelf) opt.disabled = true;
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                }),
                                                await makeDom("div", div => div.innerHTML = "+"),
                                                await makeDom("select", async select => {
                                                    select.id = filtersid + "-groups";
                                                    select.name = "val-groups";
                                                    [
                                                        await makeDom("option", opt => {
                                                            opt.value = "-4";
                                                            opt.innerHTML = "不使用分组选择器"
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "-3";
                                                            opt.innerHTML = "没有分组的用户"
                                                            if(!datas.isSelf) opt.disabled = true;
                                                        }),
                                                        await makeDom("option", opt => {
                                                            opt.value = "-2";
                                                            opt.innerHTML = "已有分组的用户"
                                                            if(!datas.isSelf) opt.disabled = true;
                                                        }),
                                                    ].forEach(s => select.appendChild(s));
                                                    if (datas.isSelf && Object.keys(datas.tags).length > 0) {
                                                        select.appendChild(await makeDom("option", opt => {
                                                            opt.innerHTML = "------------";
                                                            opt.disabled = true;
                                                        }));
                                                        for (let tag of Object.values(datas.tags)) {
                                                            select.appendChild(await makeDom("option", opt => {
                                                                opt.innerHTML = tag.name;
                                                                opt.value = tag.tagid;
                                                            }))
                                                        }
                                                    }
                                                }),
                                                divider(),
                                                await makeDom("label", async label => {
                                                    label.setAttribute("for", filtersid + "-beforetime");
                                                    label.innerHTML = "在什么时间前关注:";
                                                }),
                                                await makeDom("input", async choose => {
                                                    choose.id = filtersid + "-beforetime";
                                                    choose.name = "val-beforetime";
                                                    choose.setAttribute("type", "datetime-local");
                                                }),
                                                divider(),
                                                await makeDom("label", async label => {
                                                    label.setAttribute("for", filtersid + "-aftertime");
                                                    label.innerHTML = "在什么时间后关注:";
                                                }),
                                                await makeDom("input", async choose => {
                                                    choose.id = filtersid + "-aftertime";
                                                    choose.name = "val-aftertime";
                                                    choose.setAttribute("type", "datetime-local");
                                                }),
                                            ].forEach(el => filters.appendChild(el));
                                        }),
                                        divider(),
                                        await makeDom("div", async btns => {
                                            btns.style.display = "flex";
                                            btns.style.flexDirection = "row";
                                            btns.style.flexWrap = "nowrap";
                                            btns.style.alignContent = "stretch";
                                            btns.style.justifyContent = "space-around";
                                            btns.style.alignItems = "stretch";
                                            [
                                                await makeDom("button", btn => {
                                                    btn.className = "CKFOMAN-toolbar-btns";
                                                    btn.innerHTML = "应用";
                                                    btn.onclick = async () => {
                                                        const form = get("#" + filtersid);
                                                        const config = {
                                                            clear: form['val-clear'].value + "",
                                                            invalid: form['val-invalid'].value + "",
                                                            vip: form['val-vip'].value + "",
                                                            official: form['val-official'].value + "",
                                                            fans: form['val-fans'].value + "",
                                                            groups: form['val-groups'].value + "",
                                                            special: form['val-special'].value + "",
                                                            beforetime: {
                                                                enabled: form['val-beforetime'].value.length > 0,
                                                                before: form['val-beforetime'].valueAsNumber
                                                            },
                                                            aftertime: {
                                                                enabled: form['val-aftertime'].value.length > 0,
                                                                after: form['val-aftertime'].valueAsNumber
                                                            }
                                                        };
                                                        await applyFilters(config);
                                                        hideModal();
                                                    }
                                                }),
                                                await makeDom("button", btn => {
                                                    btn.className = "CKFOMAN-toolbar-btns";
                                                    btn.innerHTML = "取消";
                                                    btn.onclick = () => hideModal();
                                                }),
                                            ].forEach(el => btns.appendChild(el));
                                        })
                                    ].forEach(el => el&&container.appendChild(el));
                                }))
                            }
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '排序 <i class="mdi mdi-18px mdi-chevron-down"></i>';
                            btn.onclick = async e => {
                                openModal("选择排序方式", await makeDom("div", async select => {
                                    select.style.alignContent = "stretch";
                                    select.style.flexDirection = "row";
                                    select.id = "CKFOMAN-sortbtns-container";
                                    [
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "已选中优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按已选中优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => {
                                                    const xint = (datas.checked.includes(x.mid + "") || datas.checked.includes(parseInt(x.mid))) ? 1 : 0;
                                                    const yint = (datas.checked.includes(y.mid + "") || datas.checked.includes(parseInt(y.mid))) ? 1 : 0;
                                                    return yint - xint;
                                                })
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "按最新关注";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按最新关注排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(y.mtime) - parseInt(x.mtime))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "按最早关注";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按最早关注排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(x.mtime) - parseInt(y.mtime))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"));
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "大会员优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按大会员优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(y.vip.vipType) - parseInt(x.vip.vipType))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "无会员优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按无会员优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(x.vip.vipType) - parseInt(y.vip.vipType))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "认证优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按认证优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(y.official_verify.type) - parseInt(x.official_verify.type))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "无认证优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按无认证优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(x.official_verify.type) - parseInt(y.official_verify.type))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "已注销优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按已注销优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => {
                                                    const xint = isInvalid(x) ? 1 : 0;
                                                    const yint = isInvalid(y) ? 1 : 0;
                                                    return yint - xint;
                                                })
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "特别关注优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按特别关注优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(y.special) - parseInt(x.special))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "互相关注优先";
                                            btn.onclick = async e => {
                                                setInfoBar("正在按互相关注优先排序...");
                                                await alertModal("正在排序...", "请稍等...");
                                                refreshChecked();
                                                datas.followings.sort((x, y) => parseInt(y.attribute) - parseInt(x.attribute))
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,true);
                                                hideModal();
                                            }
                                        }),
                                        //divider(),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns CKFOMAN-sortbtns";
                                            btn.innerHTML = "不修改 | 取消";
                                            btn.onclick = e => hideModal();
                                        })
                                    ].forEach(el => select.appendChild(el));
                                }));
                            }
                        }))
                        toolbar.appendChild(await makeDom("button", btn => {
                            btn.className = "CKFOMAN-toolbar-btns";
                            btn.innerHTML = '更多 <i class="mdi mdi-18px mdi-chevron-down"></i>';
                            btn.onclick = async e => {
                                openModal("更多...", await makeDom("div", async select => {
                                    select.style.alignContent = "stretch";
                                    [
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "快速选中...";
                                            btn.onclick = async e => {
                                                hideModal();
                                                await wait(300);
                                                openModal("快速选中", await makeDom("div", async select => {
                                                    select.style.alignContent = "stretch";
                                                    [
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "加选: 悄悄关注用户";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理加选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (d.attribut===1||d.isWhisper) {
                                                                        toggleSwitch(d.mid, true);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "加选: 所有已注销用户";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理加选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (isInvalid(d)) {
                                                                        toggleSwitch(d.mid, true);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "加选: 所有两年前的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理加选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (isLongAgo(d.mtime)) {
                                                                        toggleSwitch(d.mid, true);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "加选: 所有两个月内的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理加选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (isNearly(d.mtime)) {
                                                                        toggleSwitch(d.mid, true);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        divider(),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 悄悄关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (d.attribute===1||d.isWhisper) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有两年前的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (isLongAgo(d.mtime)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有两个月内的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (isNearly(d.mtime)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有有大会员的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                const hasVIP = d => {
                                                                    return d.vip.vipType !== 0;
                                                                }
                                                                for (let d of datas.followings) {
                                                                    if (hasVIP(d)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有认证账号的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                const isVerified = d => {
                                                                    return d.official_verify.type > 0;
                                                                }
                                                                for (let d of datas.followings) {
                                                                    if (isVerified(d)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有特别关注的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                const isSpecial = d => {
                                                                    return d.special === 1;
                                                                }
                                                                for (let d of datas.followings) {
                                                                    if (isSpecial(d)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有互相关注的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                for (let d of datas.followings) {
                                                                    if (isFans(d)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "减选: 所有有分组的关注";
                                                            btn.onclick = async e => {
                                                                setInfoBar("正在处理减选");
                                                                await alertModal("正在处理...", "请稍等...");
                                                                const hasGroup = d => {
                                                                    return d.tag !== null;
                                                                }
                                                                for (let d of datas.followings) {
                                                                    if (hasGroup(d)) {
                                                                        toggleSwitch(d.mid, false);
                                                                    }
                                                                }
                                                                resetInfoBar();
                                                                hideModal();
                                                            }
                                                        }),
                                                        divider(),
                                                        await makeDom("button", btn => {
                                                            btn.className = "CKFOMAN-toolbar-btns";
                                                            btn.style.margin = "4px 0";
                                                            btn.innerHTML = "不修改 | 取消";
                                                            btn.onclick = e => hideModal();
                                                        })
                                                    ].forEach(el => select.appendChild(el));
                                                }));
                                            }
                                        }),
                                        divider(),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "管理分组 (增加/删除) (Beta)";
                                            if (!datas.isSelf) {
                                                btn.classList.add("grey");
                                                btn.disabled = true;
                                                btn.title = "非个人空间,无法操作。";
                                                btn.onclick = () => createOtherSpaceAlert();
                                            } else btn.onclick = e => createGroupInfoModal();
                                        }),
                                        divider(),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            refreshChecked();
                                            if (datas.checked.length > 0)
                                                btn.innerHTML = "导出所有选中的UID列表..."
                                            else
                                                btn.innerHTML = "导出所有关注的UID列表...";
                                            btn.onclick = async e => {
                                                let list;
                                                if (datas.checked.length > 0)
                                                    list = datas.checked.join(',');
                                                else
                                                    list = Object.keys(datas.mappings).join(',');
                                                let mtitle = "";
                                                if(await copy(list)){
                                                    mtitle+="✅ 内容已经自动复制到剪贴板, 你可以粘贴到别处";
                                                }else{
                                                    mtitle+="请单击列表并按Ctrl+C手动复制";
                                                }
                                                unsafeWindow.CKFOMAN_EXPORTUIDS = list;
                                                unsafeWindow.CKFOMAN_EXPORTTOFILE = ()=>{
                                                    download("export_uids.txt",unsafeWindow.CKFOMAN_EXPORTUIDS);
                                                }
                                                mtitle+=`,或者:<button class="CKFOMAN-toolbar-btns" onclick="CKFOMAN_EXPORTTOFILE()">保存为文件</button>`
                                                await alertModal("导出UID", `
                                                ${mtitle}
                                                <br>
                                                <textarea readonly style="width: 400px;" onclick="this.select()" >${list}</textarea>
                                                `, "确定");
                                                resetInfoBar();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            refreshChecked();
                                            if (datas.checked.length > 0)
                                                btn.innerHTML = "导出所有选中的UID结构数据..."
                                            else
                                                btn.innerHTML = "导出所有关注的UID结构数据...";
                                            btn.onclick = async e => {
                                                let list;
                                                if (datas.checked.length > 0)
                                                    list = datas.checked//listdom;
                                                else
                                                    list = Object.keys(datas.mappings);
                                                const mapToObj = (uid)=>{
                                                    if(datas.mappings.hasOwnProperty(+uid)){
                                                        const {mid,name,uname,tag} = datas.mappings[+uid];
                                                        let tags = tag?.map(t=>datas.tags[t]?.name??null).filter(t=>!!t);
                                                        return {mid,name:name??uname??'',tag:tags??[]};
                                                    }else return null;
                                                }
                                                let infoList = list.map(it=>mapToObj(it)).filter(it=>!!it);
                                                let copyList = JSON.stringify(infoList);
                                                let mtitle = "";
                                                if(await copy(copyList)){
                                                    mtitle+="✅ 内容已经自动复制到剪贴板, 你可以粘贴到别处";
                                                }else{
                                                    mtitle+="请单击列表并按Ctrl+C手动复制";
                                                }
                                                unsafeWindow.CKFOMAN_EXPORTUIDS = copyList;
                                                unsafeWindow.CKFOMAN_EXPORTTOFILE = ()=>{
                                                    download("export_uids.json",unsafeWindow.CKFOMAN_EXPORTUIDS);
                                                }
                                                mtitle+=`,或者:<button class="CKFOMAN-toolbar-btns" onclick="CKFOMAN_EXPORTTOFILE()">保存为文件</button>`
                                                await alertModal("导出UID结构数据", `
                                                ${mtitle}
                                                <br>
                                                <textarea readonly style="width: 400px;" onclick="this.select()" >${copyList}</textarea>
                                                `, "确定");
                                                resetInfoBar();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            refreshChecked();
                                            if (datas.checked.length > 0)
                                                btn.innerHTML = "导出所有被选中的UP的当前缓存数据..."
                                            else
                                                btn.innerHTML = "导出所有关注的UP的当前缓存数据...";
                                            btn.onclick = async e => {
                                                    let list;
                                                    if (datas.checked.length > 0)
                                                        list = datas.checked;//listdom;
                                                    else
                                                        list = Object.keys(datas.mappings);
                                                    const mapToObj = (uid) => {
                                                        try {
                                                            console.log(1001,{uid})
                                                            if (datas.mappings.hasOwnProperty(+uid)) {
                                                                const full = datas.mappings[+uid];
                                                                let tags = full.tag?.map(t => datas.tags[t]?.name ?? null).filter(t => !!t);
                                                                return { ...full, tag: tags ?? [] };
                                                            } else return null;
                                                        } catch (err) {
                                                            console.error('e!!',err);
                                                            return null;
                                                        }
                                                    }
                                                    let infoList = list.map(it=>mapToObj(it)).filter(it=>!!it);
                                                    let copyList = JSON.stringify(infoList);
                                                    let mtitle = "";
                                                    if(await copy(copyList)){
                                                        mtitle+="✅ 内容已经自动复制到剪贴板, 你可以粘贴到别处";
                                                    }else{
                                                        mtitle+="请单击列表并按Ctrl+C手动复制";
                                                    }
                                                    unsafeWindow.CKFOMAN_EXPORTUIDS = copyList;
                                                    unsafeWindow.CKFOMAN_EXPORTTOFILE = ()=>{
                                                        download("export_userdetails.json",unsafeWindow.CKFOMAN_EXPORTUIDS);
                                                    }
                                                    mtitle+=`,或者:<button class="CKFOMAN-toolbar-btns" onclick="CKFOMAN_EXPORTTOFILE()">保存为文件</button>`
                                                    await alertModal("导出完整缓存数据", `
                                                    ${mtitle}
                                                    <br>
                                                    <textarea readonly style="width: 400px;" onclick="this.select()" >${copyList}</textarea>
                                                    `, "确定");
                                                    resetInfoBar();
                                                }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "从UID列表导入关注...";
                                            if (!datas.isSelf) {
                                                btn.classList.add("grey");
                                                btn.disabled = true;
                                                btn.title = "非个人空间,无法操作。";
                                                btn.onclick = () => createOtherSpaceAlert();
                                            } else
                                                btn.onclick = async e => {
                                                    hideModal();
                                                    await wait(300);
                                                    openModal("导入UID", await makeDom("div", async modaldiv => {
                                                        [
                                                            await makeDom("tip", tip => tip.innerHTML = "请输入导入的UID列表,用英文半角逗号','分割"),
                                                            await makeDom("textarea", input => {
                                                                input.id = "CKFOMAN-import-textarea";
                                                                input.placeholder = "1111111,2222222,3333333..."
                                                            }),
                                                            divider(),
                                                            await makeDom("div", async btns => {
                                                                btns.style.display = "flex";
                                                                [
                                                                    await makeDom("button", btn => {
                                                                        btn.className = "CKFOMAN-toolbar-btns orange";
                                                                        btn.innerHTML = "批量关注";
                                                                        btn.onclick = async e => {
                                                                            const value = get("#CKFOMAN-import-textarea").value;
                                                                            if (value.length === 0) {
                                                                                await alertModal("无法导入", "空白数据", "确定");
                                                                                return;
                                                                            }
                                                                            setInfoBar("正在验证导入");
                                                                            await alertModal("正在导入", "正在处理刚刚输入的列表,请稍等...");
                                                                            const parts = value.split(',');
                                                                            const finalList = [];
                                                                            const followed = Object.keys(datas.mappings);
                                                                            for (let part of parts) {
                                                                                if (part.trim().length === 0) {
                                                                                    log(part, "is empty, skipped");
                                                                                } else if (part.trim().match(/[^0-9]/) === null) {
                                                                                    const int = parseInt(part.trim());
                                                                                    if (followed.includes(int) || followed.includes(int + "")) {
                                                                                        log(part, "has already followed, skipped");
                                                                                    } else if (int <= 0) {
                                                                                        log(part, "smaller than zero, skipped");
                                                                                    } else {
                                                                                        finalList.push(int);
                                                                                    }
                                                                                } else {
                                                                                    log(part, "is not a number, skipped");
                                                                                }
                                                                            }
                                                                            await alertModal("正在导入", `正在导入${finalList.length}个关注...`);
                                                                            const result = await batchOperateUser(finalList, RELE_ACTION.FOLLOW);
                                                                            if (result.ok) {
                                                                                await alertModal("导入完成", `${finalList.length}个关注全部导入成功!`, "确定");
                                                                                return createMainWindow(true);
                                                                            } else {
                                                                                if ("data" in result) {
                                                                                    if (result.data !== null && "failed_fids" in result.data)
                                                                                        await alertModal("导入完成,但部分失败", `尝试导入了${finalList.length}个关注,但是有${result.data.failed_fids.length}个导入失败:
                                                                                <br>
                                                                                <textarea readonly onclick="this.select()">${result.data.failed_fids.join(',')}</textarea>`, "确定");
                                                                                    else
                                                                                        await alertModal("导入失败", `尝试导入了${finalList.length}个关注但失败了,原因:<br><pre>${result.res}</pre>`, "确定");
                                                                                    return createMainWindow(true);
                                                                                } else {
                                                                                    await alertModal("导入失败", `尝试导入了${finalList.length}个关注但失败了,原因:<br><pre>${result.res}</pre>`, "确定");
                                                                                    return createMainWindow(true);
                                                                                }
                                                                            }
                                                                        };
                                                                    }),
                                                                    await makeDom("button", btn => {
                                                                        btn.className = "CKFOMAN-toolbar-btns";
                                                                        btn.innerHTML = "取消操作";
                                                                        btn.onclick = e => hideModal();
                                                                    })
                                                                ].forEach(el => btns.appendChild(el));
                                                            })
                                                        ].forEach(el => modaldiv.appendChild(el));
                                                    }));
                                                }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "基于UID列表批量取关...";
                                            if (!datas.isSelf) {
                                                btn.classList.add("grey");
                                                btn.disabled = true;
                                                btn.title = "非个人空间,无法操作。";
                                                btn.onclick = () => createOtherSpaceAlert();
                                            } else
                                                btn.onclick = async e => {
                                                    hideModal();
                                                    await wait(300);
                                                    openModal("取关UID", await makeDom("div", async modaldiv => {
                                                        [
                                                            await makeDom("tip", tip => tip.innerHTML = "请输入取关的UID列表,用英文半角逗号','分割"),
                                                            await makeDom("textarea", input => {
                                                                input.id = "CKFOMAN-import-textarea";
                                                                input.placeholder = "1111111,2222222,3333333..."
                                                            }),
                                                            divider(),
                                                            await makeDom("div", async btns => {
                                                                btns.style.display = "flex";
                                                                [
                                                                    await makeDom("button", btn => {
                                                                        btn.className = "CKFOMAN-toolbar-btns orange";
                                                                        btn.innerHTML = "批量取关";
                                                                        btn.onclick = async e => {
                                                                            const value = get("#CKFOMAN-import-textarea").value;
                                                                            if (value.length === 0) {
                                                                                await alertModal("无法取关", "空白数据", "确定");
                                                                                return;
                                                                            }
                                                                            setInfoBar("正在验证数据");
                                                                            await alertModal("正在取关", "正在处理刚刚输入的列表,请稍等...");
                                                                            const parts = value.split(',');
                                                                            const finalList = [];
                                                                            const followed = Object.keys(datas.mappings);
                                                                            for (let part of parts) {
                                                                                if (part.trim().length === 0) {
                                                                                    log(part, "is empty, skipped");
                                                                                } else if (part.trim().match(/[^0-9]/) === null) {
                                                                                    const int = parseInt(part.trim());
                                                                                    if (!followed.includes(int) && !followed.includes(int + "")) {
                                                                                        log(part, "has not been followed, skipped");
                                                                                    } else if (int <= 0) {
                                                                                        log(part, "smaller than zero, skipped");
                                                                                    } else {
                                                                                        finalList.push(int);
                                                                                    }
                                                                                } else {
                                                                                    log(part, "is not a number, skipped");
                                                                                }
                                                                            }
                                                                            await alertModal("正在取关", `正在取消${finalList.length}个关注...`);
                                                                            const result = await unfollowUsers(finalList);
                                                                            if (result.ok) {
                                                                                await alertModal("批量取关完成", `${finalList.length}个关注全部取关成功!`, "确定");
                                                                                return createMainWindow(true);
                                                                            } else {
                                                                                if ("data" in result) {
                                                                                    if (result.data !== null && "failed_fids" in result.data)
                                                                                        await alertModal("批量取关完成,但部分失败", `尝试移除了${finalList.length}个关注,但是有${result.data.failed_fids.length}个移除失败:
                                                                                <br>
                                                                                <textarea readonly onclick="this.select()">${result.data.failed_fids.join(',')}</textarea>`, "确定");
                                                                                    else
                                                                                        await alertModal("批量取关失败", `尝试移除了${finalList.length}个关注但失败了,原因:<br><pre>${result.res}</pre>`, "确定");
                                                                                    return createMainWindow(true);
                                                                                } else {
                                                                                    await alertModal("批量取关失败", `尝试移除了${finalList.length}个关注但失败了,原因:<br><pre>${result.res}</pre>`, "确定");
                                                                                    return createMainWindow(true);
                                                                                }
                                                                            }
                                                                        };
                                                                    }),
                                                                    await makeDom("button", btn => {
                                                                        btn.className = "CKFOMAN-toolbar-btns";
                                                                        btn.innerHTML = "取消操作";
                                                                        btn.onclick = e => hideModal();
                                                                    })
                                                                ].forEach(el => btns.appendChild(el));
                                                            })
                                                        ].forEach(el => modaldiv.appendChild(el));
                                                    }));
                                                }
                                        }),
                                        divider(),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "重新载入列表";
                                            btn.onclick = async e => {
                                                await alertModal("重新载入列表", "正在重新载入列表。此重载不会重新获取数据。");
                                                datas.dommappings = {};
                                                await renderListTo(get("#CKFOMAN-MAINLIST"),datas.followings,false);
                                                resetInfoBar();
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "重新载入数据";
                                            btn.onclick = async e => {
                                                await alertModal("重新载入数据", "正在重新载入数据和列表。将会重新获取所有数据。");
                                                datas.dommappings = {};
                                                await createMainWindow(true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("div", div => {
                                            div.style.margin = "4px 0";
                                            const size = CacheManager.getSize();
                                            div.innerHTML = "ℹ 本地缓存空间已占用 " + size + " MB。";
                                            if(size < 1.8){
                                                div.innerHTML += "无需处理。定期整理缓存可以减少空间占用。";
                                            }else if (size < 2.5) {
                                                div.innerHTML += "<b>建议整理缓存。</b>";
                                            } else {
                                                div.innerHTML += "<b>建议整理或清理缓存以避免缓存空间超出配额。</b>";
                                            }
                                            div.onclick = e => showCacheQuotaModal();
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "整理缓存";
                                            btn.onclick = async e => {
                                                await alertModal("整理缓存", "正在整理缓存并移除额外数据,稍后会重新加载。");
                                                CacheManager.prune();
                                                await alertModal("重新载入数据", "正在重新载入数据和列表。");
                                                datas.dommappings = {};
                                                await createMainWindow();
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "清空缓存";
                                            btn.onclick = async e => {
                                                await alertModal("清空全部缓存", "正在清空全部缓存,稍后会自动重新加载所有数据。");
                                                CacheManager.clean();
                                                await alertModal("重新载入数据", "正在重新载入数据和列表。将会重新获取所有数据。");
                                                datas.dommappings = {};
                                                await createMainWindow(true);
                                                hideModal();
                                            }
                                        }),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "关于和反馈";
                                            btn.onclick = async e => {
                                                await alertModal("关于 “关注管理器 FoMan”", (await makeDom("div", async div => {
                                                    div.style.textAlign = "left";
                                                    div.style.width = "400px";
                                                    [
                                                        document.createElement("br"),
                                                        await makeDom("i", i => {
                                                            i.className = "mdi mdi-48px mdi-broom"
                                                            i.style.color = "#0091ea";
                                                            i.style.textAlign = "center";
                                                            i.style.display = "block";
                                                            i.style.width = "fit-content";
                                                            i.style.margin = "0 auto";
                                                        }),
                                                        document.createElement("br"),
                                                        await makeDom("p", span =>
                                                            span.innerHTML = `版本: v${cfg.VERSION}<br>`
                                                                + `License: GPLv3<br>`
                                                                + `作者: CKylinMC`
                                                        ),
                                                        await makeDom("p", span =>
                                                            span.innerHTML = `脚本首页: <a href="https://greasyfork.org/zh-CN/scripts/428895">GreasyFork</a> | <a href="https://github.com/CKylinMC/UserJS">Github</a>`
                                                        ),
                                                        divider(),
                                                        await makeDom("p", span =>
                                                            span.innerHTML = `如果出现问题,请前往GreasyFork反馈区或Github Issues进行反馈,如果好用,还请给我一个好评!十分感谢!`
                                                        ),
                                                        document.createElement("br"),
                                                    ].forEach(el => div.appendChild(el));
                                                })).outerHTML, "确定");
                                                resetInfoBar();
                                            }
                                        }),
                                        divider(),
                                        await makeDom("button", btn => {
                                            btn.className = "CKFOMAN-toolbar-btns";
                                            btn.style.margin = "4px 0";
                                            btn.innerHTML = "返回";
                                            btn.onclick = e => hideModal();
                                        })
                                    ].forEach(el => select.appendChild(el));
                                }));
                            }
                        }))
                    });
                    const list = await makeDom("div", async list => {
                        list.className = "CKFOMAN-scroll-list";
                        list.id = "CKFOMAN-MAINLIST";
                        await renderListTo(list,datas.followings,!forceRefetch);
                    })
                    screen.appendChild(toolbar);
                    screen.appendChild(list);
                }));
            })
            .catch(async (e) => {
                log(e);
                setInfoBar();
                let errtitle = "获取数据失败";
                let errdesc = "请尝试刷新页面重试";
                log(datas.fetchstat);
                switch(datas.fetchstat){
                    case "GUEST-LIMIT":
                        errtitle = "访客限制";
                        errdesc = "由于访客限制,获取数据失败。"
                        break;
                    case "PERMS-DENIED":
                        errtitle = "无权查看";
                        errdesc = "由于当前空间主人已设置访客不可见,因此无法查看到任何信息。"
                        break;
                }
                createScreen(await makeDom("div", dom => {
                    dom.style.position = "fixed";
                    dom.style.left = "50%";
                    dom.style.top = "50%";
                    dom.style.transform = "translate(-50%,-50%)";
                    dom.style.textAlign = "center";
                    dom.innerHTML = `<h2><i class="mdi mdi-alert-remove" style="color:orangered;font-size: xx-large"></i><br>${errtitle}</h2>${errdesc}`;
                }));
            })
    }
    const setToggleStatus = (mid, status = false, operateDom = true) => {
        if (operateDom) {
            const selection = getAll(`input.CKFOMAN-data-inforow-toggle[data-targetmid="${mid}"]`);
            if (selection) {
                for (let el of selection) {
                    el.checked = status;
                }
            }
        }
        if (status) {
            if (!datas.checked.includes(mid + "") && !datas.checked.includes(parseInt(mid)))
                datas.checked.push(mid);
        } else {
            if (datas.checked.includes(mid + "")) datas.checked.splice(datas.checked.indexOf(mid + ""), 1);
            else if (datas.checked.includes(parseInt(mid))) datas.checked.splice(datas.checked.indexOf(parseInt(mid)), 1);
        }
        resetInfoBar();
    }
    const renderListTo = async (dom, datalist = datas.followings, cacheAndreuse = false) => {
        setInfoBar("正在渲染列表...");
        await wait(1);
        const isMainList = cacheAndreuse||datalist===datas.followings;
        dom.innerHTML = '';
        const getDomForData = async it=>{
            if(cacheAndreuse&&(datas.dommappings[it.mid+""]&& datas.dommappings[it.mid+""] instanceof HTMLElement)) return datas.dommappings[it.mid+""];
            return upinfoline(it);
        }
        for (let it of datalist) {
            const upinfolinedom = await getDomForData(it);
            dom.appendChild(upinfolinedom);
            if(isMainList) datas.dommappings[it.mid+""] = upinfolinedom;
        }
        resetInfoBar();
    }
    const renderTagListTo = async (dom,selectedId=[],cb = ()=>{},inManager = true) => {
        setInfoBar("正在渲染列表...");
        await wait(100);
        dom.innerHTML = '';
        for (let it of Object.values(datas.tags)) {
            log(it);
            dom.appendChild(await taginfoline(it,cb,selectedId.includes(it.tagid),inManager,inManager));
        }
        resetInfoBar();
    }
    const createScreen = async (content) => {
        getContainer().innerHTML = '';
        getContainer().appendChild(content);
    }

    const callAlertWindow = () => {
        cfg.closedByBlocker++;
        if (cfg.I_KNOW_WHAT_IM_DOING) return hideModal();
        cfg.disableCloseModalFromBlockWindow = true;
        const waitTimer = cfg.debug ? 10 : 5;
        alertModal("等一下,这不是正确的关闭方式!",
            `点击空白处可以关闭弹窗,但是有些窗口下这样可能会导致未知问题,<b>请尽量减少使用此方式关闭弹窗。</b>${cfg.debug ? "<br><br><i>修改脚本第53行附近的'I_KNOW_WHAT_IM_DOING:false'的false为true可以永久阻止此弹窗出现直到下一次更新。</i>" : ""}<br><br>此消息每页面只会显示一次,此窗口 ${waitTimer} 秒后自动关闭。<br><progress value=0 max=100 style="width: 100%;height: 4px" id='CKFOMAN-TIMERPROGRESS'></progress>`);
        wait(10).then(async () => {
            await CKTools.waitForDom('#CKFOMAN-TIMERPROGRESS');
            const interval = setInterval(() => {
                const pg = CKTools.get('#CKFOMAN-TIMERPROGRESS');
                if (!pg) return (log('pg not found',pg??null),clearInterval(interval));
                pg.value = pg.value + (cfg.debug?1:2);
                if(pg>100) return (log('pg is full',pg??null),clearInterval(interval));
            },100);
        });
        wait((waitTimer * 1000)+100).then(() => {
            cfg.disableCloseModalFromBlockWindow = false;
            hideModal();
        });
    }

    const closeModalFromBlockWindow = () => {
        if (cfg.disableCloseModalFromBlockWindow) return;
        if (!cfg.closedByBlocker) {
            cfg.closedByBlocker = 1;
        } else if (cfg.closedByBlocker == 3) {
            callAlertWindow();
        } else {
            cfg.closedByBlocker++;
            closeModal();
        }
    }

    const blockWindow = (block = true) => {
        addStyle(`
        #CKFOMAN-blockWindow{
            z-index: 99005;
            display: block;
            background: #00000080;
            opacity: 0;
            transition: all .3s;
            position: fixed;
            left: 0;
            top: 0;
            width: 100vw;
            height: 100vh;
        }
        #CKFOMAN-blockWindow.hide{
            pointer-events: none;
            opacity: 0;
        }
        #CKFOMAN-blockWindow.show{
            opacity: 1;
        }
        `, "CKFOMAN-blockWindow-css", "unique");
        let dom = get("#CKFOMAN-blockWindow");
        if (!dom) {
            dom = document.createElement("div");
            dom.id = "CKFOMAN-blockWindow";
            dom.className = "hide";
            document.body.appendChild(dom);
        }
        dom.onclick = e => closeModalFromBlockWindow();
        datas.preventUserCard = block;
        if (block) {
            dom.className = "show";
        } else {
            dom.className = "hide";
        }
    }

    const injectSideBtn = () => {
        addStyle(`
        #CKFOMAN-floatbtn{
            box-sizing: border-box;
            z-index: 9999;
            position: fixed;
            left: -15px;
            width: 30px;
            height: 30px;
            background: black;
            opacity: 0.8;
            color: white;
            cursor: pointer;
            border-radius: 50%;
            text-align: right;
            line-height: 24px;
            border: solid 3px #00000000;
            transition: opacity .3s 1s, background .3s, color .3s, left .3s, border .3s;
            top: 120px;
            top: 30vh;
        }
        #CKFOMAN-floatbtn::after,#CKFOMAN-floatbtn::before{
            z-index: 9990;
            content: "关注管理器";
            pointer-events: none;
            position: fixed;
            left: -20px;
            height: 30px;
            background: black;
            opacity: 0;
            color: white;
            cursor: pointer;
            border-radius: 8px;
            padding: 0 12px;
            text-align: right;
            line-height: 30px;
            transition: all .3s;
            top: 123px;
            top: 30vh;
        }
        #CKFOMAN-floatbtn::after{
            content: "← 关注管理器";
            animation:CKFOMAN-tipsOut forwards 5s 3.5s;
        }
        #CKFOMAN-floatbtn:hover::before{
            left: 30px;
            opacity: 1;
        }
        #CKFOMAN-floatbtn:hover{
            border: solid 3px black;
            transition: opacity .3s 0s, background .3s, color .3s, left .3s, border .3s;
            background: white;
            color: black;
            opacity: 1;
            left: -5px;
        }
        #CKFOMAN-floatbtn.hide{
            left: -40px;
        }
        @keyframes CKFOMAN-tipsOut{
            5%,95%{
                opacity: 1;
                left: 20px;
            }
            0%,100%{
                left: -20px;
                opacity: 0;
            }
        }
        `, "CKFOMAN-floatbtn-css", "unique");

        const toggle = document.createElement("div");
        toggle.id = "CKFOMAN-floatbtn";
        toggle.innerHTML = `<i class="mdi mdi-18px mdi-wrench" style="display: inline-block;transform: rotateY(180deg) translateX(3px);"></i>`;
        toggle.onclick = () => createMainWindow();
        document.body.appendChild(toggle);
    }

    const startInject = () => {
        if(!unsafeWindow.FoManPlugins){
            unsafeWindow.FoManPlugins = {}
        }
        initModal();
        // unsafeWindow.addEventListener("message", event => {
        //     if (!event.data) return;
        //     if (!(event.data instanceof String)) return;
        //     if (event.data.startsWith("CKFOMANSTATUSCHANGES|")) {
        //         log(event.data)
        //         const parts = event.data.split("|");
        //         setToggleStatus(parts[1], parts[2] === "1");
        //     }
        // })
        injectSideBtn();
        if (cfg.debug) {
            unsafeWindow.CKFOMAN_DBG = {
                cfg, datas
            }
        }
        unsafeWindow.openFollowManager = forceRefetch=>createMainWindow(forceRefetch);
    };

    startInject();
})();