Sleazy Fork is available in English.

VoiceLinks

Makes RJ codes more useful.(8-bit RJCode supported.)

// ==UserScript==
// @name        VoiceLinks
// @namespace   Sanya
// @description Makes RJ codes more useful.(8-bit RJCode supported.)
// @match       *://*/*
// @match       file:///*
// @version     4.8.9
// @connect     dlsite.com
// @connect     media.ci-en.jp
// @grant       GM_setClipboard
// @grant       GM_openInTab
// @grant       GM_registerMenuCommand
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_addElement
// @grant       GM.xmlHttpRequest
// @grant       GM_xmlhttpRequest
// @run-at      document-start
// @homepage    https://sleazyfork.org/zh-CN/scripts/456775-voicelinks
// ==/UserScript==

(function () {
    'use strict';

    const IS_PREVIEW = false;

    //------持久化设置项------
    let settings = {
        //语言设置
        _s_lang: "zh_CN",
        _s_popup_lang: "zh_CN",

        //常规设置
        _s_parse_url: true,
        _s_parse_url_in_dl: false,
        _s_show_translated_title_in_dl: true,
        _s_copy_as_filename_btn: true,
        _s_show_compatibility_warning: true,
        _s_url_insert_mode: "before_rj",
        _s_url_insert_text: "🔗",
        _s_sfw_mode: false,
        _s_sfw_blur_level: "medium",
        _s_sfw_remove_when_hover: true,
        _s_sfw_blur_transition: true,

        //信息显示设置
        _s_category_preset: "voice",
        _s_voice__info_display_order: [
            "voice__dl_count",
            "voice__circle_name",
            "voice__translator_name",
            "voice__release_date",
            "voice__update_date",
            "voice__age_rating",
            "voice__scenario",
            "voice__illustration",
            "voice__voice_actor",
            "voice__music",
            "voice__genre",
            "voice__file_size"
        ],
        _s_voice__dl_count: false,
        _s_voice__circle_name: true,
        _s_voice__translator_name: true,
        _s_voice__release_date: true,
        _s_voice__update_date: true,
        _s_voice__age_rating: true,
        _s_voice__scenario: false,
        _s_voice__illustration: false,
        _s_voice__voice_actor: true,
        _s_voice__music: true,
        _s_voice__genre: true,
        _s_voice__file_size: true,
        _s_game__info_display_order: [
            "game__dl_count",
            "game__circle_name",
            "game__translator_name",
            "game__release_date",
            "game__update_date",
            "game__age_rating",
            "game__scenario",
            "game__illustration",
            "game__voice_actor",
            "game__music",
            "game__genre",
            "game__file_size"
        ],
        _s_game__dl_count: false,
        _s_game__circle_name: true,
        _s_game__translator_name: true,
        _s_game__release_date: true,
        _s_game__update_date: true,
        _s_game__age_rating: true,
        _s_game__scenario: true,
        _s_game__illustration: true,
        _s_game__voice_actor: true,
        _s_game__music: true,
        _s_game__genre: true,
        _s_game__file_size: true,
        _s_manga__info_display_order:[
            "manga__dl_count",
            "manga__circle_name",
            "manga__translator_name",
            "manga__release_date",
            "manga__update_date",
            "manga__age_rating",
            "manga__scenario",
            "manga__illustration",
            "manga__voice_actor",
            "manga__music",
            "manga__genre",
            "manga__file_size"
        ],
        _s_manga__dl_count: false,
        _s_manga__circle_name: true,
        _s_manga__translator_name: true,
        _s_manga__release_date: true,
        _s_manga__update_date: true,
        _s_manga__age_rating: true,
        _s_manga__scenario: true,
        _s_manga__illustration: true,
        _s_manga__voice_actor: true,  //音声漫画
        _s_manga__music: true,
        _s_manga__genre: true,
        _s_manga__file_size: true,
        _s_video__info_display_order: [
            "video__dl_count",
            "video__circle_name",
            "video__translator_name",
            "video__release_date",
            "video__update_date",
            "video__age_rating",
            "video__scenario",
            "video__illustration",
            "video__voice_actor",
            "video__music",
            "video__genre",
            "video__file_size"
        ],
        _s_video__dl_count: false,
        _s_video__circle_name: true,
        _s_video__translator_name: true,
        _s_video__release_date: true,
        _s_video__update_date: true,
        _s_video__age_rating: true,
        _s_video__scenario: true,
        _s_video__illustration: true,
        _s_video__voice_actor: true,
        _s_video__music: true,
        _s_video__genre: true,
        _s_video__file_size: true,
        _s_novel__info_display_order: [
            "novel__dl_count",
            "novel__circle_name",
            "novel__translator_name",
            "novel__release_date",
            "novel__update_date",
            "novel__age_rating",
            "novel__scenario",
            "novel__illustration",
            "novel__voice_actor",
            "novel__music",
            "novel__genre",
            "novel__file_size"
        ],
        _s_novel__dl_count: false,
        _s_novel__circle_name: true,
        _s_novel__translator_name: true,
        _s_novel__release_date: true,
        _s_novel__update_date: true,
        _s_novel__age_rating: true,
        _s_novel__scenario: true,
        _s_novel__illustration: true,
        _s_novel__voice_actor: false,
        _s_novel__music: false,
        _s_novel__genre: true,
        _s_novel__file_size: true,
        _s_other__info_display_order: [
            "other__dl_count",
            "other__circle_name",
            "other__translator_name",
            "other__release_date",
            "other__update_date",
            "other__age_rating",
            "other__scenario",
            "other__illustration",
            "other__voice_actor",
            "other__music",
            "other__genre",
            "other__file_size"
        ],
        _s_other__dl_count: false,
        _s_other__circle_name: true,
        _s_other__translator_name: true,
        _s_other__release_date: true,
        _s_other__update_date: true,
        _s_other__age_rating: true,
        _s_other__scenario: true,
        _s_other__illustration: true,
        _s_other__voice_actor: true,
        _s_other__music: true,
        _s_other__genre: true,
        _s_other__file_size: true,

        //标签显示设置
        _s_tag_main_switch: true,
        _s_tag_display_order: [
            "tag_no_longer_available",
            "tag_rate",
            "tag_work_type",
            "tag_translatable",
            "tag_not_translatable",
            "tag_translated",
            "tag_bonus_work",
            "tag_has_bonus",
            "tag_language_support",
            "tag_file_format",
            "tag_ai",
        ],
        _s_tag_rate: true,
        _s_tag_work_type: true,
        _s_tag_translatable: true,
        _s_tag_not_translatable: true,
        _s_tag_translated: true,
        _s_tag_language_support: true,
        _s_tag_bonus_work: true,
        _s_tag_has_bonus: true,
        _s_tag_file_format: false,
        _s_tag_no_longer_available: true,
        _s_tag_ai: true,

        _s_show_rate_count: false,

        _s_tag_translation_request: true,
        _s_tag_translation_request_display_order: [
            "tag_translation_request_simplified_chinese",
            "tag_translation_request_traditional_chinese",
            "tag_translation_request_english",
            "tag_translation_request_korean",
            "tag_translation_request_spanish",
            "tag_translation_request_german",
            "tag_translation_request_french",
            "tag_translation_request_indonesian",
            "tag_translation_request_italian",
            "tag_translation_request_portuguese",
            "tag_translation_request_swedish",
            "tag_translation_request_thai",
            "tag_translation_request_vietnamese",
        ],
        _s_tag_translation_request_english: false,
        _s_tag_translation_request_simplified_chinese: true,
        _s_tag_translation_request_traditional_chinese: true,
        _s_tag_translation_request_korean: false,
        _s_tag_translation_request_spanish: false,
        _s_tag_translation_request_german: false,
        _s_tag_translation_request_french: false,
        _s_tag_translation_request_indonesian: false,
        _s_tag_translation_request_italian: false,
        _s_tag_translation_request_portuguese: false,
        _s_tag_translation_request_swedish: false,
        _s_tag_translation_request_thai: false,
        _s_tag_translation_request_vietnamese: false,

        backup: function() {
            let backup = {};
            for (let key in this) {
                if(!key.startsWith("_s_")) continue;
                //如果类型为列表,则需要将其拷贝出来
                if(this[key] && Array.isArray(this[key])){
                    backup[key] = [...this[key]];
                }else{
                    backup[key] = this[key];
                }
            }
            this.default_backup = backup;
        },

        //备份默认值
        default_backup: {},

        //暂存已修改值,不更新到设置
        temp_edited: {},

        load: function(){
            let need_reorder = false;
            for(let key in this){
                if(!key.startsWith("_s_")) continue;
                let val = GM_getValue(key.substring(3), this[key]);
                if(typeof val !== typeof this[key]){
                    val = this[key];
                }
                if(Array.isArray(val) && val.length !== this[key].length){
                    val = this[key];
                    need_reorder = true;
                }
                this[key] = val !== undefined ? val : this[key];
            }

            if(need_reorder) {
                window.addEventListener("load", () => {
                    window.alert("VoiceLinks: " + localize(localizationMap.need_reorder));
                });
                this.save();
            }
        },

        save: function () {
            //将暂存修改应用至Settings
            for (let key in this.temp_edited) {
                if(!key.startsWith("_s_")) continue;
                if(this[key] === undefined || this.temp_edited[key] === undefined) continue;
                this[key] = this.temp_edited[key];
                this.temp_edited[key] = undefined;
            }

            //将修改保存至GM
            for(let key in this){
                if(!key.startsWith("_s_")) continue;
                GM_setValue(key.substring(3), this[key]);
            }
        },

        //保存临时修改
        saveTemp: function (key, value){
            if(!key.startsWith("_s_")) key = "_s_" + key;
            this.temp_edited[key] = value;
        },

        clearTemp: function (){
            this.temp_edited = {};
        },

        reset: function () {
            if(!this.default_backup) return;
            for(let key in this.default_backup){
                if(!key.startsWith("_s_")) continue;
                GM_setValue(key.substring(3), this.default_backup[key]);
            }
        },

        hasEdited: function (key) {
            if(!key.startsWith("_s_")) key = "_s_" + key;
            if(this[key] === undefined) return false;
            return this[key] !== this.default_backup[key];
        },

        getDefaultValue: function (key) {
            if(!key.startsWith("_s_")) key = "_s_" + key;
            return this.default_backup[key];
        }
    }
    settings.backup();
    settings.load();
    //----------------------

    //------本地化-----------
    const localizationMap = {
        notice_update: {
            zh_CN: "VoiceLinks公告更新,可能包含重要的新功能说明,是否跳转至说明页面?",
            zh_TW: "VoiceLinks公告更新,可能包含重要的新功能説明,是否跳轉至説明頁面?",
            en_US: "VoiceLinks Notice Update, may contain important new features, do you want to jump to the notice page?"
        },

        title_settings: {
            zh_CN: "VoiceLinks 设置",
            zh_TW: "VoiceLinks 設定",
            en_US: "VoiceLinks Settings"
        },

        title_language_settings: {
            zh_CN: "语言设置",
            zh_TW: "語言設定",
            en_US: "Language Settings"
        },

        display_language: {
            zh_CN: "显示语言",
            zh_TW: "顯示語言",
            en_US: "Language"
        },

        popup_language: {
            zh_CN: "弹窗语言",
            zh_TW: "彈窗語言",
            en_US: "Popup Language"
        },

        popup_language_tooltip: {
            zh_CN: "仅修改标题和标签显示语言,信息本身的语言以DLSite网页设置的语言为准。",
            zh_TW: "只修改標題和標籤顯示語言,資訊本身的語言以DLSite網頁設定的語言為準。",
            en_US: "Only modify the title and tag display language, the language of the information itself is determined by the language of the DLSite page settings."
        },

        title_general_settings: {
            zh_CN: "常规",
            zh_TW: "常規",
            en_US: "General"
        },

        parse_url: {
            zh_CN: "解析URL",
            zh_TW: "解析URL",
            en_US: "Parse URL"
        },

        parse_url_tooltip: {
            zh_CN: "鼠标悬停导指向DLSite作品页面的URL时,同样显示作品信息",
            zh_TW: "鼠標懸停導向DLSite作品頁面的URL時,同樣顯示作品資訊",
            en_US: "Show work info when hovering over DLSite work URL"
        },

        parse_url_in_dl: {
            zh_CN: "在DLSite上解析URL",
            zh_TW: "在DLSite上解析URL",
            en_US: "Parse URL in DLSite"
        },

        parse_url_in_dl_tooltip: {
            zh_CN: "URL较多可能影响正常阅读",
            zh_TW: "URL較多可能影響正常閱讀",
            en_US: "URL is more likely to affect normal reading"
        },

        show_translated_title_in_dl: {
            zh_CN: "在DLSite显示对应语言的翻译标题",
            zh_TW: "在DLSite顯示對應語言的翻譯標題",
            en_US: "Show translated title in DLSite"
        },

        show_translated_title_in_dl_tooltip: {
            zh_CN: "作品信息页面的标题将会被修改为与翻译语言对应的标题,避免简中看繁中作品标题为日文的问题",
            zh_TW: "作品資訊頁面的標題將會被修改為與翻譯語言對應的標題,避免繁中看簡中作品標題為日文的問題",
            en_US: "The title of the work info page will be modified to match the corresponding translation language, to avoid viewing the title as Japanese when viewing a work in non-English language."
        },

        copy_as_filename_btn: {
            zh_CN: "“复制为有效文件名”按钮",
            zh_TW: "“複製為有效檔案名”按鈕",
            en_US: '"Copy as filename" button'
        },

        copy_as_filename_btn_tooltip: {
            zh_CN: "鼠标悬停至DLSite作品标题部分将会出现该按钮,点击即可将标题复制为有效文件名,有效文件名指的是会将标题中的非法部分用相似的符号代替",
            zh_TW: "鼠標懸停至DLSite作品標題部分將會出現按鈕,點擊即可將標題複製為有效檔案名,有效檔案名指的是會將標題中的非法部分用相似的符號代替",
            en_US: "Show button when hovering over DLSite work title. Clicking it will copy the title to a valid filename, which will replace the illegal part of the title with similar symbols."
        },

        show_compatibility_warning: {
            zh_CN: "显示兼容性警告",
            zh_TW: "顯示兼容性警告",
            en_US: "Show compatibility warning"
        },

        show_compatibility_warning_tooltip: {
            zh_CN: "如果脚本中,修改DLSite页面元素的功能覆盖了其它脚本的修改,则会触发该弹窗警告",
            zh_TW: "如果腳本中,修改DLSite頁面元素的功能覆蓋了其它腳本的修改,则會觸發該彈窗警告",
            en_US: "If the script modifies the functionality of DLSite elements that are covered by other scripts, the warning will be triggered"
        },

        url_insert_mode: {
            zh_CN: "导向文本的插入方式",
            zh_TW: "導向文本的插入方式",
            en_US: "Type of the insertion"
        },

        url_insert_mode_tooltip: {
            zh_CN: "如果某段链接中的RJ号被解析成功,为了保证原链接不被完全覆盖,会根据需要,在URL的文本前/后插入特定导向文本",
            zh_TW: "如果某段連結中的RJ號被解析成功,為了保證原連結不被完全覆蓋,會根據需要,在URL的文本前/後插入特定導向文本",
            en_US: "If the RJ number in a link is parsed successfully, it is necessary to insert a specific text in the URL before/after the link when the link is almost completely covered by the script"
        },

        url_insert_mode_none: {
            zh_CN: "不插入",
            zh_TW: "不插入",
            en_US: "None"
        },

        url_insert_mode_prefix: {
            zh_CN: "前缀插入代替原链接",
            zh_TW: "前綴插入代替原連結",
            en_US: "Insert before the link as original link."
        },

        url_insert_mode_before_rj: {
            zh_CN: "插入到RJ号前代替RJ链接",
            zh_TW: "插入到RJ號前代替RJ連結",
            en_US: "Insert before the RJ link as the RJ link."
        },

        url_insert_text: {
            zh_CN: "导向文本",
            zh_TW: "導向文本",
            en_US: "Text to insert"
        },

        sfw_mode: {
            zh_CN: "SFW 模式",
            zh_TW: "SFW 模式",
            en_US: "SFW Mode"
        },

        sfw_mode_tooltip: {
            zh_CN: "启用后,所有作品封面均会模糊处理(固定窗口时将鼠标移动到图片上可临时去除模糊)",
            zh_TW: "啟用後,所有作品封面均會模糊處理(固定視窗時將滑鼠移動到圖片上可暫時去除模糊)",
            en_US: "Turn on to blur the cover of all works (temporarily remove the blur by moving the mouse over the image when the window is fixed)."
        },

        sfw_blur_level: {
            zh_CN: "模糊程度",
            zh_TW: "模糊程度",
            en_US: "Blur level"
        },

        sfw_remove_when_hover: {
            zh_CN: "鼠标移到图片上时移除模糊",
            zh_TW: "滑鼠移到圖片上時移除模糊",
            en_US: "Remove the blur when the mouse moves over the image"
        },

        sfw_blur_transition: {
            zh_CN: "模糊动画(卡顿请关闭)",
            zh_TW: "模糊動畫(卡頓請關閉)",
            en_US: "Blur animation"
        },

        low: {
            zh_CN: "低 - 仅模糊细节",
            zh_TW: "低 - 僅模糊細節",
            en_US: "Low - Only blur details"
        },

        medium: {
            zh_CN: "中 - 依稀可见",
            zh_TW: "中 - 依稀可見",
            en_US: "Medium - Hard to see"
        },

        high: {
            zh_CN: "高 - 完全无法辨认",
            zh_TW: "高 - 完全無法辨識",
            en_US: "High - Unrecognizable"
        },

        title_info_settings: {
            zh_CN: "信息显示",
            zh_TW: "信息顯示",
            en_US: "Info Display"
        },

        category_preset: {
            zh_CN: "类别预设",
            zh_TW: "類別預設",
            en_US: "Category Preset"
        },

        category_preset_tooltip: {
            zh_CN: "使不同类别的作品根据需要显示不同的信息<br/><br/>注意:即使勾选了显示,若作品中不存在该信息则也会隐藏。",
            zh_TW: "使不同類別的作品根據需要顯示不同的信息<br/><br/>注意:即使勾選了顯示,若作品中不存在該信息則也會隱藏。",
            en_US: "Show the information of different categories of works. <br/><br/>Note: even if checked, the information of a work that does not exist will be hidden."
        },

        rate: {
            zh_CN: "评分",
            zh_TW: "評分",
            en_US: "Rate"
        },

        rate_tooltip: {
            zh_CN: "星数★ (评分人数 (设置开启))",
            zh_TW: "星數★ (評分人數 (設置開啟))",
            en_US: "Star★ (number of ratings (enable in settings))"
        },

        dl_count: {
            zh_CN: "销量",
            zh_TW: "銷量",
            en_US: "Sales"
        },

        circle_name: {
            zh_CN: "社团名",
            zh_TW: "社團名",
            en_US: "Circle Name"
        },

        translator_name: {
            zh_CN: "翻译者",
            zh_TW: "翻譯者",
            en_US: "Translator"
        },

        release_date: {
            zh_CN: "发售日",
            zh_TW: "發售日",
            en_US: "Release Date"
        },

        update_date: {
            zh_CN: "更新日",
            zh_TW: "更新日",
            en_US: "Update Date"
        },

        age_rating: {
            zh_CN: "年龄指定",
            zh_TW: "年齡指定",
            en_US: "Age Rating"
        },

        scenario: {
            zh_CN: "剧情",
            zh_TW: "劇情",
            en_US: "Scenario"
        },

        illustration: {
            zh_CN: "插画",
            zh_TW: "插圖",
            en_US: "Illustration"
        },

        voice_actor: {
            zh_CN: "声优",
            zh_TW: "聲優",
            en_US: "Voice Actor"
        },

        music: {
            zh_CN: "音乐",
            zh_TW: "音樂",
            en_US: "Music"
        },

        genre: {
            zh_CN: "分类",
            zh_TW: "分類",
            en_US: "Genre"
        },

        file_size: {
            zh_CN: "文件容量",
            zh_TW: "檔案容量",
            en_US: "File Size"
        },

        title_tag_settings: {
            zh_CN: "标签显示",
            zh_TW: "標籤顯示",
            en_US: "Tag Display"
        },

        tag_main_switch: {
            zh_CN: "标签总开关",
            zh_TW: "標籤總開關",
            en_US: "Tag Main Switch"
        },

        tag_main_switch_tooltip: {
            zh_CN: "关闭则所有标签均不显示",
            zh_TW: "關閉則所有標籤都不顯示",
            en_US: "If turned off, all tags will not be displayed"
        },

        tag_work_type: {
            zh_CN: "作品类型",
            zh_TW: "作品類型",
            en_US: "Work Type"
        },

        work_type_game: {
            zh_CN: "游戏",
            zh_TW: "遊戲",
            en_US: "Game"
        },

        work_type_comic: {
            zh_CN: "漫画",
            zh_TW: "漫畫",
            en_US: "Manga"
        },

        work_type_illustration: {
            zh_CN: "CG・插画",
            zh_TW: "CG・插畫",
            en_US: "CG + Illustrations"
        },

        work_type_novel: {
            zh_CN: "小说",
            zh_TW: "小說",
            en_US: "Novel"
        },

        work_type_video: {
            zh_CN: "视频",
            zh_TW: "影片",
            en_US: "Video"
        },

        work_type_voice: {
            zh_CN: "音声・ASMR",
            zh_TW: "聲音作品・ASMR",
            en_US: "Voice / ASMR"
        },

        work_type_music: {
            zh_CN: "音乐",
            zh_TW: "音樂",
            en_US: "Music"
        },

        work_type_tool: {
            zh_CN: "工具/装饰",
            zh_TW: "工具/配件",
            en_US: "Tools / Accessories"
        },

        work_type_voice_comic: {
            zh_CN: "音声漫画",
            zh_TW: "有聲漫畫",
            en_US: "Voiced Comics"
        },

        work_type_other: {
            zh_CN: "其他",
            zh_TW: "其他",
            en_US: "Miscellaneous"
        },

        tag_translatable: {
            zh_CN: "可翻译",
            zh_TW: "可翻譯",
            en_US: "Translatable"
        },

        tag_translatable_tooltip: {
            zh_CN: "大家一起来翻译 授权作品",
            zh_TW: "大家一起翻譯 授权作品",
            en_US: "Translators Unite translation permitted work"
        },

        tag_not_translatable: {
            zh_CN: "不可翻译",
            zh_TW: "不可翻譯",
            en_US: "Not Translatable"
        },

        tag_not_translatable_tooltip: {
            zh_CN: "未授权 大家一起来翻译",
            zh_TW: "未授權 大家一起來翻譯",
            en_US: "Not Translators Unite translation permitted work"
        },

        tag_translated: {
            zh_CN: "翻译作品",
            zh_TW: "翻譯作品",
            en_US: "Translated"
        },

        tag_translated_tooltip: {
            zh_CN: "当前作品为 大家一起来翻译 作品",
            zh_TW: "當前作品為 大家一起來翻譯 作品",
            en_US: "Current work is Translators Unite translation work"
        },

        tag_language_support: {
            zh_CN: "语言支持",
            zh_TW: "語言支援",
            en_US: "Language Support"
        },

        language_japanese: {
            zh_CN: "日文",
            zh_TW: "日文",
            en_US: "Japanese"
        },

        language_english: {
            zh_CN: "英文",
            zh_TW: "英文",
            en_US: "English"
        },

        language_korean: {
            zh_CN: "韩语",
            zh_TW: "韓語",
            en_US: "Korean"
        },

        language_simplified_chinese: {
            zh_CN: "简体中文",
            zh_TW: "簡體中文",
            en_US: "Simplified Chinese"
        },

        language_traditional_chinese: {
            zh_CN: "繁体中文",
            zh_TW: "繁體中文",
            en_US: "Traditional Chinese"
        },

        language_german: {
            zh_CN: "德语",
            zh_TW: "德語",
            en_US: "German"
        },

        language_french: {
            zh_CN: "法语",
            zh_TW: "法語",
            en_US: "French"
        },

        language_russian: {
            zh_CN: "俄语",
            zh_TW: "俄語",
            en_US: "Russian"
        },

        language_spanish: {
            zh_CN: "西班牙语",
            zh_TW: "西班牙語",
            en_US: "Spanish"
        },

        language_indonesian: {
            zh_CN: "印尼文",
            zh_TW: "印尼文",
            en_US: "Indonesian"
        },

        language_italian: {
            zh_CN: "意大利语",
            zh_TW: "義大利語",
            en_US: "Italian"
        },

        language_arabic: {
            zh_CN: "阿拉伯语",
            zh_TW: "阿拉伯語",
            en_US: "Arabic"
        },

        language_portuguese: {
            zh_CN: "葡萄牙语",
            zh_TW: "葡萄牙語",
            en_US: "Portuguese"
        },

        language_finnish: {
            zh_CN: "芬兰语",
            zh_TW: "芬蘭語",
            en_US: "Finnish"
        },

        language_polish: {
            zh_CN: "波兰语",
            zh_TW: "波蘭語",
            en_US: "Polish"
        },

        language_swedish: {
            zh_CN: "瑞典文",
            zh_TW: "瑞典文",
            en_US: "Swedish"
        },

        language_thai: {
            zh_CN: "泰语",
            zh_TW: "泰語",
            en_US: "Thai"
        },

        language_vietnamese: {
            zh_CN: "越南语",
            zh_TW: "越南語",
            en_US: "Vietnamese"
        },

        language_japanese_abbr: {
            zh_CN: "日",
            zh_TW: "日",
            en_US: "JP"
        },

        language_english_abbr: {
            zh_CN: "英",
            zh_TW: "英",
            en_US: "EN"
        },

        language_korean_abbr: {
            zh_CN: "韩",
            zh_TW: "韩",
            en_US: "KO"
        },

        language_simplified_chinese_abbr: {
            zh_CN: "简中",
            zh_TW: "簡中",
            en_US: "ZH"
        },

        language_traditional_chinese_abbr: {
            zh_CN: "繁中",
            zh_TW: "繁中",
            en_US: "TW"
        },

        language_german_abbr: {
            zh_CN: "德",
            zh_TW: "德",
            en_US: "DE"
        },

        language_french_abbr: {
            zh_CN: "法",
            zh_TW: "法",
            en_US: "FR"
        },

        language_spanish_abbr: {
            zh_CN: "西",
            zh_TW: "西",
            en_US: "ES"
        },

        language_indonesian_abbr: {
            zh_CN: "印",
            zh_TW: "印",
            en_US: "ID"
        },

        language_italian_abbr: {
            zh_CN: "意",
            zh_TW: "意",
            en_US: "IT"
        },

        language_portuguese_abbr: {
            zh_CN: "葡",
            zh_TW: "葡",
            en_US: "PT"
        },

        language_swedish_abbr: {
            zh_CN: "瑞典",
            zh_TW: "瑞典",
            en_US: "SV"
        },

        language_thai_abbr: {
            zh_CN: "泰",
            zh_TW: "泰",
            en_US: "TH"
        },

        language_vietnamese_abbr: {
            zh_CN: "越",
            zh_TW: "越",
            en_US: "VN"
        },

        show_rate_count: {
            zh_CN: "显示评分人数",
            zh_TW: "顯示評分人數",
            en_US: "Show Rate Count"
        },

        tag_translation_request: {
            zh_CN: "翻译申请情况",
            zh_TW: "翻譯申請情况",
            en_US: "Translation Request"
        },

        tag_translation_request_tooltip: {
            zh_CN: "当前作品目前的翻译申请情况,格式为:语言简写 申请数-发售数",
            zh_TW: "當前作品目前的翻譯申請情況,格式為:语言簡稱 申請數-發售數",
            en_US: "Current work's translation request. Format: Language_Abbr Number_of_Requests - Number_of_Sales"
        },

        tag_bonus_work: {
            zh_CN: "特典",
            zh_TW: "特典",
            en_US: "Bonus"
        },

        tag_bonus_work_tooltip: {
            zh_CN: "当前作品是某部作品的特典",
            zh_TW: "當前作品是某部作品的特典",
            en_US: "Current work is a bonus work"
        },

        tag_has_bonus: {
            zh_CN: "有特典",
            zh_TW: "有特典",
            en_US: "Has Bonus"
        },

        tag_has_bonus_tooltip: {
            zh_CN: "当前作品目前附赠特典,若特典已下架则不会显示该标签",
            zh_TW: "當前作品目前附赠特典,若特典已下架則不會顯示該標籤",
            en_US: "Current work has bonus. If bonus is not available, the tag will not be displayed."
        },

        tag_file_format: {
            zh_CN: "文件格式",
            zh_TW: "檔案形式",
            en_US: "File Format"
        },

        tag_file_format_tooltip: {
            zh_CN: "WAV、EXE、MP3等",
            zh_TW: "WAV、EXE、MP3等",
            en_US: "WAV, EXE, MP3, etc."
        },

        tag_no_longer_available: {
            zh_CN: "已下架",
            zh_TW: "已下架",
            en_US: "Unavailable"
        },

        tag_announce: {
            zh_CN: "预告",
            zh_TW: "預告",
            en_US: "Announce"
        },

        tag_ai: {
            zh_CN: "AI & 部分AI",
            zh_TW: "AI & 部分AI",
            en_US: "AI & Partial AI"
        },

        tag_aig: {
            zh_CN: "AI生成",
            zh_TW: "AI生成",
            en_US: "AI Gen",
        },

        tag_aip: {
            zh_CN: "AI部分使用",
            zh_TW: "AI部分使用",
            en_US: "AI Partial",
        },

        tag_ai_tooltip: {
            zh_CN: "全部或部分使用AI的作品",
            zh_TW: "全部或部分使用AI的作品",
            en_US: "Full or partial use of AI",
        },

        button_save: {
            zh_CN: "保存设置",
            zh_TW: "保存設置",
            en_US: "Save",
        },

        button_cancel: {
            zh_CN: "取消设置",
            zh_TW: "取消設置",
            en_US: "Cancel",
        },

        button_reset: {
            zh_CN: "重置设置",
            zh_TW: "重置設置",
            en_US: "Reset",
        },

        need_reorder: {
            zh_CN: "检测到设置更新,可能添加了新的信息位,请重新设置对应设置项的排列",
            zh_TW: "檢查到設置更新,可能添加了新的信息位,请重新設置對應設置項的排列",
            en_US: "There is a new setting item added. Please reorder the corresponding setting item",
        },

        save_complete: {
            zh_CN: "设置已保存,部分设置需要刷新对应页面以生效",
            zh_TW: "設置已保存,部分設置需要刷新對應頁面以生效",
            en_US: "Settings saved, some settings need to refresh the corresponding page to take effect",
        },

        save_failed: {
            zh_CN: "设置保存失败",
            zh_TW: "設置保存失敗",
            en_US: "Settings save failed",
        },

        reset_confirm: {
            zh_CN: "确定要将设置重置到最初始的状态吗?(重置后,需要再点击保存才会生效)",
            zh_TW: "確定要將設置重置到最初始的狀態嗎?(重置後,需要再點擊保存才會生效)",
            en_US: "Are you sure you want to reset the settings to the initial state? (After resetting, you need to click Save to take effect)",
        },

        reset_complete: {
            zh_CN: "设置已重置",
            zh_TW: "設置已重置",
            en_US: "Settings reset",
        },

        reset_failed: {
            zh_CN: "设置重置失败",
            zh_TW: "設置重置失敗",
            en_US: "Settings reset failed",
        },

        reset_order: {
            zh_CN: "重置顺序",
            zh_TW: "重置順序",
            en_US: "Reset Order",
        },

        reset_order_confirm: {
            zh_CN: "确定要将元素顺序重置到最初始的状态吗?",
            zh_TW: "確定要將元素順序重置到最初始的狀態嗎?",
            en_US: "Are you sure you want to reset the element order to the initial state?",
        },

        reset_order_and_setting: {
            zh_CN: "重置元素顺序和各自的设置值",
            zh_TW: "重置元素順序和各自的設置值",
            en_US: "Reset element order and their settings",
        },

        hint_pin: {
            zh_CN: "按住CTRL以固定弹框,固定时可复制信息",
            zh_TW: "按住CTRL以固定彈窗,固定時可複製資訊",
            en_US: "Hold CTRL to pin the popup, info can be copied.",
        },

        hint_unpin: {
            zh_CN: "抬起CTRL以关闭弹框 & 查看其它作品RJ信息",
            zh_TW: "抬起CTRL以關閉彈窗 & 查看其它作品RJ信息",
            en_US: "Release CTRL to close the popup & view other works.",
        },

        hint_copy: {
            zh_CN: "左键单击以复制信息",
            zh_TW: "左鍵單擊以複製資訊",
            en_US: "Left click to copy info.",
        },

        hint_copy_all: {
            zh_CN: "左键单击以复制内部所有信息",
            zh_TW: "左鍵單擊以複製內部所有資訊",
            en_US: "Left click to copy all contained info.",
        },

        hint_copy_work_title: {
            zh_CN: "单击复制标题,Alt+单击复制为有效文件名",
            zh_TW: "單擊複製標題,Alt+單擊複製為有效檔名",
            en_US: "Click to copy title, Alt+click to copy as valid filename.",
        },

        get: function (key, langKey = "_s_lang") {
            return typeof key === "string" ? localizationMap[key][settings[langKey]] : key[settings[langKey]];
        }
    }

    function localize(key) {
        return localizationMap.get(key);
    }

    function localizePopup(key) {
        return localizationMap.get(key, "_s_popup_lang");
    }
    //----------------------


    const RJ_REGEX = new RegExp("(R[JE][0-9]{8})|(R[JE][0-9]{6})|([VB]J[0-9]{8})|([VB]J[0-9]{6})", "gi");
    const URL_REGEX = new RegExp("dlsite.com/.*/product_id/((R[JE][0-9]{8})|(R[JE][0-9]{6})|([VB]J[0-9]{8})|([VB]J[0-9]{6}))", "g");
    const VOICELINK_CLASS = 'voicelink-' + Math.random().toString(36).slice(2);
    const VOICELINK_IGNORED_CLASS = `${VOICELINK_CLASS}_ignored`;
    const RJCODE_ATTRIBUTE = 'rjcode';
    const POPUP_CSS = `
    .${VOICELINK_CLASS}_voicepopup {
        min-width: 630px !important;
        z-index: 2147483646 !important;
        max-width: 80% !important;
        position: fixed !important;
        line-height: normal !important;  /*原1.4em !important;*/
        font-size:1.1em!important;
        margin-bottom: 10px !important;
        box-shadow: 0 0 .125em 0 rgba(0,0,0,.5) !important;
        border-radius: 0.5em !important;
        background-color:#8080C0 !important;
        color:#F6F6F6 !important;
        text-align: left !important;
        padding: 10px !important;
        pointer-events: none !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup[pin][mouse-in] *[copy-text] {
        text-decoration: underline !important;
        cursor: pointer !important;
    }
    .${VOICELINK_CLASS}_voicepopup[pin][mouse-in] *[copy-text]:active {
        opacity: 0.5 !important;
    }
    
    #${VOICELINK_CLASS}_info-container {
        font-size: 1em !important;
    }
    #${VOICELINK_CLASS}_info-container > div {
        margin-bottom: 3px !important;
        font-size: 1em !important;
    }
    #${VOICELINK_CLASS}_info-container > div > .info-title {
        margin-right: 5px !important;
    }
    #${VOICELINK_CLASS}_info-container > div > .info-title::after {
        content: ":" !important;
        text-decoration: none !important;
        display: inline-block !important;
    }
    #${VOICELINK_CLASS}_info-container .${VOICELINK_CLASS}_tags {
        margin-top: 12px !important;
        margin-bottom: 0 !important;
        font-size: 0.909091em !important;
    }
    
    .${VOICELINK_CLASS}_loader {
        display: flex !important;
        justify-content: center !important;
        align-items: center !important;
        position: absolute !important;
        top: 50% !important;
        left: 50% !important;
        transform: translate(-50%, -50%) !important;
        width: 100% !important;
        height: 100% !important;
        min-width: 300px !important;
        min-height: 30px !important;
        z-index: -1 !important;
    }
    .${VOICELINK_CLASS}_dot {
        width: 20px !important;
        height: 20px !important;
        margin: 0 8px !important;
        background-color: #fbfbfb !important;
        border-radius: 50% !important;
        animation: ${VOICELINK_CLASS}_scale 1s infinite !important;
    }
    .${VOICELINK_CLASS}_dot:nth-child(1) {
        animation-delay: 0s !important;
    }
    .${VOICELINK_CLASS}_dot:nth-child(2) {
        animation-delay: 0.2s !important;
    }
    .${VOICELINK_CLASS}_dot:nth-child(3) {
        animation-delay: 0.4s !important;
    }
    @keyframes ${VOICELINK_CLASS}_scale {
      0%, 100% {
          transform: scale(1);
      }
      50% {
          transform: scale(1.5);
      }
    }
    
    .${VOICELINK_CLASS}_voicepopup-maniax{
        background-color:#8080C0 !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup-girls{
        background-color:#B33761 !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup .${VOICELINK_CLASS}_left_panel{
        display: flex !important;
        flex-direction: column !important;
        justify-content: space-between !important;
        margin: 0 16px 0 0 !important;
        width: 310px !important;
        flex-shrink: 0 !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup .${VOICELINK_CLASS}_img_container{
        width: 100% !important;
        padding: 3px !important;
    }

    .${VOICELINK_CLASS}_img_container img {
        width: 100% !important;
        height: auto !important;
    }
    
    #${VOICELINK_CLASS}_hint {
        font-size: 0.8em !important;
        opacity: 0.5 !important;
        max-width: 300px !important;
        margin-top: 5px !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup a {
        text-decoration: none !important;
        color: pink !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup .${VOICELINK_CLASS}_age-18{
        color: hsl(300deg 76% 77%) !important;
    }
    
    .${VOICELINK_CLASS}_voicepopup .${VOICELINK_CLASS}_age-all{
        color: hsl(157deg 82% 52%) !important;
    }

    .${VOICELINK_CLASS}_voice-title {
        font-size: 1.363636em !important;   /*原1.4em*/
        font-weight: bold !important;
        text-align: center !important;
        margin: 5px 10px 0 0 !important;
        display: block !important;
    }

    .${VOICELINK_CLASS}_rjcode {
        text-align: center !important;
        margin: 5px 0 !important;
        font-size: 1.2012987em !important;  /*原1.2em !important;*/
        font-style: italic !important;
        opacity: 0.3 !important;
    }

    .${VOICELINK_CLASS}_error {
        height: 210px !important;
        line-height: 210px !important;
        text-align: center !important;
    }

    .${VOICELINK_CLASS}_discord-dark {
        background-color: #36393f !important;
        color: #dcddde !important;
        font-size: 0.9375rem !important;
    }
    
    .${VOICELINK_CLASS}_work_title:hover #${VOICELINK_CLASS}_copy_btn {
        opacity: 1 !important;
    }
    
    #${VOICELINK_CLASS}_copy_btn {
        background: transparent !important;
        border-color: transparent !important;
        cursor: pointer !important;
        transition: all 0.3s !important;
        opacity: 0 !important;
        font-size: 0.75em !important;
        user-select: none !important;
        position: absolute !important;
    }
    
    #${VOICELINK_CLASS}_copy_btn:hover {
        scale: 1.2 !important;
    }
    
    #${VOICELINK_CLASS}_copy_btn:active {
        scale: 1.1 !important;
    }
    
  `
    const SETTINGS_CSS = `
        #${VOICELINK_CLASS}_settings-container {
            font-family: Arial, sans-serif !important;
            background-color: #f4f4f9 !important;
            margin: auto !important;
            padding: 20px 30px !important;
            line-height: unset !important;
            
            position: fixed !important;
            overflow-y: auto !important;
            overflow-x: hidden !important;
            top: 20px !important;
            bottom: 20px !important;
            left: 50% !important;
            transform: translateX(-50%) !important;
            box-sizing: border-box !important;
            max-width: 800px !important;
            width: 100% !important;
            height: calc(100% - 40px) !important;
            z-index: 2147483647 !important;
            border-radius: 20px !important;
            box-shadow: darkgray 0px 0px 17px 2px !important;
            
            /*scrollbar-width: none;*/
            /*-ms-overflow-style: none;*/
        }
        #${VOICELINK_CLASS}_settings-container::-webkit-scrollbar {
            width: 5px !important;
            height: 5px !important;
        }
        #${VOICELINK_CLASS}_settings-container::-webkit-scrollbar-track {
            background-color: #f4f4f9 !important;
            border-radius: 5px !important;
        }
        #${VOICELINK_CLASS}_settings-container::-webkit-scrollbar-thumb {
            background-color: #888 !important;
            border-radius: 5px !important; 
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_container {
            max-width: 800px !important;
            margin: auto !important;
            background: #fff !important;
            padding: 20px !important;
            border-radius: 10px !important;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) !important;
        }
        #${VOICELINK_CLASS}_settings-container h1 {
            display: block !important;
            text-align: center !important;
            color: #333 !important;
            font-size: 32px !important;
            margin: 21.44px 0 !important;
            font-weight: bold !important;
            line-height: normal !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_section-container {
            margin: 20px 0 !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_section-container h2 {
            display: block !important;
            color: #007bff !important;
            font-size: 24px !important;
            margin: 22px 0 14px 0 !important;
            font-weight: bold !important;
            line-height: normal !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting {
            /*display: flex;*/
            /*align-items: center;*/
            /*justify-content: space-between;*/
            margin: 10px 0 !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting .${VOICELINK_CLASS}_row-title {
            margin: 0 0 0 10px !important;
            color: #555 !important;
            font-size: 18px !important;
            font-weight: normal !important;
            /*flex-grow: 1;*/
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="text"],
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="password"],
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="number"],
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="email"],
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting select {
            width: 100% !important;
            padding: 10px !important;
            border: 1px solid #ddd !important;
            border-radius: 5px !important;
            background: #fafafa !important;
            box-sizing: border-box !important;
            color: #666666FF !important;
            font-size: 13.3333px !important;
            height: unset !important;
            max-height: unset !important;
            max-width: unset !important;
            /*margin-bottom: 10px;*/
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="checkbox"] {
            display: none !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_toggle-container {
            display: flex !important;
            flex-direction: row !important;
            align-items: center !important;
            justify-content: flex-end !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting .${VOICELINK_CLASS}_toggle {
            display: inline-block !important;
            margin: 0 !important;
            width: 60px !important;
            height: 30px !important;
            padding: 0 !important;
            background: #ccc !important;
            border-radius: 15px !important;
            position: relative !important;
            cursor: pointer !important;
            transition: background 0.3s !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_toggle:before {
            content: "" !important;
            display: block !important;
            width: 24px !important;
            height: 24px !important;
            background: #fff !important;
            border-radius: 50% !important;
            position: absolute !important;
            top: 3px !important;
            left: 3px !important;
            transition: transform 0.3s !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="checkbox"]:checked + label {
            background: #007bff !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_setting input[type="checkbox"]:checked + label:before {
            transform: translateX(30px) !important;
        }
        #${VOICELINK_CLASS}_button-close{
            position: absolute !important;
            top: 20px !important;
            right: 20px !important;
            font-size: 24px !important;
            cursor: pointer !important;
            background: rgba(0, 0, 0, 0.05) !important;
            border: none !important;
            width: 42px !important;
            height: 42px !important;
            border-radius: 50% !important;
        }
        #${VOICELINK_CLASS}_button-save,
        #${VOICELINK_CLASS}_button-cancel,
        #${VOICELINK_CLASS}_button-reset{
            display: block !important;
            width: 100% !important;
            padding: 10px !important;
            border: none !important;
            border-radius: 5px !important;
            background: #007bff !important;
            color: #fff !important;
            font-size: 16px !important;
            cursor: pointer !important;
            margin-top: 10px !important;

            transition: background 0.3s, filter 0.3s !important;
        }
        #${VOICELINK_CLASS}_button-reset{
            background: #999 !important;
        }
        #${VOICELINK_CLASS}_button-save:hover,
        #${VOICELINK_CLASS}_button-cancel:hover,
        #${VOICELINK_CLASS}_button-reset:hover{
            filter: brightness(1.3) !important;
        }
        #${VOICELINK_CLASS}_button-save:active,
        #${VOICELINK_CLASS}_button-cancel:active,
        #${VOICELINK_CLASS}_button-reset:active{
            filter: brightness(0.9) !important;
        }

        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_tooltip {
            position: relative !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_tooltip .${VOICELINK_CLASS}_tooltip-text {
            visibility: hidden !important;
            min-width: 200px !important;
            max-width: 100% !important;
            background-color: #555 !important;
            color: #fff !important;
            font-size: 14px !important;
            text-align: center !important;
            border-radius: 5px !important;
            padding: 8px 10px !important;
            position: absolute !important;
            z-index: 1 !important;
            bottom: 125% !important;
            left: 0 !important;
            /*margin-left: -100px;*/
            opacity: 0 !important;
            filter: brightness(1.0) !important;
            transition: opacity 0.3s !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_tooltip:hover .${VOICELINK_CLASS}_tooltip-text {
            visibility: visible !important;
            opacity: 1 !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_sortable {
            cursor: move !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_sortable span{
            cursor: default !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_dragging{
            background-color: #1e82ff38 !important;
            user-select: none !important;
            transition: background-color 0.3s !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_sortable .${VOICELINK_CLASS}_setting {
            cursor: move !important;
        }
        #${VOICELINK_CLASS}_settings-container table {
            width: 100% !important;
            margin-bottom: 20px !important;
            border-collapse: collapse !important;
            font-size: unset !important;
        }
        #${VOICELINK_CLASS}_settings-container table,
        #${VOICELINK_CLASS}_settings-container th,
        #${VOICELINK_CLASS}_settings-container td {
            border: 0 solid #ddd !important;
        }
        #${VOICELINK_CLASS}_settings-container th,
        #${VOICELINK_CLASS}_settings-container td {
            border-bottom: 1px dashed rgba(221, 221, 221, 0.64) !important;
            /*border-top: 1px solid #ddd;*/
            padding: 8px 10px !important;
            text-align: left !important;
            vertical-align: middle !important;
        }

        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_hidden{
            display: none !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_input-cell{
            text-align: right !important;
            padding-right: 20px !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_indent-1 > td {
            padding: 8px 24px !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_indent-1 .${VOICELINK_CLASS}_input-cell {
            padding: 8px 20px !important;
        }

        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_tags {
            font-size: 14px;
        }
        .${VOICELINK_CLASS}_tags {
            display: flex !important;
            flex-wrap: wrap !important;
            justify-content: left !important;
            align-items: stretch !important;
        }
        .${VOICELINK_CLASS}_tags > label,
        .${VOICELINK_CLASS}_tags > span {
            border-radius: 5px !important;
            font-size: 1em !important;
            margin-right: 8px !important;
            margin-bottom: 8px !important;
            padding: 5px 8px !important;
            
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;

            transition: color 0.3s, background-color 0.3s !important;
        }
        .${VOICELINK_CLASS}_tags > label.${VOICELINK_CLASS}_tag_tight,
        .${VOICELINK_CLASS}_tags > span.${VOICELINK_CLASS}_tag_tight{
            padding: 2px 7px !important;
        }
        .${VOICELINK_CLASS}_tags > label.${VOICELINK_CLASS}_tag_small,
        .${VOICELINK_CLASS}_tags > span.${VOICELINK_CLASS}_tag_small{
            padding: 2px 7px !important;
            font-size: 0.857143em !important;
        }

        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_tag-off{
            background-color: #ffffff !important;
            color: #aaaaaa !important;
        }

        .${VOICELINK_CLASS}_tag-purple{
            background-color: #EED9F2 !important;
            color: #7B1FA2 !important;
        }

        .${VOICELINK_CLASS}_tag-blue{
            background-color: #d9eefc !important;
            color: #4285F4 !important;
        }

        .${VOICELINK_CLASS}_tag-red{
            background-color: #ffd6da !important;
            color: #EA4335 !important;
        }

        .${VOICELINK_CLASS}_tag-yellow{
            background-color: #FFF8E1 !important;
            color: #F57F17 !important;
        }

        .${VOICELINK_CLASS}_tag-green{
            background-color: #dcf5e4 !important;
            color: #34A853 !important;
        }

        .${VOICELINK_CLASS}_tag-teal{
            background-color: #d8eced !important;
            color: #0097A7 !important;
        }

        .${VOICELINK_CLASS}_tag-gray{
            background-color: #E0E0E0 !important;
            color: #424242 !important;
        }

        .${VOICELINK_CLASS}_tag-pink{
            background-color: #ffd9e7 !important;
            color: #f032a7 !important;
        }

        .${VOICELINK_CLASS}_tag-orange{
            background-color: #ffebcc !important;
            color: #f04000 !important;
        }

        .${VOICELINK_CLASS}_tag-darkblue{
            background-color: #d2e7fa !important;
            color: #0D47A1 !important;
        }

        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_reset-btn-small {
            position: relative !important;
            display: inline-block !important;
            width: 16px !important;
            height: 16px !important;
            margin-right: 4px !important;
            padding: 0 !important;
            color: transparent !important;
            background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAA6xJREFUeF7tmlFy2jAQhn+ZHIScJOEixYQ++BYJt/BDSU0vUnKSuvcoVkeq3RpXsndlScYAMzwwyLL20+rXarUCN/4RN24/7gDuHnDjBO5LYEoHSF+ytHjPiynHMJkHrLfZVwApgOKwzzdTQZgEQMv4xu7JIEQHYDB+UghRAfQYDyGwKr7kx9hLIRqASzRewY4C4FKNjwKgz3gAZesLIfGhBhVzawzqAQPG9y13DUZIHFDhWBS5+h3kEwxA+jl7lhLfPYxaGX8UJ+xCgAgHIM2WVYK1EHjzAEF1UUqJ4tt7vvPUn+4mGADVeeofggYhTlj58oagAAgQSiGwwS+UeMASFZZS4KkOkXs1wpc3BAdAgmCYUeU9coHXPhhS4m3skogCwBVC85xcQB2cnk0uMRZCNAAECMfDPl+ZjBzQEr2MXMPoqAB6IJSHff44pO6fXrJXy67iLIzRARggkIxv4KgkihR6SXQ/rH6ahycB0IbgImI2TxASG24YPRmAIXcf+t8Cge0FswVQb5Mq1F62YXG9YLYA9DIy6wHLC+YN4E+w9F+MwMkuzRqA8gKTFnCCo9kDqLXgR0c0rUFVV1xnD0AZtN5mSgzPQuXDPifZRmo0tCVN/b8JgDjhkXJkvhYAzS3T37mgCuFVADAJoVcASmiQdI6jgZOVnGVligeoARHJA0wJTs5WwzHGpa3RA4jngqsAYEq/exXBsXuty6xyngkOoN5rZWdQrJibYxC37XqbqUDo7FDkPQ4w7rUT3eh2Aa23WXdy4B3A2JibO6vU9pYTIbnggiSCTQZHLuAcc1MN4rYbEwWqd5EB2GJuasDBNYzS3pYfpLo/G4CPBATFMGobk/hx4xOWB1i2Q3BfSjWwr50tMcqZfbYHaC2wpKGExI6bkXUFYbt6p4a/7feyPKAlhqarqlE3NFQYNi9Ut8aUy5Xue9gANAR78YPzDQ0FQF/RhasYOwFQg+27pvJ1dd2G0ms88eBjguwMYACCFsakwoGSlembfUKRBTn/5x0AYXDOZS2EvpU9o4x32gW6FIkD/Vf1laC0XWW3+lIJTmM9QPN+X1vvqCXQhtGjCTYPb5e+nZ3khgTRVfC8LwGTNwyVtQwZN/C/KpfbjNWVUXEAxYB6r7aWtVD66LQJFmN4WwImo+qokVL1ZXpcC2gC/AwZYQYF0Fils8oPWFYVnoTQmZv2t9ECLZRSokwSfLjW/HC9KwoA7qBitr8DiEn7Et9194BLnJWYY7p5D/gNXP0HX03p5E0AAAAASUVORK5CYII=") !important;
            background-position: center !important;
            background-size: contain !important;
            background-color: transparent !important;
            border-radius: 3px !important;
            border: none !important;
            opacity: 0.5 !important;
        }
        #${VOICELINK_CLASS}_settings-container button.${VOICELINK_CLASS}_reset-btn-small:hover {
            opacity: 1 !important;
        }

        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_button-flat {
            background-color: transparent !important;
            border: none !important;
            color: #aaa !important;
            cursor: pointer !important;
            border-radius: 5px !important;
            padding: 5px 5px !important;
            margin-bottom: 6px !important;
            margin-right: 6px !important;

            display: inline-flex !important;
            align-items: center !important;
            justify-content: center !important;

            transition: background-color 0.3s !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_button-flat:hover {
            background-color: rgba(0, 0, 0, 0.1) !important;
        }
        #${VOICELINK_CLASS}_settings-container .${VOICELINK_CLASS}_button-flat span{
            display: inline-block !important;
        }
    `

    /**
     * Work promise cache
     * @type {{info:{}, api:{}, api2: {}, circle: {}}}
     */
    const work_promise = {};

    function getAdditionalPopupClasses() {
        const hostname = document.location.hostname;
        switch (hostname) {
            case "boards.4chan.org": return "post reply";
            case "discordapp.com": return `${VOICELINK_CLASS}_discord-dark`;
            default: return null;
        }
    }

    function getOS() {
        const userAgent = navigator.userAgent;
        if (userAgent.indexOf("Windows NT 10.0") !== -1) return "Windows 10";
        if (userAgent.indexOf("Windows NT 6.2") !== -1) return "Windows 8";
        if (userAgent.indexOf("Windows NT 6.1") !== -1) return "Windows 7";
        if (userAgent.indexOf("Windows NT 6.0") !== -1) return "Windows Vista";
        if (userAgent.indexOf("Windows NT 5.1") !== -1) return "Windows XP";
        if (userAgent.indexOf("Windows NT 5.0") !== -1) return "Windows 2000";
        if (userAgent.indexOf("Mac") !== -1) return "Mac";
        if (userAgent.indexOf("X11") !== -1) return "UNIX";
        if (userAgent.indexOf("Linux") !== -1) return "Linux";
        return "Other";
    }

    function getVoiceLinkTarget(target){
        while (target && !target.classList.contains(VOICELINK_CLASS)){
            target = target.parentElement;
        }
        return target;
    }

    function isInDLSite(){
        return document.location.hostname.endsWith("dlsite.com");
    }

    /**
     * Convert to valid file name.
     * @param {String} original
     */
    function convertToValidFileName(original){
        const charMapRegs = {
            "\\/": "/",
            "\\\\": "\",
            "\\:": ":",
            "\\*": "*",
            "\\?": "?",
            "\"": """,
            "\\<": "<",
            "\\>": ">",
            "\\|": "|"
        }

        let fileName = original;
        for (let key in charMapRegs){
            fileName = fileName.replaceAll(new RegExp(key, "g"), charMapRegs[key]);
        }
        return fileName;
    }

    function setUserSelectTitle(){
        // Make title selectable
        const hostname = document.location.hostname;
        if(!hostname.endsWith("dlsite.com")){
            return;
        }
        const rjList = document.URL.match(RJ_REGEX)
        const rj = rjList[rjList.length - 1]

        const title = document.getElementById("work_name");
        if(!title){
            return;
        }
        let titleStr = title.innerText;
        let titleHtml = title.innerHTML;

        const button = document.createElement("button");
        button.id = `${VOICELINK_CLASS}_copy_btn`;
        button.innerText = "📃";
        button.addEventListener("mouseenter", function(){
            button.innerText = "📃 复制为有效文件名";
        });
        button.addEventListener("mouseleave", function(){
            button.innerText = "📃";
        });
        button.addEventListener("click", function(){
            const fileName = convertToValidFileName(titleStr);
            // const promise = navigator.clipboard.writeText(fileName);
            const promise = GM_setClipboard(fileName, "text");
            promise?.then(() => {
                button.innerText = "✔ 复制成功";
            });
            promise?.catch(e => {
                window.prompt("复制失败,请手动复制", fileName);
                button.innerText = "📃";
            });
        });

        title.style.setProperty("user-select", "text", "important");  //userSelect = "text !important";
        title.classList.add(`${VOICELINK_CLASS}_work_title`);

        if(settings._s_show_translated_title_in_dl){
            //将Title替换成大家翻对应的语言翻译版本
            WorkPromise.getTranslationInfo(rj).then(info => {
                if(info.is_original) {
                    return null;
                }
                else{
                    return WorkPromise.getWorkTitle(rj);
                }
            }).then(t => {
                if(!t){
                    if(settings._s_copy_as_filename_btn) title.appendChild(button);
                    return;
                }
                compatibilityCheck(title, titleHtml);
                titleStr = t
                title.innerText = t
                if(settings._s_copy_as_filename_btn) title.appendChild(button);
            })
        }else{
            if(settings._s_copy_as_filename_btn) title.appendChild(button);
        }
    }

    function compatibilityCheck(titleElement, titleHtml){
        if(!settings._s_show_compatibility_warning) return;

        if(titleElement.innerHTML.trim() === titleHtml.trim()){
            return;
        }

        //其它脚本修改了标题内部,进行警告
        window.alert("警告:\n" +
            "VoiceLinks检测到DL作品标题元素发生变化,该变化可能是脚本与其它插件冲突导致的。\n" +
            "可以关闭本脚本中的 “在DLSite显示对应语言的翻译标题” 设置项,以尝试解决冲突。(也可根据情况酌情关闭 “在DL作品标题旁添加复制为文件名按钮” 选项)\n\n" +
            "本脚本的设置方法:点击Tampermonkey等扩展程序的按钮,在弹出的脚本列表中找到当前脚本,点击下方的Settings按钮即可打开设置页面。\n\n" +
            "注意:如果不想看到该警告,可以同时关闭“显示兼容性警告”设置项。")
    }

    function getXmlHttpRequest() {
        return (typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : GM_xmlhttpRequest);
    }

    const Parser = {
        walkNodes: function (elem) {
            const rjNodeTreeWalker = document.createTreeWalker(
                elem,
                NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
                {
                    acceptNode: function (node) {
                        if(node.nodeName === "SCRIPT" || node.parentElement && node.parentElement.nodeName === "SCRIPT"){
                            return NodeFilter.FILTER_REJECT;
                        }

                        if(node.parentElement.isContentEditable){
                            return NodeFilter.FILTER_SKIP;
                        }

                        if(settings._s_parse_url && node.nodeName === "A"){
                            if(!settings._s_parse_url_in_dl && document.location.hostname.endsWith("dlsite.com")){
                                return NodeFilter.FILTER_SKIP;
                            }

                            let href = node.href;
                            if(href.match(URL_REGEX) && !node.classList.contains(VOICELINK_IGNORED_CLASS)){
                                return NodeFilter.FILTER_ACCEPT;
                            }
                        }

                        if (node.nodeName !== "#text") return NodeFilter.FILTER_SKIP;
                        if(node.parentElement.classList.contains(VOICELINK_IGNORED_CLASS)
                            || node.parentElement.hasAttribute(RJCODE_ATTRIBUTE)){
                            return NodeFilter.FILTER_SKIP;
                        }

                        if (node.parentElement.classList.contains(VOICELINK_CLASS))
                            return NodeFilter.FILTER_ACCEPT;
                        if (node.nodeValue.match(RJ_REGEX))
                            return NodeFilter.FILTER_ACCEPT;

                        return NodeFilter.FILTER_SKIP;
                    }
                },
                false,
            );
            while (rjNodeTreeWalker.nextNode()) {
                const node = rjNodeTreeWalker.currentNode;

                //Ignore Element which let user input (textarea), input can be ignored because it's not a text node.
                if(node.parentElement.nodeName === "TEXTAREA"){
                    continue;
                }

                if (node.parentElement.classList.contains(VOICELINK_CLASS)) {
                    Parser.rebindEvents(node.parentElement);
                }else if(node.nodeName === "A") {
                    // alert("准备解析链接:" + node.nodeValue)
                    Parser.linkifyURL(node);
                }else{
                    // alert("准备解析文本:" + node.nodeValue)
                    Parser.linkify(node);
                }
            }
        },

        wrapPlaceholder: function (content) {
            let e;
            e = document.createElement("span");
            e.classList = VOICELINK_CLASS;
            e.innerText = content;
            e.classList.add(VOICELINK_IGNORED_CLASS);
            e.setAttribute(RJCODE_ATTRIBUTE, "");
            return e;
        },

        wrapRJCode: function (rjCode) {
            let e;
            e = document.createElement("a");
            e.classList = VOICELINK_CLASS;
            e.href = `https://www.dlsite.com/maniax/work/=/product_id/${rjCode.toUpperCase()}.html`
            e.innerText = rjCode;
            e.target = "_blank";
            e.rel = "noreferrer";
            e.classList.add(VOICELINK_IGNORED_CLASS);
            e.style.setProperty("display", "inline", "important");  //display = "inline !important";

            e.setAttribute(RJCODE_ATTRIBUTE, rjCode.toUpperCase());
            e.setAttribute("voicelink-linkified", "true");
            e.addEventListener("mouseover", Popup.over);
            e.addEventListener("mouseout", Popup.out);
            e.addEventListener("mousemove", Popup.move);
            e.addEventListener("keydown", Popup.keydown);
            //e.addEventListener("keyup", Popup.keyup);
            return e;
        },

        calculateCoverage: function(text){
            const matches = text.match(RJ_REGEX);
            if (!matches) return 0;
            //覆盖大小 = 所有匹配项的长度总和
            const coverSize = matches.reduce((total, current) => total + current.length, 0);
            return (coverSize / text.length) * 100;
        },

        /***
         * 处理直链
         * @param {Node} node
         ***/
        linkifyURL: function(node) {
            const e = node;
            const href = e.href;
            const rjs = href.match(RJ_REGEX);
            const rj = rjs[rjs.length - 1];
            if(!rj) return;

            // alert(`解析链接:${e.nodeValue}`)

            e.classList.add(VOICELINK_CLASS);
            e.setAttribute(RJCODE_ATTRIBUTE, rj.toUpperCase());
            e.addEventListener("mouseover", Popup.over);
            e.addEventListener("mouseout", Popup.out);
            e.addEventListener("mousemove", Popup.move);
            e.addEventListener("keydown", Popup.keydown);
            //e.addEventListener("keyup", Popup.keyup);
        },

        linkify: function (textNode) {
            const nodeOriginalText = textNode.nodeValue;
            const matches = [];

            let insert = settings._s_url_insert_mode;
            let tagA = textNode.parentElement.closest("a");
            let tagB = textNode.parentElement.closest("button");
            let tag = tagA ? tagA : tagB;
            if((!tagA && !tagB) || insert.trim() !== "none" && this.calculateCoverage(tag.innerText) < 71){
                insert = "none";
            }

            let match;
            while (match = RJ_REGEX.exec(nodeOriginalText)) {
                matches.push({
                    index: match.index,
                    value: match[0],
                });
            }
            if(matches.length === 0) return;

            // alert(`解析文本:${textNode.nodeValue}`)

            // Keep text in text node until first RJ code
            textNode.nodeValue = nodeOriginalText.substring(0, matches[0].index);
            if(insert.startsWith("prefix")){
                //加前缀
                textNode.nodeValue = `${settings._s_url_insert_text}${textNode.nodeValue}`
            }

            // Insert rest of text while linkifying RJ codes
            let prevNode = null;
            for (let i = 0; i < matches.length; ++i) {
                // Insert linkified RJ code
                let code = matches[i].value
                let rjLinkNode = Parser.wrapRJCode(code);
                //保证后续游走时忽略当前节点
                if(insert.startsWith("before_rj")){
                    //用导向文本替代RJ号链接,RJ号保留到后面的文本里不变
                    rjLinkNode.innerText = settings._s_url_insert_text;
                    textNode.parentNode.insertBefore(
                        rjLinkNode,
                        prevNode ? prevNode.nextSibling : textNode.nextSibling,
                    );
                    prevNode = rjLinkNode;
                    rjLinkNode = Parser.wrapPlaceholder(code);
                }
                textNode.parentNode.insertBefore(
                    rjLinkNode,
                    prevNode ? prevNode.nextSibling : textNode.nextSibling,
                );

                // Insert text after if there is any
                //找到当前RJ和下一个RJ之间的字符串
                let nextRJ = undefined;
                if (i < matches.length - 1) {
                    nextRJ = matches[i + 1].index;
                }
                let substring = nodeOriginalText.substring(matches[i].index + matches[i].value.length, nextRJ);

                if (substring) {
                    const subtextNode = document.createTextNode(substring);
                    textNode.parentNode.insertBefore(
                        subtextNode,
                        rjLinkNode.nextElementSibling,
                    );
                    prevNode = subtextNode;
                }
                else {
                    prevNode = rjLinkNode;
                }
            }
        },

        rebindEvents: function (elem) {
            if (elem.nodeName === "A") {
                elem.addEventListener("mouseover", Popup.over);
                elem.addEventListener("mouseout", Popup.out);
                elem.addEventListener("mousemove", Popup.move);
                elem.addEventListener("keydown", Popup.keydown);
                //elem.addEventListener("keyup", Popup.keyup);
            }
            else {
                const voicelinks = elem.querySelectorAll("." + VOICELINK_CLASS);
                for (let i = 0, j = voicelinks.length; i < j; i++) {
                    const voicelink = voicelinks[i];
                    voicelink.addEventListener("mouseover", Popup.over);
                    voicelink.addEventListener("mouseout", Popup.out);
                    voicelink.addEventListener("mousemove", Popup.move);
                    voicelink.addEventListener("keydown", Popup.keydown);
                    //voicelink.addEventListener("keyup", Popup.keyup);
                }
            }
        },

    }

    const DateParser = {
        parseDateStr: function(dateStr, lang){
            dateStr = dateStr.trim().replace(/ /g, "");
            lang = lang.trim().toLowerCase().replace(/_/g, "-");
            let nums = this.parseNumbers(dateStr);
            if(!nums || nums.length < 3 && lang !== "en-us" || nums.length < 2 && lang === "en-us"){
                //数字不够,无法解析
                return null;
            }

            let parsers = [
                this.parseAsiaDateStr,
                this.parseEnglishDateStr,
                this.parseEuropeanDateStr,
                this.parseSpanishDateStr
            ]
            let date = null;
            for (let i = 0; i < parsers.length; i++){
                date = parsers[i](dateStr, nums, lang);
                if(date){
                    break;
                }
            }

            return date;
        },
        parseNumbers: function (dateStr){
            let nums = dateStr.match(/\d+/g);
            if(!nums) return null;

            for (let i = 0; i < nums.length; i++) {
                nums[i] = Number(nums[i]);
            }
            return nums;
        },
        parseAsiaDateStr: function(dateStr, nums, lang){
            //2024年10月05日
            //2024년 10월 05일(已去除空格)
            if (!dateStr.match(/\d{4}年\d{1,2}月\d{1,2}日/)
                && !dateStr.match(/\d{4}년\d{1,2}월\d{1,2}일/)) {
                return null;
            }
            return new Date(nums[0], nums[1] - 1, nums[2]);
        },
        parseEnglishDateStr: function(dateStr, nums, lang){
            //Oct/05/2024
            if(!dateStr.match(/[a-zA-Z]{3}\/\d{1,2}\/\d{4}/)){
                return null;
            }
            const monthMap = {
                "Jan": 0, "Feb": 1, "Mar": 2,
                "Apr": 3, "May": 4, "Jun": 5,
                "Jul": 6, "Aug": 7, "Sep": 8,
                "Oct": 9, "Nov": 10, "Dec": 11
            }
            let monthStr = dateStr.substring(0, dateStr.indexOf("/")).toLowerCase();
            monthStr = monthStr[0].toUpperCase() + monthStr.substring(1);
            return new Date(nums[1], monthMap[monthStr], nums[0])
        },
        parseSpanishDateStr: function (dateStr, nums, lang) {
            //10/05/2024
            if(lang !== "es-es" || !dateStr.match(/\d{1,2}\/\d{1,2}\/\d{4}/)){
                return null;
            }
            return new Date(nums[2], nums[0] - 1, nums[1]);
        },
        parseEuropeanDateStr: function (dateStr, nums, lang) {
            //05/10/2024
            if(lang === "es-es" || !dateStr.match(/\d{1,2}\/\d{1,2}\/\d{4}/)){
                return null;
            }
            return new Date(nums[2], nums[1] - 1, nums[0]);
        },
        /***
         获得带倒计时的文本HTML
         @param date {Date}
         ***/
        getCountDownDateElement: function(date){
            if(!date) return "";

            const today = new Date();
            today.setHours(0);
            today.setMinutes(0);
            today.setSeconds(0);
            today.setMilliseconds(0);
            date.setHours(0);
            date.setMinutes(0);
            date.setSeconds(0);
            date.setMilliseconds(0);

            if(date.getTime() < today.getTime()) return "";
            let days = (date.getTime() - today.getTime()) / (1000 * 60 * 60 * 24);
            let element = document.createElement("span");
            element.innerText = `(Coming in ${days} day${(days > 1 ? "s" : "")})`;
            element.style.setProperty("color", "#ffeb3b", "important");
            element.style.setProperty("font-style", "italic", "important");
            return element;
            //return `<span style="color:#ffeb3b !important; font-size: 16px !important; font-style: italic !important; margin-left: 16px !important"></span>`
        },
    }

    const Popup = {
        popupElement: {
            popup: null,
            not_found: null,
            left_panel: null,
            img: {container: null},
            right_panel: null,
            title: null,
            rj_code: null,
            info_container: null,
            loader: null,
            flag: null,
            tags: null,
            dl_count: null,
            circle_name: null,
            debug: null,
            translator_name: null,
            release_date: null,
            update_date: null,
            age_rating: null,
            scenario: null,
            illustration: null,
            voice_actor: null,
            music: null,
            genre: null,
            file_size: null,

            _state: {
                mouseX: 0,
                mouseY: 0
            }
        },

        makePopup: function (display) {
            const popup = document.createElement("div");
            const ele = Popup.popupElement;
            ele.popup = popup;

            popup.className = `${VOICELINK_CLASS}_voicepopup ${VOICELINK_CLASS}_voicepopup-maniax ` + (getAdditionalPopupClasses() || '');
            popup.id = `${VOICELINK_CLASS}-voice-popup`;  // + rjCode;
            popup.style.setProperty("display", display === false ? "none" : "flex", "important");  //display = display === false ? "none" : "flex";
            document.body.appendChild(popup);

            popup.addEventListener("mouseenter", () => {
                popup.setAttribute("mouse-in", "");
            });
            popup.addEventListener("mouseleave", () => {
                popup.removeAttribute("mouse-in");
            })

            const notFoundElement = document.createElement("div");
            ele.not_found = notFoundElement;
            //占满整个popup
            //"display: none; width: 100%; height: 100%";
            notFoundElement.style.setProperty("display", "none", "important");
            notFoundElement.style.setProperty("width", "100%", "important");
            notFoundElement.style.setProperty("height", "100%", "important");
            notFoundElement.innerText = "Work Not Found.";
            popup.appendChild(notFoundElement);

            const leftPanel = document.createElement("div");
            leftPanel.classList.add(`${VOICELINK_CLASS}_left_panel`);
            popup.appendChild(leftPanel);
            ele.left_panel = leftPanel;

            const imgContainer = document.createElement("div")
            imgContainer.classList.add(`${VOICELINK_CLASS}_img_container`);
            ele.img.container = imgContainer;
            leftPanel.appendChild(imgContainer);

            //左下角提示状态栏
            ele.hint = document.createElement("div");
            leftPanel.appendChild(ele.hint);
            ele.hint.id = `${VOICELINK_CLASS}_hint`;

            const rightPanel = document.createElement("div");
            ele.right_panel = rightPanel;

            const titleElement = Popup.createCopyTag("div", "", false, localizePopup(localizationMap.hint_copy_work_title));
            ele.title = titleElement;
            titleElement.classList.add(`${VOICELINK_CLASS}_voice-title`);
            rightPanel.appendChild(titleElement);

            const rjCodeElement = document.createElement("div");
            ele.rj_code = rjCodeElement;
            rjCodeElement.classList.add(`${VOICELINK_CLASS}_rjcode`);
            rightPanel.appendChild(rjCodeElement);

            const infoContainer = document.createElement("div");
            ele.info_container = infoContainer;
            infoContainer.id = `${VOICELINK_CLASS}_info-container`;
            infoContainer.style.setProperty("position", "relative", "important");  //position = "relative !important";
            infoContainer.style.setProperty("min-height", "70px", "important");  //minHeight = "70px !important";
            rightPanel.appendChild(infoContainer);

            const loader = document.createElement("div");
            loader.className = `${VOICELINK_CLASS}_loader`;
            loader.innerHTML = Csp.createHTML(`
            <div class="${VOICELINK_CLASS}_dot"></div>
            <div class="${VOICELINK_CLASS}_dot"></div>
            <div class="${VOICELINK_CLASS}_dot"></div>
            `);
            ele.loader = loader;
            infoContainer.appendChild(loader);

            ele.tags = document.createElement("div");
            infoContainer.appendChild(ele.tags);

            ele.dl_count = document.createElement("div");
            ele.circle_name = document.createElement("div");
            ele.debug = document.createElement("div");
            ele.translator_name = document.createElement("div");
            ele.release_date = document.createElement("div");
            ele.update_date = document.createElement("div");
            ele.age_rating = document.createElement("div");
            ele.scenario = document.createElement("div");
            ele.illustration = document.createElement("div");
            ele.voice_actor = document.createElement("div");
            ele.music = document.createElement("div");
            ele.genre = document.createElement("div");
            ele.file_size = document.createElement("div");

            rightPanel.style.setProperty("padding-bottom", "3px", "important");  //paddingBottom = "3px !important";
            rightPanel.style.setProperty("flex-grow", "1", "important");  //flexGrow = "1 !important";
            popup.appendChild(rightPanel);
            popup.insertBefore(leftPanel, popup.childNodes[0]);
        },

        updatePopup: function(e, rjCode, isParent=false) {
            const ele = Popup.popupElement;
            const popup = ele.popup;
            popup.className = `${VOICELINK_CLASS}_voicepopup ${VOICELINK_CLASS}_voicepopup-maniax ` + (getAdditionalPopupClasses() || '');
            // popup.id = "voice-" + rjCode;
            popup.style.setProperty("display", "flex", "important");  //= "display: flex";
            popup.setAttribute(RJCODE_ATTRIBUTE, rjCode);

            //------检查作品存在情况------
            let workFound = true;
            Popup.setFoundState(true);
            WorkPromise.getFound(rjCode).then(async found => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;

                if(found){
                    //找到则直接返回交给下一级处理
                    return {found: true, parentRJ: rjCode};
                }

                //没找到则尝试找到父作品的RJ号,填补子作品信息的缺失
                let parentRJ = await WorkPromise.getParentRJ(rjCode);
                if(parentRJ === rjCode || !parentRJ) {
                    return {found: false, parentRJ: rjCode};
                }
                found = await WorkPromise.getFound(parentRJ);
                return {found: found, parentRJ: parentRJ};

            }).then((state) => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;

                const found = state.found;
                const rj = state.parentRJ;
                if(found && rj !== rjCode){
                    //如果找到了父作品的信息但子作品找不到,就重新update
                    Popup.updatePopup(e, rj, true);
                    return;
                }

                ele.not_found.style.setProperty("display", found ? "none" : "block", "important");  //display = found ? "none" : "block";
                Popup.setFoundState(found);
                workFound = found;
            });

            //------检查是否为女性向------
            WorkPromise.getGirls(rjCode).then(isGirls => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                if(isGirls) popup.className += (` ${VOICELINK_CLASS}_voicepopup-girls`)
            }).catch(e => {});

            //------获取作品封面------
            const imgContainer = ele.img.container;

            //NSFW模糊等级
            const blur_map = {
                low: "6px",
                medium: "12px",
                high: "24px"
            };

            //先对Container内的所有img进行隐藏
            for (let i = 0; i < imgContainer.childNodes.length; ++i) {
                imgContainer.childNodes[i].style.setProperty("display", "none", "important");  //display = "none !important";
            }

            //NOTE: 注意这里可能因为快速的多次获取导致同时加载两个图片,可使用占位符来预先占用图片位置
            new Promise((resolve, reject) => {
                let img = ele.img[rjCode];
                if(img) resolve(img);
                else throw Error("首次加载图片");
            }).catch(_ => {
                //首次加载图片,对图片添加占位
                ele.img[rjCode] = 1;
                return WorkPromise.getImgLink(rjCode);
            }).then(link => {
                if(typeof link !== "string"){
                    //图片已经加载过,传递的是img,不通过当前then
                    return link;  //实际上是img
                }

                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) {
                    //清除占位
                    ele.img[rjCode] = null;
                    return null;
                }
                let img;
                try{
                    img = GM_addElement("img", {
                        src: link,
                    });
                    if(!img) { // noinspection ExceptionCaughtLocallyJS
                        throw new Error("API调用生成失败");
                    }
                }catch (e) {
                    img = document.createElement("img");
                    img.src = link;
                }

                imgContainer.appendChild(img);
                console.warn("添加封面!")
                ele.img[rjCode] = img;

                //开启动画
                if(settings._s_sfw_blur_transition){
                    img.style.setProperty("transition", "all 0.3s", "important");
                }

                //鼠标移动上去解除模糊
                img.addEventListener("mouseenter", e => {
                    if(!settings._s_sfw_remove_when_hover){
                        return;
                    }
                    img.style.setProperty("filter", "inherit", "important");
                });
                img.addEventListener("mouseleave", e => {
                    if(settings._s_sfw_mode){
                        img.style.setProperty("filter", `blur(${blur_map[settings._s_sfw_blur_level]})`, "important");
                    }else{
                        img.style.setProperty("filter", "inherit", "important");
                    }
                });

                return img;
            }).then(img => {
                if(!(img instanceof HTMLElement)) return;
                img.style.setProperty("display", "block", "important");  //display = "block"

                //设置NSFW模糊
                if(settings._s_sfw_mode){
                    img.style.setProperty("filter", `blur(${blur_map[settings._s_sfw_blur_level]})`, "important");
                }else{
                    img.style.setProperty("filter", "inherit", "important");
                }
            }).catch(e => {
                //清理并在下次重试
                if(ele.img[rjCode] instanceof HTMLElement) img.remove();
                ele.img[rjCode] = null;
                console.error(e)
            });

            //------设置hint可见------
            ele.hint.style.setProperty("display", "block", "important");

            //------设置标题------
            const titleElement = ele.title;
            titleElement.innerText = "Loading...";
            titleElement.setHint(localizePopup(localizationMap.hint_copy_work_title));
            titleElement.setCopyText(null);
            titleElement.setSecondaryCopyText(null);
            WorkPromise.getWorkTitle(rjCode).then(title => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                titleElement.innerText = title;
                titleElement.setCopyText(title);
                titleElement.setSecondaryCopyText(convertToValidFileName(title));
            }).catch(_ => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                titleElement.innerHTML = Csp.createHTML("");
            })

            //------设置RJ号------
            const rjCodeElement = ele.rj_code;
            rjCodeElement.innerHTML = Csp.createHTML(`[ ${isParent ? " ↑ " : ""}<span class="${VOICELINK_IGNORED_CLASS}" style="font-weight: bold !important;text-decoration-line: underline !important;">${rjCode}</span> ]`);
            WorkPromise.getRJChain(rjCode).then(chain => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                rjCodeElement.innerText = "[ ";
                //构造chain
                for (let i = 0; i < chain.length; i++) {
                    const rj = chain[i];
                    let e = Popup.createCopyTag("span", rj);
                    e.innerText = rj;
                    e.classList.add(VOICELINK_IGNORED_CLASS);
                    if(i === 0) {
                        //第一个元素,也就是当前RJ号
                        e.style.setProperty("font-weight", "bold", "important");
                        e.style.setProperty("text-decoration", "underline", "important");
                    }else{
                        rjCodeElement.appendChild(document.createTextNode(" → "));
                    }
                    rjCodeElement.appendChild(e);
                }
                rjCodeElement.appendChild(document.createTextNode(" ]"));
            });

            //清除原有信息并展示加载界面
            for(let child of [...this.popupElement.info_container.children]){
                if(child === this.popupElement.loader) continue;
                child.remove();
            }
            ele.loader.style.setProperty("display", "flex", "important");  //display = "flex !important";
            WorkPromise.getWorkCategory(rjCode).then(category => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                this.set_info_container(rjCode, category);
            }).catch(e => {
                if (rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                //默认other
                this.set_info_container(rjCode, "other");
            });

            Popup.move(e);
        },

        setFoundState(found){
            const ele = Popup.popupElement;

            ele.not_found.style.setProperty("display", found ? "none" : "block", "important");
            //ele.img.container.style.setProperty("display", found && !Popup.hideImg ? "block" : "none", "important");
            ele.left_panel.style.setProperty("display", found ? "flex" : "none", "important");
            ele.right_panel.style.setProperty("display", found ? "block" : "none", "important");
            ele.title.style.setProperty("display", found ? "block" : "none", "important");
            ele.rj_code.style.setProperty("display", found ? "block" : "none", "important");
            ele.info_container.style.setProperty("display", found ? "block" : "none", "important");
            ele.hint.style.setProperty("display", found ? "block" : "none", "important");
            /*ele.dl_count.style.setProperty("display", found ? "block" : "none", "important");
            ele.circle_name.style.setProperty("display", found ? "block" : "none", "important");
            ele.debug.style.setProperty("display", found ? "block" : "none", "important");
            ele.translator_name.style.setProperty("display", found ? "block" : "none", "important");
            ele.release_date.style.setProperty("display", found ? "block" : "none", "important");
            ele.update_date.style.setProperty("display", found ? "block" : "none", "important");
            ele.age_rating.style.setProperty("display", found ? "block" : "none", "important");
            ele.voice_actor.style.setProperty("display", found ? "block" : "none", "important");
            ele.music.style.setProperty("display", found ? "block" : "none", "important");
            ele.genre.style.setProperty("display", found ? "block" : "none", "important");
            ele.file_size.style.setProperty("display", found ? "block" : "none", "important");*/
        },

        /**
         * 创建可复制标签
         * @param tag {string|HTMLElement} 标签名或标签对象(如果为标签对象,则会将对象原地转化成可复制标签)
         * @param copyText {string} 需要复制的文本
         * @param isTitle {boolean} 是否为标题元素(使用特殊class)
         * @param hint {string} 提示栏显示的提示文本
         * @returns {HTMLElement} 创建/转换后的标签
         */
        createCopyTag: function (tag, copyText, isTitle = false, hint = isTitle ? localizePopup(localizationMap.hint_copy_all) : localizePopup(localizationMap.hint_copy)) {
            tag = (typeof tag === "string") ? document.createElement(tag) : tag;
            if(isTitle) tag.classList.add("info-title");

            //添加自定义方法
            tag.getCopyText = () => tag.getAttribute("copy-text");
            tag.setCopyText = text => {
                if(!text){
                    tag.removeAttribute("copy-text");
                    return;
                }
                tag.setAttribute("copy-text", text);
            };

            tag.getSecondaryCopyText = () => tag.getAttribute("sec-copy-text");
            tag.setSecondaryCopyText = text => {
                if(!text){
                    tag.removeAttribute("sec-copy-text");
                    return;
                }
                tag.setAttribute("sec-copy-text", text);
            };

            tag.getHint = () => tag.getAttribute("hint");
            tag.setHint = hint => {
                tag.setAttribute("hint", hint);
            };

            tag.setCopyText(copyText);
            tag.setHint(hint);
            tag.addEventListener("click", e => {
                const attr = e.altKey ? "sec-copy-text" : "copy-text";
                if(!tag.hasAttribute(attr)) return;
                GM_setClipboard(tag.getAttribute(attr), "text")?.finally();
                // navigator.clipboard.writeText(tag.getAttribute(attr)).finally();
            });
            tag.addEventListener("mouseenter", e => {
                let hint = tag.getHint();
                Popup.popupElement.hint.innerText = hint ? hint : Popup.popupElement.hint.innerText;
            })
            return tag;
        },

        /**
         * 显示某行的信息
         * @param rjCode {string} 信息对应的RJ号
         * @param id {string} 信息对应的ID
         * @param rowElement {HTMLElement} 行对应的Element元素
         * @param title {string} 行信息标题
         * @param contentProvider {Promise<any|Array|HTMLElement>} 无参内容Provider,返回字符串/字符串列表等用于生成文本
         * @param suffixProvider {Promise<HTMLElement>} 无参后缀Provider,返回一个Element用于放在Content后面
         * @param contentSeperator {string, HTMLElement}  Content如果是列表,则该内容为作为分隔符
         * @param contentSeperatorText {string} 如果采用HTMLElement的分隔符,则在复制的时候需要有一个文本表示
         */
        set_info_row: function (rjCode, id, rowElement, title, contentProvider, suffixProvider, contentSeperator = " ", contentSeperatorText = undefined ){
            const settingId = `_s_${id}`;
            //如果设置了不展示信息,或信息没在设置中定义,则不显示
            if(!settings[settingId]) return;

            const ele = Popup.popupElement;
            const popup = ele.popup;
            const titleElement = Popup.createCopyTag("span", "", true);
            titleElement.innerText = title;
            const contentElement = Popup.createCopyTag("span", null);
            contentElement.innerText = "Loading...";

            rowElement.innerHTML = Csp.createHTML("");
            rowElement.appendChild(titleElement);
            rowElement.appendChild(contentElement);

            contentProvider.then(contents => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                if(!Array.isArray(contents)){
                    //单个结果转化成列表
                    contents = [contents];
                }

                //处理结果列表
                contentElement.setCopyText(null);
                contentElement.innerText = "";
                //以指定的分隔符文本为准,不指定分隔符文本再使用分隔符内的文本
                let sepText = contentSeperatorText ? contentSeperatorText : contentSeperator.toString();
                let sep;
                if(typeof contentSeperator === "string"){
                    sep = document.createElement("span");
                    sep.innerText = contentSeperator;
                }else{
                    sep = contentSeperator;
                }

                let contentsText = [];
                for (let i = 0; i < contents.length; i++) {
                    let c = contents[i];
                    if(i > 0) {
                        //如果Seperator是Element,则直接复制一个出来添加,否则创建一个span然后把内容转换成文本放进去。
                        contentElement.appendChild(sep.cloneNode(true));
                    }

                    let element;
                    if(c instanceof HTMLElement){
                        element = c;
                    }else{
                        element = Popup.createCopyTag("a", c);
                        element.innerText = c;
                    }

                    //将复制文本加入复制列表
                    const copyText = element.getAttribute("copy-text");
                    if(copyText) contentsText.push(copyText);

                    contentElement.appendChild(element);
                }

                //为标题添加复制文本
                titleElement.setCopyText(contentsText.join(sepText));
            }).catch(e => {
                if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                rowElement.innerHTML = Csp.createHTML("");
                //console.error(e);
            }).finally(() => {
                Popup.adjustPopup(ele._state.mouseX, ele._state.mouseY, true);
            });

            if(suffixProvider){
                suffixProvider.then((element) => {
                    if(rjCode !== popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                    rowElement.appendChild(element);
                }).catch(_ => {});
            }

            return rowElement;
        },
        set_dl_count: function (rjCode, category){
            const id = `${category}__dl_count`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.dl_count, localizePopup(localizationMap.dl_count),
                WorkPromise.getDLCount(rjCode));
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_circle_name: function (rjCode, category){
            const id = `${category}__circle_name`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.circle_name, localizePopup(localizationMap.circle_name),
                WorkPromise.getCircle(rjCode), null);
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_translator_name: function (rjCode, category){
            const id = `${category}__translator_name`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.translator_name,
                localizePopup(localizationMap.translator_name), WorkPromise.getTranslatorName(rjCode), null);
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_release_date: function (rjCode, category){
            const id = `${category}__release_date`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.release_date, localizePopup(localizationMap.release_date),
                WorkPromise.getReleaseDate(rjCode).then(async (date) => {
                    const [dateStr, isAnnounce] = date;
                    const e = Popup.createCopyTag("a", dateStr);
                    e.innerText = dateStr;
                    if(isAnnounce) e.style.setProperty("color", "gold", "important");
                    return e;
                }), WorkPromise.getReleaseCountDownElement(rjCode).then(element => {
                    element.style.setProperty("margin-left", "16px", "important");
                    return element;
                }));

            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_update_date: function (rjCode, category){
            const id = `${category}__update_date`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.update_date, localizePopup(localizationMap.update_date),
                WorkPromise.getUpdateDate(rjCode));
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_age_rating: function (rjCode, category){
            const id = `${category}__age_rating`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.age_rating, localizePopup(localizationMap.age_rating),
                WorkPromise.getAgeRating(rjCode).then(rating => {
                    let ratingClass = `${VOICELINK_CLASS}_age-all`;
                    if(rating.includes("18")){
                        ratingClass = `${VOICELINK_CLASS}_age-18`;
                    }
                    let e = Popup.createCopyTag("a", rating);
                    e.innerText = rating;
                    e.classList.add(ratingClass);
                    return e;
                }));
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_scenario: function (rjCode, category){
            const id = `${category}__scenario`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.scenario, localizePopup(localizationMap.scenario),
                WorkPromise.getScenario(rjCode), null, " / ");
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_illustration: function (rjCode, category){
            const id = `${category}__illustration`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.illustration, localizePopup(localizationMap.illustration),
                WorkPromise.getIllustrator(rjCode), null, " / ");
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_voice_actor: function (rjCode, category){
            const id = `${category}__voice_actor`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.voice_actor, localizePopup(localizationMap.voice_actor),
                WorkPromise.getCV(rjCode), null, " / ");
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_music: function (rjCode, category) {
            const id = `${category}__music`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.music, localizePopup(localizationMap.music),
                WorkPromise.getMusic(rjCode), null, " / ");
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_genre: function (rjCode, category){
            const id = `${category}__genre`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.genre, localizePopup(localizationMap.genre),
                WorkPromise.getTags(rjCode), null, "\u3000");
            if(element) Popup.popupElement.info_container.appendChild(element);
        },
        set_file_size: function (rjCode, category){
            const id = `${category}__file_size`;
            const element = Popup.set_info_row(rjCode, id, Popup.popupElement.file_size, localizePopup(localizationMap.file_size),
                WorkPromise.getFileSize(rjCode));
            if(element) Popup.popupElement.info_container.appendChild(element);
        },

        get_tag: function (text, tagClass) {
            if(!tagClass.startsWith(`${VOICELINK_CLASS}_`)){
                tagClass = `${VOICELINK_CLASS}_${tagClass}`
            }
            const tag = document.createElement("span");
            tag.classList.add(`${VOICELINK_CLASS}_tag_tight`);
            tag.classList.add(tagClass);
            tag.innerText = text;
            return tag;
        },
        get_tag_rate: async function (rjCode) {
            let rate = await WorkPromise.getRateAvg(rjCode);
            let cot = await WorkPromise.getRateCount(rjCode);
            return Popup.get_tag(`${rate.toFixed(2)}★` + (settings._s_show_rate_count ? ` (${cot})` : ""), "tag-yellow");
        },
        get_tag_no_longer_available: async function (rjCode) {
            let sale = await WorkPromise.getSale(rjCode);
            if(sale) return;
            return Popup.get_tag(localizePopup(localizationMap.tag_no_longer_available),
                "tag-gray");
        },
        get_tag_work_type: async function (rjCode) {
            let type = await WorkPromise.getWorkTypeText(rjCode);
            let tagClass = "tag-gray";
            switch (type) {
                case localizePopup(localizationMap.work_type_game):
                    tagClass = "tag-purple";
                    break;
                case localizePopup(localizationMap.work_type_comic):
                    tagClass = "tag-green";
                    break;
                case localizePopup(localizationMap.work_type_illustration):
                    tagClass = "tag-teal";
                    break;
                case localizePopup(localizationMap.work_type_novel):
                    tagClass = "tag-gray";
                    break;
                case localizePopup(localizationMap.work_type_video):
                    tagClass = "tag-darkblue";
                    break;
                case localizePopup(localizationMap.work_type_voice):
                    tagClass = "tag-orange";
                    break;
                case localizePopup(localizationMap.work_type_music):
                    tagClass = "tag-yellow";
                    break;
                case localizePopup(localizationMap.work_type_tool):
                    tagClass = "tag-gray";
                    break;
                case localizePopup(localizationMap.work_type_voice_comic):
                    tagClass = "tag-blue";
                    break;
                case localizePopup(localizationMap.work_type_other):
                    tagClass = "tag-gray";
                    break;
                default:
                    tagClass = "tag-gray";
                    break;
            }
            return Popup.get_tag(type, tagClass);
        },
        get_tag_translatable: async function (rjCode) {
            let able = await WorkPromise.getTranslatable(rjCode);
            if(!able) return;
            return Popup.get_tag(localizePopup(localizationMap.tag_translatable),
                "tag-green");
        },
        get_tag_not_translatable: async function (rjCode) {
            let able = await WorkPromise.getTranslatable(rjCode);
            let translated = await WorkPromise.getTranslated(rjCode);
            if(able || translated) return;
            return Popup.get_tag(localizePopup(localizationMap.tag_not_translatable),
                "tag-red");
        },
        get_tag_translated: async function (rjCode) {
            let translated = await WorkPromise.getTranslated(rjCode);
            if(!translated) return;
            return Popup.get_tag(localizePopup(localizationMap.tag_translated), "tag-teal");
        },
        get_tag_bonus_work: async function (rjCode) {
            let bonus = await WorkPromise.getBonus(rjCode);
            if(!bonus) return;
            return Popup.get_tag(localizePopup(localizationMap.tag_bonus_work),
                "tag-yellow");
        },
        get_tag_has_bonus: async function (rjCode) {
            let has = await WorkPromise.getHasBonus(rjCode);
            if(!has) return;
            return Popup.get_tag(localizePopup(localizationMap.tag_has_bonus),
                "tag-orange");
        },
        get_tag_language_support: async function (rjCode) {
            const lang = await WorkPromise.getLanguages(rjCode);
            if(!lang || lang.length <= 0){
                return;
            }
            let txt = "";
            lang.forEach(l => {
                txt += ` | ${l}`;
            });
            txt = txt.substring(3);
            return Popup.get_tag(txt, "tag-pink");
        },
        get_tag_file_format: async function (rjCode) {
            const format = await WorkPromise.getFileFormats(rjCode);
            if(!format || format.length <= 0){
                return;
            }
            let txt = "";
            format.forEach(f => {
                txt += ` | ${f}`;
            });
            txt = txt.substring(3);
            return Popup.get_tag(txt, "tag-darkblue");
        },
        get_tag_ai: async function (rjCode) {
            const ai = await WorkPromise.getAIUsedText(rjCode);
            if(!ai) return;
            return Popup.get_tag(ai, "tag-purple");
        },
        get_translatable_tag: async function (rjCode, tag_id) {
            if(settings[`_s_${tag_id}`] !== true) return;

            if(tag_id.startsWith("tag_")) tag_id = tag_id.substring(4);
            const t = await WorkPromise.getWorkPromise(rjCode).translatable;
            const stat = t[tag_id];

            const hasRequest = stat.request > 0;
            const hasSale = stat.sale > 0;
            const displayCount = stat.agree || hasRequest || hasSale;
            const lang = tag_id.substring("translation_request_".length);
            const tag = Popup.get_tag(`${localizePopup(localizationMap[`language_${lang}_abbr`])}${stat.agree ? "" : (stat.agree === false ? " ✘" : " ?")} ${displayCount ? ` ${stat.request}-${stat.sale}` : ""}`,
                hasSale ? "tag-green" : (hasRequest ? "tag-orange" : "tag-gray"));
            tag.classList.add(`${VOICELINK_CLASS}_tag_small`);
            return tag;
        },

        get_tag_container: function (rjCode, tag_list) {
            const container = document.createElement("div");
            container.classList.add(`${VOICELINK_CLASS}_tags`);
            for (const tag_id of tag_list) {
                if(settings[`_s_${tag_id}`] !== true) continue;

                let shadowTag = document.createElement("span");
                shadowTag.style.setProperty("display", "none", "important");  //display = "none !important";
                shadowTag.setAttribute("data-id", tag_id);
                container.appendChild(shadowTag);

                let tag_get = this[`get_${tag_id}`];
                tag_get(rjCode).then(tag => {
                    if(tag){
                        container.insertBefore(tag, shadowTag);
                        shadowTag.remove();
                    }
                });
            }
            return container;
        },
        get_translatable_tag_container: function (rjCode, tag_list) {
            const container = document.createElement("div");
            container.classList.add(`${VOICELINK_CLASS}_tags`);
            container.style.setProperty("margin-top", "0", "important");  //marginTop = "0 !important";
            for (const tag_id of tag_list) {
                let shadowTag = document.createElement("span");
                shadowTag.style.setProperty("display", "none", "important");  //display = "none !important";
                shadowTag.setAttribute("data-id", tag_id);
                container.appendChild(shadowTag);

                Popup.get_translatable_tag(rjCode, tag_id).then(tag => {
                    if(tag){
                        container.insertBefore(tag, shadowTag);
                        shadowTag.remove();
                    }
                }).catch(e => {});
            }
            return container;
        },

        //整合顺序
        set_info_container: function (rjCode, category) {
            //清除上次的信息
            for(let child of [...this.popupElement.info_container.children]){
                if(child === this.popupElement.loader) {
                    child.style.setProperty("display", "none", "important");  //display = "none !important";
                    continue;
                }
                child.remove();
            }

            //TAG部分
            const infoContainer = this.popupElement.info_container;
            let tagContainer = null;
            if(settings._s_tag_main_switch === true){
                const container = this.get_tag_container(rjCode,
                    settings[`_s_tag_display_order`]);
                tagContainer = container;
                infoContainer.appendChild(container);
            }

            //翻译申请情况
            const shadowContainer = document.createElement("div");
            shadowContainer.style.setProperty("display", "none", "important");  //display = "none !important";
            infoContainer.appendChild(shadowContainer);
            WorkPromise.getTranslatable(rjCode).then(able => {
                if(rjCode !== Popup.popupElement.popup.getAttribute(RJCODE_ATTRIBUTE)) return;
                if(able && settings._s_tag_translation_request === true){
                    const translatableContainer = this.get_translatable_tag_container(rjCode,
                        settings._s_tag_translation_request_display_order);
                    infoContainer.insertBefore(translatableContainer, shadowContainer);
                    shadowContainer.remove();
                }
            }).catch(e => {});

            //信息部分
            const order = settings[`_s_${category}__info_display_order`];
            order.forEach(id => {
                try{
                    id = id.substring(id.indexOf("__") + 2);
                    this["set_" + id](rjCode, category);
                }catch (e) {
                    console.error(e);
                }
            });

            const debugElement = document.createElement("div");
            this.popupElement.info_container.appendChild(debugElement);
            WorkPromise.getDebug(rjCode).then(t => {
                debugElement.innerHTML = Csp.createHTML(t);
            });
        },

        //调整弹框位置
        adjustPopup: function (mouseX, mouseY, force = false){
            // console.log("定位修正")

            //定位修正
            const popup = Popup.popupElement.popup;
            if(!Popup.pinRJ || force){
                if (popup.offsetWidth + mouseX + 10 < window.innerWidth - 10) {
                    popup.style.setProperty("left", (mouseX + 10) + "px", "important");
                }
                else {
                    popup.style.setProperty("left", (window.innerWidth - popup.offsetWidth - 10) + "px", "important");
                }
            }

            let rect = popup.getBoundingClientRect();
            if(!Popup.pinRJ || force || rect.top < 0 || rect.bottom > window.innerHeight){
                if (mouseY > window.innerHeight / 2) {
                    let top = Math.max(mouseY - popup.offsetHeight - 8, 0);
                    popup.style.setProperty("top", top + "px", "important");
                }
                else {
                    let top = Math.min(mouseY + 20, window.innerHeight - popup.offsetHeight);
                    popup.style.setProperty("top", top + "px", "important");
                }
            }

            //大小修正
            let currentFontSize = popup.computedStyleMap().get("font-size").toString();
            currentFontSize = parseFloat(currentFontSize.substring(0, Math.max(currentFontSize.indexOf("px"), 1)));
            const sizeLevel = [15, 14.5, 14, 13.5, 13, 12.5, 12];
            let size = sizeLevel[sizeLevel.length - 1];
            if(popup.offsetHeight > window.innerHeight){
                //计算popup的高度与window高度的比值,找到离它最相近且更大的当前字体大小和sizeLevel的比值
                for (const s of sizeLevel) {
                    if(popup.offsetHeight / window.innerHeight < currentFontSize / s){
                        size = s;
                        break;
                    }
                }
                popup.style.setProperty("font-size", size + "px", "important");
            }
        },

        pinRJ: undefined,
        setPinState: function (rjCode, pin, close = true){
            const ele = Popup.popupElement;
            const popup = ele.popup;
            if(!pin){
                //关闭弹框
                popup.style.setProperty("pointer-events", "none", "important");
                Popup.pinRJ = undefined;
                popup.removeAttribute("pin");

                if(close) popup.style.setProperty("display", "none", "important");

                //取消注册自动关闭监听
                document.removeEventListener("keyup", Popup.keyup);
                document.removeEventListener("mousemove", Popup.domMove);
                return
            }

            popup.style.setProperty("pointer-events", "auto", "important");
            Popup.pinRJ = rjCode;
            popup.setAttribute("pin", "");

            //添加监听器
            document.addEventListener("keyup", Popup.keyup);
            document.addEventListener("mousemove", Popup.domMove);
        },
        hasPinned: function (){
            return Popup.popupElement.popup.hasAttribute("pin");
        },
        /**
         * @param e {KeyboardEvent}
         */
        isHoldPinKey: function(e){
            if(getOS() === "Mac"){
                return e.metaKey;
            }
            return e.ctrlKey;
        },
        /**
         * @param e {KeyboardEvent}
         */
        isPinKeyDown: function (e) {
            if(getOS() === "Mac"){
                return e.key === "Meta";
            }
            return e.key === "Control";
        },

        /**
         * 鼠标离开固定弹窗时,如果没有按住pin键则消失
         * @param e {MouseEvent}
         */
        /*pinLeave: function (e) {
            if(Popup.isHoldPinKey(e)){
                return;
            }
            Popup.setPinState(null, false, true);
        },*/
        /**
         * 监听网页内的鼠标移动事件,来保证弹框正常移除
         * @param e {MouseEvent}
         */
        domMove: function (e) {
            if(!Popup.hasPinned() || Popup.isHoldPinKey(e)){
                return;
            }
            Popup.setPinState(null, false);
        },
        /**
         * 鼠标移动到链接上触发
         * @param e {MouseEvent}
         */
        over: function (e) {
            const target = isInDLSite() ? e.target : getVoiceLinkTarget(e.target);
            if(!target || !target.classList.contains(VOICELINK_CLASS)) return;

            const rjCode = target.getAttribute(RJCODE_ATTRIBUTE);
            if(rjCode === null) return;

            //记录鼠标位置
            let ele = Popup.popupElement;
            ele._state.mouseX = e.clientX;
            ele._state.mouseY = e.clientY;

            //如果用户固定了弹框,则提示用户必须ctrl关闭弹框才能解析
            if(Popup.isHoldPinKey(e) && Popup.pinRJ){
                ele.hint.innerText = localizePopup(localizationMap.hint_unpin);
                return;
            }else{
                //没有固定弹框的话清理pinRJ,因为有时候pinRJ没办法被keyup清理(如keyup未触发)
                Popup.pinRJ = undefined
                ele.hint.innerText = localizePopup(localizationMap.hint_pin);
            }

            //修正链接
            if(target.hasAttribute("voicelink-linkified")){
                WorkPromise.getWorkPromise(rjCode).info.then(info => {
                    if(info.is_announce === true){
                        target.href = `https://www.dlsite.com/maniax/announce/=/product_id/${rjCode}.html`;
                    }
                });
            }

            let popup = document.querySelector(`div#${VOICELINK_CLASS}-voice-popup`);  // + rjCode);
            if (popup) {
                popup.style.setProperty("display", "flex", "important");  //display = "flex !important";
                //先将字体大小变回原样
                popup.style.setProperty("font-size", "15.4px", "important");
            }
            else {
                Popup.makePopup();
                popup = ele.popup;
            }
            Popup.updatePopup(e, rjCode);

            //如果按住了CTRL,则将popup可被点击,否则设置穿透
            //并设置Copy显示情况
            if(Popup.isHoldPinKey(e)){
                Popup.setPinState(rjCode, true)
                ele.hint.innerText = localizePopup(localizationMap.hint_unpin);
            }else{
                Popup.setPinState(rjCode, false, false)
            }

            //设置焦点至链接上
            target.focus();
            target.style.setProperty("outline", "none", "important");
        },

        /**
         * 鼠标离开时触发
         * @param e {MouseEvent}
         */
        out: function (e) {
            //如果固定则禁止关闭
            if(Popup.isHoldPinKey(e)) {
                return
            }

            const target = isInDLSite() ? e.target : getVoiceLinkTarget(e.target);
            if(!target || !target.classList.contains(VOICELINK_CLASS)) return;

            const rjCode = target.getAttribute(RJCODE_ATTRIBUTE);
            if(rjCode === null) return;

            //取消固定并关闭
            Popup.setPinState(rjCode, false)

            //取消focus
            target.blur();
            target.style.setProperty("outline", null);
        },

        /**
         * 鼠标移动时触发
         * @param e {MouseEvent}
         */
        move: function (e) {
            const target = isInDLSite() ? e.target : getVoiceLinkTarget(e.target);
            if(!target || !target.classList.contains(VOICELINK_CLASS)) return;

            const popup = document.querySelector(`div#${VOICELINK_CLASS}-voice-popup`);  // + rjCode);
            if(!popup) return;

            const rjCode = e.target.getAttribute(RJCODE_ATTRIBUTE);
            if(rjCode === null) return;

            let ele = Popup.popupElement;
            ele._state.mouseX = e.clientX;
            ele._state.mouseY = e.clientY;

            //焦点不在浏览器上的时候无法触发keydown,因此需要用move来辅助激活pin
            if(Popup.isHoldPinKey(e) && !Popup.pinRJ){
                //按下pin键但没激活pin模式的时候手动激活
                Popup.setPinState(rjCode, true);
            }

            //如果弹框已固定且固定的并非当前所选链接RJ号,则不进行定位修正
            if(Popup.pinRJ && rjCode !== Popup.pinRJ){
                return;
            }

            Popup.adjustPopup(e.clientX, e.clientY);

        },

        /**
         * 按键按下时触发
         * @param e {KeyboardEvent}
         */
        keydown: function (e) {
            const target = isInDLSite() ? e.target : getVoiceLinkTarget(e.target);
            if(!target || !target.classList.contains(VOICELINK_CLASS)) return;

            const rjCode = target.getAttribute(RJCODE_ATTRIBUTE);
            if(rjCode === null) return;

            let popup = Popup.popupElement.popup;
            if(popup.style.display !== "none" && Popup.isPinKeyDown(e)){
                //按住CTRL以固定显示弹框
                Popup.setPinState(rjCode, true);
            }
        },

        /**
         * 按键抬起时触发
         * @param e {KeyboardEvent}
         */
        keyup: function (e) {
            let popup = Popup.popupElement.popup;
            if(popup && Popup.isPinKeyDown(e)){
                Popup.setPinState(null, false);
            }
        }
    }

    const WorkPromise = {
        /**
         * 标题、社团、发行日期、更新日期、年龄指定
         * CV、标签、文件大小、封面地址
         */

        checkNotNull: function (obj){
            if(obj === null || obj === undefined) throw new Error();
            return obj;
        },

        getWorkPromise: function (rjCode){
            if(work_promise[rjCode]){
                return work_promise[rjCode];
            }
            work_promise[rjCode] = DLsite.getWorkRequestPromise(rjCode);
            return work_promise[rjCode];
        },

        getFound: async function(rjCode){
            try{
                const data = await WorkPromise.getWorkPromise(rjCode).api2;
                if(data && data.product_id !== undefined) return true;

                //否则再次检查api1
                const api = await WorkPromise.getWorkPromise(rjCode).api;
                return api && api.is_sale !== undefined;
            }catch (e){
                //说明是网络问题,删除缓存并返回true
                delete work_promise[rjCode];
                return true;
            }
        },

        getTranslationInfo: async function(rjCode){
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api2;
            if(data.translation_info) return data.translation_info;

            data = await p.api;
            return data.translation_info ? data.translation_info : {};
        },

        getRJChain: async function(rjCode) {
            //RJxxx → RJxxx → RJxxx,这样从子级指向父级
            const trans = await WorkPromise.getTranslationInfo(rjCode);
            let chain = [rjCode];
            if(trans.is_child){
                chain.push(trans.parent_workno, trans.original_workno);
            }else if(trans.is_parent){
                chain.push(trans.original_workno);
            }
            return chain;
        },

        getParentRJ: async function(rjCode){
            try{
                const p = WorkPromise.getWorkPromise(rjCode);
                let trans = await WorkPromise.getTranslationInfo(rjCode);
                if(trans.is_original || trans.is_parent) return rjCode;
                if(trans.parent_workno) return trans.parent_workno;

                let data = await p.info;
                return data.parentWork;
            }catch (e){
                return null;
            }
        },

        getGirls: async function(rjCode){
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api2;
            if(data.sex_category && data.sex_category === 2) return true;
            if(data.site_id === "girls") return true;

            //否则再次检查api1
            data = await WorkPromise.getWorkPromise(rjCode).api;
            WorkPromise.checkNotNull(data.is_girls)
            return data.is_girls;
        },

        getAnnounce: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const info = await p.info;
            return info.is_announce;
        },

        getSale: async function(rjCode, checkAnnounce = true){
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            if(!checkAnnounce){
                return data.is_sale;
            }
            return data.is_sale || await WorkPromise.getAnnounce(rjCode);
        },

        getDLCount: async function (rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            WorkPromise.checkNotNull(data.dl_count);
            return data.dl_count;
        },

        getRateAvg: async function (rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            if(data.rate_average_2dp) return data.rate_average_2dp;

            //还可以累加api2的结果获得
            data = await p.api2;
            this.checkNotNull(data.rate_count_detail);
            let sum = 0;
            let count = 0;
            for (const key in data.rate_count_detail) {
                let rate = parseInt(key);
                let cot = parseInt(data.rate_count_detail[key]);
                count += cot
                sum += rate * cot;
            }
            return sum / count;
        },

        getRateCount: async function (rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            if(data.rate_count) return data.rate_count;

            //还可以累加api2的结果获得
            data = await p.api2;
            this.checkNotNull(data.rate_count_detail);
            let count = 0;
            for (const key in data.rate_count_detail) {
                count += parseInt(data.rate_count_detail[key]);
            }
            return count;
        },

        getWishlistCount: async function (rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            this.checkNotNull(data.wishlist_count);
            return data.wishlist_count;
        },

        getPriceText: async function (rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            //TODO: 价格以后再加,还要考虑汇率和添加设置项
        },

        getBonus: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            return !data.is_sale && data.is_free && data.is_oly && data.wishlist_count === 0;
            // return data.is_bonus;
        },

        getHasBonus: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            let data = await p.api;
            return data.bonuses && data.bonuses.length > 0;
        },

        getTranslatable: async function(rjCode) {
            const trans = await WorkPromise.getTranslationInfo(rjCode);
            return trans.is_translation_agree === true;
        },

        getTranslated: async function(rjCode) {
            const trans = await WorkPromise.getTranslationInfo(rjCode);
            return trans.is_parent === true || trans.is_child === true;
        },

        getLanguages: async function(rjCode){
            //返回字符串数组,根据popup设置的语言返回支持的语言列表
            const map = {
                JPN: localizePopup(localizationMap.language_japanese),
                ENG: localizePopup(localizationMap.language_english),
                CHI_HANS: localizePopup(localizationMap.language_simplified_chinese),
                CHI_HANT: localizePopup(localizationMap.language_traditional_chinese),
                KO_KR: localizePopup(localizationMap.language_korean),
                SPA: localizePopup(localizationMap.language_spanish),
                FRE: localizePopup(localizationMap.language_french),
                RUS: localizePopup(localizationMap.language_russian),
                THA: localizePopup(localizationMap.language_thai),
                GER: localizePopup(localizationMap.language_german),
                FIN: localizePopup(localizationMap.language_finnish),
                POR: localizePopup(localizationMap.language_portuguese),
                VIE: localizePopup(localizationMap.language_vietnamese),
                ITA: localizePopup(localizationMap.language_italian),
                ARA: localizePopup(localizationMap.language_arabic),
                POL: localizePopup(localizationMap.language_polish),
            }
            const p = WorkPromise.getWorkPromise(rjCode);
            let api = await p.api2;
            api = api.options ? api : await p.api;
            const options = api.options?.split("#");
            const result = [];
            for (const key in map) {
                const lang = map[key];
                if(options?.includes(key)) result.push(lang);
            }
            return result;
        },

        getFileFormats: async function(rjCode){
            //返回字符串数组,返回文件格式列表
            const result = [];
            const p = WorkPromise.getWorkPromise(rjCode);
            let api = await p.api2;
            if(api.file_type === "EXE"){
                result.push("EXE");
            }else if(api.file_type_string){
                result.push(api.file_type_string);
            }
            if(api.file_type_special) result.push(api.file_type_special);

            if(!api.options) api = await p.api;
            if(api.options && api.options.includes("WPD")){
                result.push("PDF");
            }
            if(api.options && api.options.includes("WAP")){
                result.push("APK");
            }

            return result;
        },

        getAIUsedText: async function(rjCode) {
            //返回是否使用或部分使用AI,根据popup语言返回字符串。
            const p = WorkPromise.getWorkPromise(rjCode);
            let api = await p.api2;
            api = api.options ? api : await p.api;
            const options = api.options ? api.options : "";
            if(options.includes("AIG")){
                return localizePopup(localizationMap.tag_aig);
            }else if(options.includes("AIP")){
                return localizePopup(localizationMap.tag_aip);
            }
            return null;
        },

        getDebug: async function(rjCode){
            return "";
            const work = WorkPromise.getWorkPromise(rjCode);
            const api2 = await work.api2;
            const api = await work.api;
            const info = await work.info;
            const circle = work.circle;

            return `is_ana_api2: ${api2.is_ana}<br/>
                    is_ana_api: ${api.is_ana}`;
        },

        getWorkCategory: async function(rjCode){
            const type = await WorkPromise.getWorkType(rjCode);
            /* voice: 音声
             * game: 游戏
             * manga: 漫画/插画/音声漫画
             * video: 视频
             * novel: 小说
             * other: 其它
            */
            switch (type) {
                case 0:
                    return "voice";
                case 1:
                    return "game";
                case 2 || 3 || 8:
                    return "manga";
                case 5:
                    return "video";
                case 4:
                    return "novel";
                default:
                    return "other";
            }
        },

        getWorkTypeText: async function(rjCode) {
            const mapping = [
                localizePopup(localizationMap.work_type_voice),
                localizePopup(localizationMap.work_type_game),
                localizePopup(localizationMap.work_type_comic),
                localizePopup(localizationMap.work_type_illustration),
                localizePopup(localizationMap.work_type_novel),
                localizePopup(localizationMap.work_type_video),
                localizePopup(localizationMap.work_type_music),
                localizePopup(localizationMap.work_type_tool),
                localizePopup(localizationMap.work_type_voice_comic),
                localizePopup(localizationMap.work_type_other),
            ];
            return mapping[await WorkPromise.getWorkType(rjCode)];
        },

        getWorkType: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const api2 = await p.api2;
            let workType = api2.work_type;
            if(!workType) workType = (await p.api).work_type;

            switch (workType) {
                case "SOU":
                    return 0;
                case (["ACN", "QIZ", "ADV", "RPG", "TBL", "DNV", "SLN", "TYP", "STG", "PZL", "ETC"]
                    .includes(workType) ? workType : "ERR"):
                    return 1;
                case (["MNG", "SCM", "WBT"]
                    .includes(workType) ? workType : "ERR"):
                    return 2;
                case "ICG":
                    return 3;
                case (["NRE", "KSV"].includes(workType) ? workType : "ERR"):
                    return 4;
                case "MOV":
                    return 5;
                case "MUS":
                    return 6;
                case (["TOL", "IMT", "AMT"]
                    .includes(workType) ? workType : "ERR"):
                    return 7;
                case "VCM":
                    return 8;
                case "ET3":
                    return 9;
                default:
                    throw new Error("无法获取作品类型/未知作品类型:" + workType);
            }
        },

        getImgLink: async function(rjCode){
            let link = undefined;
            const p = WorkPromise.getWorkPromise(rjCode);

            try {
                let data = await p.api2;
                if (data.image_main && data.image_main.url) link = "https:" + data.image_main.url;
            } catch (e) {}

            if(link && !link.includes("no_img_main.gif")){
                return link;
            }

            try{
                const info = await p.info;
                WorkPromise.checkNotNull(info.img);
                return info.img;
            }catch (e) {
            }

            try{
                const apiData = await WorkPromise.getWorkPromise(rjCode).api;
                if(apiData.work_image) return "https:" + apiData.work_image;
            }catch (e){}

            throw new Error("无法获取图片链接");
        },

        getWorkTitle: async function(rjCode){
            return await WorkPromise.getWorkPromise(rjCode).translated_title;
        },

        getAgeRating: async function(rjCode){
            let p = WorkPromise.getWorkPromise(rjCode);
            let api = await p.api2;
            if(!api.age_category) api = await p.api;
            switch (api.age_category){
                case 1:
                    return "All";
                case 2:
                    return "R15";
                case 3:
                    return "R18";
            }

            const info = await p.info;
            WorkPromise.checkNotNull(info.rating);
            return info.rating;
        },

        getCircle: async function(rjCode, findOriginal = true){
            let trans = await WorkPromise.getTranslationInfo(rjCode);
            if(!trans.is_original && findOriginal){
                //使用原作RJ号开始寻找,如果找不到翻译信息就没办法了
                rjCode = trans.original_workno ? trans.original_workno : rjCode;
            }

            let work = WorkPromise.getWorkPromise(rjCode);
            let api2 = await work.api2;
            if(api2.maker_name) return api2.maker_name;

            /**
             * 接下来有两种搜索方式:
             * 1. api1 + circle接口
             * 2. info搜索
             * 前者成功率更高(下架后还能获取到api1,社团没解散就能获得社团信息),两个加载速度不确定谁快谁慢,所以把1放在前面
             */

            const circleInfo = await work.circle;
            if(circleInfo && circleInfo.name) return circleInfo.name;

            let info = await work.info;
            if(info.circle) return info.circle.trim();

            throw new Error("无法获取社团信息");
        },

        getTranslatorName: async function(rjCode){
            let trans = await WorkPromise.getTranslationInfo(rjCode);
            if(!trans.is_child) throw new Error("非翻译作品RJ号");
            return await WorkPromise.getCircle(rjCode, false);
        },

        getReleaseDate: async function(rjCode){
            const p = WorkPromise.getWorkPromise(rjCode);
            const info = await p.info;
            if(info && !info.is_announce && info.date) return [info.date.trim(), false];
            if(info && info.is_announce && info.dateAnnounce) return [info.dateAnnounce.trim(), true];

            //从api中查找发售时间
            let api = await p.api2;
            api = api.regist_date ? api : await p.api;
            WorkPromise.checkNotNull(api.regist_date)

            return [api.regist_date, api.is_announce];
        },

        getReleaseCountDownElement: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const info = await p.info;
            if(info && info.is_announce && info.dateAnnounce) {
                return DateParser.getCountDownDateElement(DateParser.parseDateStr(info.dateAnnounce, info.lang));
            }
            return null;
        },

        getUpdateDate: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const info = await p.info;
            if(info["update"]) return info["update"].trim();

            throw new Error();
        },

        getScenario: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const api2 = await p.api2;
            if(api2.creaters && api2.creaters.scenario_by && api2.creaters.scenario_by.length > 0){
                return api2.creaters.scenario_by.map(v => v.name);
            }

            //无法获取api2则直接通过html获取
            const info = await WorkPromise.getWorkPromise(rjCode).info;
            WorkPromise.checkNotNull(info.scenario);
            return info.scenario;
        },

        getIllustrator: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const api2 = await p.api2;
            if(api2.creaters && api2.creaters.illust_by && api2.creaters.illust_by.length > 0){
                return api2.creaters.illust_by.map(v => v.name);
            }

            //无法获取api2则直接通过html获取
            const info = await WorkPromise.getWorkPromise(rjCode).info;
            WorkPromise.checkNotNull(info.illustration);
            return info.illustration;
        },

        getCV: async function(rjCode){
            const p = WorkPromise.getWorkPromise(rjCode);
            const api2 = await p.api2;
            if(api2.creaters && api2.creaters.voice_by && api2.creaters.voice_by.length > 0){
                return api2.creaters.voice_by.map(v => v.name);
            }

            //无法获取api2则直接通过html获取
            const info = await WorkPromise.getWorkPromise(rjCode).info;
            WorkPromise.checkNotNull(info.cv);
            return info.cv;
        },

        getMusic: async function(rjCode) {
            const p = WorkPromise.getWorkPromise(rjCode);
            const api2 = await p.api2;
            if(api2.creaters && api2.creaters.music_by && api2.creaters.music_by.length > 0){
                return api2.creaters.music_by.map(v => v.name);
            }

            //无法获取api2则直接通过html获取
            const info = await WorkPromise.getWorkPromise(rjCode).info;
            WorkPromise.checkNotNull(info.music);
            return info.music;
        },

        getTags: async function(rjCode) {
            //注意该方法返回字符串数组而不是纯字符串
            const p = WorkPromise.getWorkPromise(rjCode);
            const api2 = await p.api2;
            if(api2.genres && api2.genres.length > 0){
                return api2.genres.map(genre => genre.name);
            }

            //无法获取api2时通过html获取
            const info = await p.info;
            WorkPromise.checkNotNull(info.tags);
            return info.tags;
        },

        getFileSizeStr: function(byteCount = 0){
            const units = ["B", "KB", "MB", "GB", "TB"];
            let unit = "B";
            for (let i = 1; byteCount >= 1024; i++){
                byteCount /= 1024;
                unit = units[i];
            }
            return `${Math.round(byteCount * 100) / 100}${unit}`;
        },

        getFileSize: async function(rjCode) {
            const trans = await WorkPromise.getTranslationInfo(rjCode);
            if(trans.is_parent){
                //翻译版本的父级没有内容信息,自然无法显示文件大小,所以需要获得原作品的大小信息
                //Child和Original都有各自的大小信息,正常获取计算即可
                rjCode = trans.original_workno ? trans.original_workno : rjCode;
            }

            const p = WorkPromise.getWorkPromise(rjCode);
            let api2 = await p.api2;
            if(api2.contents_file_size && api2.contents_file_size > 0){
                return WorkPromise.getFileSizeStr(api2.contents_file_size);
            }

            //通过html获取
            let info = trans.is_child && trans.original_workno ? await WorkPromise.getWorkPromise(trans.original_workno).info : await p.info;
            if(info.filesize) return info.filesize;

            throw new Error("无法获取文件大小信息");
        },
    }

    const DLsite = {
        parseWorkDOM: function (dom, rj) {
            // workInfo: {
            //     rj: any;
            //     img: string;
            //     title: any;
            //     circle: any;
            //     date: any;
            //     rating: any;
            //     tags: any[];
            //     cv: any;
            //     filesize: any;
            //     dateAnnounce: any;
            // }
            const workInfo = {};
            workInfo.rj = rj;

            let metaList = dom.getElementsByTagName("meta")
            for (let i = 0; i < metaList.length; i++){
                let meta = metaList[i];
                if(meta.getAttribute("property") === 'og:image'){
                    workInfo.img = meta.content;
                    break;
                }
            }

            workInfo.lang = dom.querySelector("html").getAttribute("lang");
            workInfo.title = dom.getElementById("work_name").innerText;
            workInfo.circle = dom.querySelector("span.maker_name").innerText;
            workInfo.circleId = dom.querySelector("#work_maker a").href;
            workInfo.circleId = workInfo.circleId.substring(workInfo.circleId.lastIndexOf("/") + 1, workInfo.circleId.lastIndexOf(".")).trim();

            const table_outline = dom.querySelector("table#work_outline");
            for (let i = 0, ii = table_outline.rows.length; i < ii; i++) {
                const row = table_outline.rows[i];
                const row_header = row.cells[0].innerText.trim();
                const row_data = row.cells[1];
                const lambda = text => row_header === text;
                switch (true) {
                    case (["販売日", "贩卖日", "販賣日", "Release date", "판매일", "Lanzamiento", "Veröffentlicht",
                        "Date de sortie", "Tanggal rilis", "Data di rilascio", "Lançamento", "Utgivningsdatum",
                        "วันที่ขาย", "Ngày phát hành"].some(lambda)):
                        workInfo.date = row_data.innerText.trim();
                        break;
                    case (["更新情報", "更新信息", "更新資訊", "Update information", "갱신 정보", "Actualizar información",
                        "Aktualisierungen", "Mise à jour des informations", "Perbarui informasi", "Aggiorna informazioni",
                        "Atualizar informações", "Uppdatera information", "ข้อมูลอัปเดต", "Thông tin cập nhật"].some(lambda)):
                        workInfo.update = row_data.firstChild.data.trim();
                        break;
                    case (["年齢指定", "年龄指定", "年齡指定", "Age", "연령 지정", "Edad", "Altersfreigabe", "Âge", "Batas usia",
                        "Età", "Idade", "Ålder", "การกำหนดอายุ", "Độ tuổi chỉ định"].some(lambda)):
                        workInfo.rating = row_data.innerText.trim();
                        break;
                    case (["ジャンル", "分类", "分類", "Genre", "장르", "Género", "Genre", "Genre", "Genre", "Genere", "Gênero",
                        "Genre", "ประเภท", "Thể loại"].some(lambda)):
                        const tag_nodes = row_data.querySelectorAll("a");
                        workInfo.tags = [...tag_nodes].map(a => { return a.innerText.trim() });
                        break;
                    case (["シナリオ", "Scenario", "剧情", "劇本", "시나리오", "Guión", "Szenario", "Scénario", "Skenario",
                        "Scenario", "Cenário", "Scenario", "บทละคร", "Kịch bản"].some(lambda)):
                        workInfo.scenario = row_data.innerText.trim();
                        break;
                    case (["イラスト", "Illustration", "插画", "插畫", "일러스트", "Ilustración", "AbbilDung", "Illustration",
                        "Ilustrasi", "Illustrazione", "Ilustração", "Illustration", "ภาพประกอบ", "Tranh minh họa"].some(lambda)):
                        workInfo.illustration = row_data.innerText.trim();
                        break;
                    case (["声優", "声优", "聲優", "Voice Actor", "성우", "Doblador", "Synchronsprecher", "Doubleur",
                        "Pengisi suara", "Doppiatore/Doppiatrice", "Ator de voz", "Röstskådespelare", "นักพากย์",
                        "Diễn viên lồng tiếng"].some(lambda)):
                        workInfo.cv = row_data.innerText.trim();
                        break;
                    case (["音楽", "Music", "音乐", "音樂", "음악", "Música", "Musik", "Musique", "Musik", "Musica.",
                        "Música", "musik", "ดนตรี", "Âm nhạc"].some(lambda)):
                        workInfo.music = row_data.innerText.trim();
                        break;
                    case (["ファイル容量", "文件容量", "檔案容量", "File size", "파일 용량", "Tamaño del Archivo", "Dateigröße",
                        "Taille du fichier", "Ukuran file", "Dimensione del file", "Tamanho do arquivo", "Filstorlek",
                        "ขนาดไฟล์", "Dung lượng tệp"].some(lambda)):
                        workInfo.filesize = row_data.innerText.trim();
                        break;
                    default:
                        break;
                }
            }

            //获取发售预告时间
            const work_date_ana = dom.querySelector("strong.work_date_ana");
            if (work_date_ana) {
                workInfo.dateAnnounce = work_date_ana.innerText;
                //workInfo.img = "https://img.dlsite.jp/modpub/images2/ana/doujin/" + rj_group + "/" + rj + "_ana_img_main.jpg"
            }

            return workInfo;
        },

        // Get language code for DLSite API
        getLangCode: function (lang) {
            if(!lang) return "ja-JP";

            switch (lang.toUpperCase()) {
                case "JPN":
                    return "ja-JP";
                case "ENG":
                    return "en-US";
                case "KO_KR":
                    return "ko-KR";
                case "CHI_HANS":
                    return "zh-CN";
                case "CHI_HANT":
                    return "zh-TW";
                default:
                    return "ja-JP"
            }
        },

        parseApiData: function (rjCode, data){
            if(!data) data = {};
            let apiData = data;
            apiData.is_bonus = !data.is_sale && data.is_free && data.is_oly && data.wishlist_count === false;
            apiData.is_girls = (data.options && data.options.indexOf("OTM") >= 0) || (data.site_id === "girls");

            if(data.regist_date){
                let reg_date = data.regist_date.replace(/-/g, '/');
                let releaseDate = new Date(reg_date);
                apiData.regist_timestamp = releaseDate.getTime();
                apiData.regist_date = `${releaseDate.getFullYear()} / ${releaseDate.getMonth() + 1} / ${releaseDate.getDate()}`;
                if(apiData.regist_timestamp > Date.now()){
                    apiData.is_announce = true;
                }
            }
            return apiData;
        },

        parseApi2Data: function (rjCode, data) {
            const translation_info = data.translation_info ? data.translation_info : {};
            data.lang = DLsite.getLangCode(translation_info.lang);

            if(data.regist_date){
                let reg_date = data.regist_date.replace(/-/g, '/');
                let releaseDate = new Date(reg_date);
                data.regist_timestamp = releaseDate.getTime();
                data.regist_date = `${releaseDate.getFullYear()} / ${releaseDate.getMonth() + 1} / ${releaseDate.getDate()}`;
                if(data.regist_timestamp > Date.now()){
                    data.is_announce = true;
                }
            }

            return data;
        },

        getHttpAsync: async function (url, anonymous = false){
            return new Promise((resolve, reject) => {
                getXmlHttpRequest()({
                    method: "GET",
                    url,
                    headers: {
                        "Accept": "text/xml",
                        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0)",
                        "Cache-Control": "no-cache"
                    },
                    onload: resolve,
                    onerror: reject,
                    anonymous: anonymous
                });
            })
        },

        getAnnouncePromise: async function (rjCode, parentRJ) {
            const url = `https://www.dlsite.com/maniax/announce/=/product_id/${rjCode}.html`;
            let resp = await DLsite.getHttpAsync(url);
            if (resp.readyState === 4 && resp.status === 200) {
                const dom = new DOMParser().parseFromString(Csp.createHTML(resp.responseText), "text/html");
                const workInfo = DLsite.parseWorkDOM(dom, rjCode);
                workInfo.parentWork = parentRJ === rjCode ? null : parentRJ;
                workInfo.is_announce = true;
                return workInfo;
            }
            else if (resp.readyState === 4 && resp.status === 404) {
                return {
                    parentWork: parentRJ === rjCode ? null : parentRJ,
                    is_announce: false
                };
            }

        },

        getHtmlPromise: async function (rjCode) {
            const url = `https://www.dlsite.com/maniax/work/=/product_id/${rjCode}.html`;
            let resp = await DLsite.getHttpAsync(url);
            if (resp.readyState === 4 && resp.status === 200) {
                const dom = new DOMParser().parseFromString(Csp.createHTML(resp.responseText), "text/html");
                const workInfo = DLsite.parseWorkDOM(dom, rjCode);
                workInfo.parentWork = DLsite.getParentWorkRjCode(resp.finalUrl);
                workInfo.parentWork = workInfo.parentWork === rjCode ? null : workInfo.parentWork;
                workInfo.is_announce = false;
                return workInfo;
            }
            else if (resp.readyState === 4 && resp.status === 404) {
                return await DLsite.getAnnouncePromise(rjCode, DLsite.getParentWorkRjCode(resp.finalUrl));
            }
        },

        getApi2Promise: async function (rjCode, locale = undefined) {
            let url = `https://www.dlsite.com/maniax/api/=/product.json?workno=${rjCode}` + (locale ? `&locale=${locale}` : "");
            let resp = await DLsite.getHttpAsync(url);
            let data;
            if (resp.readyState === 4 && resp.status === 200) {
                data = JSON.parse(resp.responseText);
                data = data ? data[0] : {};
                data = data ? data : {}
            }
            else if (resp.readyState === 4 && resp.status === 404) {
                return {};
            }
            else {
                throw new Error(`无法通过API2获取${rjCode}的信息:${resp.status} ${resp.statusText}`);
            }

            return DLsite.parseApi2Data(rjCode, data);
        },

        getApiPromise: async function (rjCode, locale = undefined) {
            //获取对应语言下的实际信息
            let url = `https://www.dlsite.com/maniax/product/info/ajax?product_id=${rjCode}&cdn_cache_min=1` + (locale ? `&locale=${locale}` : "");
            let resp = await DLsite.getHttpAsync(url);
            let data;
            if (resp.readyState === 4 && resp.status === 200) {
                data = JSON.parse(resp.responseText);
                data = data ? data[rjCode] : {};
                data = data ? data : {};
            }
            else if(resp.readyState === 4 && resp.status === 404){
                return {};
            }
            else {
                throw new Error(`无法通过API获取${rjCode}的信息:${resp.status} ${resp.statusText}`);
            }

            const translation_info = data.translation_info ? data.translation_info : {};
            data.lang = DLsite.getLangCode(translation_info.lang);

            return DLsite.parseApiData(rjCode, data);
        },

        getCirclePromise: async function (rjCode, apiPromise){
            let apiData = await apiPromise;
            if(!apiData.maker_id) return null;
            const maker_id = apiData.maker_id;

            let url;
            let resp;
            let data;
            try {
                url = `https://media.ci-en.jp/dlsite/lookup/${maker_id}.json`;
                resp = await DLsite.getHttpAsync(url);
                data = undefined;
                if (resp.readyState === 4 && resp.status === 200) {
                    data = JSON.parse(resp.responseText);
                    data = data ? data[0] : {};
                    data = data ? data : {};
                    data.maker_id = maker_id;
                }
            }catch (e){}

            if(!data || !data.name){
                //未获取到社团名称则使用html解析获取
                url = `https://www.dlsite.com/maniax/circle/profile/=/maker_id/${maker_id}.html`;
                resp = await DLsite.getHttpAsync(url);
                data = data ? data : {};
                if(resp.readyState === 4 && resp.status === 200){
                    let doc = new DOMParser().parseFromString(Csp.createHTML(resp.responseText), "text/html");
                    let name = doc.querySelector("strong.prof_maker_name");
                    name = name ? name.innerText : null;
                    data.name = name;
                }
            }

            return data;
        },

        getTranslatablePromise: async function (rjCode, site = "maniax") {
            rjCode = rjCode.toUpperCase();
            const result = {
                translation_request_english: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_simplified_chinese:{
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_traditional_chinese:{
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_korean: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_spanish: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_german: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_french: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_indonesian: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_italian: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_portuguese: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_swedish: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_thai: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
                translation_request_vietnamese: {
                    agree: undefined,
                    request: undefined,
                    sale: undefined
                },
            };
            const data = await DLsite.getTranslatableApiPromise(rjCode, site);
            if(!data.translationStatusForTranslator){
                return result;
            }

            const map = {
                translation_request_english: "ENG",
                translation_request_simplified_chinese: "CHI_HANS",
                translation_request_traditional_chinese: "CHI_HANT",
                translation_request_korean: "KO_KR",
                translation_request_spanish: "SPA",
                translation_request_german: "GER",
                translation_request_french: "FRE",
                translation_request_indonesian: "IND",
                translation_request_italian: "ITA",
                translation_request_portuguese: "POR",
                translation_request_swedish: "SWE",
                translation_request_thai: "THA",
                translation_request_vietnamese: "VIE",
            };
            for (let key in map) {
                let lang = map[key];
                let status = data.translationStatusForTranslator[lang];
                if(!status){
                    //状况未知
                    continue;
                }
                result[key].agree = status.available;
                result[key].request = status.count;
                result[key].sale = status.on_sale_count;
            }

            return result;
        },

        getTranslatableApiPromise: async function (rjCode, site = "maniax") {
            //新的可用api,用于搜索作品翻译情况,但也可以获得其它信息。
            rjCode = rjCode.toUpperCase();
            let url = `https://www.dlsite.com/${site}/api/=/translatableProducts.json?keyword=${rjCode}`;    //可以使用locale参数指定语言,但这里不需要
            let resp = await DLsite.getHttpAsync(url, true);
            let data;
            if (resp.readyState === 4 && resp.status === 200) {
                data = JSON.parse(resp.responseText);
            }
            else {
                throw new Error(`无法通过API获取${rjCode}的翻译信息:${resp.status} ${resp.statusText}`);
            }

            //从结果中找到对应RJ号,由于关键字是RJ号的话结果一般都在第一页,所以就放弃翻页寻找了
            if(data.meta && data.meta.code !== 200){
                throw new Error(`无法通过API查询${rjCode}的翻译信息:${data.meta.code} - ${data.meta.errorType} - ${data.meta.errorMessage}`);
            }
            if(!data.data || !Array.isArray(data.data.products)){
                throw new Error(`无法通过API查询${rjCode}的翻译信息:未预料到的响应格式。`);
            }

            for (const work of data.data.products) {
                if(work.id === rjCode){
                    return work;
                }
            }

            //未找到则返回空对象
            return {};

        },

        getWorkRequestPromise: function (rjCode) {
            return {
                _info: undefined,
                _api: undefined,
                _api2: undefined,
                _circle: undefined,
                _translatable: undefined,
                _translated_title: undefined,
                get info(){
                    return this._info ? this._info : this._info = DLsite.getHtmlPromise(rjCode);
                },
                get api() {
                    return this._api ? this._api : this._api = DLsite.getApiPromise(rjCode);
                },
                get api2() {
                    return this._api2 ? this._api2 : this._api2 = DLsite.getApi2Promise(rjCode);
                },
                get circle(){
                    return this._circle ? this._circle : this._circle = DLsite.getCirclePromise(rjCode, this.api);
                },
                get translatable() {
                    async function getter(t){
                        let api = await t.api2;
                        if(!api.site_id) api = await t.api;

                        return t._translatable ? t._translatable : t._translatable = DLsite.getTranslatablePromise(rjCode,
                            api.site_id ? api.site_id : "maniax");
                    }
                    return getter(this);
                },
                get translated_title(){
                    async function getter(t){
                        if(t._translated_title) return t._translated_title;

                        let api = await t.api2;
                        if(api.translation_info){
                            //api2有效
                            if(!api.translation_info.is_original) {
                                //通过再次查询获得翻译标题
                                api = await DLsite.getApi2Promise(rjCode, api.lang);
                            }
                            t._translated_title = api.work_name;
                            return t._translated_title;
                        }

                        //api2无效,通过api查询
                        api = await t.api;
                        if(!api.translation_info){
                            //api无效则无法获取标题(网页获取希望渺茫)
                            t._translated_title = null;
                            return null;
                        }

                        if(!api.translation_info.is_original) {
                            //非原作则再次查询
                            api = await DLsite.getApiPromise(rjCode, api.lang);
                        }
                        t._translated_title = api.work_name;
                        return t._translated_title;
                    }

                    return getter(this);
                }
            }
        },

        getParentWorkRjCode: function (redirectUrl){
            const reg = new RegExp("(?<=product_id/)((R[JE][0-9]{8})|(R[JE][0-9]{6})|([VB]J[0-9]{8})|([VB]J[0-9]{6}))")
            return redirectUrl.match(reg)[0];
        }
    }

    function getSettingsUi() {
        return {
            //这一层是设置界面最顶层,编辑大标题信息
            title: localize(localizationMap.title_settings),
            items: [
                {
                    //这一层是设置界面的大分类
                    title: localize(localizationMap.title_language_settings),
                    items: [
                        {
                            //这一层是设置项列表集合(使用表格呈现设置项)"
                            items: [
                                {
                                    //这一层是设置项
                                    type: "dropdown",
                                    title: localize(localizationMap.display_language),
                                    id: "lang",
                                    ignore_reset: true,
                                    options: [
                                        {
                                            title: "简体中文",
                                            value: "zh_CN"
                                        },
                                        {
                                            title: "繁體中文",
                                            value: "zh_TW"
                                        },
                                        {
                                            title: "English",
                                            value: "en_US"
                                        }
                                    ]
                                },
                                {
                                    //这一层是设置项
                                    type: "dropdown",
                                    title: localize(localizationMap.popup_language),
                                    id: "popup_lang",
                                    ignore_reset: true,
                                    tooltip: localize(localizationMap.popup_language_tooltip),
                                    options: [
                                        {
                                            title: "简体中文",
                                            value: "zh_CN"
                                        },
                                        {
                                            title: "繁體中文",
                                            value: "zh_TW"
                                        },
                                        {
                                            title: "English",
                                            value: "en_US"
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                },

                {
                    //这一层是设置界面的大分类
                    title: localize(localizationMap.title_general_settings),
                    items: [
                        {
                            //这一层是设置项列表集合(使用表格呈现设置项)
                            items: [
                                {
                                    //解析URL
                                    type: "checkbox",
                                    title: localize(localizationMap.parse_url),
                                    id: "parse_url",
                                    tooltip: localize(localizationMap.parse_url_tooltip)
                                },
                                {
                                    //DL上解析URL
                                    binding: {
                                        target: "parse_url",
                                        value: true
                                    },

                                    type: "checkbox",
                                    title: localize(localizationMap.parse_url_in_dl),
                                    id: "parse_url_in_dl",
                                    indent: 1,  //设置项缩进
                                    tooltip: localize(localizationMap.parse_url_in_dl_tooltip)
                                },
                                {
                                    //DL显示翻译标题
                                    type: "checkbox",
                                    title: localize(localizationMap.show_translated_title_in_dl),
                                    id: "show_translated_title_in_dl",
                                    tooltip: localize(localizationMap.show_translated_title_in_dl_tooltip)
                                },
                                {
                                    //“复制为有效文件名”按钮
                                    type: "checkbox",
                                    title: localize(localizationMap.copy_as_filename_btn),
                                    id: "copy_as_filename_btn",
                                    tooltip: localize(localizationMap.copy_as_filename_btn_tooltip)
                                },
                                {
                                    //**显示兼容性警告**
                                    type: "checkbox",
                                    title: `<strong>**${localize(localizationMap.show_compatibility_warning)}**</strong>`,
                                    id: "show_compatibility_warning",
                                    tooltip: localize(localizationMap.show_compatibility_warning_tooltip)
                                },
                                {
                                    //导向文本插入方式
                                    type: "dropdown",
                                    title: localize(localizationMap.url_insert_mode),
                                    id: "url_insert_mode",
                                    tooltip: localize(localizationMap.url_insert_mode_tooltip),
                                    options: [
                                        {
                                            title: localize(localizationMap.url_insert_mode_none),
                                            value: "none"
                                        },
                                        {
                                            title: localize(localizationMap.url_insert_mode_prefix),
                                            value: "prefix"
                                        },
                                        {
                                            title: localize(localizationMap.url_insert_mode_before_rj),
                                            value: "before_rj"
                                        }
                                    ]
                                },
                                {
                                    //导向文本
                                    type: "input",
                                    title: localize(localizationMap.url_insert_text),
                                    id: "url_insert_text",
                                    indent: 1
                                },

                                {
                                    //NSFW模式
                                    type: "checkbox",
                                    title: localize(localizationMap.sfw_mode),
                                    id: "sfw_mode",
                                    tooltip: localize(localizationMap.sfw_mode_tooltip)
                                },
                                {
                                    //模糊程度
                                    binding: {
                                        target: "sfw_mode",
                                        value: true
                                    },

                                    type: "dropdown",
                                    title: localize(localizationMap.sfw_blur_level),
                                    id: "sfw_blur_level",
                                    indent: 1,
                                    options: [
                                        {
                                            title: localize(localizationMap.low),
                                            value: "low"
                                        },
                                        {
                                            title: localize(localizationMap.medium),
                                            value: "medium"
                                        },
                                        {
                                            title: localize(localizationMap.high),
                                            value: "high"
                                        }
                                    ]
                                },
                                {
                                    //鼠标移至图片上方移除模糊
                                    binding: {
                                        target: "sfw_mode",
                                        value: true
                                    },

                                    type: "checkbox",
                                    title: localize(localizationMap.sfw_remove_when_hover),
                                    id: "sfw_remove_when_hover",
                                    indent: 1,
                                },
                                {
                                    //是否开启模糊动画
                                    binding: {
                                        target: "sfw_mode",
                                        value: true
                                    },

                                    type: "checkbox",
                                    title: localize(localizationMap.sfw_blur_transition),
                                    id: "sfw_blur_transition",
                                    indent: 1,
                                }
                            ]
                        }
                    ]
                },
                {
                    //分类:信息显示
                    title: localize(localizationMap.title_info_settings),
                    items: [
                        {
                            //预设表格
                            items: [
                                {
                                    type: "dropdown",
                                    title: localize(localizationMap.category_preset),
                                    id: "category_preset",
                                    tooltip: localize(localizationMap.category_preset_tooltip),
                                    ignore_reset: true,  //不显示重置按钮
                                    options: [
                                        {
                                            title: localize(localizationMap.work_type_voice),
                                            value: "voice"
                                        },
                                        {
                                            title: localize(localizationMap.work_type_game),
                                            value: "game"
                                        },
                                        {
                                            title: `${localize(localizationMap.work_type_comic)} / ${localize(localizationMap.work_type_illustration)} / ${localize(localizationMap.work_type_voice_comic)}`,
                                            value: "manga"
                                        },
                                        {
                                            title: localize(localizationMap.work_type_video),
                                            value: "video"
                                        },
                                        {
                                            title: localize(localizationMap.work_type_novel),
                                            value: "novel"
                                        },
                                        {
                                            title: localize(localizationMap.work_type_other),
                                            value: "other"
                                        }
                                    ]
                                }
                            ]
                        },
                        {
                            //音声Preset对应的表格,注意使用Binding来决定表格是否显示
                            binding: {
                                target: "category_preset",
                                value: "voice"
                            },
                            sortable: true,  //为true则代表该表格内的行可以排序
                            sort_id: "voice__info_display_order",  //若排序则一定要指定id,该id将会存储在设置项中,作为列表记录每个元素的顺序
                            items: [
                                {
                                    //销量
                                    type: "checkbox",
                                    title: localize(localizationMap.dl_count),
                                    id: "voice__dl_count",  //注意这里不同的preset要改成不同的值
                                },
                                {
                                    //社团名
                                    type: "checkbox",
                                    title: localize(localizationMap.circle_name),
                                    id: "voice__circle_name",  //注意这里不同的preset要改成不同的值
                                },
                                {
                                    //翻译者
                                    type: "checkbox",
                                    title: localize(localizationMap.translator_name),
                                    id: "voice__translator_name",
                                },
                                {
                                    //发售日
                                    type: "checkbox",
                                    title: localize(localizationMap.release_date),
                                    id: "voice__release_date",
                                },
                                {
                                    //更新日
                                    type: "checkbox",
                                    title: localize(localizationMap.update_date),
                                    id: "voice__update_date",
                                },
                                {
                                    //年龄指定
                                    type: "checkbox",
                                    title: localize(localizationMap.age_rating),
                                    id: "voice__age_rating",
                                },
                                {
                                    //剧情作者
                                    type: "checkbox",
                                    title: localize(localizationMap.scenario),
                                    id: "voice__scenario",
                                },
                                {
                                    //插画作者
                                    type: "checkbox",
                                    title: localize(localizationMap.illustration),
                                    id: "voice__illustration",
                                },
                                {
                                    //配音者
                                    type: "checkbox",
                                    title: localize(localizationMap.voice_actor),
                                    id: "voice__voice_actor",
                                },
                                {
                                    //音乐作者
                                    type: "checkbox",
                                    title: localize(localizationMap.music),
                                    id: "voice__music",
                                },
                                {
                                    //作品标签/分类
                                    type: "checkbox",
                                    title: localize(localizationMap.genre),
                                    id: "voice__genre",
                                },
                                {
                                    //文件大小
                                    type: "checkbox",
                                    title: localize(localizationMap.file_size),
                                    id: "voice__file_size",
                                }
                            ]
                        },
                        {
                            //游戏Preset对应的表格
                            binding: {
                                target: "category_preset",
                                value: "game"
                            },
                            sortable: true,  //为true则代表该表格内的行可以排序
                            sort_id: "game__info_display_order",
                            items: [
                                {
                                    //销量
                                    type: "checkbox",
                                    title: localize(localizationMap.dl_count),
                                    id: "game__dl_count",
                                },
                                {
                                    //社团名
                                    type: "checkbox",
                                    title: localize(localizationMap.circle_name),
                                    id: "game__circle_name",
                                },
                                {
                                    //翻译者
                                    type: "checkbox",
                                    title: localize(localizationMap.translator_name),
                                    id: "game__translator_name",
                                },
                                {
                                    //发售日
                                    type: "checkbox",
                                    title: localize(localizationMap.release_date),
                                    id: "game__release_date",
                                },
                                {
                                    //更新日
                                    type: "checkbox",
                                    title: localize(localizationMap.update_date),
                                    id: "game__update_date",
                                },
                                {
                                    //年龄指定
                                    type: "checkbox",
                                    title: localize(localizationMap.age_rating),
                                    id: "game__age_rating",
                                },
                                {
                                    //剧情作者
                                    type: "checkbox",
                                    title: localize(localizationMap.scenario),
                                    id: "game__scenario",
                                },
                                {
                                    //插画作者
                                    type: "checkbox",
                                    title: localize(localizationMap.illustration),
                                    id: "game__illustration",
                                },
                                {
                                    //配音者
                                    type: "checkbox",
                                    title: localize(localizationMap.voice_actor),
                                    id: "game__voice_actor",
                                },
                                {
                                    //音乐作者
                                    type: "checkbox",
                                    title: localize(localizationMap.music),
                                    id: "game__music",
                                },
                                {
                                    //作品标签/分类
                                    type: "checkbox",
                                    title: localize(localizationMap.genre),
                                    id: "game__genre",
                                },
                                {
                                    //文件大小
                                    type: "checkbox",
                                    title: localize(localizationMap.file_size),
                                    id: "game__file_size",
                                }
                            ]
                        },
                        {
                            //漫画对应preset
                            binding: {
                                target: "category_preset",
                                value: "manga"
                            },
                            sortable: true,  //为true则代表该表格内的行可以排序
                            sort_id: "manga__info_display_order",
                            items: [
                                {
                                    //销量
                                    type: "checkbox",
                                    title: localize(localizationMap.dl_count),
                                    id: "manga__dl_count",
                                },
                                {
                                    //社团名
                                    type: "checkbox",
                                    title: localize(localizationMap.circle_name),
                                    id: "manga__circle_name",
                                },
                                {
                                    //翻译者
                                    type: "checkbox",
                                    title: localize(localizationMap.translator_name),
                                    id: "manga__translator_name",
                                },
                                {
                                    //发售日
                                    type: "checkbox",
                                    title: localize(localizationMap.release_date),
                                    id: "manga__release_date",
                                },
                                {
                                    //更新日
                                    type: "checkbox",
                                    title: localize(localizationMap.update_date),
                                    id: "manga__update_date",
                                },
                                {
                                    //年龄指定
                                    type: "checkbox",
                                    title: localize(localizationMap.age_rating),
                                    id: "manga__age_rating",
                                },
                                {
                                    //剧情作者
                                    type: "checkbox",
                                    title: localize(localizationMap.scenario),
                                    id: "manga__scenario",
                                },
                                {
                                    //插画作者
                                    type: "checkbox",
                                    title: localize(localizationMap.illustration),
                                    id: "manga__illustration",
                                },
                                {
                                    //配音者
                                    type: "checkbox",
                                    title: localize(localizationMap.voice_actor),
                                    id: "manga__voice_actor",
                                    tooltip: localize(localizationMap.work_type_voice_comic)
                                },
                                {
                                    //音乐作者
                                    type: "checkbox",
                                    title: localize(localizationMap.music),
                                    id: "manga__music",
                                },
                                {
                                    //作品标签/分类
                                    type: "checkbox",
                                    title: localize(localizationMap.genre),
                                    id: "manga__genre",
                                },
                                {
                                    //文件大小
                                    type: "checkbox",
                                    title: localize(localizationMap.file_size),
                                    id: "manga__file_size",
                                }
                            ]
                        },
                        {
                            //视频对应preset
                            binding: {
                                target: "category_preset",
                                value: "video"
                            },
                            sortable: true,  //为true则代表该表格内的行可以排序
                            sort_id: "video__info_display_order",
                            items: [
                                {
                                    //销量
                                    type: "checkbox",
                                    title: localize(localizationMap.dl_count),
                                    id: "video__dl_count",
                                },
                                {
                                    //社团名
                                    type: "checkbox",
                                    title: localize(localizationMap.circle_name),
                                    id: "video__circle_name",
                                },
                                {
                                    //翻译者
                                    type: "checkbox",
                                    title: localize(localizationMap.translator_name),
                                    id: "video__translator_name",
                                },
                                {
                                    //发售日
                                    type: "checkbox",
                                    title: localize(localizationMap.release_date),
                                    id: "video__release_date",
                                },
                                {
                                    //更新日
                                    type: "checkbox",
                                    title: localize(localizationMap.update_date),
                                    id: "video__update_date",
                                },
                                {
                                    //年龄指定
                                    type: "checkbox",
                                    title: localize(localizationMap.age_rating),
                                    id: "video__age_rating",
                                },
                                {
                                    //剧情作者
                                    type: "checkbox",
                                    title: localize(localizationMap.scenario),
                                    id: "video__scenario",
                                },
                                {
                                    //插画作者
                                    type: "checkbox",
                                    title: localize(localizationMap.illustration),
                                    id: "video__illustration",
                                },
                                {
                                    //配音者
                                    type: "checkbox",
                                    title: localize(localizationMap.voice_actor),
                                    id: "video__voice_actor",
                                },
                                {
                                    //音乐作者
                                    type: "checkbox",
                                    title: localize(localizationMap.music),
                                    id: "video__music",
                                },
                                {
                                    //作品标签/分类
                                    type: "checkbox",
                                    title: localize(localizationMap.genre),
                                    id: "video__genre",
                                },
                                {
                                    //文件大小
                                    type: "checkbox",
                                    title: localize(localizationMap.file_size),
                                    id: "video__file_size",
                                }
                            ]
                        },
                        {
                            //小说对应Preset
                            binding: {
                                target: "category_preset",
                                value: "novel"
                            },
                            sortable: true,  //为true则代表该表格内的行可以排序
                            sort_id: "novel__info_display_order",
                            items: [
                                {
                                    //销量
                                    type: "checkbox",
                                    title: localize(localizationMap.dl_count),
                                    id: "novel__dl_count",
                                },
                                {
                                    //社团名
                                    type: "checkbox",
                                    title: localize(localizationMap.circle_name),
                                    id: "novel__circle_name",
                                },
                                {
                                    //翻译者
                                    type: "checkbox",
                                    title: localize(localizationMap.translator_name),
                                    id: "novel__translator_name",
                                },
                                {
                                    //发售日
                                    type: "checkbox",
                                    title: localize(localizationMap.release_date),
                                    id: "novel__release_date",
                                },
                                {
                                    //更新日
                                    type: "checkbox",
                                    title: localize(localizationMap.update_date),
                                    id: "novel__update_date",
                                },
                                {
                                    //年龄指定
                                    type: "checkbox",
                                    title: localize(localizationMap.age_rating),
                                    id: "novel__age_rating",
                                },
                                {
                                    //剧情作者
                                    type: "checkbox",
                                    title: localize(localizationMap.scenario),
                                    id: "novel__scenario",
                                },
                                {
                                    //插画作者
                                    type: "checkbox",
                                    title: localize(localizationMap.illustration),
                                    id: "novel__illustration",
                                },
                                {
                                    //配音者
                                    type: "checkbox",
                                    title: localize(localizationMap.voice_actor),
                                    id: "novel__voice_actor",
                                },
                                {
                                    //音乐作者
                                    type: "checkbox",
                                    title: localize(localizationMap.music),
                                    id: "novel__music",
                                },
                                {
                                    //作品标签/分类
                                    type: "checkbox",
                                    title: localize(localizationMap.genre),
                                    id: "novel__genre",
                                },
                                {
                                    //文件大小
                                    type: "checkbox",
                                    title: localize(localizationMap.file_size),
                                    id: "novel__file_size",
                                }
                            ]
                        },
                        {
                            //其他对应Preset
                            binding: {
                                target: "category_preset",
                                value: "other"
                            },
                            sortable: true,  //为true则代表该表格内的行可以排序
                            sort_id: "other__info_display_order",
                            items: [
                                {
                                    //销量
                                    type: "checkbox",
                                    title: localize(localizationMap.dl_count),
                                    id: "other__dl_count",
                                },
                                {
                                    //社团名
                                    type: "checkbox",
                                    title: localize(localizationMap.circle_name),
                                    id: "other__circle_name",
                                },
                                {
                                    //翻译者
                                    type: "checkbox",
                                    title: localize(localizationMap.translator_name),
                                    id: "other__translator_name",
                                },
                                {
                                    //发售日
                                    type: "checkbox",
                                    title: localize(localizationMap.release_date),
                                    id: "other__release_date",
                                },
                                {
                                    //更新日
                                    type: "checkbox",
                                    title: localize(localizationMap.update_date),
                                    id: "other__update_date",
                                },
                                {
                                    //年龄指定
                                    type: "checkbox",
                                    title: localize(localizationMap.age_rating),
                                    id: "other__age_rating",
                                },
                                {
                                    //剧情作者
                                    type: "checkbox",
                                    title: localize(localizationMap.scenario),
                                    id: "other__scenario",
                                },
                                {
                                    //插画作者
                                    type: "checkbox",
                                    title: localize(localizationMap.illustration),
                                    id: "other__illustration",
                                },
                                {
                                    //配音者
                                    type: "checkbox",
                                    title: localize(localizationMap.voice_actor),
                                    id: "other__voice_actor",
                                },
                                {
                                    //音乐作者
                                    type: "checkbox",
                                    title: localize(localizationMap.music),
                                    id: "other__music",
                                },
                                {
                                    //作品标签/分类
                                    type: "checkbox",
                                    title: localize(localizationMap.genre),
                                    id: "other__genre",
                                },
                                {
                                    //文件大小
                                    type: "checkbox",
                                    title: localize(localizationMap.file_size),
                                    id: "other__file_size",
                                }
                            ]
                        }
                    ]
                },
                {
                    //分类:标签显示
                    title: localize(localizationMap.title_tag_settings),
                    items: [
                        {
                            //总开关表格
                            items: [
                                {
                                    type: "checkbox",
                                    title: localize(localizationMap.tag_main_switch),
                                    tooltip: localize(localizationMap.tag_main_switch_tooltip),
                                    id: "tag_main_switch"
                                },
                                {
                                    //显示评分人数
                                    type: "checkbox",
                                    title: localize(localizationMap.show_rate_count),
                                    id: "show_rate_count",
                                    indent: 1
                                }
                            ]
                        },
                        {
                            //标签开关表格
                            binding: {
                                target: "tag_main_switch",
                                value: true
                            },
                            items: [
                                {
                                    //所有的标签开关集合
                                    type: "tag_switch",
                                    //标签之间可以排序
                                    sortable: true,
                                    sort_id: "tag_display_order",
                                    items: [
                                        {
                                            //销量
                                            title: localize(localizationMap.rate),
                                            id: "tag_rate",
                                            class: "tag-yellow",
                                            tooltip: localize(localizationMap.rate_tooltip)
                                        },
                                        {
                                            //作品类型
                                            title: localize(localizationMap.tag_work_type),
                                            id: "tag_work_type",
                                            class: "tag-darkblue",
                                            tooltip: `
<div class="${VOICELINK_CLASS}_tags">
    <span class="${VOICELINK_CLASS}_tag-purple">${localize(localizationMap.work_type_game)}</span>
    <span class="${VOICELINK_CLASS}_tag-green">${localize(localizationMap.work_type_comic)}</span>
    <span class="${VOICELINK_CLASS}_tag-teal">${localize(localizationMap.work_type_illustration)}</span>
    <span class="${VOICELINK_CLASS}_tag-gray">${localize(localizationMap.work_type_novel)}</span>
    <span class="${VOICELINK_CLASS}_tag-darkblue">${localize(localizationMap.work_type_video)}</span>
    <span class="${VOICELINK_CLASS}_tag-orange">${localize(localizationMap.work_type_voice)}</span>
    <span class="${VOICELINK_CLASS}_tag-yellow">${localize(localizationMap.work_type_music)}</span>
    <span class="${VOICELINK_CLASS}_tag-gray">${localize(localizationMap.work_type_tool)}</span>
    <span class="${VOICELINK_CLASS}_tag-blue">${localize(localizationMap.work_type_voice_comic)}</span>
    <span class="${VOICELINK_CLASS}_tag-gray">${localize(localizationMap.work_type_other)}</span>
</div>`,
                                        },
                                        {
                                            //可翻译
                                            title: localize(localizationMap.tag_translatable),
                                            id: "tag_translatable",
                                            class: "tag-green",
                                            tooltip: localize(localizationMap.tag_translatable_tooltip)
                                        },
                                        {
                                            //不可翻译
                                            title: localize(localizationMap.tag_not_translatable),
                                            id: "tag_not_translatable",
                                            class: "tag-red",
                                            tooltip: localize(localizationMap.tag_not_translatable_tooltip)
                                        },
                                        {
                                            //翻译作品
                                            title: localize(localizationMap.tag_translated),
                                            id: "tag_translated",
                                            class: "tag-teal",
                                            tooltip: localize(localizationMap.tag_translated_tooltip)
                                        },
                                        {
                                            //支持语言
                                            title: localize(localizationMap.tag_language_support),
                                            id: "tag_language_support",
                                            class: "tag-pink",
                                            tooltip: `
<div class="${VOICELINK_CLASS}_tags">
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_japanese)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_simplified_chinese)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_traditional_chinese)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_english)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_korean)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_german)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_french)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_indonesian)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_italian)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_portuguese)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_swedish)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_thai)}</span>
<span class="${VOICELINK_CLASS}_tag-pink">${localize(localizationMap.language_vietnamese)}</span>
</div>
                  `
                                        },
                                        {
                                            //特典作品
                                            title: localize(localizationMap.tag_bonus_work),
                                            id: "tag_bonus_work",
                                            class: "tag-yellow",
                                            tooltip: localize(localizationMap.tag_bonus_work_tooltip)
                                        },
                                        {
                                            //含特典
                                            title: localize(localizationMap.tag_has_bonus),
                                            id: "tag_has_bonus",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.tag_has_bonus_tooltip)
                                        },
                                        {
                                            //文件格式
                                            title: localize(localizationMap.tag_file_format),
                                            id: "tag_file_format",
                                            class: "tag-darkblue",
                                            tooltip: localize(localizationMap.tag_file_format_tooltip)
                                        },
                                        {
                                            //已下架
                                            title: localize(localizationMap.tag_no_longer_available),
                                            id: "tag_no_longer_available",
                                            class: "tag-gray",
                                        },
                                        {
                                            //AI & 部分AI
                                            title: localize(localizationMap.tag_ai),
                                            id: "tag_ai",
                                            class: "tag-purple",
                                            tooltip: localize(localizationMap.tag_ai_tooltip)
                                        }
                                    ]
                                },
                            ]
                        },
                        {
                            //翻译情况显示表格
                            items: [
                                {
                                    //翻译情况显示开关
                                    type: "checkbox",
                                    title: localize(localizationMap.tag_translation_request),
                                    id: "tag_translation_request",
                                    tooltip: localize(localizationMap.tag_translation_request_tooltip)
                                },
                            ]
                        },
                        {
                            //翻译情况标签显示表格
                            binding: {
                                target: "tag_translation_request",
                                value: true
                            },
                            items: [
                                {
                                    //各种翻译情况显示
                                    type: "tag_switch",
                                    sortable: true,
                                    sort_id: "tag_translation_request_display_order",
                                    items: [
                                        {
                                            //英语
                                            title: `${localize(localizationMap.language_english_abbr)} 1-1`,
                                            id: "tag_translation_request_english",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_english)
                                        },
                                        {
                                            //简体中文
                                            title: `${localize(localizationMap.language_simplified_chinese_abbr)} 1-1`,
                                            id: "tag_translation_request_simplified_chinese",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_simplified_chinese)
                                        },
                                        {
                                            //繁体中文
                                            title: `${localize(localizationMap.language_traditional_chinese_abbr)} 1-1`,
                                            id: "tag_translation_request_traditional_chinese",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_traditional_chinese)
                                        },
                                        {
                                            //韩语
                                            title: `${localize(localizationMap.language_korean_abbr)} 1-1`,
                                            id: "tag_translation_request_korean",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_korean)
                                        },
                                        {
                                            //西班牙语
                                            title: `${localize(localizationMap.language_spanish_abbr)} 1-1`,
                                            id: "tag_translation_request_spanish",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_spanish)
                                        },
                                        {
                                            //德语
                                            title: `${localize(localizationMap.language_german_abbr)} 1-1`,
                                            id: "tag_translation_request_german",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_german)
                                        },
                                        {
                                            //法语
                                            title: `${localize(localizationMap.language_french_abbr)} 1-1`,
                                            id: "tag_translation_request_french",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_french)
                                        },
                                        {
                                            //印尼语
                                            title: `${localize(localizationMap.language_indonesian_abbr)} 1-1`,
                                            id: "tag_translation_request_indonesian",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_indonesian)
                                        },
                                        {
                                            //意大利语
                                            title: `${localize(localizationMap.language_italian_abbr)} 1-1`,
                                            id: "tag_translation_request_italian",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_italian)
                                        },
                                        {
                                            //葡萄牙语
                                            title: `${localize(localizationMap.language_portuguese_abbr)} 1-1`,
                                            id: "tag_translation_request_portuguese",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_portuguese)
                                        },
                                        {
                                            //瑞典语
                                            title: `${localize(localizationMap.language_swedish_abbr)} 1-1`,
                                            id: "tag_translation_request_swedish",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_swedish)
                                        },
                                        {
                                            //泰语
                                            title: `${localize(localizationMap.language_thai_abbr)} 1-1`,
                                            id: "tag_translation_request_thai",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_thai)
                                        },
                                        {
                                            //越南语
                                            title: `${localize(localizationMap.language_vietnamese_abbr)} 1-1`,
                                            id: "tag_translation_request_vietnamese",
                                            class: "tag-orange",
                                            tooltip: localize(localizationMap.language_vietnamese)
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        };
    }
    class SettingPageBuilder {
        constructor(structure, settings) {
            this.structure = structure;
            this.settings = settings;
            this.container = null;
        }

        getClass(name) {
            if(!VOICELINK_CLASS || VOICELINK_CLASS === "") return name;
            return `${VOICELINK_CLASS}_${name}`;
        }

        build(useTemp = false) {
            const f = this.structure;
            let tempSettings = this.settings;

            //若未要求则清空设置暂存
            if(useTemp){
                tempSettings = {};
                for(let key in this.settings.temp_edited){
                    if(!key.startsWith("_s_")) continue;
                    tempSettings[key] = this.settings.temp_edited[key];
                }
            }else{
                this.settings.clearTemp();
            }

            //创建container
            const container = document.createElement("div");
            container.className = this.getClass("container");
            container.id = this.getClass("settings-container");
            this.container = container;

            //创建关闭按钮
            const closeButton = document.createElement("button");
            closeButton.id = this.getClass("button-close");
            closeButton.innerText = "X";
            closeButton.onclick = () => {
                container.remove();
            };
            container.appendChild(closeButton);

            //创建标题
            const title = document.createElement("h1");
            title.innerText = f.title;
            container.appendChild(title);

            //遍历构建Section
            for(const section of f.items){
                container.appendChild(this.buildSection(section));
            }

            //添加保存、重置按钮
            const buttonContainer = document.createElement("div");
            buttonContainer.className = this.getClass("button-container");
            const saveButton = document.createElement("button");
            saveButton.id = this.getClass("button-save");
            saveButton.innerText = localize(localizationMap.button_save);
            saveButton.onclick = () => {
                try{
                    this.settings.save();
                    window.alert(localize(localizationMap.save_complete))
                    container.remove();
                }catch (e){
                    window.alert(e);
                }
            };
            const resetButton = document.createElement("button");
            resetButton.id = this.getClass("button-reset");
            resetButton.innerText = localize(localizationMap.button_reset);
            resetButton.onclick = () => {
                if(!window.confirm(localize(localizationMap.reset_confirm))){
                    return;
                }
                try{
                    // this.settings.reset();
                    window.alert(localize(localizationMap.reset_complete))
                }catch (e) {
                    window.alert(e);
                }
                this.refreshSettings(this.settings.default_backup);
            };
            buttonContainer.appendChild(saveButton);
            buttonContainer.appendChild(resetButton);
            container.appendChild(buttonContainer);

            this.initSortableElement();
            this.refreshSettings(tempSettings);
            return container;
        };

        buildSection(section){
            //创建容器
            const container = document.createElement("div");
            container.className = this.getClass("section-container");

            //创建标题
            const title = document.createElement("h2");
            title.innerText = section.title;
            container.appendChild(title);

            //遍历构建Table
            for(const item of section.items){
                container.appendChild(this.buildTable(item, container));
            }

            return container;
        };

        buildTable(table, section){
            //创建table
            const tableElement = document.createElement("table");

            //创建可能存在的preset绑定
            this.createBinding(table, tableElement, section);

            //遍历构建Row(先把row缓存在列表里,经过排序后构建)
            let rowList = [];
            const nodesCache = document.createElement("div");
            for(const row of table.items){
                let rowElement = this.buildRow(row, nodesCache);
                if(table.sortable){
                    this.setSortable(rowElement);
                }
                rowList.push(rowElement);

                //存入缓存用于绑定
                nodesCache.appendChild(rowElement);
            }

            //重置按钮行
            let resetRow;
            if(table.sortable){
                tableElement.setAttribute("data-sort-id", table.sort_id);
                this.sortSortable(rowList, table.sort_id);

                //若可排序则还要添加重置按钮
                if(table.ignore_reset !== true){
                    resetRow = document.createElement("tr");
                    const resetCell = document.createElement("td");
                    resetCell.classList.add(this.getClass("input-cell"))
                    resetCell.colSpan = 2;
                    resetCell.style.setProperty("text-align", "right", "important");  //textAlign = "right !important";
                    const resetButton = document.createElement("button");
                    resetButton.classList.add(this.getClass("button-flat"));
                    resetButton.title = localize(localizationMap.reset_order);
                    resetButton.style.setProperty("margin-right", "0", "important");  //marginRight = "0 !important";
                    resetButton.style.setProperty("margin-bottom", "0", "important");  //marginBottom = "0 !important";
                    const icon = document.createElement("span");
                    icon.classList.add(this.getClass("reset-btn-small"));
                    resetButton.appendChild(icon);
                    const title = document.createElement("span");
                    title.innerText = localize(localizationMap.reset_order);
                    resetButton.appendChild(title);
                    resetButton.onclick = () => {
                        if(!window.confirm(localize(localizationMap.reset_order_confirm))){
                            return;
                        }
                        this.reorderSortable(table.sort_id, this.settings.getDefaultValue(table.sort_id));
                        // tableElement.insertBefore(resetRow, tableElement.firstChild);
                    };
                    resetCell.appendChild(resetButton);
                    resetRow.appendChild(resetCell);
                    // tableElement.appendChild(resetRow);
                }
            }

            rowList.forEach((row) => {
                tableElement.appendChild(row);
            });
            if(resetRow){
                tableElement.appendChild(resetRow);
            }

            return tableElement;
        };

        buildRow(row, table){
            //创建row
            let rowElement = document.createElement("tr");

            //根据类型创建内容
            switch (row.type) {
                case "checkbox":
                    rowElement = this.createToggleRow(row);
                    break;
                case "dropdown":
                    rowElement = this.createDropdownRow(row);
                    break;
                case "input":
                    rowElement = this.createInputRow(row);
                    break;
                case "tag_switch":
                    rowElement = this.createTagSwitchRow(row);
                    break;
                default:
                    console.error(`Unknown row type: ${row.type}`);
                    break;
            }

            //设置Binding
            if(row.binding){
                this.createBinding(row, rowElement, table)
            }

            rowElement.classList.add(this.getClass("setting"));
            if(row.id){
                rowElement.setAttribute("data-id", row.id);
            }
            return rowElement;
        };

        createToggleRow(row){
            //创建Row
            const rowElement = document.createElement("tr");
            if(row.indent) {
                rowElement.classList.add(this.getClass(`indent-${row.indent}`));
            }

            //创建设置项标题
            const titleCell = document.createElement("td");
            titleCell.className = this.getClass("tooltip");
            const title = document.createElement("span");
            title.className = this.getClass("row-title") + " " + this.getClass("ignore-drag");
            title.innerHTML = Csp.createHTML(row.title);
            titleCell.appendChild(title);

            if(row.tooltip) {
                const tooltip = document.createElement("span");
                tooltip.className = this.getClass("tooltip-text");
                tooltip.innerHTML = Csp.createHTML(row.tooltip);
                titleCell.appendChild(tooltip);
            }

            //创建开关和重置按钮
            const settingId = `_s_${row.id}`;
            const inputCell = document.createElement("td");
            inputCell.classList.add(this.getClass("input-cell"));
            const inputContainer = document.createElement("div");
            inputContainer.classList.add(this.getClass("toggle-container"));
            inputCell.appendChild(inputContainer);

            //创建开关
            const input = document.createElement("input");
            input.type = "checkbox";
            input.id = this.getClass(row.id);
            input.name = input.id;
            // inputCell.appendChild(input);
            inputContainer.appendChild(input);

            const label = document.createElement("label");
            label.classList.add(this.getClass("toggle"));
            label.setAttribute("for", input.id);
            const hidden = document.createElement("span");
            hidden.classList.add(this.getClass("hidden"));
            hidden.innerHTML = Csp.createHTML(row.title);
            label.appendChild(hidden);
            // inputCell.appendChild(label);
            inputContainer.appendChild(label);

            //创建重置按钮
            if(row.ignore_reset !== true){
                const defaultValue = this.settings.getDefaultValue(settingId);
                const resetButton = document.createElement("button");
                resetButton.className = this.getClass("reset-btn-small") + " " + this.getClass("ignore-drag");
                resetButton.title = localize(localizationMap.button_reset);
                resetButton.onclick = () => {
                    input.checked = defaultValue === true;
                    input.dispatchEvent(new Event("change"));
                };
                // inputCell.insertBefore(resetButton, inputCell.firstChild);
                inputContainer.insertBefore(resetButton, inputContainer.firstChild);

                input.addEventListener("change", () => {
                    //决定是否显示重置按钮
                    resetButton.style.setProperty("display", input.checked === defaultValue ? "none" : "inline-block", "important");  //display = input.checked === defaultValue ? "none" : "inline-block";
                });
            }

            //监听器都创建好了再设置初始值
            input.addEventListener("change", () => {
                //更新到暂存设置
                this.settings.saveTemp(settingId, input.checked);
            })
            input.checked = this.settings[settingId] === true;
            input.dispatchEvent(new Event("change"));

            rowElement.appendChild(titleCell);
            rowElement.appendChild(inputCell);
            return rowElement;
        };

        createDropdownRow(row){
            const rowElement = document.createElement("tr");
            if(row.indent) {
                rowElement.classList.add(this.getClass(`indent-${row.indent}`));
            }

            const titleCell = document.createElement("td");
            titleCell.className = this.getClass("tooltip");
            const label = document.createElement("label");
            label.className = this.getClass("row-title") + " " + this.getClass("ignore-drag");
            label.setAttribute("for", this.getClass(row.id));
            label.innerHTML = Csp.createHTML(row.title);
            titleCell.appendChild(label);

            if(row.tooltip) {
                const tooltip = document.createElement("span");
                tooltip.className = this.getClass("tooltip-text");
                tooltip.innerHTML = Csp.createHTML(row.tooltip);
                titleCell.appendChild(tooltip);
            }

            const inputCell = document.createElement("td");
            inputCell.classList.add(this.getClass("input-cell"));
            const select = document.createElement("select");
            select.className = this.getClass("ignore-drag");
            select.id = this.getClass(row.id);
            select.name = select.id;
            for(const item of row.options){
                const option = document.createElement("option");
                option.value = item.value;
                option.innerText = item.title;
                select.appendChild(option);
            }
            inputCell.appendChild(select);

            //创建重置按钮
            const settingId = `_s_${row.id}`;
            if(row.ignore_reset !== true){
                const defaultValue = this.settings.getDefaultValue(settingId);
                const resetButton = document.createElement("button");
                resetButton.className = this.getClass("reset-btn-small") + " " + this.getClass("ignore-drag");
                resetButton.title = localize(localizationMap.button_reset);
                resetButton.onclick = () => {
                    select.value = defaultValue;
                    select.dispatchEvent(new Event("change"));
                };
                inputCell.insertBefore(resetButton, inputCell.firstChild);

                select.addEventListener("change", () => {
                    //决定是否显示重置按钮
                    resetButton.style.setProperty("display", select.value === defaultValue ? "none" : "inline-block", "important");  //display = select.value === defaultValue ? "none" : "inline-block";
                });
            }

            //监听器都创建好了再设置初始值
            select.addEventListener("change", () => {
                //更新到暂存设置
                this.settings.saveTemp(settingId, select.value);
            })
            select.value = this.settings[settingId];
            select.dispatchEvent(new Event("change"));

            rowElement.appendChild(titleCell);
            rowElement.appendChild(inputCell);
            return rowElement;
        };

        createInputRow(row){
            const rowElement = document.createElement("tr");
            if(row.indent) {
                rowElement.classList.add(this.getClass(`indent-${row.indent}`));
            }

            const titleCell = document.createElement("td");
            titleCell.className = this.getClass("tooltip");
            const label = document.createElement("label");
            label.className = this.getClass("row-title") + " " + this.getClass("ignore-drag");
            label.setAttribute("for", this.getClass(row.id));
            label.innerHTML = Csp.createHTML(row.title);
            titleCell.appendChild(label);

            if(row.tooltip) {
                const tooltip = document.createElement("span");
                tooltip.className = this.getClass("tooltip-text");
                tooltip.innerHTML = Csp.createHTML(row.tooltip);
                titleCell.appendChild(tooltip);
            }

            const inputCell = document.createElement("td");
            inputCell.classList.add(this.getClass("input-cell"));
            const input = document.createElement("input");
            input.type = "text";
            input.id = this.getClass(row.id);
            input.name = input.id;
            inputCell.appendChild(input);

            //创建重置按钮
            const settingId = `_s_${row.id}`;
            if(row.ignore_reset !== true){
                const defaultValue = this.settings.getDefaultValue(settingId);
                const resetButton = document.createElement("button");
                resetButton.className = this.getClass("reset-btn-small") + " " + this.getClass("ignore-drag");
                resetButton.title = localize(localizationMap.button_reset);
                resetButton.onclick = () => {
                    input.value = defaultValue;
                    input.dispatchEvent(new Event("change"));
                };
                inputCell.insertBefore(resetButton, inputCell.firstChild);

                input.addEventListener("change", () => {
                    //决定是否显示重置按钮
                    resetButton.style.setProperty("display", input.value === defaultValue ? "none" : "inline-block", "important");  //display = input.value === defaultValue ? "none" : "inline-block";
                });
            }

            //监听器都创建好了再设置初始值
            input.addEventListener("change", () => {
                //更新到暂存设置
                this.settings.saveTemp(settingId, input.value);
            })
            input.value = this.settings[settingId];
            input.dispatchEvent(new Event("change"));

            rowElement.appendChild(titleCell);
            rowElement.appendChild(inputCell);
            return rowElement;
        };

        createTagSwitchRow(row){
            const rowElement = document.createElement("tr");
            if(row.indent) {
                rowElement.classList.add(this.getClass(`indent-${row.indent}`));
            }

            const tagCell = document.createElement("td");
            tagCell.colSpan = 2;
            //用内部容器再次包裹标签,保证重置按钮的位置
            const tagContainer = document.createElement("div");
            tagContainer.className = this.getClass("tags");
            tagCell.appendChild(tagContainer);

            const tagList = [];
            for(const tag of row.items){
                const tagSpan = document.createElement("label");
                tagSpan.classList.add(this.getClass(tag["class"]));
                tagSpan.innerText = tag.title;
                tagSpan.setAttribute("for", this.getClass(tag.id));
                tagSpan.setAttribute("data-id", tag.id);
                this.setSortable(tagSpan);

                //添加switch
                const settingId = `_s_${tag.id}`;
                const switchInput = document.createElement("input");
                switchInput.style.setProperty("display", "none", "important");  //display = "none !important";
                switchInput.type = "checkbox";
                switchInput.id = this.getClass(tag.id);
                switchInput.name = switchInput.id;
                switchInput.addEventListener("change", (event) => {
                    if(event.target.checked) {
                        tagSpan.classList.remove(this.getClass("tag-off"));
                    }else if(!tagSpan.classList.contains(this.getClass("tag-off"))) {
                        tagSpan.classList.add(this.getClass("tag-off"));
                    }

                    //更新到暂存设置
                    this.settings.saveTemp(settingId, switchInput.checked);
                })
                switchInput.checked = this.settings[`_s_${tag.id}`] === true;
                switchInput.dispatchEvent(new Event("change"));

                tagSpan.classList.toggle(this.getClass("tag-off"), !switchInput.checked);
                tagSpan.appendChild(switchInput);

                if(tag.tooltip) {
                    tagSpan.classList.add(this.getClass("tooltip"));
                    const tooltip = document.createElement("span");
                    tooltip.className = this.getClass("tooltip-text");
                    tooltip.innerHTML = Csp.createHTML(tag.tooltip);
                    tagSpan.appendChild(tooltip);
                }

                tagList.push(tagSpan);
            }

            if(row.sortable){
                tagContainer.setAttribute("data-sort-id", row.sort_id);
                this.sortSortable(tagList, row.sort_id);

                //添加排列重置按钮
                if(row.ignore_reset !== true){
                    const resetOrderButton = document.createElement("button");
                    resetOrderButton.classList.add(this.getClass("button-flat"));
                    resetOrderButton.title = localize(localizationMap.button_reset);
                    resetOrderButton.onclick = () => {
                        if(!window.confirm(localize(localizationMap.reset_order_confirm))){
                            return;
                        }
                        this.reorderSortable(row.sort_id, this.settings.getDefaultValue(row.sort_id));
                    };

                    const icon = document.createElement("span");
                    icon.classList.add(this.getClass("reset-btn-small"));
                    resetOrderButton.appendChild(icon);

                    const title = document.createElement("span");
                    title.innerText = localize(localizationMap.reset_order);
                    resetOrderButton.appendChild(title);

                    tagCell.appendChild(resetOrderButton);
                    // tagCell.insertBefore(resetOrderButton, tagCell.firstChild);
                }
            }

            //添加设置重置按钮
            if(row.ignore_reset !== true){
                const resetSettingsButton = document.createElement("button");
                resetSettingsButton.classList.add(this.getClass("button-flat"));
                resetSettingsButton.title = localize(localizationMap.button_reset);
                resetSettingsButton.onclick = () => {
                    if(!window.confirm(localize(localizationMap.reset_confirm))){
                        return;
                    }
                    for (const item of row.items) {
                        let tag = tagContainer.querySelector("#" + this.getClass(item.id));
                        if(!tag) continue;
                        tag.checked = this.settings.getDefaultValue(item.id);
                        tag.dispatchEvent(new Event("change"));
                    }
                };

                const icon = document.createElement("span");
                icon.classList.add(this.getClass("reset-btn-small"));
                resetSettingsButton.appendChild(icon);

                const title = document.createElement("span");
                title.innerText = localize(localizationMap.button_reset);
                resetSettingsButton.appendChild(title);

                tagCell.appendChild(resetSettingsButton);
                // tagCell.insertBefore(resetOrderButton, tagCell.firstChild);
            }


            tagList.forEach((tag) => {
                tagContainer.appendChild(tag);
            });
            rowElement.appendChild(tagCell);
            return rowElement;
        };

        createBinding(data, element, section){
            if(!data.binding || !this.container) return;

            const binding = data.binding;
            const target = section.querySelector("#" + this.getClass(binding.target));
            if(!target) return;

            let showState = "unset";
            switch (element.nodeName){
                case "TABLE":
                    showState = "table";
                    break;
                case "TR":
                    showState = "table-row";
                    break;
            }

            target.addEventListener("change", (event) => {
                const value = event.target.value;
                const checked = event.target.checked;
                element.style.setProperty("display", value === binding.value || checked === binding.value ? showState : "none", "important");  //display = value === binding.value || checked === binding.value ? showState : "none";
            });

            element.style.setProperty("display", target.value === binding.value || target.checked === binding.value ? showState : "none", "important");  //display = target.value === binding.value || target.checked === binding.value ? showState : "none";
        };

        sortSortable(sortList, sortId, orderList){
            orderList = orderList ? orderList : this.settings[`_s_${sortId}`];
            if(!orderList) return;

            for (let i = 0; i < orderList.length; ++i) {
                for (let j = 0; j < sortList.length; ++j){
                    if(orderList[i] === sortList[j].getAttribute("data-id")) {
                        let tmp = sortList[i];
                        sortList[i] = sortList[j];
                        sortList[j] = tmp;
                        break;
                    }
                }
            }
        };

        setSortable(ele){
            ele.classList.add(this.getClass("sortable"));
        };

        refreshSettings(settings) {
            //刷新设置项值的展示情况(保持input值和设置中的实际值一致)
            //清空设置暂存
            this.settings.clearTemp();
            settings = settings ? settings : this.settings;

            //刷新设置项
            for(const key in settings){
                if(!key.startsWith("_s_")) continue;
                const targetId = this.getClass(key.substring(3));

                if(settings[key] && Array.isArray(settings[key])){
                    //可能是排序对象,先搜索排序目标,搜不到再按默认值处理
                    if(this.reorderSortable(key.substring(3), settings[key])) continue;
                }

                //非排序对象
                const target = this.container.querySelector("#" + targetId);
                if(!target) continue;

                if(target.tagName === "INPUT") {
                    if(target.type === "checkbox") {
                        target.checked = settings[key] === true;
                    }else{
                        target.value = settings[key];
                    }
                }else if(target.tagName === "SELECT") {
                    target.value = settings[key];
                }

                if(key === "_s_lang") continue;
                target.dispatchEvent(new Event("change"));
            }
        };

        reorderSortable(sort_id, orderList) {
            const target = this.container.querySelector(`*[data-sort-id="${sort_id}"]`);
            if(target) {
                let list = [...target.children];
                this.sortSortable(list, sort_id, orderList);

                for(let i = 0; i < list.length; ++i) {
                    target.removeChild(list[i]);
                }

                for(let i = 0; i < list.length; ++i) {
                    target.appendChild(list[i]);
                }

                this.settings.saveTemp(sort_id, list.map(ele => ele.getAttribute("data-id")).filter(val => val && val !== ""));
                return true;
            }
            return false;
        }

        initSortableElement() {
            //初始化排序组件
            const sortableGroups = this.container.querySelectorAll(`.${this.getClass('sortable')}`);
            sortableGroups.forEach(group => {
                new Sortable(group, group.parentElement.getAttribute("data-sort-id"), this.settings);
            });
        };

    }
    class Sortable {
        constructor(element, sort_id, settings, options) {
            this.element = element;
            this.options = options;
            this.sortId = sort_id;
            this.settings = settings;
            this.dragging = null;

            if(!sort_id) {
                throw new Error("sort_id is required");
            }

            this.init();
        }

        getClass(name) {
            if(!VOICELINK_CLASS || VOICELINK_CLASS === "") return name;
            return `${VOICELINK_CLASS}_${name}`;
        }

        init() {
            this.element.addEventListener('mousedown', this.onMouseDown.bind(this));
            document.addEventListener('mousemove', this.onMouseMove.bind(this));
            document.addEventListener('mouseup', this.onMouseUp.bind(this));
        }

        onMouseDown(event) {
            if(event.target.nodeName === "INPUT" || event.target.classList.contains(this.getClass("toggle")) || event.target.classList.contains(this.getClass("ignore-drag"))) return;
            if (event.target.closest(`.${this.getClass('sortable')}`)) {
                this.dragging = event.target.closest(`.${this.getClass('sortable')}`);
                this.dragging.classList.add(this.getClass('dragging'));
            }
        }

        onMouseMove(event) {
            if (this.dragging) {
                event.preventDefault();

                try{
                    let sibling = document.elementFromPoint(event.clientX, event.clientY).closest(`.${this.getClass('sortable')}`);
                    if (sibling && sibling !== this.dragging) {
                        this.element.parentNode.insertBefore(this.dragging, sibling.nextSibling === this.dragging ? sibling : sibling.nextSibling);
                        //暂存当前顺序
                        const order = [...this.element.parentNode.children].map(item => item.getAttribute("data-id")).filter(item => item || item === "");
                        this.settings.saveTemp(this.sortId, order);
                    }
                }catch (e) {
                    console.error(e)
                }

            }
        }

        onMouseUp(event) {
            if (this.dragging) {
                this.dragging.classList.remove(this.getClass('dragging'));
                this.dragging = null;
            }
        }
    }
    const SettingsPopup = {
        showPopup(useTemp = false) {
            let uiBuilder = new SettingPageBuilder(getSettingsUi(), settings);
            let ui = uiBuilder.build(useTemp);
            const displayLangElement = ui.querySelector(`#${VOICELINK_CLASS}_lang`);
            displayLangElement.addEventListener("change", e => {
                settings._s_lang = displayLangElement.value;
                GM_setValue("lang", settings._s_lang);
                ui.remove();
                SettingsPopup.showPopup(true);
            });
            document.body.appendChild(ui);
        }
    };

    let isInit = false;
    let observing = false;
    function init () {
        if(!isInit) {
            const style = document.createElement("style");
            style.innerHTML = Csp.createHTML(POPUP_CSS + SETTINGS_CSS);
            document.head.appendChild(style);
            // SettingsPopup.getPopup()
            GM_registerMenuCommand("Settings - 设置", () => SettingsPopup.showPopup())
            GM_registerMenuCommand("Notice - 公告", () => showUpdateNotice(true));
            GM_registerMenuCommand("Home / Update - 主页 / 更新", () =>
                GM_openInTab(IS_PREVIEW ? "https://sleazyfork.org/zh-CN/scripts/521128-voicelinks-preview" : "https://sleazyfork.org/zh-CN/scripts/456775-voicelinks", {active: true}));

            document.addEventListener("securitypolicyviolation", function (e) {
                if (e.blockedURI.includes("img.dlsite.jp")) {
                    const img = document.querySelector(`img[src="${e.blockedURI}"]`);
                    img.remove();

                    const imgContainer = Popup.popupElement.img.container;
                    if(imgContainer){
                        imgContainer.style.setProperty("display", "none", "important");  //display = "none !important";
                        Popup.hideImg = true;
                    }
                }
            });

            isInit = true;
        }

        if(!document.body || observing){
            return;
        }

        Parser.walkNodes(document.body);
        if(!document.getElementById(`${VOICELINK_CLASS}-voice-popup`)) Popup.makePopup(false);

        const observer = new MutationObserver(function (m) {
            for (let i = 0; i < m.length; ++i) {
                let addedNodes = m[i].addedNodes;
                let removedNodes = m[i].removedNodes;

                for (let j = 0; j < removedNodes.length; ++j){
                    let node = removedNodes[j];
                }

                for (let j = 0; j < addedNodes.length; ++j) {
                    Parser.walkNodes(addedNodes[j]);
                }
            }
        });

        observer.observe(document.body, { childList: true, subtree: true})
        setUserSelectTitle();

        //显示重要通知
        showUpdateNotice();

        observing = true;
    }

    document.addEventListener("DOMContentLoaded", init);

    function showUpdateNotice(force = false) {
        const firstTimeToken = 105;
        if(GM_getValue("first_token", undefined) === firstTimeToken && !force){
            return;
        }
        GM_setValue("first_token", firstTimeToken);

        if(!force && window.confirm(localize(localizationMap.notice_update)) === false) {
            return;
        }

        GM_openInTab(
            IS_PREVIEW ? `https://github.com/IceFoxy062/VoiceLinks-Extend/blob/dev/docs/major_updates/v4.8.6/v4.8.6-${settings._s_lang}.md` : `https://github.com/IceFoxy062/VoiceLinks-Extend/blob/dev/docs/major_updates/v4.8.6/v4.8.6-${settings._s_lang}.md`,
            {active: true});

        /*let popup = document.createElement("div");
        popup.style = `
        position: fixed;
        width: 60%;
        max-width: 800px;
        height: auto;
        margin: 20px auto;
        padding: 10px;
        left: 0;
        right: 0;
        top: 0;
        background: rgba(255, 255, 255, 0.9);
        z-index: 999;

        border-radius: 10px;
        border: 2px solid gray`;
        popup.innerHTML = Csp.createHTML(`
        <h1 style="text-indent: 0; color: black;">Notice from VoiceLinks</h1>
        <p>
        <strong><span style="font-size:14px;">重大更新,添加大量自定义设置,部分原有设置被重置,请打开<a data-link="settings">设置</a>界面重新设置。</span></strong>
        </p>
        <p>
        <strong><span style="font-size:14px;">A large number of custom settings have been added, and some original settings have been reset. Please open the <a data-link="settings">settings</a> interface to set up.</span></strong>
        </p>
        <p>
        <br />
        </p>
        <p>
        具体更新:
        </p>
        <p>
        - <strong>设置界面完全重构</strong>,并增加大量可设置项,包括<strong>自定义信息的显示情况与显示顺序</strong>
        </p>
        <p>
        - 使用<strong>标签</strong>来标记额外的作品信息
        </p>
        <p>
        - 现在可以查看<strong>翻译申请情况</strong>了
        </p>
        <input style="font-size: 16px; text-align: center; width: 100%; padding: 5px 10px" type="button" value="OK">
        `);
        popup.querySelectorAll("a[data-link=settings]").forEach(link => {
            link.style.color = "blue !important";
            link.style.cursor = "pointer !important";
            link.style.textDecoration = "underline !important";
            link.addEventListener("click", function () {
                SettingsPopup.showPopup();
            })
        })
        popup.querySelector("input[type=button][value=OK]").addEventListener("click", function(){
            popup.remove();
            GM_setValue("first_token", firstTimeToken);
        })

        document.body.appendChild(popup);*/
    }

    //Deal with Trusted Types

    let Csp = {
        createHTML: (str) => str
    };
    if(window.isSecureContext === true && trustedTypes){
        Csp = trustedTypes.createPolicy(
            trustedTypes.defaultPolicy ? "VoiceLinkTrustedTypes" : "VoiceLinkTrustedTypes",
            Csp);
    }

    init();
})();