bgm_wikihelper.core.js

bgm自动加条目和关联的基础库

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/407699/845520/bgm_wikihelpercorejs.js

 async function baseGet(url,type,ua) {
        if (!type) {
            type = 'document'
        };
        if (ua) {
            ua= "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Mobile Safari/537.36''Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Mobile Safari/537.36";
        }else{
            ua=undefined;
        }
        return new Promise((resolve,reject) => {
           GM_xmlhttpRequest({
                method: 'get',
                url: url,
                headers: {
                    'User-Agent': ua,
                    'Referer': url
                },
                responseType: type,
                onload: function(res){
                    if(res.status==200){
                        // console.log(res)
                        return resolve(res)
                    }else if(res.status.toString().startsWith('50')){
                        reject(res);
                       
                    }
                    else{
                        // console.log('ret')
                        resolve();
                        return false;
                    }
                }
            })
        })
    }

async function get(url,type,ua){
    return baseGet(url,type,ua).catch(function(err){
        // console.log(err)
        if(err){
            return get(url,type,ua)
        }
    })
}


    /**/
    async function basePost(url, data, fd) {
        let ctType
        if (fd) {
            ctType = undefined
        } else {
            ctType = "application/x-www-form-urlencoded"
        }
        return new Promise((resolve,reject) => {
            GM_xmlhttpRequest({
                method: 'post',
                url: url,
                data: data,
                headers: {
                    "Content-Type": ctType,
                    'Referer': url
                },
                onload: function(res){
                    if(res.status==200){
                        // console.log(res)
                        return resolve(res)
                    }else{
                        // console.log('ret')
                        reject(res);
                    }
                }
            })
        })
    }


    async function post(url, data, fd){
        return basePost(url, data, fd).catch(function(err){
            // console.log(err)
            if(err){
                return post(url, data, fd)
            }
        })
    }


    //获取url的html内容
    async function getResBody(url,ua) {
            let res = await get(url,undefined,ua);
            let body;
            if(res){
                body = res.responseText;
                return body
            }else{
                console.error(`链接${url}返回40x,无法正常访问`)
            }
            // console.log(res)
            // if(res.status!==200){
            //  return getResBody(url)
            // }else{
            //     body = res.responseText;
            //     return $(body);
            // }
        }

    //过滤特殊字符串
    function stripscript(s) {
        let pattern = new RegExp("[`~★☆○·!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“’。,、?]")
        let rs = "";
        for (var i = 0; i < s.length; i++) {
            rs = rs + s.substr(i, 1).replace(pattern, ' ');
        }
        return rs;
    }


    //将rgb()改成hex值
    function convertRGBDecimalToHex(rgb) {
        var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/;
        var values = regex.exec(rgb);
        if (values.length != 4) {
            return rgb; // fall back to what was given.
        }
        var r = Math.round(parseFloat(values[1]));
        var g = Math.round(parseFloat(values[2]));
        var b = Math.round(parseFloat(values[3]));
        return "#" +
            (r + 0x10000).toString(16).substring(3).toUpperCase() +
            (g + 0x10000).toString(16).substring(3).toUpperCase() +
            (b + 0x10000).toString(16).substring(3).toUpperCase();
    }

    //formhash不是一成不变的,所以加上了时间戳判定,每24小时更新一次
    async function getFormhash() {
        let localFormhash = localStorage.bgm_wikihelper_formhash||''.split(',');
        let time=+new Date();
        if (localFormhash[0]-time>=-86400000&&localFormhash[1]) {
            return localFormhash[1];
        } else {
            let res = await get('https://bgm.tv/new_subject/4');
            let doc = res.response;
            let formhash = $(doc).find('input[name=formhash]').attr('value');
            localStorage.bgm_wikihelper_formhash = `${time},${formhash}`;
            return formhash
        }

    }

/*    替换链接。主要是如果在a页面解析B页面结果的话,出来的的绝对连接的host部分会被替换成a链接的。
    所以要换回来.currentUrl指的是当前页面获取到的错误的绝对连接,official指的是正确的链接的host部分
    function replaceUrl(currentUrl,officialUrl){
        return currentUrl.replace(window.location.origin,officialUrl)
    }*/

    //搜索条目,name是条目名字,type是类型。1=Book 2=Anime 3=Music 4=Game 6=Real。默认anime
    /*    async function searchSubject(name, type) {
            if (!type) {
                type = 2
            }
            let url = `https://bgm.tv/subject_search/${name}?cat=${type}`;
            let res = await get(url);
            let doc = res.response;
            let items = $(doc).find('#browserItemList li');
            if (items.length > 0) {
                let userConfirm = confirm("看起来已经有同名条目了,去看看吧");
                if (userConfirm) {
                    GM_openInTab(url, 'active');
                    let addConfirm = confirm("已经确认过是否有重复条目了,要继续添加吗?");
                    if (addConfirm) {
                        //添加条目函数
                    }
                }
            } else {
                //后续添加条目函数
            }
        }
    */


    //type默认和charaKind值一致
    //cv检测之所以用这个不用api是为了防止同名角色混乱,所以人工检验
    //返回值为 是否没有重复条目
    async function searchChara(name, type) {
        if (type == "chara") {
            type = 'crt'
        } else {
            type = 'prsn'
        }

        //过滤掉charaName里的特殊字符,否则部分特殊字符会影响原文搜索结果
        name = stripscript(name);

        let url = `https://bgm.tv/mono_search/${name}?cat=${type}`;
        let res = await get(url);
        let doc = res.response;
        let items = $(doc).find('#columnSearchB .light_odd');
        await new Promise((resolve, reject) => {
            if (items.length > 0) {
                let userConfirm = confirm("看起来已经有同名条目了,去看看吧。");
                if (userConfirm) {
                    GM_openInTab(url, false);
                    let asked = false;
                    document.addEventListener('visibilitychange', () => {
                        if (!document.hidden && !asked) {
                            if (confirm('已经确认过是否有重复条目了,要继续添加吗?')) {
                                resolve(true)
                            } else {
                                //第二次选择否
                                reject(false)
                            }
                            asked = true;
                        }
                    });

                } else {
                    //第一次选择否
                    reject(false);
                }
            } else {
                //后续添加条目函数
                resolve(true);

            }
        })
    }

    async function relatedAllSub(obj, charaId) {
        for (let i in obj) {
            if (obj[i]) {
                await relatedSub(i, obj[i], charaId)
            }

        }
    }


    //角色关联主条目。subid是主条目ID,最外层relateSetting里指定,未指定则默认不关联
    //角色类型(主角配角客串)一次只能设置一个值,默认主角。在全局变量里改
    //目前是只关联提供的subid(也就是说是替换型关联而不是新增型关联。主要是考虑到替换条目的时候的需求)
    async function relatedSub(type, subid, charaId) {
        if (subid) {
            let url = `https://bgm.tv/character/${charaId}/add_related/${type}`;
            let subItems = subid.split(',');
            let infoArrlist = '';
            for (let n = 0; n < subItems.length; n++) {
                infoArrlist += `infoArr[n${n}][crt_type]=${charaType}&infoArr[n${n}][subject_id]=${subItems[n]}&`
            }
            let data = `formhash=${formhash}&${infoArrlist}submit=保存关联数据`
            let res = await post(url, data)
            if ($(res.responseText).find('#crtRelateSubjects li').length < 1) {
                alert(`关联${type}失败`)
            } else {
                alert(`关联${type}成功`)
            }
        }

    }

    //新增型关联条目,会在原先关联的基础上进行条目关联

    async function addRelatedAllSub(obj, charaId) {
        for (let i in obj) {
            if (obj[i]) {
                await addRelatedSub(i, obj[i], charaId)
            }

        }
    }


    async function addRelatedSub(type, subid, charaId) {
        if (subid) {
            let url = `https://bgm.tv/character/${charaId}/add_related/${type}`;
            let page=await getResBody(url);
            let subItems = subid.split(',');
            let infoArrlist = '';
            let relatedItem=$(page).find('ul#crtRelateSubjects li')
            relatedItem.map((k,v)=>{
               let type= $(v).find('select').prop('name')+'='+$(v).find('select').prop('value');
               let sub= $(v).find('input').prop('name')+'='+$(v).find('input').prop('value');
               infoArrlist+=`${type}&${sub}&`
            });

            for (let n = 0; n < subItems.length; n++) {
                let j=n+relatedItem.length;
                infoArrlist += `infoArr[n${j}][crt_type]=${charaType}&infoArr[n${j}][subject_id]=${subItems[n]}&`
            }
            let data = `formhash=${formhash}&${infoArrlist}submit=保存关联数据`
            let res = await post(url, data)
            if ($(res.responseText).find('#crtRelateSubjects li').length < 1) {
                alert(`关联${type}失败`)
            } else {
                alert(`关联${type}成功`)
            }
        }

    }



    //查询CV条目id,仅支持全名搜索,默认取第一条
    async function getCVId(name) {
        if (!name) {
            alert('未找到声优相关信息');
            return false;
        }
        let url = `https://bgm.tv/json/search-cv_person/${name}`;
        let res = await get(url, 'json');
        CVId = Object.keys(res.response)[0];
        let missCV = localStorage.bgm_wikihelper_misscv;
        if (!missCV) {
            missCV = '';
        }
        if (CVId) {
            return true;
        } else {
            missCV += (`,${name}`);
            localStorage.bgm_wikihelper_misscv = missCV;
            alert(`找不到该声优${name}`)
            return false
        }
    }

    //关联CV
    async function relatedCV(type, subid, charaId) {
        let subTypeId = subTypeArr.indexOf(type) + 1;
        let subItems = subid.split(',');
        let infoArrlist = '';
        if (subid) {
            let url = `https://bgm.tv/character/${charaId}/add_related/person/${type}`;
            for (let n = 0; n < subItems.length; n++) {
                infoArrlist += `infoArr[n${n}][prsn_id]=${CVId}&infoArr[n${n}][subject_id]=${subItems[n]}&infoArr[n${n}][subject_type_id]=${subTypeId}&`
            }
            let data = `formhash=${formhash}&${infoArrlist}submit=保存关联数据`
        //    console.log(data);
            // let data = `formhash=${formhash}&infoArr[n0][prsn_id]=${CVId}&infoArr[n0][subject_id]=${subid}&infoArr[n0][subject_type_id]=${subTypeId}&submit=保存关联数据`
            let res = await post(url, data)
           // console.log(res)
            if ($(res.responseText).find('#crtRelateSubjects li').length < 1) {
                alert(`${type}关联CV失败`)
            } else {
                alert(`${type}关联CV成功`)
            }
        }

    }

    //obj是最上面的关联设置,CVRelatedSetting和subRelatedSetting
    async function relatedAllCV(obj, charaId) {
        let checkCV = await getCVId(CVName);
        if (!checkCV) {
            return false;
        } else {
            for (let i in obj) {
                if (obj[i]) {
                    await relatedCV(i, obj[i], charaId)
                }
            }
        }

    }

//新增型cv关联
    async function addRelatedCV(type, subid, charaId) {
        let subTypeId = subTypeArr.indexOf(type) + 1;
        let subItems = subid.split(',');
        if (subid) {
            let url = `https://bgm.tv/character/${charaId}/add_related/person/${type}`;
            let page=await getResBody(url);
            let infoArrlist = '';
            let relatedItem=$(page).find('ul#crtRelateSubjects li')
            relatedItem.map((k,v)=>{
               let type= $(v).find('.tip input').prop('name')+'='+$(v).find('.tip input').prop('value');
               let sub= $(v).find('input').eq(1).prop('name')+'='+$(v).find('input').eq(1).prop('value');
               infoArrlist+=`${type}&${sub}&`
            });

            for (let n = 0; n < subItems.length; n++) {
                let j=n+relatedItem.length;
                infoArrlist += `infoArr[n${j}][prsn_id]=${CVId}&infoArr[n${j}][subject_id]=${subItems[n]}&infoArr[n${j}][subject_type_id]=${subTypeId}&`
                console.log(infoArrlist)

            }
            let data = `formhash=${formhash}&${infoArrlist}submit=保存关联数据`
        //    console.log(data);
            // let data = `formhash=${formhash}&infoArr[n0][prsn_id]=${CVId}&infoArr[n0][subject_id]=${subid}&infoArr[n0][subject_type_id]=${subTypeId}&submit=保存关联数据`
            let res = await post(url, data)
           // console.log(res)
            if ($(res.responseText).find('#crtRelateSubjects li').length < 1) {
                alert(`${type}关联CV失败`)
            } else {
                alert(`${type}关联CV成功`)
            }
        }

    }

    async function addRelatedAllCV(obj, charaId) {
        let checkCV = await getCVId(CVName);
        if (!checkCV) {
            return false;
        } else {
            for (let i in obj) {
                if (obj[i]) {
                    await addRelatedCV(i, obj[i], charaId)
                }
            }
        }

    }



    //角色排序,subid为角色关联的条目id,sortNo是角色排序的序号,未指定的话默认当前角色类型末尾
    async function charaSort(subId,sortNo){
        let url=`https://bgm.tv/subject/${subId}/add_related/character`;
        let sortPage=await get(url);
        let charaSortArr=$(sortPage.response).find('#crtRelateSubjects li');
        // console.log(charaSortArr)
        let order;
        if(typeof(sortNo)=='string'){
            return false;
        }
        if(isNaN(sortNo)){
            order=$(charaSortArr).find(`option[value="${charaType}"]:selected`).length;
        }else{
            order=sortNo;
        }
      //  console.log(order);
        let sortData='';
        charaId=charaId;
        charaSortArr.map((k,v)=>{
            // console.log(v);
            let crtType=$(v).find(`select`).prop('value');
            let crtOrder=$(v).find('input.item_sort').prop('value');
            let crtId=$(v).find('.title a').prop('href').split('/').pop();
         //   console.log(crtId)
        //    console.log(charaId);
            if(crtId==charaId){
            sortData+=`infoArr[${k}][crt_type]=${crtType}&infoArr[${k}][crt_order]=${order}&infoArr[${k}][crt_id]=${charaId}&`;

        }else{
           sortData+=`infoArr[${k}][crt_type]=${crtType}&infoArr[${k}][crt_order]=${crtOrder}&infoArr[${k}][crt_id]=${crtId}&`

        }
        });
           // console.log(sortData);
            // let data=`formhash=${formhash}&submit=保存关联数据${sortData}`;
            // data=encodeURI(data);
            
            //sortdata的位置会影响encode的必要性。如果将其放在submit后面的话则必须encode。
            //目前还不知道原因。
            let data=`formhash=${formhash}&${sortData}submit=保存关联数据`;
            // data=encodeURI(data);
            let res=await post(url,data);
          //  console.log(res);
            if(res.status!=='200'){
                // alert('排序失败');
                // await charaSort(subId,sortNo);
            }
    }

    async function sortAllSub(obj,charaId,sortNo){
        for (let i in obj) {
            if (obj[i]) {
                let subArr=obj[i].split(',');
                    for(let n=0;n<subArr.length;n++){
                        await charaSort(subArr[n],sortNo)
                    }
                }

        }
    }

    //根据名字从谷歌cse找图片,默认取第一个结果。别问我为什么不直接取谷歌图片的结果.stie填域名即可。比如twitter.com
    //搜索引擎:https://cse.google.com/cse?cx=016521770207998520683:b6l6luja61k
    async function getImageUrlFromGoogle(name,site) {
        if(!site){
            site='';
        }
        let cxID = '016521770207998520683:b6l6luja61k';
        let apiKey = 'AIzaSyDoTuZttbaby57Cf-DLvdAX6WkzM4uWzOs';
        let baseurl = `https://www.googleapis.com/customsearch/v1?key=${apiKey}&cx=${cxID}&q=${name}&siteSearch=${site}&searchType=image&gl=jp&cr=countryJP&alt=json`;
        let result = await get(baseurl, 'json');
        let imageUrl;
        try {
            imageUrl = result.response.items[0].link || '';
            return imageUrl;
        } catch (e) {
            let usrselect=window.confirm('头像搜索失败,尝试再次搜索吗');
            if(usrselect){
                getImageUrlFromGoogle(name);
            }else{
                return `https://bgm.tv/img/info_only.png`;
            }
        }
    }

/*    从别的网站获取图片,返回图片链接。
    url必须是是图片来源网站,带http头的绝对链接;selector是图片所在选择器,不是jquery对象*/
    async function getImageUrlFromSite(url,selector){
        let resPage=await get(url);
       // console.log(resPage);
        let imgUrl=$(resPage.responseText).find(selector).prop('src')
        return imgUrl;
    }

     function cropImage(img){
        let image=new Image();
        image.src=URL.createObjectURL(img);
        return new Promise((res,rej)=>{
            image.onload=function(){
                URL.revokeObjectURL(this.src); 
                var canvas=document.createElement('canvas');
                canvas.width=cropSetting[2];
                canvas.height=cropSetting[3];
                let cw=canvas.width;
                let ch=canvas.height;
                let ctx=canvas.getContext('2d');
                ctx.drawImage(this,cropSetting[0],cropSetting[1],cw,ch,0,0,cw,ch)
                canvas.toBlob(function(blob){
                    img=blob;
                    // console.log(img)
                    res(img)
                    return img
                },img.type)
            }
        })
    }

    async function ImagetoBlob(url) {
        //必须res然后res.response,否则会读不到文件
        if(!url){
            url='https://bgm.tv/img/info_only.png';
        }
        let res = await get(url, 'blob');
        if (res.status!==200){
            charaAvatarImage=new Blob()
        }else{
            charaAvatarImage = res.response;
            if(cropSetting.length>0){
                charaAvatarImage=await cropImage(charaAvatarImage)
                // console.log(charaAvatarImage)
            }
        }
        return charaAvatarImage

    }


    //table是某个jquery表格对象。比如$('table.indexbox')这样
    //从萌百页面右边的表格里获取信息
    //萌百的表格和wiki的构造不一样,没有th标签
    async function moeTable2InfoTpl(table) {
        table ? table : (infoTplDetail = null);
        let items = table.find('tr');
        let avatar = items.find('img');
        CVName ?CVName:CVName= table.find('tr:contains("声优")').children().eq(1).text().replace('\n', '') || null;
        console.log(`声优为${CVName}`)
        if (!charaAvatarUrl) {
            if (avatar.length) {
                charaAvatarUrl = avatar[0].src;
            } else {
                charaAvatarUrl = await getImageUrlFromGoogle(charaName);
            }
        }
        console.log(`头像链接:${charaAvatarUrl}`);
        kana = items.find('rt').append(' ').text();

        let linkArr = window.location.href.split('/');
        chnName = decodeURI(linkArr[linkArr.length - 1]);
        if(!charaName){
            let charaNameTmp='';
            charaNameTmp = (table.find('tr:contains("名")').eq(0).children().eq(1).text());
            if (charaNameTmp.indexOf('(') >= 0) {
                charaName = charaNameTmp.split('(')[0]
            } else {
                charaName = charaNameTmp.split('(')[0]
            }
            if (!charaName) {
                charaName = chnName
            }
        }


        try {
            romaji = table.find('tr:contains("名")').eq(0).children().eq(1).text().split('(')[1].split(')')[0];
            // romaji = romajiTemp.match(/(([^)]*))/)[1] || romajiTemp.match(/\(([^)]*)\)/)[1];
        } catch (e) {
            romaji = ''
        }
        //姓名信息已经由上面获取,所以删掉这一行。
        items.find('td:contains("姓名")').parent('tr').remove();

        //萌百词条没有强制保准,导致有的假名是上标有的是放在本名里。
        items.find('th:contains("本名")').parent('tr').remove();
        //删除出道角色代表角色等信息
        table.find('tr:contains("角色")').remove();
        table.find('tr:contains("声优")').remove();
        table.find('tr:contains("萌点")').remove();
        table.find('sup').remove();
        items = table.find('tr');

        items.map(k => {
            let infoName = $(items[k]).children()[0].innerText.replace('\n', '/');
            let infoDetail;
            if ($(items[k]).children().length > 1) {
                infoDetail = $(items[k]).children()[1].innerText.replace('\n', '/');
                // console.log('infoDetail='+infoDetail)
            }
            if (infoName && infoDetail) {
                infoTplDetail += `|${infoName}=${infoDetail}\r\n`;
            }
        });
       // console.log(`infoTplDetail=${infoTplDetail}`)
        return infoTplDetail;
    }

    //table是某个jquery表格对象。比如$('table.indexbox')这样
    //从日文wiki页面右边的表格里获取信息
    async function wikiTable2InfoTpl(table) {
        table ? table : (infoTplDetail = null);
        let items = table.find('tr');
        let avatar = items.eq(1).find('img');
      //  console.log(avatar);
        if (!charaAvatarUrl) {
            if (avatar.length) {
                charaAvatarUrl = avatar[0].src;
            } else {
                charaAvatarUrl = await getImageUrlFromGoogle(charaName);
            }
        }
    //   console.log(charaAvatarUrl);
        kana = items.find('th').children('span').eq(0).text()
        items.map(k => {
            let infoName = $(items[k]).find('th').text();
            //生日的隐藏span以及引用出处的上标必须先删掉
            $(items[k]).find('td').children('span').remove()
            $(items[k]).find('td').children('sup').remove()
            let infoDetail
            if ($(items[k]).find('td').length > 0) {
                infoDetail = $(items[k]).find('td')[0].innerText.replace('\n', '/');
            }
            if (infoName && infoDetail) {
                infoTplDetail += `|${infoName}=${infoDetail}\r\n`;
            }
        });
        console.log(`infoTplDetail=${infoTplDetail}`)
        return infoTplDetail;
    }

    /*body是一个jquery对象。这里的话就是请求到的wiki页面本身.如果不指定的话默认就是'body'.
    功能是从wiki和萌百页面获取名字、个人简介(charaInfo)和infoTplDetal*/
    async function cvInfoFromWiki(body) {
        body ? body : body = 'body';
        //不从表格拿是因为不是所有条目都有表格
        if (!charaName) {
            let linkArr = window.location.href.split('/');
            charaName = decodeURI(linkArr[linkArr.length - 1]);
        }

        //寻找声优简介信息并删除上标号
        let descP = $(body).find('#toc').prevAll('p');
        let cvDesc = [];
        descP.map(i => {
            descP.eq(i).find('sup').remove();
            cvDesc.unshift(descP[i].innerText);
        })
        charaInfo = cvDesc.join().replace(' ', '');

        //将边上的表格信息整合成infoTpl,边上没有表格的话直接为空
        let table = $(body).find('table.infobox');
        if (pageHost.indexOf('moegirl.org') < 0) {
            await wikiTable2InfoTpl(table);
        } else {
            await moeTable2InfoTpl(table);
        }
    }


    async function charaInfoFromWiki(body) {
        body ? body : body = 'body';
        //不从表格拿是因为不是所有条目都有表格
        //寻找简介信息并删除黑条
        let descP = $(body).find('.mw-parser-output').children('h2').eq(0).nextUntil(":not(p)");
        let charaDesc = [];
        descP.map(i => {
            descP.eq(i).find('.heimu').remove();
            descP.eq(i).find('sup').remove();
            charaDesc.push(descP[i].innerText);
        })
        charaInfo = charaDesc.join().replace(' ', '');

        //将边上的表格信息整合成infoTpl,边上没有表格的话直接为空
        let table = $(body).find('.mw-parser-output table').eq(0);
        //萌百词条的表格两套html模板,角色和wiki 的声优表格类似
        await moeTable2InfoTpl(table);
    }

    //生成需要提交用户信息body,无参数的时候默认chara,大小写皆可。其他时候需要和charaKindArr一致
    async function makeFormData(subtype) {
        //新增条目类型。目前有chara,cv等,默认chara。这个主要是formdata的数据结构
        charaAvatarImage = await ImagetoBlob(charaAvatarUrl);
        var subOpt = {
            'chara': {
                formhash: formhash,
                crt_name: charaName,
                crt_infobox: infoTpl,
                crt_summary: charaInfo,

            },
            'person': {
                formhash: formhash,
                crt_name: charaName,
                crt_infobox: infoTpl,
                crt_summary: charaInfo,

            },
        };

        subtype ? subtype : subtype = 'chara';
        subtype = subtype.toLowerCase();
        if (subtype !== 'chara') {
            subOpt['person'][`prsn_pro[${subtype}]`] = 1;
            subtype = 'person'
        }
        let fd = new FormData();
        //console.log(charaAvatarImage);
        Object.entries(subOpt[subtype]).map(obj => {
            fd.append(obj[0], obj[1])
        });

        return fd
    }


    //获取特定条目下关联的所有角色名称.id可以是数组,比如[1,2]
   async function getRelatedCharas(id){
        let subjects=id.toString().split(',');
        let relatedCharas={};
        for(let i in subjects){
            let url=`https://bgm.tv/subject/${subjects[i]}/characters`;
            let page=await getResBody(url);
            let charas=$(page).find('.mainWrapper .light_odd h2 a');
            charas.map((k,v)=>{
                let name=$(v).text();
                let id=$(v).attr('href').split('/character/')[1];
                relatedCharas[name]=id
            })
            // relatedCharas+=names.append('||&*').text();
        // console.log(relatedCharas)
        }
        // relatedCharas=(relatedCharas.split('||&*'));
        // relatedCharas.splice(-1,1);
        return relatedCharas
    }

    //subtype为chara或者其他charaKindArr里的值,不能为空
    //editmode是编辑条目,参数类型给人物条目id,为空的情况下默认添加模式
    //nosort指添加完后角色不做排序(大量角色的时候排序很浪费时间.默认排序
    async function addSubject(subtype, editmode,sortNum) {
        //如果不检查重复以及开启静默模式的话,不会有任何的弹窗
        if(!checkDupe&&silent){
            window.alert=console.log;
            window.confirm=()=>false;
        }
        subtype = subtype.toLowerCase();
        let isNODupe=true;
        if (checkDupe) {
            await searchChara(charaName, subtype)
                .then(e => {
                    isNODupe = true
                })
                .catch(e => {
                    alert('取消添加')
                    isNODupe = false
                });
        }
        if (isNODupe) {
            let urlPath, url;
            if (subtype == 'chara') {
                urlPath = 'character'
            } else {
                urlPath = 'person'
            }

            let fd = await makeFormData(subtype);
            //单独拿出来是因为传参的方式加进去的不是file类型而是普通blob,没法被服务器端识别
            fd.append('picfile', charaAvatarImage, 'new.png');
            if (!editmode) {
                url = `https://bgm.tv/${urlPath}/new`;
                fd.append('crt_role', charaRole);
                fd.append('submit', '添加新人物');
            } else {
                url = `https://bgm.tv/${urlPath}/${editmode}/edit`;
                // let imgFile=new File([charaAvatarImage],'new.png',{type:'image/png'})
                // let imgblob=new Blob(charaAvatarImage,{type: 'application/octet-stream'});
                fd.append('editSummary', 'bgm_wikihelper脚本替换条目');
                fd.append('submit', '改好了');
            }
            if (debugFlg) {
                alert(`调试模式中,不会加条目,请手动关闭后刷新页面`)
                for (let pair of fd.entries()) {
                    console.log(pair[0] + ', ' + pair[1]);
                }
            } else {
                let pageres = await post(url, fd, 'fd');
               // console.log(pageres);
                let tmp = pageres.finalUrl.split('/');
                charaId = tmp[tmp.length - 1];

                if (subtype == 'chara') {
                   await  relatedAllSub(subRelatedSetting, charaId);
                    await relatedAllCV(CVRelatedSetting, charaId);
                    await  sortAllSub(subRelatedSetting,charaId,sortNum);                    
                }
                if (charaId.match(/\d+/g)) {
                    let cfm = confirm(`${editmode?'编辑':'添加'}成功!条目id=${charaId},要去看看吗`);
                    if (cfm) {
                        GM_openInTab(pageres.finalUrl, 'false')
                    }
                } else {
                    alert('添加失败')
                };
                return charaId;
            }

        }

    }



    var charaId, formhash, kana, jpnName,nickName, chnName, CVId, userFormData,CVName,pageHost, anotherName, charaName, charaAvatarUrl, charaAvatarImage, charaInfo,infoTpl, infoTplDetail = '';
     var artist,romaji, color, birthday,sex,part, instrument,height, weight, zodiacSign, hobby,hate,school, bwh, age, blood, grade, like, trick, weak, unit,weapon;
     var subRelatedSetting ,CVRelatedSetting ,charaType ,charaKindArr ,charaKind ,charaRole ,checkDupe ,debugFlg ,editSubId ;
    var silent,sortNum,cropSetting=[];
    var subTypeArr = ['book', 'anime', 'music', 'game', 'unkown', 'real'];