闪韵灵境歌曲搜索扩展

通过其他网站搜索更多的歌曲

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        BlitzRhythm Editor Extra Song Search
// @name:en     Extra Song Search
// @name:zh-CN     闪韵灵境歌曲搜索扩展
// @namespace   cipher-editor-mod-extra-song-search
// @version     1.1.1
// @description     Search for more songs from other websites
// @description:en  Search for more songs from other websites
// @description:zh-CN  通过其他网站搜索更多的歌曲
// @author      Moyuer
// @author:zh-CN   如梦Nya
// @source      https://github.com/CMoyuer/BlitzRhythm-Editor-Mod-Loader
// @license     MIT
// @run-at      document-body
// @grant       unsafeWindow
// @grant       GM_xmlhttpRequest
// @connect     beatsaver.com
// @match       https://cipher-editor-cn.picovr.com/*
// @match       https://cipher-editor-va.picovr.com/*
// @icon        https://cipher-editor-va.picovr.com/favicon.ico
// @require     https://code.jquery.com/jquery-3.6.0.min.js
// @require     https://greasyfork.org/scripts/473358-jszip/code/main.js
// @require     https://greasyfork.org/scripts/473361-xml-http-request-interceptor/code/main.js
// @require     https://greasyfork.org/scripts/473362-web-indexeddb-helper/code/main.js
// @require     https://greasyfork.org/scripts/474680-blitzrhythm-editor-mod-base-lib/code/main.js
// ==/UserScript==

const I18N = {
    en: { // English
        parameter: {
            search_page_sum: {
                name: "Search Page Count",
                description: "Number of pages searched from BeatSaver at one time",
            },
            search_timeout: {
                name: "Search Timeout",
                description: "Timeout for searching for songs",
            }
        },
        methods: {
            // test: {
            //     name: "Test",
            //     description: "Just a test button",
            // },
        },
        code: {
            search: {
                fail: "Search song failed!",
                tip_timeout: "It seems that the search has timed out. Do you need to modify the timeout parameter?"
            },
            convert: {
                title: "Convert To Custom Beatmap",
                description: "Convert official beatmaps to custom beatmaps to export beatmap with ogg file.",
                btn_name: "Start Convert",
                tip_failed: "Conversion failed, please refresh and try again!"
            }
        }
    },
    zh: { // Chinese
        parameter: {
            search_page_sum: {
                name: "搜索页面数量",
                description: "每次从BeatSaver搜索歌曲的页数,页数越多速度越慢",
            },
            search_timeout: {
                name: "搜索超时",
                description: "搜索歌曲的超时时间",
            }
        },
        methods: {
            // test: {
            //     name: "测试",
            //     description: "只是一个测试按钮",
            // },
        },
        code: {
            search: {
                fail: "搜索歌曲失败!",
                tip_timeout: "看来搜索超时了, 是否需要修改超时时间?"
            },
            convert: {
                title: "转换为自定义谱面",
                description: "将官方谱面转换为自定义谱面, 以导出带有Ogg文件的完整谱面压缩包。",
                btn_name: "开始转换谱面",
                tip_failed: "转换谱面失败,请刷新再试!"
            }
        }
    }
}

const PARAMETER = [
    {
        id: "search_page_sum",
        name: $t("parameter.search_page_sum.name"),
        description: $t("parameter.search_page_sum.description"),
        type: "number",
        default: 1,
        min: 1,
        max: 10
    },
    {
        id: "search_timeout",
        name: $t("parameter.search_timeout.name"),
        description: $t("parameter.search_timeout.description"),
        type: "number",
        default: 10 * 1000,
        min: 1000,
        max: 20 * 1000
    }
]

const METHODS = [
    // {
    //     name: $t("methods.test.name"),
    //     description: $t("methods.test.description"),
    //     func: () => {
    //         log($t("methods.test.name"))
    //     }
    // }, 
]

let pluginEnabled = false
let timerHandle = 0

function onEnabled() {
    pluginEnabled = true
    let timerFunc = () => {
        if (!pluginEnabled) return
        CipherUtils.waitLoading().then(() => {
            tick()
        }).catch(err => {
            console.error(err)
        }).finally(() => {
            timerHandle = setTimeout(timerFunc, 250)
        })
    }
    timerFunc()
}

function onDisabled() {
    if (timerHandle > 0) {
        clearTimeout(timerHandle)
        timerHandle = 0
    }
    pluginEnabled = false
    searchFromBeatSaver = false
}

function onParameterValueChanged(id, val) {
    log("onParameterValueChanged", id, val)
    // log("debug", $p(id))
}

// =====================================================================================

/**
 * 闪韵灵境工具类
 */
class CipherUtils {
    /**
     * 获取当前谱面的信息
     */
    static getNowBeatmapInfo() {
        let url = location.href
        // ID
        let matchId = url.match(/id=(\w*)/)
        let id = matchId ? matchId[1] : ""
        // BeatSaverID
        let beatsaverId = ""
        let nameBoxList = $(".css-tpsa02")
        if (nameBoxList.length > 0) {
            let name = nameBoxList[0].innerHTML
            let matchBeatsaverId = name.match(/\[(\w*)\]/)
            if (matchBeatsaverId) beatsaverId = matchBeatsaverId[1]
        }
        // 难度
        let matchDifficulty = url.match(/difficulty=(\w*)/)
        let difficulty = matchDifficulty ? matchDifficulty[1] : ""
        return { id, difficulty, beatsaverId }
    }

    /**
     * 添加歌曲校验数据头
     * @param {ArrayBuffer} rawBuffer 
     * @returns {Blob}
     */
    static addSongVerificationCode(rawBuffer) {
        // 前面追加数据,以通过校验
        let rawData = new Uint8Array(rawBuffer)
        let BYTE_VERIFY_ARRAY = [235, 186, 174, 235, 186, 174, 235, 186, 174, 85, 85]

        let buffer = new ArrayBuffer(rawData.length + BYTE_VERIFY_ARRAY.length)
        let dataView = new DataView(buffer)
        for (let i = 0; i < BYTE_VERIFY_ARRAY.length; i++) {
            dataView.setUint8(i, BYTE_VERIFY_ARRAY[i])
        }
        for (let i = 0; i < rawData.length; i++) {
            dataView.setUint8(BYTE_VERIFY_ARRAY.length + i, rawData[i])
        }
        return new Blob([buffer], { type: "application/octet-stream" })
    }

    /**
     * 获取当前页面类型
     * @returns 
     */
    static getPageType() {
        let url = window.location.href
        let matchs = url.match(/edit\/(\w{1,})/)
        if (!matchs) {
            return "home"
        } else {
            return matchs[1]
        }
    }

    /**
     * 显示Loading
     */
    static showLoading() {
        let maskBox = $('<div style="position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:9999;" id="loading"></div>')
        maskBox.append('<span style="display: block;position: absolute;width:40px;height:40px;left: calc(50vw - 20px);top: calc(50vh - 20px);"><svg viewBox="22 22 44 44"><circle cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6" class="css-14891ef"></circle></svg></span>')
        $("#root").append(maskBox)
    }

    /**
     * 隐藏Loading
     */
    static hideLoading() {
        $("#loading").remove()
    }

    /**
     * 网页弹窗
     */
    static showIframe(src) {
        this.hideIframe()
        let maskBox = $('<div style="position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:9999;" id="iframe_box"></div>')
        maskBox.click(this.hideIframe)
        maskBox.append('<iframe src="' + src + '" style="width:calc(100vw - 400px);height:calc(100vh - 200px);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:12px;"></iframe>')
        $("#root").append(maskBox)
    }

    /**
     * 隐藏Loading
     */
    static hideIframe() {
        $("#iframe_box").remove()
    }

    /**
     * 等待Loading结束
     * @returns 
     */
    static waitLoading() {
        return new Promise((resolve, reject) => {
            let handle = setInterval((() => {
                let loadingList = $(".css-c81162")
                if (loadingList && loadingList.length > 0) return
                clearInterval(handle)
                resolve()
            }), 500)
        })
    }
}

/**
 * BeatSaver工具类
 */
class BeatSaverUtils {
    /**
     * 搜索歌曲列表
     * @param {string} searchKey 搜索关键字
     * @param {number} pageCount 搜索页数
     * @returns 
     */
    static searchSongList(searchKey, pageCount = 1) {
        return new Promise(function (resolve, reject) {
            let songList = []
            let songInfoMap = {}
            let count = 0
            let cbFlag = false
            let timeoutCount = 0

            let beatsaverMappingStr = localStorage.getItem("BeatSaverMapping")
            let beatSaverMapping = beatsaverMappingStr ? JSON.parse(beatsaverMappingStr) : {
                mapping: {}
            }

            let funDone = () => {
                if (++count != pageCount) return
                cbFlag = true
                resolve({ songList, songInfoMap })
                if (timeoutCount > 0) {
                    let flag = confirm($t("code.search.tip_timeout"))
                    if (flag) showSetupPage()
                }
            }
            let funSuccess = data => {
                // 填充数据
                data.docs.forEach(rawInfo => {
                    let artist = rawInfo.metadata.songAuthorName
                    let bpm = rawInfo.metadata.bpm
                    let cover = rawInfo.versions[0].coverURL
                    let song_name = "[" + rawInfo.id + "]" + rawInfo.metadata.songName
                    let id = beatSaverMapping.mapping[rawInfo.id]
                    if (typeof id !== "number")
                        id = 80000000000 + parseInt(rawInfo.id, 36)
                    songList.push({ artist, bpm, cover, song_name, id })
                    let downloadURL = rawInfo.versions[0].downloadURL
                    let previewURL = rawInfo.versions[0].previewURL
                    songInfoMap[id] = { rawInfo, downloadURL, previewURL }
                })
                funDone()
            }
            let funFail = res => {
                if (res[0] === "timeout") timeoutCount++
                funDone()
            }
            for (let i = 0; i < pageCount; i++) {
                Utils.ajax({
                    url: "https://api.beatsaver.com/search/text/" + i + "?sortOrder=Relevance&q=" + searchKey,
                    method: "GET",
                    responseType: "json",
                    timeout: $p("search_timeout")
                }).then(funSuccess).catch(funFail)
            }
        })
    }


    /**
     * 从BeatSaver下载ogg文件
     * @param {number} zipUrl 歌曲压缩包链接
     * @param {function} onprogress 进度回调
     * @returns {Promise<blob, any>}
     */
    static async downloadSongFile(zipUrl, onprogress) {
        let blob = await Utils.downloadZipFile(zipUrl, onprogress)
        // 解压出ogg文件
        return await BeatSaverUtils.getOggFromZip(blob)
    }

    /**
     * 从压缩包中提取出ogg文件
     * @param {blob} zipBlob 
     * @param {boolean | undefined} verification 
     * @returns 
     */
    static async getOggFromZip(zipBlob, verification = true) {
        let zip = await JSZip.loadAsync(zipBlob)
        let eggFile = undefined
        for (let fileName in zip.files) {
            if (!fileName.endsWith(".egg")) continue
            eggFile = zip.file(fileName)
            break
        }
        if (verification) {
            let rawBuffer = await eggFile.async("arraybuffer")
            return CipherUtils.addSongVerificationCode(rawBuffer)
        } else {
            return await eggFile.async("blob")
        }
    }
}

/**
 * 通用工具类
 */
class Utils {
    /**
     * 下载压缩包文件
     * @param {number} zipUrl 歌曲压缩包链接
     * @param {function | undefined} onprogress 进度回调
     * @returns {Promise}
     */
    static downloadZipFile(zipUrl, onprogress) {
        return new Promise(function (resolve, reject) {
            Utils.ajax({
                url: zipUrl,
                method: "GET",
                responseType: "blob",
                onprogress,
            }).then(data => {
                resolve(new Blob([data], { type: "application/zip" }))
            }).catch(reject)
        })
    }

    /**
     * 异步发起网络请求
     * @param {object} config 
     * @returns 
     */
    static ajax(config) {
        return new Promise((resolve, reject) => {
            config.onload = res => {
                if (res.status >= 200 && res.status < 300) {
                    try {
                        resolve(JSON.parse(res.response))
                    } catch {
                        resolve(res.response)
                    }
                }
                else {
                    reject("HTTP Code: " + res.status)
                }
            }
            config.onerror = (...data) => {
                reject(["error", ...data])
            }
            config.ontimeout = (...data) => {
                reject(["timeout", ...data])
            }
            GM_xmlhttpRequest(config)
        })
    }
}

// =====================================================================================

let searchFromBeatSaver = false
let songInfoMap = {}
let lastPageType = "other"

// 加载XHR拦截器
function initXHRIntercept() {
    let _this = this
    let xhrIntercept = new XHRIntercept()
    /**
     * @param {XMLHttpRequest} self
     * @param {IArguments} args
     * @param {function} complete
     * @returns {boolean} 是否匹配
     */
    let onSend = function (self, args, complete) {
        let url = self._url
        if (!url || !searchFromBeatSaver) return
        if (url.startsWith("/song/staticList")) {
            // 获取歌曲列表
            let result = decodeURI(url).match(/songName=(\S*)&/)
            let key = ""
            if (result) key = result[1].replace("+", " ")
            CipherUtils.showLoading()
            BeatSaverUtils.searchSongList(key, $p("search_page_sum")).then(res => {
                self.extraSongList = res.songList
                songInfoMap = res.songInfoMap
                complete()
            }).catch(err => {
                alert($t("code.search.fail"))
                console.error(err)
                self.extraSongList = []
                complete()
            }).finally(() => {
                CipherUtils.hideLoading()
            })

            self.addEventListener("readystatechange", function () {
                if (this.readyState !== this.DONE) return
                const res = JSON.parse(this.responseText)
                if (this.extraSongList) {
                    res.data.data = this.extraSongList
                    res.data.total = res.data.data.length
                    this.extraSongList = []
                }
                Object.defineProperty(this, 'responseText', {
                    writable: true
                });
                this.responseText = JSON.stringify(res)
                setTimeout(() => {
                    fixSongListStyle()
                    addPreviewFunc()
                }, 200)
            });
            return true
        } else if (url.startsWith("/beatsaver/")) {
            let _onprogress = self.onprogress
            self.onprogress = undefined

            // 从BeatSaver下载歌曲
            let result = decodeURI(url).match(/\d{1,}/)
            let id = parseInt(result[0])
            BeatSaverUtils.downloadSongFile(songInfoMap[id].downloadURL, _onprogress).then(oggBlob => {
                songInfoMap[id].ogg = oggBlob
                saveBeatSaverMapping(id, songInfoMap[id].rawInfo)
                complete()
            }).catch(err => {
                console.error(err)
                self.onerror(err)
            })

            self.addEventListener("readystatechange", function () {
                if (this.readyState !== this.DONE) return
                let result = decodeURI(url).match(/\d{1,}/)
                let id = parseInt(result[0])
                Object.defineProperty(this, 'response', {
                    writable: true
                });
                this.response = songInfoMap[id].ogg
            });
            return true
        } else if (url.startsWith("/song/ogg")) {
            // 获取ogg文件下载链接
            let result = decodeURI(url).match(/id=(\d*)/)
            let id = parseInt(result[1])
            if (id < 80000000000) return
            self.addEventListener("readystatechange", function () {
                if (this.readyState !== this.DONE) return
                const res = JSON.parse(this.responseText)
                res.code = 0
                res.data = { link: "/beatsaver/" + id }
                res.msg = "success"
                Object.defineProperty(this, 'responseText', {
                    writable: true
                });
                this.responseText = JSON.stringify(res)
            });
            complete()
            return true
        }
    }
    xhrIntercept.onSend(onSend)
}

// Save BeatSaver Info
function saveBeatSaverMapping(id, rawInfo) {
    let beatsaverMappingStr = localStorage.getItem("BeatSaverMapping")
    let beatSaverMapping = beatsaverMappingStr ? JSON.parse(beatsaverMappingStr) : {}
    if (!beatSaverMapping.mapping) beatSaverMapping.mapping = {}
    beatSaverMapping.mapping[rawInfo.id] = id
    localStorage.setItem("BeatSaverMapping", JSON.stringify(beatSaverMapping))
}

/**
 * 更新数据库
 * @param {Boolean} isForce 强制转换
 * @returns 
 */
async function updateDatabase(isForce) {
    let BLITZ_RHYTHM = await WebDB.open("BLITZ_RHYTHM")
    let BLITZ_RHYTHM_files = await WebDB.open("BLITZ_RHYTHM-files")
    let BLITZ_RHYTHM_official = await WebDB.open("BLITZ_RHYTHM-official")
    let songInfos = []
    let hasChanged = false
    let songsInfo
    // 更新歌曲信息
    {
        let rawSongs = await BLITZ_RHYTHM.get("keyvaluepairs", "persist:songs")
        songsInfo = JSON.parse(rawSongs)
        let songsById = JSON.parse(songsInfo.byId)
        for (let key in songsById) {
            let officialId = songsById[key].officialId
            if (typeof officialId != "number" || (!isForce && officialId < 80000000000)) continue
            let songInfo = songsById[key]
            songInfos.push(JSON.parse(JSON.stringify(songInfo)))
            songInfo.coverArtFilename = songInfo.coverArtFilename.replace("" + songInfo.officialId, songInfo.id)
            songInfo.songFilename = songInfo.songFilename.replace("" + songInfo.officialId, songInfo.id)
            songInfo.officialId = ""

            // Add Source Info
            if (!songInfo.modSettings) songInfo.modSettings = {}
            if (!songInfo.modSettings.source) songInfo.modSettings.source = {}
            try {
                let beatsaverMapping = JSON.parse(localStorage.getItem("BeatSaverMapping") || "{}")
                let mapping = beatsaverMapping.mapping || {}
                for (let bsId in mapping) {
                    if (mapping[bsId] !== officialId) continue
                    songInfo.modSettings.source.beatsaverId = bsId
                    break
                }
            } catch (error) {
                console.error("Add source info failed:", error)
            }
            songsById[key] = songInfo
            hasChanged = true
        }
        songsInfo.byId = JSON.stringify(songsById)
    }
    // 处理文件
    for (let index in songInfos) {
        let songInfo = songInfos[index]
        // 复制封面和音乐文件
        let cover = await BLITZ_RHYTHM_official.get("keyvaluepairs", songInfo.coverArtFilename)
        let song = await BLITZ_RHYTHM_official.get("keyvaluepairs", songInfo.songFilename)
        await BLITZ_RHYTHM_files.put("keyvaluepairs", songInfo.coverArtFilename.replace("" + songInfo.officialId, songInfo.id), cover)
        await BLITZ_RHYTHM_files.put("keyvaluepairs", songInfo.songFilename.replace("" + songInfo.officialId, songInfo.id), song)
        // 添加info记录
        await BLITZ_RHYTHM_files.put("keyvaluepairs", songInfo.id + "_Info.dat", JSON.stringify({ _songFilename: "song.ogg" }))
    }
    // 保存数据
    if (hasChanged) await BLITZ_RHYTHM.put("keyvaluepairs", "persist:songs", JSON.stringify(songsInfo))
    BLITZ_RHYTHM.close()
    BLITZ_RHYTHM_files.close()
    BLITZ_RHYTHM_official.close()
    return hasChanged
}
/**
 * 修复歌单布局
 */
function fixSongListStyle() {
    let songListBox = $(".css-10szcx0")[0]
    songListBox.style["grid-template-columns"] = "repeat(3, minmax(0px, 1fr))"
    let songBox = songListBox.parentNode
    if ($(".css-1wfsuwr").length > 0) {
        songBox.style["overflow-y"] = "hidden"
        songBox.parentNode.style["margin-bottom"] = ""
    } else {
        songBox.style["overflow-y"] = "auto"
        songBox.parentNode.style["margin-bottom"] = "44px"
    }
    let itemBox = $(".css-bil4eh")
    for (let index = 0; index < itemBox.length; index++)
        itemBox[index].style.width = "230px"
}
/**
 * 在歌曲Card中添加双击预览功能
 */
function addPreviewFunc() {
    let searchBox = $(".css-1d92frk")
    $("#preview_tip").remove()
    searchBox.after("<div style='text-align: center;color:gray;padding-bottom:10px;' id='preview_tip'>双击歌曲可预览曲谱</div>")
    let infoViewList = $(".css-bil4eh")
    for (let index = 0; index < infoViewList.length; index++) {
        infoViewList[index].ondblclick = () => {
            let name = $(infoViewList[index]).find(".css-1y1rcqj")[0].innerHTML
            let result = name.match(/^\[(\w*)\]/)
            if (!result) return
            let previewUrl = "https://skystudioapps.com/bs-viewer/?id=" + result[1]
            CipherUtils.showIframe(previewUrl)
            // window.open(previewUrl)
        }
    }
}
/**
 * 添加通过BeatSaver搜索歌曲的按钮
 */
function applySearchButton() {
    let boxList = $(".css-1u8wof2") // 弹窗
    try {
        if (boxList.length == 0) throw "Box not found"
        let searchBoxList = boxList.find(".css-70qvj9")
        if (searchBoxList.length == 0) throw "item too few" // 搜索栏元素数量
        if (searchBoxList[0].childNodes.length >= 3) return // 搜索栏元素数量
    } catch {
        if (searchFromBeatSaver) searchFromBeatSaver = false
        return
    }

    let rawSearchBtn = $(boxList[0]).find("button")[0] // 搜索按钮

    // 添加一个按钮
    let searchBtn = document.createElement("button")
    searchBtn.className = rawSearchBtn.className
    searchBtn.innerHTML = "BeatSaver"
    $(rawSearchBtn.parentNode).append(searchBtn);

    // 绑定事件
    rawSearchBtn.onmousedown = () => {
        searchFromBeatSaver = false
        $("#preview_tip").remove()
    }
    searchBtn.onmousedown = () => {
        searchFromBeatSaver = true
        $(rawSearchBtn).click()
    }
}
/**
 * 添加转换官方谱面的按钮
 * @returns 
 */
async function applyConvertCiphermapButton() {
    let BLITZ_RHYTHM = await WebDB.open("BLITZ_RHYTHM")
    try {
        let rawSongs = await BLITZ_RHYTHM.get("keyvaluepairs", "persist:songs")
        let songsInfo = JSON.parse(rawSongs)
        let songsById = JSON.parse(songsInfo.byId)
        let songId = CipherUtils.getNowBeatmapInfo().id
        let officialId = songsById[songId].officialId
        if (!officialId) return
    } catch (error) {
        console.error(error)
        return
    } finally {
        BLITZ_RHYTHM.close()
    }

    let divList = $(".css-1tiz3p0")
    if (divList.length > 0) {
        if ($("#div-custom").length > 0) return
        let divBox = $(divList[0]).clone()
        divBox[0].id = "div-custom"
        divBox.find(".css-ujbghi")[0].innerHTML = $t("code.convert.title")
        divBox.find(".css-1exyu3y")[0].innerHTML = $t("code.convert.description")
        divBox.find(".css-1y7rp4x")[0].innerText = $t("code.convert.btn_name")
        divBox[0].onclick = e => {
            // 更新歌曲信息
            this.updateDatabase(true).then((hasChanged) => {
                if (hasChanged) setTimeout(() => { window.location.reload() }, 1000)
            }).catch(err => {
                console.log("Convert map failed:", err)
                alert($t("code.convert.btn_name"))
            })
        }
        $(divList[0].parentNode).append(divBox)
    }
}

/**
 * 隐藏按钮
 */
function hideConvertCiphermapButton() {
    $("#div-custom").remove()
}
/**
 * 定时任务 1s
 */
function tick() {
    let pageType = CipherUtils.getPageType()
    if (pageType !== "home") {
        if (pageType != lastPageType) {
            // 隐藏按钮
            if (pageType !== "download")
                hideConvertCiphermapButton()
            // 更新歌曲信息
            updateDatabase().then((hasChanged) => {
                if (hasChanged) setTimeout(() => { window.location.reload() }, 1000)
            }).catch(err => {
                console.log("Update map info failed:", err)
                alert($t("tip_failed"))
            })
        } else if (pageType === "download") {
            applyConvertCiphermapButton()
        }
    } else {
        applySearchButton()
    }
    lastPageType = pageType
}

(function () {
    'use strict'

    // 初始化XHR拦截器
    initXHRIntercept()
})()