Greasy Fork is available in English.

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

 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'];