A
B

atcoder better

// UserScript
// @name Atcoder Better!
// @namespace https://greasyfork.org/users/747162
// @version 1.19.0
// @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
// @author 北极小狐
// @match 😕/atcoder.jp/
// @run-at document-start
// @connect www2.deepl.com
// @connect api-free.deepl.com
// @connect api.deepl.com
// @connect api.deeplx.org
// @connect www.iflyrec.com
// @connect dict.youdao.com
// @connect api.interpreter.caiyunai.com
// @connect translate.google.com
// @connect openai.api2d.net
// @connect api.openai.com
// @connect www.luogu.com.cn
// @connect vjudge.net
// @connect clist.by
// @connect greasyfork.org
// @connect sustech.edu.cn
// @connect aowuucdn.oss-cn-beijing.aliyuncs.com
// @connect aowuucdn.oss-accelerate.aliyuncs.com
// @connect 127.0.0.1
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant GM_getResourceText
// @icon https://aowuucdn.oss-accelerate.aliyuncs.com/atcoder.png
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/turndown/7.2.0/turndown.min.js#sha512-sJzEecN5Nk8cq81zKtGq6/z9Z/r3q38zV9enY75IVxiG7ybtlNUt864sL4L1Kf36bYIwxTMVKQOtU4VhD7hGrw==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/markdown-it/13.0.2/markdown-it.js#sha512-2LtYcLGnCbAWz9nDIrfG2pHFiFu9n+3oGecQlzLuYsLgen/oxiYscGWnDST9J9EZanlsQkDD0ZP2n/6peDuALQ==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/crypto-js/4.2.0/crypto-js.min.js#sha512-a+SUDuwNzXDvz4XrIcXHuCf089/iJAoN4lmrXJg18XnduKK6YlDHNRalv4yd1N40OKI80tFidF+rqTFKGPoWFQ==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/chroma-js/2.4.2/chroma.min.js#sha512-zInFF17qBFVvvvFpIfeBzo7Tj7+rQxLeTJDmbxjBz5/zIr89YVbTNelNhdTT+/DCrxoVzBeUPVFJsczKbB7sew==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/xterm/5.5.0/xterm.js#sha512-Gujw5GajF5is3nMoGv9X+tCMqePLL/60qvAv1LofUZTV9jK8ENbM9L+maGmOsNzuZaiuyc/fpph1KT9uR5w3CQ==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dexie/4.0.7/dexie.min.js#sha512-882VotT07mOQRzqIxsyxHzJX0XUaoeee3qXp4THg1A0KI0XFnWFAaLFQm0x6OW3pHSIipVZW+gzQ1w9b6uvkVw==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/i18next/23.11.5/i18next.min.js#sha512-3RSGkmT48HnO+hlmzGYDx5/w2LIBX0O5hSuYX6KWAxmvVlSjFgoxIaWa2tlMExheGvt3lLyxeTsXfpC47yb8CQ==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/i18next-http-backend/2.5.2/i18nextHttpBackend.min.js#sha512-bBb+wrGRTx4MvHpksYb1Iv5oJ1o8ineCqpc0cnTgdJQhuAFJJ93SEVXxUOCptvt0vAqYdjzWO5emorYUBt6Ceg==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/jquery-i18next/1.2.1/jquery-i18next.min.js#sha512-79RgNpOyaf8AvNEUdanuk1x6g53UPoB6Fh2uogMkOMGADBG6B0DCzxc+dDktXkVPg2rlxGvPeAFKoZxTycVooQ==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/highlight.js/11.9.0/highlight.min.js#sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==
// @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dialog-polyfill/0.5.6/dialog-polyfill.min.js#sha512-qUIG93zKzcLBVD5RGRbx2PBmbVRu+tJIl+EPLTus0z8I1AMru9sQYdlf6cBacSzYmZVncB9rcc8rYBnazqgrxA==
// @resource acwing_cpp_code_completer https://aowuucdn.oss-accelerate.aliyuncs.com/acwing_cpp_code_completer-0.0.11.json#sha512-DQVpao4qMMExToRdid0g/S0nbO/C9hwCECjI5aW8A0g7nvi8hEcD2Lw3QIqdJBV7haP15oJOocfwuiw7ryTO9w==
// @resource wandboxlist https://wandbox.org/api/list.json
// @resource xtermcss https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/xterm/5.5.0/xterm.min.css#sha512-XpXUuzg5afNt1bsgnrOesXP70TLH8tXYYK5sK+Y0UV+YBvJn9EfRFYWy4HT3TVDfH0nl1CO0lwOxIrt2gk9qjg==
// @resource selectpagecss https://aowuucdn.oss-accelerate.aliyuncs.com/css/selectpage.css#sha512-cRXJfA2tEcAxHEKylJfxteY17N7j9fia3waahHOVnvl63uVZT9OQ7jjjpofZMVZ4JSX3BRET+mI8UvKnsXd3NA==
// @resource dialogpolyfillcss https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dialog-polyfill/0.5.6/dialog-polyfill.min.css#sha512-J2+1q+RsZuJXabBfH1q/fgRr6jMy9By5SwVLk7bScEW7NFJkMUXxfeOyyxtDe6fsaJ4jsciexSlGrPYn9YbBIg==
// @license GPL3
// @compatible Chrome
// @compatible Firefox
// @compatible Edge
// @incompatible safari
// @supportURL https://github.com/beijixiaohu/OJBetter/issues
// @name:zh-TW AtCoder Better!
// @name:en AtCoder Better!
// @name:de AtCoder Better!
// @name:fr AtCoder Better!
// @name:ko AtCoder Better!
// @name:pt AtCoder Better!
// @name:ja AtCoder Better!
// @name:es AtCoder Better!
// @name:it AtCoder Better!
// @name:hi AtCoder Better!
// @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
// @description:zh-TW 一個適用於 AtCoder 的 Tampermonkey 腳本,增強功能與界面。
// @description:en A Tampermonkey script for AtCoder that enhances functionality and interface.
// @description:de Ein Tampermonkey-Skript für AtCoder, das Funktionalität und Benutzeroberfläche verbessert.
// @description:fr Un script Tampermonkey pour AtCoder qui améliore les fonctionnalités et l'interface.
// @description:ko AtCoder를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
// @description:pt Um script Tampermonkey para AtCoder que aprimora a funcionalidade e a interface.
// @description:ja AtCoder用のTampermonkeyスクリプトで機能とインターフェースを強化します。
// @description:es Un script Tampermonkey para AtCoder que mejora la funcionalidad y la interfaz.
// @description:it Uno script Tampermonkey per AtCoder che migliora la funzionalità e l'interfaccia.
// @description:hi AtCoder के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
// @downloadURL https://update.greasyfork.org/scripts/471106/Atcoder Better!.user.js
// @updateURL https://update.greasyfork.org/scripts/471106/Atcoder Better!.meta.js
// /UserScript

/**

  • @namespace OJBetter
  • @desc 主命名空间
    */
    const OJBetter = {};

/**

  • @namespace state
  • @desc 描述脚本的当前状态。
  • @memberof OJBetter
    /
    OJBetter.state = {
    /
    * @type {string} 脚本名/
    name: GM_info.script.name,
    /** @type {string} 格式化后的脚本名
    /
    formatName: undefined,
    /** @type {string} 版本号/
    version: GM_info.script.version,
    /** @type {boolean?} 是否跳过页面加载等待 /
    notWaiteLoaded: undefined,
    /
    * @type {string} 最后公告版本,用于标识版本更新完成提示 /
    lastAnnounceVer: undefined,
    /
    * @type {string} 最后读取的有效公告版本 /
    lastReadAnnounceVer: undefined,
    /
    * @type {number} 当前已打开的模态对话框数量
    /
    openDialogCount: 0
    };

/**

  • @namespace common
  • @desc 通用设置和属性。
  • @memberof OJBetter
    /
    OJBetter.common = {
    /
    * @type {string} 网站的主机地址 /
    hostAddress: location.origin,
    /
    * @type {string} 网站当前真实的黑暗模式 /
    realDarkMode: undefined,
    /
    * @type {string?} AtCoder的CSRF令牌 /
    at_csrf_token: undefined,
    /
    * @type {Array?} 任务队列 /
    taskQueue: undefined,
    /
    * @type {object} OJBetter数据库连接实例/
    database: undefined,
    /** @type {object} turndownService实例
    /
    turndownService: undefined,
    };

/**

  • @namespace basic
  • @desc 基本的用户界面设置。
  • @memberof OJBetter
    /
    OJBetter.basic = {
    /
    * @type {string} 黑暗模式设置 /
    darkMode: undefined,
    /
    * @type {boolean?} 是否展开折叠块 /
    expandFoldingblocks: undefined,
    /
    * @type {boolean?} 是否开启折叠块渲染性能优化 /
    renderPerfOpt: undefined,
    /
    * @type {boolean?} 是否开启下拉选择框性能优化 /
    selectElementPerfOpt: undefined,
    /
    * @type {boolean?} 评论区分页 /
    commentPaging: undefined,
    /
    * @type {boolean?} 显示跳转到Luogu按钮 /
    showJumpToLuogu: undefined,
    /
    * @type {boolean?} 显示跳转到Virtual Judge按钮 /
    showCF2vjudge: undefined,
    /
    * @type {boolean?} 比赛排行榜重新着色 */
    standingsRecolor: undefined
    };

/**

  • @namespace typeOfPage
  • @desc 页面类型判断。
  • @memberof OJBetter
    /
    OJBetter.typeOfPage = {
    /
    * @type {boolean?} 是否是轻量站 /
    is_mSite: false,
    /
    * @type {boolean?} 是否是acmsguru页面 /
    is_acmsguru: false,
    /
    * @type {boolean?} 是否是旧版LaTeX页面 /
    is_oldLatex: false,
    /
    * @type {boolean?} 是否是题目集页面 /
    is_contest: undefined,
    /
    * @type {boolean?} 是否是题目页面 /
    is_problem: undefined,
    /
    * @type {boolean?} 是否是完整的问题集页面 /
    is_completeProblemset: false,
    /
    * @type {boolean?} 是否是问题集中的问题页面 /
    is_problemset_problem: false,
    /
    * @type {boolean?} 是否是问题集页面 /
    is_problemset: false,
    /
    * @type {boolean?} 是否是Codeforces排名页面 /
    is_cfStandings: false,
    /
    * @type {boolean?} 是否是提交页面 /
    is_submitPage: false,
    /
    * @type {boolean?} 是否是代码状态页面 /
    is_statePage: false,
    /
    * @type {boolean?} 是否是提交记录页面 /
    is_submissions: false,
    /
    * @type {boolean?} 是否是主页 /
    is_homepage: undefined,
    /
    * @type {boolean?} 是否选择的是英语页面 /
    isEnglishLanguage: undefined,
    /
    * @type {boolean?} 是否是题解页面 */
    isEditorial: undefined,
    };

/**

  • @namespace localization
  • @desc 本地化设置。
  • @memberof OJBetter
    /
    OJBetter.localization = {
    /
    * @type {string?} 网站语言 /
    websiteLang: undefined,
    /
    * @type {string?} 脚本语言 */
    scriptLang: undefined
    };

/**

  • @namespace translation
  • @desc 翻译设置。
  • @memberof OJBetter
    /
    OJBetter.translation = {
    /
    * @type {string?} 翻译服务选择 /
    choice: undefined,
    /
    * @type {string?} 目标语言 /
    targetLang: undefined,
    comment: {
    /
    * @type {string?} 评论翻译服务选择 /
    choice: undefined,
    /
    * @type {string?} 评论翻译模式 /
    transMode: undefined
    },
    auto: {
    /
    * @type {boolean?} 自动翻译开关 /
    enabled: undefined,
    /
    * @type {number?} 短文本长度限制 /
    shortTextLength: undefined,
    mixTrans: {
    /
    * @type {boolean?} 混合翻译开关 /
    enabled: undefined,
    /
    * @type {Array?} 混合翻译服务列表 /
    servers: undefined
    }
    },
    memory: {
    /
    * @type {boolean?} 翻译记忆开关 /
    enabled: undefined,
    /
    * @type {Object?} 翻译记忆树 /
    ttTree: undefined
    },
    /
    * @type {string?} 重翻译时的行为 /
    retransAction: undefined,
    /
    * @type {number?} 等待时间 /
    waitTime: undefined,
    /
    * @type {boolean?} 替换符 /
    replaceSymbol: undefined,
    /
    * @type {boolean?} 过滤文本中的*号 */
    filterTextWithoutEmphasis: undefined
    };

/**

  • @namespace clist
  • @desc Clist相关设置。
  • @memberof OJBetter
    /
    OJBetter.clist = {
    enabled: {
    /
    * @type {boolean?} 比赛页面开关 /
    contest: undefined,
    /
    * @type {boolean?} 问题页面开关 /
    problem: undefined,
    /
    * @type {boolean?} 问题集页面开关 /
    problemset: undefined
    },
    /
    * @type {boolean?} Rating数据防剧透 /
    ratingHidden: undefined,
    /
    * @type {string?} Clist key */
    authorization: undefined
    };

/**

  • @namespace monaco
  • @desc Monaco编辑器配置。
  • @memberof OJBetter
    /
    OJBetter.monaco = {
    /
    * @type {boolean?} 在问题页面上启用Monaco编辑器 /
    enableOnProblemPage: undefined,
    /
    * @type {boolean?} 美化pre代码块 /
    beautifyPreBlocks: undefined,
    /
    * @type {boolean} Monaco编辑器加载完成标志 /
    loaderOnload: false,
    lsp: {
    /
    * @type {Array?} LSP套接字数组 /
    socket: [],
    /
    * @type {boolean?} 是否启用LSP /
    enabled: undefined,
    /
    * @type {string?} 工作路径 /
    workUri: undefined,
    /
    * @type {string?} 套接字URL /
    socketUrl: undefined
    },
    complet: {
    /
    * @type {boolean?} 是否启用C++代码补全模板 /
    cppCodeTemplate: undefined,
    /
    * @type {Object?} 自定义配置 /
    customConfig: undefined
    },
    /
    * @type {Object?} Monaco编辑器实例 /
    editor: null,
    /
    * @type {Array?} 代码块美化的Monaco编辑器实例 /
    beautifyEditor: [],
    /
    * @type {string?} 在线编译器选择 /
    onlineCompilerChoice: undefined,
    /
    * @type {string?} 记忆编译器语言选择 /
    compilerSelection: undefined,
    /
    * @type {string?} 当前选择的语言 /
    nowLangSelect: undefined,
    setting: {
    /
    * @type {Array?} 语言设置数组 /
    language: [],
    /
    * @type {string?} 位置 /
    position: undefined,
    /
    * @type {boolean} 位置初始化标志 /
    position_initialized: false,
    /
    * @type {number?} 字体大小 /
    fontsize: undefined,
    /
    * @type {boolean?} 鼠标滚动锁定 /
    alwaysConsumeMouseWheel: undefined,
    /
    * @type {boolean?} 提交代码二次确认 /
    isCodeSubmitDoubleConfirm: undefined,
    /
    * @type {boolean?} 测试通过后自动提交 /
    autoSubmitAfterPass: undefined,
    /
    * @type {string?} 提交按钮位置 /
    submitButtonPosition: undefined,
    /
    * @type {boolean?} 自动保存代码 */
    autoMemoryCode: undefined
    }
    };

/**

  • @namespace deepl
  • @desc DeepL翻译服务配置。
  • @memberof OJBetter
    /
    OJBetter.deepl = {
    /
    * @type {Object?} DeepL配置对象 /
    configs: undefined,
    config: {
    /
    * @type {string?} 类型 /
    type: undefined,
    /
    * @type {string?} 名称 /
    name: undefined,
    /
    * @type {string?} API类型 /
    apiGenre: undefined,
    /
    * @type {string?} API密钥 /
    key: undefined,
    /
    * @type {string?} 代理 /
    proxy: undefined,
    /
    * @type {Object?} 额外请求头 /
    header: undefined,
    /
    * @type {Object?} 额外请求数据 /
    data: undefined,
    quota: {
    /
    * @type {string?} 余额URL /
    url: undefined,
    /
    * @type {string?} 余额请求方法 /
    method: undefined,
    /
    * @type {Object?} 余额请求头 /
    header: undefined,
    /
    * @type {Object?} 余额请求数据 /
    data: undefined,
    /
    * @type {number?} 剩余配额 /
    surplus: undefined
    }
    },
    /
    * @type {boolean?} 启用重点保护 /
    enableEmphasisProtection: undefined,
    /
    * @type {boolean?} 启用链接保护 */
    enableLinkProtection: undefined
    };

/**

  • @namespace chatgpt
  • @desc ChatGPT服务配置。
  • @memberof OJBetter
    /
    OJBetter.chatgpt = {
    /
    * @type {Object?} ChatGPT配置对象 /
    configs: undefined,
    config: {
    /
    * @type {string?} 名称 /
    name: undefined,
    /
    * @type {string?} 模型 /
    model: undefined,
    /
    * @type {string?} API密钥 /
    key: undefined,
    /
    * @type {string?} 代理 /
    proxy: undefined,
    /
    * @type {Object?} 额外请求头 /
    header: undefined,
    /
    * @type {Object?} 额外请求数据 /
    data: undefined,
    quota: {
    /
    * @type {string?} 余额URL /
    url: undefined,
    /
    * @type {string?} 余额请求方法 /
    method: undefined,
    /
    * @type {Object?} 余额请求头 /
    header: undefined,
    /
    * @type {Object?} 余额请求数据 /
    data: undefined,
    /
    * @type {number?} 剩余配额 /
    surplus: undefined
    }
    },
    /
    * @type {boolean?} 是否为流式传输 /
    isStream: undefined,
    /
    * @type {string?} 是否使用自定义Prompt /
    customPrompt: undefined,
    /
    * @type {boolean?} 是否作为系统Prompt */
    asSystemPrompt: undefined
    };

/**

  • @namespace preference
  • @desc 偏好设置
  • @memberof OJBetter
    /
    OJBetter.preference = {
    /
    * @type {boolean?} 是否显示加载动画 /
    showLoading: undefined,
    /
    * @type {boolean?} 是否显示悬停目标区域 /
    hoverTargetAreaDisplay: undefined,
    /
    * @type {string?} 按钮图标大小 /
    iconButtonSize: undefined,
    /
    * @type {boolean?} 是否显示同比赛题目列表*/
    showSameContestProblems: undefined,
    };

/**

  • @namespace dev
  • @desc 维护
  • @memberof OJBetter
    /
    OJBetter.dev = {
    /
    * @type {boolean?} 是否显示规则标记 */
    isRuleMarkingEnabled: undefined,
    };

/**

  • @namespace about
  • @desc 关于页信息
  • @memberof OJBetter
    /
    OJBetter.about = {
    /
    * @type {string?} 更新通道 /
    updateChannel: undefined,
    /
    * @type {string?} 更新源 */
    updateSource: undefined
    };

/**

  • @namespace supportList
  • @desc 支持列表
  • @memberof OJBetter
    /
    OJBetter.supportList = {
    /
    * @type {object} 翻译支持列表和对应语言代码/
    translationSupport: {
    'deepl': { 'zh': 'ZH', 'de': 'DE', 'fr': 'FR', 'ko': 'KO', 'pt': 'PT', 'ja': 'JA', 'es': 'ES', 'it': 'IT' },
    'iflyrec': { 'zh': '1' },
    'youdao': { 'zh': 'zh-CHS', 'zh-Hant': 'zh-CHT', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
    'google': { 'zh': 'zh-CN', 'zh-Hant': 'zh-TW', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
    'caiyun': { 'zh': 'auto2zh', 'ja': 'auto2ja', 'ko': 'auto2ko', 'es': 'auto2es', 'fr': 'auto2fr' },
    'openai': { 'zh': 'Chinese', 'zh-Hant': 'Traditional Chinese', 'de': 'German', 'fr': 'French', 'ko': 'Korean', 'pt': 'Portuguese', 'ja': 'Japanese', 'es': 'Spanish', 'it': 'Italian', 'hi': 'Hindi' }
    },
    /** @type {object} 更新源支持列表
    /
    updateSourceSupportList: {
    'greasyfork': {
    'release': true,
    'dev': false
    },
    'github': {
    'release': true,
    'dev': true
    },
    'aliyunoss': {
    'release': true,
    'dev': true
    }
    }
    }

// ------------------------------
// 一些工具函数
// ------------------------------

/**

  • 延迟函数
  • @param {number} ms 延迟时间(毫秒)
  • @returns {Promise}
    */
    function OJB_delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
    }

/**

  • 等待直到指定的条件函数返回true。
  • @param {() => boolean} conditionCheck 一个无参数的函数,用于检查条件是否满足。当函数返回true时,表示条件已满足。
  • @param {number} [interval=100] 检查条件的间隔时间,单位为毫秒。默认为100毫秒。
  • @returns {Promise} 返回一个Promise,在条件满足时解决。
    */
    async function OJB_waitUntilTrue(conditionCheck, interval = 100) {
    return new Promise((resolve) => {
    const checkCondition = async () => {
    if (conditionCheck()) {
    resolve();
    } else {
    await OJB_delay(interval);
    checkCondition();
    }
    };
    checkCondition();
    });
    }

/**

  • 动态加载JavaScript库并返回一个Promise,该Promise在脚本加载完成后解决。

  • @param {string} url - 要加载的JavaScript库的URL地址。

  • @param {string} [expectedHash] - 可选的Base64编码的SHA-512哈希值,用于校验脚本内容。格式为 "sha512-<Base64编码的哈希值>"。

  • @returns {Promise} 一个Promise,它在脚本加载并执行完成后解决。
    /
    async function OJB_LoadJS(url, expectedHash) {
    /
    *

    • 计算给定数据的SHA-512哈希值,并将其转换为十六进制字符串。
    • @param {string} data - 要计算哈希值的数据。
    • @returns {Promise} 一个Promise,它解析为数据的SHA-512哈希值的十六进制字符串。
      */
      const calculateHash = async (data) => {
      const encoder = new TextEncoder();
      const dataBuffer = encoder.encode(data);
      const hashBuffer = await crypto.subtle.digest('SHA-512', dataBuffer);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
      };

    /**

    • 将Base64编码的字符串转换为十六进制字符串。
    • @param {string} base64 - Base64编码的字符串。
    • @returns {string} 转换后的十六进制字符串。
      */
      const base64ToHex = (base64) => {
      const binaryString = atob(base64);
      const byteArray = new Uint8Array(binaryString.length);
      for (let i = 0; i < binaryString.length; i++) {
      byteArray[i] = binaryString.charCodeAt(i);
      }
      return Array.from(byteArray).map(b => b.toString(16).padStart(2, '0')).join('');
      };

    try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(Failed to fetch script: ${response.statusText});
    const scriptContent = await response.text();

     if (expectedHash) {
         // 去掉前缀 "sha512-"
         const base64Hash = expectedHash.replace(/^sha512-/, '');
         const actualHash = await calculateHash(scriptContent);
         const expectedHashHex = base64ToHex(base64Hash);
         if (actualHash !== expectedHashHex) throw new Error('SHA-512 hash mismatch');
     }
    
     const scriptElement = document.createElement("script");
     scriptElement.textContent = scriptContent;
     document.head.prepend(scriptElement);
    
     return Promise.resolve();
    

    } catch (error) {
    return Promise.reject(error);
    }
    }

/**

  • 安全地创建JQuery对象
  • @description 通过字符串创建JQuery对象时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
  • @param {string} string - 字符串。
  • @returns {JQuery} JQuery对象
    */
    const OJB_safeCreateJQElement = function (string) {
    return $(string.replace(/^\s+/, ""));
    }

/**

  • 将数字或者字符串解析为数字。
  • @memberof OJBetter.common
  • @param {string} val 要解析的字符串
  • @param {boolean} [strict=false] 是否进行严格类型检查
  • @returns {number} 解析结果
  • @throws {Error} 如果解析失败,则抛出错误
    */
    const OJB_parseNumber = (val, strict = false) => {
    const num = Number(val);
    if (isNaN(num) || (strict && val.toString() !== num.toString())) {
    throw new Error('Invalid number');
    }
    return num;
    };

/**

  • 将字符串解析为布尔值
  • @param {string} val - 要解析的字符串
  • @param {boolean} strict - 是否进行严格类型检查
  • @returns {boolean} - 解析结果
  • @throws {Error} - 如果解析失败,则抛出错误
    */
    const OJB_parseBoolean = (val, strict) => {
    if (strict) {
    if (val === true || val === false) return val;
    throw new Error('Invalid boolean');
    }
    return val === 'true' ? true : val === 'false' ? false : val;
    };

/**

  • 将字符串解析为对象
  • @param {string} val - 要解析的字符串
  • @returns {Object} - 解析结果
  • @throws {Error} - 如果解析失败,则抛出错误
    */
    const OJB_parseObject = val => {
    try {
    return JSON.parse(val);
    } catch {
    throw new Error('Invalid JSON');
    }
    };

/**

  • 将字符串解析为键值对数组
  • @param {string} val - 要解析的字符串
  • @returns {Object[]} - 解析结果
  • @throws {Error} - 如果解析失败,则抛出错误
    */
    const OJB_parseLinePairArray = val => {
    if (typeof val !== 'string' || val.trim() === '') return [];
    return val.split("\n").filter(line => line.trim() !== '').map(line => {
    const indexOfFirstColon = line.indexOf("😊;
    if (indexOfFirstColon === -1) throw new Error('Invalid LinePairArray format: ":" is missing');
    const key = line.substring(0, indexOfFirstColon).trim();
    const value = line.substring(indexOfFirstColon + 1).trim();
    return { [key]: value };
    });
    };

/**

  • 移除文本中的HTML标签
  • @param {string} text - 包含HTML标签的文本
  • @returns {string} - 移除HTML标签后的文本
    /
    const OJB_removeHTMLTags = function (text) {
    return text.replace(/</?[a-zA-Z]+("["]*"|'[']
    '|[^'">])*>/g, '');
    }

/**

  • 获取对象中指定路径表达式的值
  • @param {Object} obj - 要计算的对象
  • @param {string} pathOrExpression - 要计算的路径表达式
  • @returns {any} - 计算结果
  • @example
  • const obj = {
  • "a": {
  • "b": 1
    
  • },
  • "c": 2
  • };
  • OJB_evaluatePathOrExpression(obj, "a.b"); // 1
  • OJB_evaluatePathOrExpression(obj, "a.b + c"); // 3
  • OJB_evaluatePathOrExpression(obj, "a.b + a.c"); // 1
    */
    function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
    const hasOperator = /[+-*/]/.test(pathOrExpression);
    const getPathValue = (obj, path) => {
    return path.split('.').reduce((acc, part) => {
    return acc !== undefined && acc !== null && acc.hasOwnProperty(part) ? acc[part] : undefined;
    }, obj);
    };
    const evaluateExpression = (obj, expression) => {
    const tokens = expression.split(/([+-*/])/).map(token => token.trim());
    const values = tokens.map(token => {
    if (/[+-*/]/.test(token)) {
    return token;
    } else {
    const value = getPathValue(obj, token);
    return value !== undefined ? value : 0;
    }
    });
    const evaluatedExpression = values.join(' ');
    try {
    return Function('use strict'; return (${evaluatedExpression});)();
    } catch (e) {
    console.error('Expression evaluation error:', e);
    return undefined;
    }
    };
    return hasOperator ? evaluateExpression(obj, pathOrExpression) : getPathValue(obj, pathOrExpression);
    }

/**

  • 获取 GM 存储的值并根据类型进行处理

  • @param {string} key - 要检索的值的键。

  • @param {any} defaultValue - 如果值未找到,则返回的默认值。

  • @param {Object} [options={}] - 配置选项对象。

  • @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。

  • @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。

  • @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。

  • @returns {any} - 检索到的值。
    */
    const OJB_getGMValue = (key, defaultValue, { type = 'string', strict = false, pathOrExpression = '' } = {}) => {
    let value = GM_getValue(key);
    if (value === undefined || value === null || value === "") {
    GM_setValue?.(key, defaultValue);
    return defaultValue;
    }

    const parsers = {
    string: val => val,
    number: (val) => OJB_parseNumber(val, strict),
    boolean: (val) => OJB_parseBoolean(val, strict),
    object: OJB_parseObject,
    array: OJB_parseObject,
    linePairArray: OJB_parseLinePairArray
    };

    if (!(type in parsers)) {
    console.error(Unsupported type: ${type});
    return defaultValue;
    }

    try {
    value = parserstype;
    } catch (e) {
    console.error('Error:', e.message);
    return defaultValue;
    }

    // The pathOrExpression processing is not applicable to linePairArray type
    if ((type === 'object' || type === 'array') && pathOrExpression) {
    const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
    if (evaluated === undefined) {
    console.error('Path or expression evaluation returned undefined');
    return defaultValue;
    }
    value = evaluated;
    }

    return value;
    };

/**

  • 版本号比较方法
  • @param {string} version1 版本号1
  • @param {string} version2 版本号2
  • @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
    */
    const OJB_compareVersions = function (version1 = "0", version2 = "0") {
    const v1Array = version1.split(".").map(Number);
    const v2Array = version2.split(".").map(Number);
    const length = Math.max(v1Array.length, v2Array.length);
    for (let i = 0; i < length; i++) {
    const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
    if (diff) return Math.sign(diff);
    }
    return 0;
    }

/**

  • 获取上一个主版本号
  • @param {string} currentVersion 当前版本号
  • @returns {string} 上一个主版本号
    */
    const OJB_getPreviousVersion = function (currentVersion) {
    const versionArray = currentVersion.split(".").map(Number);
    let lastNonZeroIndex = versionArray.length - 1;
    while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
    lastNonZeroIndex--;
    }
    if (lastNonZeroIndex >= 0) {
    versionArray[lastNonZeroIndex]--;
    for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
    versionArray[i] = 0;
    }
    }
    return versionArray.join(".");
    };

/**

  • 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数

  • @param {Object} options - 配置对象

  • @param {string} options.selector - CSS选择器文本

  • @param {Function} options.callback - 回调函数,接收变动的节点作为参数

  • @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调

  • @param {Element} [options.root=document.body] - 在哪个根节点下监听变化

  • @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
    */
    function OJB_observeElement({
    selector,
    callback,
    triggerOnExist = true,
    root = document.body,
    subtree = false
    }) {
    // 尝试获取选择器指定的元素
    const targetNode = root.querySelector(selector);

    if (targetNode) {
    // 如果元素已存在,直接开始观察
    observeAndReport(targetNode, callback);
    // 如果triggerOnExist为true,则立即触发一次回调
    if (triggerOnExist) {
    callback(targetNode);
    }
    } else {
    // 如果元素不存在,监听DOM变化直到该元素被添加
    const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((node) => {
    if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
    observeAndReport(node, callback);
    if (triggerOnExist) {
    callback(node);
    }
    observer.disconnect(); // 停止监听
    }
    });
    });
    });

     observer.observe(root, { childList: true, subtree, attributes: false });
    

    }

    function observeAndReport(node, callback) {
    const childObserver = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((addedNode) => {
    if (addedNode.nodeType === Node.ELEMENT_NODE) {
    callback(addedNode); // 执行回调函数
    }
    });
    });
    });

     childObserver.observe(node, { childList: true, subtree: true, attributes: false });
    

    }
    }

/**

  • 初始化全局变量
    */
    async function initVar() {
    const { hostname, href } = window.location;
    OJBetter.state.formatName = (() => OJBetter.state.name
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^a-z0-9-]/g, ''))();
    OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
    OJBetter.state.lastReadAnnounceVer = OJB_getGMValue("lastReadAnnounceVer", "0");
    OJBetter.typeOfPage.is_contest = //contests/[^/]+/tasks/?$/.test(href);
    OJBetter.typeOfPage.is_problem = href.includes('/tasks/');
    OJBetter.typeOfPage.is_homepage = (href === 'https://atcoder.jp/' || href === 'https://atcoder.jp/?lang=ja');
    OJBetter.typeOfPage.isEnglishLanguage = $('meta[http-equiv="Content-Language"]').attr('content') === 'en';
    OJBetter.typeOfPage.isEditorial = href.includes("editorial");
    OJBetter.localization.websiteLang = OJB_getGMValue("localizationLanguage", "zh");
    OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
    OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
    OJBetter.basic.selectElementPerfOpt = OJB_getGMValue("selectElementPerfOpt", true);
    OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
    OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
    OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
    OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
    OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
    OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
    OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
    OJBetter.translation.comment.transMode = OJB_getGMValue("commentTranslationMode", "0");
    OJBetter.translation.comment.choice = OJB_getGMValue("commentTranslationChoice", "0");
    OJBetter.translation.memory.enabled = OJB_getGMValue("memoryTranslateHistory", true);
    OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
    OJBetter.translation.auto.shortTextLength = OJB_getGMValue("shortTextLength", "2000");
    OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
    OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
    OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue("allowMixTrans", true);
    OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue("mixedTranslation", ['deepl', 'iflyrec', 'youdao', 'caiyun']);
    OJBetter.common.taskQueue = new TaskQueue();
    OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
    OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue("filterTextWithoutEmphasis", false);
    OJBetter.clist.enabled.contest = OJB_getGMValue("showClistRating_contest", false);
    OJBetter.clist.enabled.problem = OJB_getGMValue("showClistRating_problem", false);
    OJBetter.clist.enabled.problemset = OJB_getGMValue("showClistRating_problemset", false);
    OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
    OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
    //deepl
    OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
    OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
    "choice": "",
    "configurations": []
    });
    if (OJBetter.deepl.configs.choice !== "" && OJBetter.deepl.configs.configurations.length !== 0) {
    const choice = OJBetter.deepl.configs.choice;
    const configuration = OJBetter.deepl.configs.configurations.find(obj => obj.name === choice);;
    if (configuration == undefined) {
    let existingConfig = GM_getValue('deepl_config');
    existingConfig.choice = "";
    GM_setValue('deepl_config', existingConfig);
    location.reload();
    }
    OJBetter.deepl.config.name = configuration.name;
    OJBetter.deepl.config.apiGenre = configuration.apiGenre;
    OJBetter.deepl.config.key = configuration.key;
    OJBetter.deepl.config.proxy = configuration.proxy;
    OJBetter.deepl.config.header = OJB_parseLinePairArray(configuration._header);
    OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
    OJBetter.deepl.config.quota.url = configuration.quota_url;
    OJBetter.deepl.config.quota.method = configuration.quota_method;
    OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
    OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
    OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
    }
    OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue("enableEmphasisProtection", true);
    OJBetter.deepl.enableLinkProtection = OJB_getGMValue("enableLinkProtection", true);
    //openai
    OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
    OJBetter.chatgpt.customPrompt = OJB_getGMValue("openai_customPrompt", '');
    OJBetter.chatgpt.asSystemPrompt = OJB_getGMValue("openai_asSystemPrompt", false);
    OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
    "choice": "",
    "configurations": []
    });
    if (OJBetter.chatgpt.configs.choice !== "" && OJBetter.chatgpt.configs.configurations.length !== 0) {
    const choice = OJBetter.chatgpt.configs.choice;
    const configuration = OJBetter.chatgpt.configs.configurations.find(obj => obj.name === choice);;
    if (configuration == undefined) {
    let existingConfig = GM_getValue('chatgpt_config');
    existingConfig.choice = "";
    GM_setValue('chatgpt_config', existingConfig);
    location.reload();
    }
    OJBetter.chatgpt.config.name = configuration.name;
    OJBetter.chatgpt.config.model = configuration.model;
    OJBetter.chatgpt.config.key = configuration.key;
    OJBetter.chatgpt.config.proxy = configuration.proxy;
    OJBetter.chatgpt.config.header = OJB_parseLinePairArray(configuration._header);
    OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
    OJBetter.chatgpt.config.quota.url = configuration.quota_url;
    OJBetter.chatgpt.config.quota.method = configuration.quota_method;
    OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
    OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
    OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
    }
    // 编辑器
    // if (!OJBetter.typeOfPage.is_mSite) OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
    // else OJBetter.common.cf_csrf_token = "";
    OJBetter.common.at_csrf_token = csrfToken;
    // OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
    OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "5001");
    OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
    OJBetter.monaco.enableOnProblemPage = OJB_getGMValue("problemPageCodeEditor", true);
    OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
    OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue("cppCodeTemplateComplete", true);
    OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue("onlineCompilerChoice", "official");
    OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue("isCodeSubmitConfirm", true);
    OJBetter.monaco.setting.autoSubmitAfterPass = OJB_getGMValue("autoSubmitAfterPass", false);
    OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue("alwaysConsumeMouseWheel", true);
    OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue("submitButtonPosition", "bottom");
    OJBetter.monaco.setting.autoMemoryCode = OJB_getGMValue("autoMemoryCode", true);
    //自定义补全
    OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
    "choice": -1,
    "configurations": []
    });
    // monaco
    OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
    OJBetter.monaco.setting.position = OJB_getGMValue("monacoEditor_position", "initial");
    OJBetter.monaco.lsp.workUri = OJB_getGMValue("OJBetter_Bridge_WorkUri", "C:/OJBetter_Bridge");
    OJBetter.monaco.lsp.socketUrl = OJB_getGMValue("OJBetter_Bridge_SocketUrl", "ws://127.0.0.1:2323/");
    OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
    OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue("hoverTargetAreaDisplay", false);
    OJBetter.preference.showSameContestProblems = OJB_getGMValue("showSameContestProblems", false);
    OJBetter.basic.expandFoldingblocks = OJB_getGMValue("expandFoldingblocks", true);
    OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
    OJBetter.dev.isRuleMarkingEnabled = OJB_getGMValue("isRuleMarkingEnabled", false);
    OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
    OJBetter.about.updateSource = OJB_getGMValue("updateSource", "aliyunoss");
    }

/**

  • 显示警告消息
    */
    function showWarnMessage() {
    if (OJBetter.typeOfPage.is_oldLatex) {
    const loadingMessage = new LoadingMessage();
    loadingMessage.updateStatus(${OJBetter.state.name} —— ${i18next.t('warning.is_oldLatex', { ns: 'alert' })}, 'warning');
    }
    if (OJBetter.typeOfPage.is_acmsguru) {
    const loadingMessage = new LoadingMessage();
    loadingMessage.updateStatus(${OJBetter.state.name} —— ${i18next.t('warning.is_acmsguru', { ns: 'alert' })}, 'warning');
    }
    if (OJBetter.translation.comment.transMode == "1") {
    const loadingMessage = new LoadingMessage();
    loadingMessage.updateStatus(${OJBetter.state.name} —— ${i18next.t('warning.trans_segment', { ns: 'alert' })}, 'warning');
    }
    if (OJBetter.translation.comment.transMode == "2") {
    const loadingMessage = new LoadingMessage();
    loadingMessage.updateStatus(${OJBetter.state.name} —— ${i18next.t('warning.trans_select', { ns: 'alert' })}, 'warning');
    }
    if (OJBetter.typeOfPage.is_submitPage && OJBetter.monaco.enableOnProblemPage) {
    const loadingMessage = new LoadingMessage();
    loadingMessage.updateStatus(${OJBetter.state.name} —— ${i18next.t('warning.is_submitPage', { ns: 'alert' })}, 'warning');
    }
    }

// 常量
const helpCircleHTML = '

';
const closeIcon = <svg t="1696693011050" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4322" width="14" height="14"><path d="M0 0h1024v1024H0z" fill-opacity="0" p-id="4323"></path><path d="M240.448 168l2.346667 2.154667 289.92 289.941333 279.253333-279.253333a42.666667 42.666667 0 0 1 62.506667 58.026666l-2.133334 2.346667-279.296 279.210667 279.274667 279.253333a42.666667 42.666667 0 0 1-58.005333 62.528l-2.346667-2.176-279.253333-279.253333-289.92 289.962666a42.666667 42.666667 0 0 1-62.506667-58.005333l2.154667-2.346667 289.941333-289.962666-289.92-289.92a42.666667 42.666667 0 0 1 57.984-62.506667z" p-id="4324"></path></svg>;
const translateIcon = <svg t="1696837407077" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6325" width="22" height="22"><path d="M536.380952 121.904762a73.142857 73.142857 0 0 1 73.142858 73.142857v219.428571h219.428571a73.142857 73.142857 0 0 1 73.142857 73.142858v341.333333a73.142857 73.142857 0 0 1-73.142857 73.142857H487.619048a73.142857 73.142857 0 0 1-73.142858-73.142857v-219.428571H195.047619a73.142857 73.142857 0 0 1-73.142857-73.142858V195.047619a73.142857 73.142857 0 0 1 73.142857-73.142857h341.333333zM243.809524 682.666667v97.523809h97.523809v73.142857h-97.523809a73.142857 73.142857 0 0 1-73.142857-73.142857v-97.523809h73.142857z m585.142857-195.047619h-219.428571v48.761904a73.142857 73.142857 0 0 1-73.142858 73.142858h-48.761904v219.428571h341.333333V487.619048z m-115.760762 89.526857L787.21219 780.190476h-62.025142l-14.043429-42.715428h-76.068571L620.739048 780.190476h-60.854858l74.605715-203.044571h78.701714z m-38.034286 50.029714h-3.510857l-21.065143 63.488h45.348572l-20.772572-63.488zM536.380952 195.047619H195.047619v341.333333h341.333333V195.047619z m-195.072 49.883429l44.78781 1.072762v37.278476h87.698286v145.359238h-87.698286v65.974857h-44.78781v-65.974857h-87.698285v-145.359238h87.698285v-38.351238z m0 83.139047h-44.787809v56.05181h44.787809v-56.05181z m89.307429 0h-44.519619v56.05181h44.519619v-56.05181zM780.190476 170.666667a73.142857 73.142857 0 0 1 73.142857 73.142857v97.523809h-73.142857v-97.523809h-97.523809V170.666667h97.523809z" p-id="6326"></path></svg>;
const clistIcon = <svg width="37.7pt" height="10pt" viewBox="0 0 181 48" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="#0057b8ff"><path fill="#0057b8" opacity="1.00" d=" M 17.36 0.00 L 18.59 0.00 C 23.84 6.49 30.28 11.92 36.01 17.98 C 34.01 19.99 32.01 21.99 30.00 23.99 C 26.02 19.97 22.02 15.98 18.02 11.99 C 14.01 15.98 10.01 19.99 6.00 23.99 C 4.16 22.04 2.30 20.05 0.00 18.61 L 0.00 17.37 C 3.44 15.11 6.00 11.84 8.96 9.03 C 11.79 6.05 15.09 3.47 17.36 0.00 Z" /></g><g id="#a0a0a0ff"><path fill="#a0a0a0" opacity="1.00" d=" M 56.76 13.74 C 61.48 4.80 76.07 3.90 81.77 12.27 C 83.09 13.94 83.44 16.10 83.91 18.12 C 81.53 18.23 79.16 18.24 76.78 18.23 C 75.81 15.72 73.99 13.31 71.14 12.95 C 67.14 12.02 63.45 15.29 62.48 18.99 C 61.30 23.27 61.71 28.68 65.34 31.70 C 67.82 34.05 72.19 33.93 74.61 31.55 C 75.97 30.18 76.35 28.23 76.96 26.48 C 79.36 26.43 81.77 26.44 84.17 26.56 C 83.79 30.09 82.43 33.49 79.89 36.02 C 74.14 41.35 64.17 40.80 58.77 35.25 C 53.52 29.56 53.18 20.38 56.76 13.74 Z" /> <path fill="#a0a0a0" opacity="1.00" d=" M 89.01 7.20 C 91.37 7.21 93.74 7.21 96.11 7.22 C 96.22 15.71 96.10 24.20 96.18 32.69 C 101.25 32.76 106.32 32.63 111.39 32.79 C 111.40 34.86 111.41 36.93 111.41 39.00 C 103.94 39.00 96.47 39.00 89.00 39.00 C 89.00 28.40 88.99 17.80 89.01 7.20 Z" /><path fill="#a0a0a0" opacity="1.00" d=" M 115.00 7.21 C 117.33 7.21 119.66 7.21 121.99 7.21 C 122.01 17.81 122.00 28.40 122.00 39.00 C 119.67 39.00 117.33 39.00 115.00 39.00 C 115.00 28.40 114.99 17.80 115.00 7.21 Z" /><path fill="#a0a0a0" opacity="1.00" d=" M 133.35 7.47 C 139.11 5.56 146.93 6.28 150.42 11.87 C 151.42 13.39 151.35 15.31 151.72 17.04 C 149.33 17.05 146.95 17.05 144.56 17.03 C 144.13 12.66 138.66 11.12 135.34 13.30 C 133.90 14.24 133.54 16.87 135.35 17.61 C 139.99 20.02 145.90 19.54 149.92 23.19 C 154.43 26.97 153.16 35.36 147.78 37.72 C 143.39 40.03 137.99 40.11 133.30 38.69 C 128.80 37.34 125.34 32.90 125.91 28.10 C 128.22 28.10 130.53 28.11 132.84 28.16 C 132.98 34.19 142.68 36.07 145.18 30.97 C 146.11 27.99 142.17 27.05 140.05 26.35 C 135.54 25.04 129.83 24.33 127.50 19.63 C 125.30 14.78 128.42 9.00 133.35 7.47 Z" /> <path fill="#a0a0a0" opacity="1.00" d=" M 153.31 7.21 C 161.99 7.21 170.67 7.21 179.34 7.21 C 179.41 9.30 179.45 11.40 179.48 13.50 C 176.35 13.50 173.22 13.50 170.09 13.50 C 170.05 21.99 170.12 30.48 170.05 38.98 C 167.61 39.00 165.18 39.00 162.74 39.00 C 162.64 30.52 162.73 22.04 162.69 13.55 C 159.57 13.49 156.44 13.49 153.32 13.50 C 153.32 11.40 153.31 9.31 153.31 7.21 Z" /></g><g id="#ffd700ff"><path fill="#ffd700" opacity="1.00" d=" M 12.02 29.98 C 14.02 27.98 16.02 25.98 18.02 23.98 C 22.01 27.99 26.03 31.97 30.00 35.99 C 34.01 31.99 38.01 27.98 42.02 23.99 C 44.02 25.98 46.02 27.98 48.01 29.98 C 42.29 36.06 35.80 41.46 30.59 48.00 L 29.39 48.00 C 24.26 41.42 17.71 36.08 12.02 29.98 Z" /></g></svg>;

/**

  • 连接数据库
    */
    async function initDB() {
    OJBetter.common.database = new Dexie('OJBetterDB');
    OJBetter.common.database.version(3).stores({
    samplesData: '&url',
    editorCode: '&url',
    translateData: '&url',
    localizeSubsData: '&lang'
    });

    // 等待数据库打开
    await OJBetter.common.database.open();
    }

/**

  • 清空数据库
    */
    async function clearDatabase() {
    const isConfirmed = await OJB_createDialog(
    i18next.t('isClearDatabase.title', { ns: 'dialog' }),
    i18next.t('isClearDatabase.content', { ns: 'dialog' }),
    [
    i18next.t('isClearDatabase.buttons.0', { ns: 'dialog' }),
    i18next.t('isClearDatabase.buttons.1', { ns: 'dialog' })
    ]
    );
    if (!isConfirmed) {
    try {
    // 开启一个读写事务,包含数据库中的所有表
    await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
    // 遍历所有表
    for (const table of OJBetter.common.database.tables) {
    // 清空当前表
    await table.clear();
    }
    });
    console.log("All tables in the database have been cleared.");
    alert("All tables in the database have been cleared.");
    } catch (error) {
    console.error("Error clearing the database:", error);
    }
    }
    }

/**

  • 导出数据库

  • @returns {Promise} 数据库的JSON字符串
    */
    async function exportDatabase() {
    try {
    // 创建一个存储数据的对象
    const exportData = {};
    // 获取数据库中所有表的名称
    const tableNames = OJBetter.common.database.tables.map(table => table.name);

     // 遍历每一个表,获取数据
     for (const tableName of tableNames) {
         const tableData = await OJBetter.common.database.table(tableName).toArray();
         exportData[tableName] = tableData;
     }
    
     // 将数据对象转换为JSON字符串
     const jsonData = JSON.stringify(exportData, null, 4);
     return jsonData;
    

    } catch (error) {
    console.error("Error exporting database:", error);
    }
    }

/**

  • 导入数据库

  • @param {string} jsonData 数据库的JSON字符串
    */
    async function importDatabase(jsonData) {
    const isConfirmed = await OJB_createDialog(
    i18next.t('isImportDatabase.title', { ns: 'dialog' }),
    i18next.t('isImportDatabase.content', { ns: 'dialog' }),
    [
    i18next.t('isImportDatabase.buttons.0', { ns: 'dialog' }),
    i18next.t('isImportDatabase.buttons.1', { ns: 'dialog' })
    ]
    );
    if (!isConfirmed) {
    try {
    // 将JSON字符串解析为对象
    const importData = JSON.parse(jsonData);

         // 开启一个事务,并清空现有数据
         await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
             // 清空所有表的数据
             for (const tableName of OJBetter.common.database.tables.map(table => table.name)) {
                 await OJBetter.common.database.table(tableName).clear();
             }
    
             // 插入新数据
             for (const [tableName, rows] of Object.entries(importData)) {
                 await OJBetter.common.database.table(tableName).bulkAdd(rows);
             }
         });
         alert("Data imported successfully");
     } catch (error) {
         console.error("Error importing database:", error);
     }
    

    }
    }

/**

  • 将数据下载为文件

  • @param {string} data 数据

  • @param {string} filename 文件名,默认为'export.json'

  • @param {string} fileType 文件MIME类型,默认为'application/json'

  • @returns {void}
    */
    function downloadDataAsFile(data, filename = 'export.json', fileType = 'application/json') {
    // 创建一个blob对象,指定文件类型
    const blob = new Blob([data], { type: fileType });
    const url = URL.createObjectURL(blob);

    // 创建一个隐藏的a标签,模拟点击进行下载
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();

    // 清理
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    }

/**

  • 从文件中读取数据

  • @param {Function} callback 回调函数

  • @returns {void}
    */
    function readFileInput(callback) {
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = '.json';
    fileInput.style.display = 'none'; // 隐藏input元素

    fileInput.onchange = (e) => {
    const file = e.target.files[0];
    if (file) {
    const reader = new FileReader();
    reader.onload = (e) => {
    const fileContent = e.target.result;
    if (callback && typeof callback === 'function') {
    callback(fileContent); // 调用回调函数,传入文件内容
    }
    };
    reader.readAsText(file);
    }
    };

    document.body.appendChild(fileInput);
    fileInput.click();
    document.body.removeChild(fileInput);
    }

/**

  • 清除所有设置
    */
    async function deleteAllConfigSettings() {
    const isConfirmed = await OJB_createDialog(
    i18next.t('isDeleteAllConfigSettings.title', { ns: 'dialog' }),
    i18next.t('isDeleteAllConfigSettings.content', { ns: 'dialog' }),
    [
    i18next.t('isDeleteAllConfigSettings.buttons.0', { ns: 'dialog' }),
    i18next.t('isDeleteAllConfigSettings.buttons.1', { ns: 'dialog' })
    ]
    );
    if (!isConfirmed) {
    const keys = GM_listValues();

     keys.forEach(key => {
         GM_deleteValue(key);
     });
    
     alert('All settings have been deleted.');
     window.location.reload();
    

    }
    }

/**

  • 导出设置到JSON

  • @returns {string} JSON字符串
    */
    function exportSettingsToJSON() {
    const keys = GM_listValues();
    let settings = {};

    keys.forEach(key => {
    settings[key] = GM_getValue(key);
    });

    return JSON.stringify(settings, null, 4);
    }

/**

  • 从JSON导入设置

  • @param {string} jsonData JSON字符串

  • @returns {void}
    */
    async function importSettingsFromJSON(jsonData) {
    const isConfirmed = await OJB_createDialog(
    i18next.t('isImportSettings.title', { ns: 'dialog' }),
    i18next.t('isImportSettings.content', { ns: 'dialog' }),
    [
    i18next.t('isImportSettings.buttons.0', { ns: 'dialog' }),
    i18next.t('isImportSettings.buttons.1', { ns: 'dialog' })
    ]
    );
    if (!isConfirmed) {
    let settings;
    try {
    settings = JSON.parse(jsonData);
    } catch (e) {
    console.error('JSON parsing error:', e);
    return;
    }

     Object.keys(settings).forEach(key => {
         GM_setValue(key, settings[key]);
     });
    
     alert('Settings imported successfully!');
     window.location.reload();
    

    }
    }

/**

  • 加载元素本地化语言数据
  • @param {JQuery} element jQuery元素
  • @param {number} [retries=10] 重试次数
  • @param {number} [interval=50] 重试间隔
    */
    function elementLocalize(element, retries = 10, interval = 50) {
    if ($.isFunction(element.localize)) {
    element.localize();
    } else if (retries > 0) {
    setTimeout(elementLocalize, interval, element, retries - 1, interval);
    } else {
    console.error('Unable to localize', element);
    }
    }

// 切换系统黑暗监听
const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
const changeEventListeners = [];

/**

  • 处理颜色模式变化事件

  • @param {MediaQueryListEvent} event - 媒体查询事件对象
    */
    const handleColorSchemeChange = (event) => {
    const theme = event.matches ? 'dark' : 'light';

    // 更新页面主题
    $('html').attr('data-theme', theme);
    OJBetter.common.realDarkMode = theme;

    const updateMonacoTheme = (theme) => {
    const intervalId = setInterval(() => {
    if (OJBetter?.monaco?.editor) {
    monaco.editor.setTheme(theme);
    clearInterval(intervalId);
    }
    }, 100);

     OJBetter.monaco.beautifyEditor.forEach((editor) => {
         editor.updateOptions({ theme });
     });
    

    };

    if (event.matches) {
    updateMonacoTheme('vs-dark');
    } else {
    const originalColor = $(this).data("original-color");
    $(this).css("background-color", originalColor);
    updateMonacoTheme('vs');
    }
    };

// 黑暗模式
(function setDark() {
// 初始化
function setDarkTheme() {
const htmlElement = document.querySelector('html');
if (htmlElement) {
htmlElement.setAttribute('data-theme', 'dark');
const intervalId = setInterval(() => {
if (OJBetter.monaco && OJBetter.monaco.editor) {
monaco.editor.setTheme('vs-dark');
clearInterval(intervalId);
}
}, 100);
} else {
setTimeout(setDarkTheme, 100);
}
}

// 设置黑暗模式和监听器
OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow");
if (OJBetter.basic.darkMode == "dark") {
    OJBetter.common.realDarkMode = 'dark';
    setDarkTheme();
} else if (OJBetter.basic.darkMode == "light") {
    OJBetter.common.realDarkMode = 'light';
} else if (OJBetter.basic.darkMode == "follow") {
    OJBetter.common.realDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    // 添加事件监听器
    changeEventListeners.push(handleColorSchemeChange);
    mediaQueryList.addEventListener('change', handleColorSchemeChange);

    if (window.matchMedia('(prefers-color-scheme: dark)').matches) setDarkTheme();
}

// 定义全局变量
GM_addStyle(`
    /* 黑暗支持 */
    html[data-theme=dark]:root {
        color-scheme: light dark;
    }
    /* 颜色 */
    :root {
        /* 文字颜色 */
        --ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
        --ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
        --ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
        --ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
        --ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
        --ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
        --ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
        --ojb-color-text-link: #4b8eda; /* 链接颜色 */

        /* 背景颜色 */
        --ojb-color-bg-primary: #22272e; /* 主背景颜色 */
        --ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
        --ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */

        /* 边框颜色 */
        --ojb-color-border-primary: #48535F; /* 主要边框颜色 */
        --ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
        --ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
        --ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */

        /* 阴影颜色 */
        --ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
        --ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */

        /* 区域遮罩颜色 */
        --ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */

        /* 文字阴影 */
        --ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
    }
    /* 边框样式 */
    :root {
        /* 边框样式 */
        --ojb-border-width: 1px; /* 边框宽度 */
        --ojb-border-style-solid: solid; /* 实线样式 */
        --ojb-border-style-dashed: dashed; /* 虚线样式 */
        --ojb-border-radius-small: 4px; /* 小圆角 */
        --ojb-border-radius-medium: 8px; /* 中圆角 */
        --ojb-border-radius-large: 12px; /* 大圆角 */

        /* 组合边框样式 */
        --ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
        --ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
        --ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
        --ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
    }
`);

// OJBetter界面样式
GM_addStyle(`
    /* 主要文字颜色 */
    html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
    html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
    html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
    html[data-theme=dark] .help_tip .tip_text,
    html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
    html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
    html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
    html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
    html[data-theme=dark] .popup .content{
        color: var(--ojb-color-text-primary);
    }
    /* 次要文字颜色 */
    html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
    html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
    html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
        color: var(--ojb-color-text-secondary);
    }
    /* 文字颜色3 */
    html[data-theme=dark] .ojb_btn{
        color: var(--ojb-color-text-tertiary);
    }
    /* 文字颜色 浅绿 */
    html[data-theme=dark] #SubmitButton{
        color: var(--ojb-color-text-success);
    }
    /* 禁止文字颜色 */
    html[data-theme=dark] .ojb_btn[disabled]{
        color: var(--ojb-color-text-disabled);
    }
    /* 主要背景层次 */
    html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
    html[data-theme=dark] .ojb_btn:hover,
    html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
    html[data-theme=dark] #OJBetter_SubmitForm input,
    html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
    html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
    html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
    html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
    html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
    html[data-theme=dark] .popup .content,
    html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog,
    html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
    html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
    html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
    html[data-theme=dark] .OJBetter_setting_menu select{
        background-color: var(--ojb-color-bg-primary);
点击查看代码

// ==UserScript==
// @name         Atcoder Better!
// @namespace    https://greasyfork.org/users/747162
// @version      1.19.0
// @description  一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
// @author       北极小狐
// @match        *://atcoder.jp/*
// @run-at       document-start
// @connect      www2.deepl.com
// @connect      api-free.deepl.com
// @connect      api.deepl.com
// @connect      api.deeplx.org
// @connect      www.iflyrec.com
// @connect      dict.youdao.com
// @connect      api.interpreter.caiyunai.com
// @connect      translate.google.com
// @connect      openai.api2d.net
// @connect      api.openai.com
// @connect      www.luogu.com.cn
// @connect      vjudge.net
// @connect      clist.by
// @connect      greasyfork.org
// @connect      sustech.edu.cn
// @connect      aowuucdn.oss-cn-beijing.aliyuncs.com
// @connect      aowuucdn.oss-accelerate.aliyuncs.com
// @connect      127.0.0.1
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @grant        GM_getResourceText
// @icon         https://aowuucdn.oss-accelerate.aliyuncs.com/atcoder.png
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/turndown/7.2.0/turndown.min.js#sha512-sJzEecN5Nk8cq81zKtGq6/z9Z/r3q38zV9enY75IVxiG7ybtlNUt864sL4L1Kf36bYIwxTMVKQOtU4VhD7hGrw==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/markdown-it/13.0.2/markdown-it.js#sha512-2LtYcLGnCbAWz9nDIrfG2pHFiFu9n+3oGecQlzLuYsLgen/oxiYscGWnDST9J9EZanlsQkDD0ZP2n/6peDuALQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/crypto-js/4.2.0/crypto-js.min.js#sha512-a+SUDuwNzXDvz4XrIcXHuCf089/iJAoN4lmrXJg18XnduKK6YlDHNRalv4yd1N40OKI80tFidF+rqTFKGPoWFQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/chroma-js/2.4.2/chroma.min.js#sha512-zInFF17qBFVvvvFpIfeBzo7Tj7+rQxLeTJDmbxjBz5/zIr89YVbTNelNhdTT+/DCrxoVzBeUPVFJsczKbB7sew==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/xterm/5.5.0/xterm.js#sha512-Gujw5GajF5is3nMoGv9X+tCMqePLL/60qvAv1LofUZTV9jK8ENbM9L+maGmOsNzuZaiuyc/fpph1KT9uR5w3CQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dexie/4.0.7/dexie.min.js#sha512-882VotT07mOQRzqIxsyxHzJX0XUaoeee3qXp4THg1A0KI0XFnWFAaLFQm0x6OW3pHSIipVZW+gzQ1w9b6uvkVw==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/i18next/23.11.5/i18next.min.js#sha512-3RSGkmT48HnO+hlmzGYDx5/w2LIBX0O5hSuYX6KWAxmvVlSjFgoxIaWa2tlMExheGvt3lLyxeTsXfpC47yb8CQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/i18next-http-backend/2.5.2/i18nextHttpBackend.min.js#sha512-bBb+wrGRTx4MvHpksYb1Iv5oJ1o8ineCqpc0cnTgdJQhuAFJJ93SEVXxUOCptvt0vAqYdjzWO5emorYUBt6Ceg==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/jquery-i18next/1.2.1/jquery-i18next.min.js#sha512-79RgNpOyaf8AvNEUdanuk1x6g53UPoB6Fh2uogMkOMGADBG6B0DCzxc+dDktXkVPg2rlxGvPeAFKoZxTycVooQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/highlight.js/11.9.0/highlight.min.js#sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dialog-polyfill/0.5.6/dialog-polyfill.min.js#sha512-qUIG93zKzcLBVD5RGRbx2PBmbVRu+tJIl+EPLTus0z8I1AMru9sQYdlf6cBacSzYmZVncB9rcc8rYBnazqgrxA==
// @resource     acwing_cpp_code_completer https://aowuucdn.oss-accelerate.aliyuncs.com/acwing_cpp_code_completer-0.0.11.json#sha512-DQVpao4qMMExToRdid0g/S0nbO/C9hwCECjI5aW8A0g7nvi8hEcD2Lw3QIqdJBV7haP15oJOocfwuiw7ryTO9w==
// @resource     wandboxlist https://wandbox.org/api/list.json
// @resource     xtermcss https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/xterm/5.5.0/xterm.min.css#sha512-XpXUuzg5afNt1bsgnrOesXP70TLH8tXYYK5sK+Y0UV+YBvJn9EfRFYWy4HT3TVDfH0nl1CO0lwOxIrt2gk9qjg==
// @resource     selectpagecss https://aowuucdn.oss-accelerate.aliyuncs.com/css/selectpage.css#sha512-cRXJfA2tEcAxHEKylJfxteY17N7j9fia3waahHOVnvl63uVZT9OQ7jjjpofZMVZ4JSX3BRET+mI8UvKnsXd3NA==
// @resource     dialogpolyfillcss https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dialog-polyfill/0.5.6/dialog-polyfill.min.css#sha512-J2+1q+RsZuJXabBfH1q/fgRr6jMy9By5SwVLk7bScEW7NFJkMUXxfeOyyxtDe6fsaJ4jsciexSlGrPYn9YbBIg==
// @license      GPL3
// @compatible	 Chrome
// @compatible	 Firefox
// @compatible	 Edge
// @incompatible safari
// @supportURL   https://github.com/beijixiaohu/OJBetter/issues
// @name:zh-TW   AtCoder Better!
// @name:en      AtCoder Better!
// @name:de      AtCoder Better!
// @name:fr      AtCoder Better!
// @name:ko      AtCoder Better!
// @name:pt      AtCoder Better!
// @name:ja      AtCoder Better!
// @name:es      AtCoder Better!
// @name:it      AtCoder Better!
// @name:hi      AtCoder Better!
// @description         一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
// @description:zh-TW   一個適用於 AtCoder 的 Tampermonkey 腳本,增強功能與界面。
// @description:en      A Tampermonkey script for AtCoder that enhances functionality and interface.
// @description:de      Ein Tampermonkey-Skript für AtCoder, das Funktionalität und Benutzeroberfläche verbessert.
// @description:fr      Un script Tampermonkey pour AtCoder qui améliore les fonctionnalités et l'interface.
// @description:ko      AtCoder를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
// @description:pt      Um script Tampermonkey para AtCoder que aprimora a funcionalidade e a interface.
// @description:ja      AtCoder用のTampermonkeyスクリプトで機能とインターフェースを強化します。
// @description:es      Un script Tampermonkey para AtCoder que mejora la funcionalidad y la interfaz.
// @description:it      Uno script Tampermonkey per AtCoder che migliora la funzionalità e l'interfaccia.
// @description:hi      AtCoder के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
// @downloadURL https://update.greasyfork.org/scripts/471106/Atcoder%20Better%21.user.js
// @updateURL https://update.greasyfork.org/scripts/471106/Atcoder%20Better%21.meta.js
// ==/UserScript==

/**
 * @namespace OJBetter
 * @desc 主命名空间
 */
const OJBetter = {};

/**
 * @namespace state
 * @desc 描述脚本的当前状态。
 * @memberof OJBetter
 */
OJBetter.state = {
    /** @type {string} 脚本名*/
    name: GM_info.script.name,
    /** @type {string} 格式化后的脚本名*/
    formatName: undefined,
    /** @type {string} 版本号*/
    version: GM_info.script.version,
    /** @type {boolean?} 是否跳过页面加载等待 */
    notWaiteLoaded: undefined,
    /** @type {string} 最后公告版本,用于标识版本更新完成提示 */
    lastAnnounceVer: undefined,
    /** @type {string} 最后读取的有效公告版本 */
    lastReadAnnounceVer: undefined,
    /** @type {number} 当前已打开的模态对话框数量*/
    openDialogCount: 0
};

/**
 * @namespace common
 * @desc 通用设置和属性。
 * @memberof OJBetter
 */
OJBetter.common = {
    /** @type {string} 网站的主机地址 */
    hostAddress: location.origin,
    /** @type {string} 网站当前真实的黑暗模式 */
    realDarkMode: undefined,
    /** @type {string?} AtCoder的CSRF令牌 */
    at_csrf_token: undefined,
    /** @type {Array?} 任务队列 */
    taskQueue: undefined,
    /** @type {object} OJBetter数据库连接实例*/
    database: undefined,
    /** @type {object} turndownService实例*/
    turndownService: undefined,
};

/**
 * @namespace basic
 * @desc 基本的用户界面设置。
 * @memberof OJBetter
 */
OJBetter.basic = {
    /** @type {string} 黑暗模式设置 */
    darkMode: undefined,
    /** @type {boolean?} 是否展开折叠块 */
    expandFoldingblocks: undefined,
    /** @type {boolean?} 是否开启折叠块渲染性能优化 */
    renderPerfOpt: undefined,
    /** @type {boolean?} 是否开启下拉选择框性能优化 */
    selectElementPerfOpt: undefined,
    /** @type {boolean?} 评论区分页 */
    commentPaging: undefined,
    /** @type {boolean?} 显示跳转到Luogu按钮 */
    showJumpToLuogu: undefined,
    /** @type {boolean?} 显示跳转到Virtual Judge按钮 */
    showCF2vjudge: undefined,
    /** @type {boolean?} 比赛排行榜重新着色 */
    standingsRecolor: undefined
};

/**
 * @namespace typeOfPage
 * @desc 页面类型判断。
 * @memberof OJBetter
 */
OJBetter.typeOfPage = {
    /** @type {boolean?} 是否是轻量站 */
    is_mSite: false,
    /** @type {boolean?} 是否是acmsguru页面 */
    is_acmsguru: false,
    /** @type {boolean?} 是否是旧版LaTeX页面 */
    is_oldLatex: false,
    /** @type {boolean?} 是否是题目集页面 */
    is_contest: undefined,
    /** @type {boolean?} 是否是题目页面 */
    is_problem: undefined,
    /** @type {boolean?} 是否是完整的问题集页面 */
    is_completeProblemset: false,
    /** @type {boolean?} 是否是问题集中的问题页面 */
    is_problemset_problem: false,
    /** @type {boolean?} 是否是问题集页面 */
    is_problemset: false,
    /** @type {boolean?} 是否是Codeforces排名页面 */
    is_cfStandings: false,
    /** @type {boolean?} 是否是提交页面 */
    is_submitPage: false,
    /** @type {boolean?} 是否是代码状态页面 */
    is_statePage: false,
    /** @type {boolean?} 是否是提交记录页面 */
    is_submissions: false,
    /** @type {boolean?} 是否是主页 */
    is_homepage: undefined,
    /** @type {boolean?} 是否选择的是英语页面 */
    isEnglishLanguage: undefined,
    /** @type {boolean?} 是否是题解页面 */
    isEditorial: undefined,
};

/**
 * @namespace localization
 * @desc 本地化设置。
 * @memberof OJBetter
 */
OJBetter.localization = {
    /** @type {string?} 网站语言 */
    websiteLang: undefined,
    /** @type {string?} 脚本语言 */
    scriptLang: undefined
};

/**
 * @namespace translation
 * @desc 翻译设置。
 * @memberof OJBetter
 */
OJBetter.translation = {
    /** @type {string?} 翻译服务选择 */
    choice: undefined,
    /** @type {string?} 目标语言 */
    targetLang: undefined,
    comment: {
        /** @type {string?} 评论翻译服务选择 */
        choice: undefined,
        /** @type {string?} 评论翻译模式 */
        transMode: undefined
    },
    auto: {
        /** @type {boolean?} 自动翻译开关 */
        enabled: undefined,
        /** @type {number?} 短文本长度限制 */
        shortTextLength: undefined,
        mixTrans: {
            /** @type {boolean?} 混合翻译开关 */
            enabled: undefined,
            /** @type {Array?} 混合翻译服务列表 */
            servers: undefined
        }
    },
    memory: {
        /** @type {boolean?} 翻译记忆开关 */
        enabled: undefined,
        /** @type {Object?} 翻译记忆树 */
        ttTree: undefined
    },
    /** @type {string?} 重翻译时的行为 */
    retransAction: undefined,
    /** @type {number?} 等待时间 */
    waitTime: undefined,
    /** @type {boolean?} 替换符 */
    replaceSymbol: undefined,
    /** @type {boolean?} 过滤文本中的*号 */
    filterTextWithoutEmphasis: undefined
};

/**
 * @namespace clist
 * @desc Clist相关设置。
 * @memberof OJBetter
 */
OJBetter.clist = {
    enabled: {
        /** @type {boolean?} 比赛页面开关 */
        contest: undefined,
        /** @type {boolean?} 问题页面开关 */
        problem: undefined,
        /** @type {boolean?} 问题集页面开关 */
        problemset: undefined
    },
    /** @type {boolean?} Rating数据防剧透 */
    ratingHidden: undefined,
    /** @type {string?} Clist key */
    authorization: undefined
};

/**
 * @namespace monaco
 * @desc Monaco编辑器配置。
 * @memberof OJBetter
 */
OJBetter.monaco = {
    /** @type {boolean?} 在问题页面上启用Monaco编辑器 */
    enableOnProblemPage: undefined,
    /** @type {boolean?} 美化pre代码块 */
    beautifyPreBlocks: undefined,
    /** @type {boolean} Monaco编辑器加载完成标志 */
    loaderOnload: false,
    lsp: {
        /** @type {Array?} LSP套接字数组 */
        socket: [],
        /** @type {boolean?} 是否启用LSP */
        enabled: undefined,
        /** @type {string?} 工作路径 */
        workUri: undefined,
        /** @type {string?} 套接字URL */
        socketUrl: undefined
    },
    complet: {
        /** @type {boolean?} 是否启用C++代码补全模板 */
        cppCodeTemplate: undefined,
        /** @type {Object?} 自定义配置 */
        customConfig: undefined
    },
    /** @type {Object?} Monaco编辑器实例 */
    editor: null,
    /** @type {Array?} 代码块美化的Monaco编辑器实例 */
    beautifyEditor: [],
    /** @type {string?} 在线编译器选择 */
    onlineCompilerChoice: undefined,
    /** @type {string?} 记忆编译器语言选择 */
    compilerSelection: undefined,
    /** @type {string?}  当前选择的语言 */
    nowLangSelect: undefined,
    setting: {
        /** @type {Array?} 语言设置数组 */
        language: [],
        /** @type {string?} 位置 */
        position: undefined,
        /** @type {boolean} 位置初始化标志 */
        position_initialized: false,
        /** @type {number?} 字体大小 */
        fontsize: undefined,
        /** @type {boolean?} 鼠标滚动锁定 */
        alwaysConsumeMouseWheel: undefined,
        /** @type {boolean?} 提交代码二次确认 */
        isCodeSubmitDoubleConfirm: undefined,
        /** @type {boolean?} 测试通过后自动提交 */
        autoSubmitAfterPass: undefined,
        /** @type {string?} 提交按钮位置 */
        submitButtonPosition: undefined,
        /** @type {boolean?} 自动保存代码 */
        autoMemoryCode: undefined
    }
};

/**
 * @namespace deepl
 * @desc DeepL翻译服务配置。
 * @memberof OJBetter
 */
OJBetter.deepl = {
    /** @type {Object?} DeepL配置对象 */
    configs: undefined,
    config: {
        /** @type {string?} 类型 */
        type: undefined,
        /** @type {string?} 名称 */
        name: undefined,
        /** @type {string?} API类型 */
        apiGenre: undefined,
        /** @type {string?} API密钥 */
        key: undefined,
        /** @type {string?} 代理 */
        proxy: undefined,
        /** @type {Object?} 额外请求头 */
        header: undefined,
        /** @type {Object?} 额外请求数据 */
        data: undefined,
        quota: {
            /** @type {string?} 余额URL */
            url: undefined,
            /** @type {string?} 余额请求方法 */
            method: undefined,
            /** @type {Object?} 余额请求头 */
            header: undefined,
            /** @type {Object?} 余额请求数据 */
            data: undefined,
            /** @type {number?} 剩余配额 */
            surplus: undefined
        }
    },
    /** @type {boolean?} 启用重点保护 */
    enableEmphasisProtection: undefined,
    /** @type {boolean?} 启用链接保护 */
    enableLinkProtection: undefined
};

/**
 * @namespace chatgpt
 * @desc ChatGPT服务配置。
 * @memberof OJBetter
 */
OJBetter.chatgpt = {
    /** @type {Object?} ChatGPT配置对象 */
    configs: undefined,
    config: {
        /** @type {string?} 名称 */
        name: undefined,
        /** @type {string?} 模型 */
        model: undefined,
        /** @type {string?} API密钥 */
        key: undefined,
        /** @type {string?} 代理 */
        proxy: undefined,
        /** @type {Object?} 额外请求头 */
        header: undefined,
        /** @type {Object?} 额外请求数据 */
        data: undefined,
        quota: {
            /** @type {string?} 余额URL */
            url: undefined,
            /** @type {string?} 余额请求方法 */
            method: undefined,
            /** @type {Object?} 余额请求头 */
            header: undefined,
            /** @type {Object?} 余额请求数据 */
            data: undefined,
            /** @type {number?} 剩余配额 */
            surplus: undefined
        }
    },
    /** @type {boolean?} 是否为流式传输 */
    isStream: undefined,
    /** @type {string?} 是否使用自定义Prompt */
    customPrompt: undefined,
    /** @type {boolean?} 是否作为系统Prompt */
    asSystemPrompt: undefined
};

/**
 * @namespace preference
 * @desc 偏好设置
 * @memberof OJBetter
 */
OJBetter.preference = {
    /** @type {boolean?} 是否显示加载动画 */
    showLoading: undefined,
    /** @type {boolean?} 是否显示悬停目标区域 */
    hoverTargetAreaDisplay: undefined,
    /** @type {string?} 按钮图标大小 */
    iconButtonSize: undefined,
    /** @type {boolean?} 是否显示同比赛题目列表*/
    showSameContestProblems: undefined,
};

/**
 * @namespace dev
 * @desc 维护
 * @memberof OJBetter
 */
OJBetter.dev = {
    /** @type {boolean?} 是否显示规则标记 */
    isRuleMarkingEnabled: undefined,
};

/**
 * @namespace about
 * @desc 关于页信息
 * @memberof OJBetter
 */
OJBetter.about = {
    /** @type {string?} 更新通道 */
    updateChannel: undefined,
    /** @type {string?} 更新源 */
    updateSource: undefined
};

/**
 * @namespace supportList
 * @desc 支持列表
 * @memberof OJBetter
 */
OJBetter.supportList = {
    /** @type {object} 翻译支持列表和对应语言代码*/
    translationSupport: {
        'deepl': { 'zh': 'ZH', 'de': 'DE', 'fr': 'FR', 'ko': 'KO', 'pt': 'PT', 'ja': 'JA', 'es': 'ES', 'it': 'IT' },
        'iflyrec': { 'zh': '1' },
        'youdao': { 'zh': 'zh-CHS', 'zh-Hant': 'zh-CHT', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
        'google': { 'zh': 'zh-CN', 'zh-Hant': 'zh-TW', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
        'caiyun': { 'zh': 'auto2zh', 'ja': 'auto2ja', 'ko': 'auto2ko', 'es': 'auto2es', 'fr': 'auto2fr' },
        'openai': { 'zh': 'Chinese', 'zh-Hant': 'Traditional Chinese', 'de': 'German', 'fr': 'French', 'ko': 'Korean', 'pt': 'Portuguese', 'ja': 'Japanese', 'es': 'Spanish', 'it': 'Italian', 'hi': 'Hindi' }
    },
    /** @type {object} 更新源支持列表*/
    updateSourceSupportList: {
        'greasyfork': {
            'release': true,
            'dev': false
        },
        'github': {
            'release': true,
            'dev': true
        },
        'aliyunoss': {
            'release': true,
            'dev': true
        }
    }
}

// ------------------------------
// 一些工具函数
// ------------------------------

/**
 * 延迟函数
 * @param {number} ms 延迟时间(毫秒)
 * @returns {Promise<void>}
 */
function OJB_delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * 等待直到指定的条件函数返回true。
 *
 * @param {() => boolean} conditionCheck 一个无参数的函数,用于检查条件是否满足。当函数返回true时,表示条件已满足。
 * @param {number} [interval=100] 检查条件的间隔时间,单位为毫秒。默认为100毫秒。
 * @returns {Promise<void>} 返回一个Promise,在条件满足时解决。
 */
async function OJB_waitUntilTrue(conditionCheck, interval = 100) {
    return new Promise((resolve) => {
        const checkCondition = async () => {
            if (conditionCheck()) {
                resolve();
            } else {
                await OJB_delay(interval);
                checkCondition();
            }
        };
        checkCondition();
    });
}

/**
 * 动态加载JavaScript库并返回一个Promise,该Promise在脚本加载完成后解决。
 *
 * @param {string} url - 要加载的JavaScript库的URL地址。
 * @param {string} [expectedHash] - 可选的Base64编码的SHA-512哈希值,用于校验脚本内容。格式为 "sha512-<Base64编码的哈希值>"。
 * @returns {Promise<void>} 一个Promise,它在脚本加载并执行完成后解决。
 */
async function OJB_LoadJS(url, expectedHash) {
    /**
     * 计算给定数据的SHA-512哈希值,并将其转换为十六进制字符串。
     *
     * @param {string} data - 要计算哈希值的数据。
     * @returns {Promise<string>} 一个Promise,它解析为数据的SHA-512哈希值的十六进制字符串。
     */
    const calculateHash = async (data) => {
        const encoder = new TextEncoder();
        const dataBuffer = encoder.encode(data);
        const hashBuffer = await crypto.subtle.digest('SHA-512', dataBuffer);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    };

    /**
     * 将Base64编码的字符串转换为十六进制字符串。
     *
     * @param {string} base64 - Base64编码的字符串。
     * @returns {string} 转换后的十六进制字符串。
     */
    const base64ToHex = (base64) => {
        const binaryString = atob(base64);
        const byteArray = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
            byteArray[i] = binaryString.charCodeAt(i);
        }
        return Array.from(byteArray).map(b => b.toString(16).padStart(2, '0')).join('');
    };

    try {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`Failed to fetch script: ${response.statusText}`);
        const scriptContent = await response.text();

        if (expectedHash) {
            // 去掉前缀 "sha512-"
            const base64Hash = expectedHash.replace(/^sha512-/, '');
            const actualHash = await calculateHash(scriptContent);
            const expectedHashHex = base64ToHex(base64Hash);
            if (actualHash !== expectedHashHex) throw new Error('SHA-512 hash mismatch');
        }

        const scriptElement = document.createElement("script");
        scriptElement.textContent = scriptContent;
        document.head.prepend(scriptElement);

        return Promise.resolve();
    } catch (error) {
        return Promise.reject(error);
    }
}

/**
 * 安全地创建JQuery对象
 * @description 通过字符串创建JQuery对象时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
 * @param {string} string - 字符串。
 * @returns {JQuery} JQuery对象
 */
const OJB_safeCreateJQElement = function (string) {
    return $(string.replace(/^\s+/, ""));
}


/**
 * 将数字或者字符串解析为数字。
 * @memberof OJBetter.common
 * @param {string} val 要解析的字符串
 * @param {boolean} [strict=false] 是否进行严格类型检查
 * @returns {number} 解析结果
 * @throws {Error} 如果解析失败,则抛出错误
 */
const OJB_parseNumber = (val, strict = false) => {
    const num = Number(val);
    if (isNaN(num) || (strict && val.toString() !== num.toString())) {
        throw new Error('Invalid number');
    }
    return num;
};

/**
 * 将字符串解析为布尔值
 * @param {string} val - 要解析的字符串
 * @param {boolean} strict - 是否进行严格类型检查
 * @returns {boolean} - 解析结果
 * @throws {Error} - 如果解析失败,则抛出错误
 */
const OJB_parseBoolean = (val, strict) => {
    if (strict) {
        if (val === true || val === false) return val;
        throw new Error('Invalid boolean');
    }
    return val === 'true' ? true : val === 'false' ? false : val;
};

/**
 * 将字符串解析为对象
 * @param {string} val - 要解析的字符串
 * @returns {Object} - 解析结果
 * @throws {Error} - 如果解析失败,则抛出错误
 */
const OJB_parseObject = val => {
    try {
        return JSON.parse(val);
    } catch {
        throw new Error('Invalid JSON');
    }
};

/**
 * 将字符串解析为键值对数组
 * @param {string} val - 要解析的字符串
 * @returns {Object[]} - 解析结果
 * @throws {Error} - 如果解析失败,则抛出错误
 */
const OJB_parseLinePairArray = val => {
    if (typeof val !== 'string' || val.trim() === '') return [];
    return val.split("\n").filter(line => line.trim() !== '').map(line => {
        const indexOfFirstColon = line.indexOf(":");
        if (indexOfFirstColon === -1) throw new Error('Invalid LinePairArray format: ":" is missing');
        const key = line.substring(0, indexOfFirstColon).trim();
        const value = line.substring(indexOfFirstColon + 1).trim();
        return { [key]: value };
    });
};

/**
 * 移除文本中的HTML标签
 * @param {string} text - 包含HTML标签的文本
 * @returns {string} - 移除HTML标签后的文本
 */
const OJB_removeHTMLTags = function (text) {
    return text.replace(/<\/?[a-zA-Z]+("[^"]*"|'[^']*'|[^'">])*>/g, '');
}

/**
 * 获取对象中指定路径表达式的值
 * @param {Object} obj - 要计算的对象
 * @param {string} pathOrExpression - 要计算的路径表达式
 * @returns {any} - 计算结果
 * @example
 * const obj = {
 *   "a": {
 *     "b": 1
 *   },
 *   "c": 2
 * };
 * OJB_evaluatePathOrExpression(obj, "a.b"); // 1
 * OJB_evaluatePathOrExpression(obj, "a.b + c"); // 3
 * OJB_evaluatePathOrExpression(obj, "a.b + a.c"); // 1
 */
function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
    const hasOperator = /[\+\-\*\/]/.test(pathOrExpression);
    const getPathValue = (obj, path) => {
        return path.split('.').reduce((acc, part) => {
            return acc !== undefined && acc !== null && acc.hasOwnProperty(part) ? acc[part] : undefined;
        }, obj);
    };
    const evaluateExpression = (obj, expression) => {
        const tokens = expression.split(/([\+\-\*\/])/).map(token => token.trim());
        const values = tokens.map(token => {
            if (/[\+\-\*\/]/.test(token)) {
                return token;
            } else {
                const value = getPathValue(obj, token);
                return value !== undefined ? value : 0;
            }
        });
        const evaluatedExpression = values.join(' ');
        try {
            return Function(`'use strict'; return (${evaluatedExpression});`)();
        } catch (e) {
            console.error('Expression evaluation error:', e);
            return undefined;
        }
    };
    return hasOperator ? evaluateExpression(obj, pathOrExpression) : getPathValue(obj, pathOrExpression);
}

/**
 * 获取 GM 存储的值并根据类型进行处理
 * @param {string} key - 要检索的值的键。
 * @param {any} defaultValue - 如果值未找到,则返回的默认值。
 * @param {Object} [options={}] - 配置选项对象。
 * @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。
 * @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。
 * @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。
 * @returns {any} - 检索到的值。
 */
const OJB_getGMValue = (key, defaultValue, { type = 'string', strict = false, pathOrExpression = '' } = {}) => {
    let value = GM_getValue(key);
    if (value === undefined || value === null || value === "") {
        GM_setValue?.(key, defaultValue);
        return defaultValue;
    }

    const parsers = {
        string: val => val,
        number: (val) => OJB_parseNumber(val, strict),
        boolean: (val) => OJB_parseBoolean(val, strict),
        object: OJB_parseObject,
        array: OJB_parseObject,
        linePairArray: OJB_parseLinePairArray
    };

    if (!(type in parsers)) {
        console.error(`Unsupported type: ${type}`);
        return defaultValue;
    }

    try {
        value = parsers[type](value);
    } catch (e) {
        console.error('Error:', e.message);
        return defaultValue;
    }

    // The pathOrExpression processing is not applicable to linePairArray type
    if ((type === 'object' || type === 'array') && pathOrExpression) {
        const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
        if (evaluated === undefined) {
            console.error('Path or expression evaluation returned undefined');
            return defaultValue;
        }
        value = evaluated;
    }

    return value;
};

/**
 * 版本号比较方法
 * @param {string} version1 版本号1
 * @param {string} version2 版本号2
 * @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
 */
const OJB_compareVersions = function (version1 = "0", version2 = "0") {
    const v1Array = version1.split(".").map(Number);
    const v2Array = version2.split(".").map(Number);
    const length = Math.max(v1Array.length, v2Array.length);
    for (let i = 0; i < length; i++) {
        const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
        if (diff) return Math.sign(diff);
    }
    return 0;
}

/**
 * 获取上一个主版本号
 * @param {string} currentVersion 当前版本号
 * @returns {string} 上一个主版本号
 */
const OJB_getPreviousVersion = function (currentVersion) {
    const versionArray = currentVersion.split(".").map(Number);
    let lastNonZeroIndex = versionArray.length - 1;
    while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
        lastNonZeroIndex--;
    }
    if (lastNonZeroIndex >= 0) {
        versionArray[lastNonZeroIndex]--;
        for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
            versionArray[i] = 0;
        }
    }
    return versionArray.join(".");
};

/**
 * 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数
 * @param {Object} options - 配置对象
 * @param {string} options.selector - CSS选择器文本
 * @param {Function} options.callback - 回调函数,接收变动的节点作为参数
 * @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调
 * @param {Element} [options.root=document.body] - 在哪个根节点下监听变化
 * @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
 */
function OJB_observeElement({
    selector,
    callback,
    triggerOnExist = true,
    root = document.body,
    subtree = false
}) {
    // 尝试获取选择器指定的元素
    const targetNode = root.querySelector(selector);

    if (targetNode) {
        // 如果元素已存在,直接开始观察
        observeAndReport(targetNode, callback);
        // 如果triggerOnExist为true,则立即触发一次回调
        if (triggerOnExist) {
            callback(targetNode);
        }
    } else {
        // 如果元素不存在,监听DOM变化直到该元素被添加
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
                        observeAndReport(node, callback);
                        if (triggerOnExist) {
                            callback(node);
                        }
                        observer.disconnect(); // 停止监听
                    }
                });
            });
        });

        observer.observe(root, { childList: true, subtree, attributes: false });
    }

    function observeAndReport(node, callback) {
        const childObserver = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((addedNode) => {
                    if (addedNode.nodeType === Node.ELEMENT_NODE) {
                        callback(addedNode); // 执行回调函数
                    }
                });
            });
        });

        childObserver.observe(node, { childList: true, subtree: true, attributes: false });
    }
}

/**
 * 初始化全局变量
 */
async function initVar() {
    const { hostname, href } = window.location;
    OJBetter.state.formatName = (() => OJBetter.state.name
        .toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[^a-z0-9-]/g, ''))();
    OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
    OJBetter.state.lastReadAnnounceVer = OJB_getGMValue("lastReadAnnounceVer", "0");
    OJBetter.typeOfPage.is_contest = /\/contests\/[^\/]+\/tasks\/?$/.test(href);
    OJBetter.typeOfPage.is_problem = href.includes('/tasks/');
    OJBetter.typeOfPage.is_homepage = (href === 'https://atcoder.jp/' || href === 'https://atcoder.jp/?lang=ja');
    OJBetter.typeOfPage.isEnglishLanguage = $('meta[http-equiv="Content-Language"]').attr('content') === 'en';
    OJBetter.typeOfPage.isEditorial = href.includes("editorial");
    OJBetter.localization.websiteLang = OJB_getGMValue("localizationLanguage", "zh");
    OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
    OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
    OJBetter.basic.selectElementPerfOpt = OJB_getGMValue("selectElementPerfOpt", true);
    OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
    OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
    OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
    OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
    OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
    OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
    OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
    OJBetter.translation.comment.transMode = OJB_getGMValue("commentTranslationMode", "0");
    OJBetter.translation.comment.choice = OJB_getGMValue("commentTranslationChoice", "0");
    OJBetter.translation.memory.enabled = OJB_getGMValue("memoryTranslateHistory", true);
    OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
    OJBetter.translation.auto.shortTextLength = OJB_getGMValue("shortTextLength", "2000");
    OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
    OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
    OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue("allowMixTrans", true);
    OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue("mixedTranslation", ['deepl', 'iflyrec', 'youdao', 'caiyun']);
    OJBetter.common.taskQueue = new TaskQueue();
    OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
    OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue("filterTextWithoutEmphasis", false);
    OJBetter.clist.enabled.contest = OJB_getGMValue("showClistRating_contest", false);
    OJBetter.clist.enabled.problem = OJB_getGMValue("showClistRating_problem", false);
    OJBetter.clist.enabled.problemset = OJB_getGMValue("showClistRating_problemset", false);
    OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
    OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
    //deepl
    OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
    OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
        "choice": "",
        "configurations": []
    });
    if (OJBetter.deepl.configs.choice !== "" && OJBetter.deepl.configs.configurations.length !== 0) {
        const choice = OJBetter.deepl.configs.choice;
        const configuration = OJBetter.deepl.configs.configurations.find(obj => obj.name === choice);;
        if (configuration == undefined) {
            let existingConfig = GM_getValue('deepl_config');
            existingConfig.choice = "";
            GM_setValue('deepl_config', existingConfig);
            location.reload();
        }
        OJBetter.deepl.config.name = configuration.name;
        OJBetter.deepl.config.apiGenre = configuration.apiGenre;
        OJBetter.deepl.config.key = configuration.key;
        OJBetter.deepl.config.proxy = configuration.proxy;
        OJBetter.deepl.config.header = OJB_parseLinePairArray(configuration._header);
        OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
        OJBetter.deepl.config.quota.url = configuration.quota_url;
        OJBetter.deepl.config.quota.method = configuration.quota_method;
        OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
        OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
        OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
    }
    OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue("enableEmphasisProtection", true);
    OJBetter.deepl.enableLinkProtection = OJB_getGMValue("enableLinkProtection", true);
    //openai
    OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
    OJBetter.chatgpt.customPrompt = OJB_getGMValue("openai_customPrompt", '');
    OJBetter.chatgpt.asSystemPrompt = OJB_getGMValue("openai_asSystemPrompt", false);
    OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
        "choice": "",
        "configurations": []
    });
    if (OJBetter.chatgpt.configs.choice !== "" && OJBetter.chatgpt.configs.configurations.length !== 0) {
        const choice = OJBetter.chatgpt.configs.choice;
        const configuration = OJBetter.chatgpt.configs.configurations.find(obj => obj.name === choice);;
        if (configuration == undefined) {
            let existingConfig = GM_getValue('chatgpt_config');
            existingConfig.choice = "";
            GM_setValue('chatgpt_config', existingConfig);
            location.reload();
        }
        OJBetter.chatgpt.config.name = configuration.name;
        OJBetter.chatgpt.config.model = configuration.model;
        OJBetter.chatgpt.config.key = configuration.key;
        OJBetter.chatgpt.config.proxy = configuration.proxy;
        OJBetter.chatgpt.config.header = OJB_parseLinePairArray(configuration._header);
        OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
        OJBetter.chatgpt.config.quota.url = configuration.quota_url;
        OJBetter.chatgpt.config.quota.method = configuration.quota_method;
        OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
        OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
        OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
    }
    // 编辑器
    // if (!OJBetter.typeOfPage.is_mSite) OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
    // else OJBetter.common.cf_csrf_token = "";
    OJBetter.common.at_csrf_token = csrfToken;
    // OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
    OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "5001");
    OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
    OJBetter.monaco.enableOnProblemPage = OJB_getGMValue("problemPageCodeEditor", true);
    OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
    OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue("cppCodeTemplateComplete", true);
    OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue("onlineCompilerChoice", "official");
    OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue("isCodeSubmitConfirm", true);
    OJBetter.monaco.setting.autoSubmitAfterPass = OJB_getGMValue("autoSubmitAfterPass", false);
    OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue("alwaysConsumeMouseWheel", true);
    OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue("submitButtonPosition", "bottom");
    OJBetter.monaco.setting.autoMemoryCode = OJB_getGMValue("autoMemoryCode", true);
    //自定义补全
    OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
        "choice": -1,
        "configurations": []
    });
    // monaco
    OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
    OJBetter.monaco.setting.position = OJB_getGMValue("monacoEditor_position", "initial");
    OJBetter.monaco.lsp.workUri = OJB_getGMValue("OJBetter_Bridge_WorkUri", "C:/OJBetter_Bridge");
    OJBetter.monaco.lsp.socketUrl = OJB_getGMValue("OJBetter_Bridge_SocketUrl", "ws://127.0.0.1:2323/");
    OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
    OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue("hoverTargetAreaDisplay", false);
    OJBetter.preference.showSameContestProblems = OJB_getGMValue("showSameContestProblems", false);
    OJBetter.basic.expandFoldingblocks = OJB_getGMValue("expandFoldingblocks", true);
    OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
    OJBetter.dev.isRuleMarkingEnabled = OJB_getGMValue("isRuleMarkingEnabled", false);
    OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
    OJBetter.about.updateSource = OJB_getGMValue("updateSource", "aliyunoss");
}

/**
 * 显示警告消息
 */
function showWarnMessage() {
    if (OJBetter.typeOfPage.is_oldLatex) {
        const loadingMessage = new LoadingMessage();
        loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_oldLatex', { ns: 'alert' })}`, 'warning');
    }
    if (OJBetter.typeOfPage.is_acmsguru) {
        const loadingMessage = new LoadingMessage();
        loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_acmsguru', { ns: 'alert' })}`, 'warning');
    }
    if (OJBetter.translation.comment.transMode == "1") {
        const loadingMessage = new LoadingMessage();
        loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_segment', { ns: 'alert' })}`, 'warning');
    }
    if (OJBetter.translation.comment.transMode == "2") {
        const loadingMessage = new LoadingMessage();
        loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_select', { ns: 'alert' })}`, 'warning');
    }
    if (OJBetter.typeOfPage.is_submitPage && OJBetter.monaco.enableOnProblemPage) {
        const loadingMessage = new LoadingMessage();
        loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_submitPage', { ns: 'alert' })}`, 'warning');
    }
}

// 常量
const helpCircleHTML = '<div class="help-icon"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm23.744 191.488c-52.096 0-92.928 14.784-123.2 44.352-30.976 29.568-45.76 70.4-45.76 122.496h80.256c0-29.568 5.632-52.8 17.6-68.992 13.376-19.712 35.2-28.864 66.176-28.864 23.936 0 42.944 6.336 56.32 19.712 12.672 13.376 19.712 31.68 19.712 54.912 0 17.6-6.336 34.496-19.008 49.984l-8.448 9.856c-45.76 40.832-73.216 70.4-82.368 89.408-9.856 19.008-14.08 42.24-14.08 68.992v9.856h80.96v-9.856c0-16.896 3.52-31.68 10.56-45.76 6.336-12.672 15.488-24.64 28.16-35.2 33.792-29.568 54.208-48.576 60.544-55.616 16.896-22.528 26.048-51.392 26.048-86.592 0-42.944-14.08-76.736-42.24-101.376-28.16-25.344-65.472-37.312-111.232-37.312zm-12.672 406.208a54.272 54.272 0 0 0-38.72 14.784 49.408 49.408 0 0 0-15.488 38.016c0 15.488 4.928 28.16 15.488 38.016A54.848 54.848 0 0 0 523.072 768c15.488 0 28.16-4.928 38.72-14.784a51.52 51.52 0 0 0 16.192-38.72 51.968 51.968 0 0 0-15.488-38.016 55.936 55.936 0 0 0-39.424-14.784z"></path></svg></div>';
const closeIcon = `<svg t="1696693011050" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4322" width="14" height="14"><path d="M0 0h1024v1024H0z" fill-opacity="0" p-id="4323"></path><path d="M240.448 168l2.346667 2.154667 289.92 289.941333 279.253333-279.253333a42.666667 42.666667 0 0 1 62.506667 58.026666l-2.133334 2.346667-279.296 279.210667 279.274667 279.253333a42.666667 42.666667 0 0 1-58.005333 62.528l-2.346667-2.176-279.253333-279.253333-289.92 289.962666a42.666667 42.666667 0 0 1-62.506667-58.005333l2.154667-2.346667 289.941333-289.962666-289.92-289.92a42.666667 42.666667 0 0 1 57.984-62.506667z" p-id="4324"></path></svg>`;
const translateIcon = `<svg t="1696837407077" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6325" width="22" height="22"><path d="M536.380952 121.904762a73.142857 73.142857 0 0 1 73.142858 73.142857v219.428571h219.428571a73.142857 73.142857 0 0 1 73.142857 73.142858v341.333333a73.142857 73.142857 0 0 1-73.142857 73.142857H487.619048a73.142857 73.142857 0 0 1-73.142858-73.142857v-219.428571H195.047619a73.142857 73.142857 0 0 1-73.142857-73.142858V195.047619a73.142857 73.142857 0 0 1 73.142857-73.142857h341.333333zM243.809524 682.666667v97.523809h97.523809v73.142857h-97.523809a73.142857 73.142857 0 0 1-73.142857-73.142857v-97.523809h73.142857z m585.142857-195.047619h-219.428571v48.761904a73.142857 73.142857 0 0 1-73.142858 73.142858h-48.761904v219.428571h341.333333V487.619048z m-115.760762 89.526857L787.21219 780.190476h-62.025142l-14.043429-42.715428h-76.068571L620.739048 780.190476h-60.854858l74.605715-203.044571h78.701714z m-38.034286 50.029714h-3.510857l-21.065143 63.488h45.348572l-20.772572-63.488zM536.380952 195.047619H195.047619v341.333333h341.333333V195.047619z
m-195.072 49.883429l44.78781 1.072762v37.278476h87.698286v145.359238h-87.698286v65.974857h-44.78781v-65.974857h-87.698285v-145.359238h87.698285v-38.351238z m0 83.139047h-44.787809v56.05181h44.787809v-56.05181z m89.307429 0h-44.519619v56.05181h44.519619v-56.05181zM780.190476 170.666667a73.142857 73.142857 0 0 1 73.142857 73.142857v97.523809h-73.142857v-97.523809h-97.523809V170.666667h97.523809z" p-id="6326"></path></svg>`;
const clistIcon = `<svg width="37.7pt" height="10pt" viewBox="0 0 181 48" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="#0057b8ff"><path fill="#0057b8" opacity="1.00" d=" M 17.36 0.00 L 18.59 0.00 C 23.84 6.49 30.28 11.92 36.01 17.98 C 34.01 19.99 32.01 21.99 30.00 23.99 C 26.02 19.97 22.02 15.98 18.02 11.99 C 14.01 15.98 10.01 19.99 6.00 23.99 C 4.16 22.04 2.30 20.05 0.00 18.61 L 0.00 17.37 C 3.44 15.11 6.00 11.84 8.96 9.03 C 11.79 6.05 15.09 3.47 17.36 0.00 Z" /></g><g id="#a0a0a0ff"><path fill="#a0a0a0" opacity="1.00" d=" M 56.76 13.74 C 61.48 4.80 76.07 3.90 81.77 12.27 C 83.09 13.94 83.44 16.10 83.91 18.12 C 81.53 18.23 79.16 18.24 76.78 18.23 C 75.81 15.72 73.99 13.31 71.14 12.95 C 67.14 12.02 63.45 15.29 62.48 18.99 C 61.30 23.27 61.71 28.68 65.34 31.70 C 67.82 34.05 72.19 33.93 74.61 31.55 C 75.97 30.18 76.35 28.23 76.96 26.48 C 79.36 26.43 81.77 26.44 84.17 26.56 C 83.79 30.09 82.43 33.49 79.89 36.02 C 74.14 41.35 64.17 40.80 58.77 35.25 C 53.52 29.56 53.18 20.38 56.76 13.74 Z" />
<path fill="#a0a0a0" opacity="1.00" d=" M 89.01 7.20 C 91.37 7.21 93.74 7.21 96.11 7.22 C 96.22 15.71 96.10 24.20 96.18 32.69 C 101.25 32.76 106.32 32.63 111.39 32.79 C 111.40 34.86 111.41 36.93 111.41 39.00 C 103.94 39.00 96.47 39.00 89.00 39.00 C 89.00 28.40 88.99 17.80 89.01 7.20 Z" /><path fill="#a0a0a0" opacity="1.00" d=" M 115.00 7.21 C 117.33 7.21 119.66 7.21 121.99 7.21 C 122.01 17.81 122.00 28.40 122.00 39.00 C 119.67 39.00 117.33 39.00 115.00 39.00 C 115.00 28.40 114.99 17.80 115.00 7.21 Z" /><path fill="#a0a0a0" opacity="1.00" d=" M 133.35 7.47 C 139.11 5.56 146.93 6.28 150.42 11.87 C 151.42 13.39 151.35 15.31 151.72 17.04 C 149.33 17.05 146.95 17.05 144.56 17.03 C 144.13 12.66 138.66 11.12 135.34 13.30 C 133.90 14.24 133.54 16.87 135.35 17.61 C 139.99 20.02 145.90 19.54 149.92 23.19 C 154.43 26.97 153.16 35.36 147.78 37.72 C 143.39 40.03 137.99 40.11 133.30 38.69 C 128.80 37.34 125.34 32.90 125.91 28.10 C 128.22 28.10 130.53 28.11 132.84 28.16 C 132.98 34.19 142.68 36.07 145.18 30.97 C 146.11 27.99 142.17 27.05 140.05 26.35 C 135.54 25.04 129.83 24.33 127.50 19.63 C 125.30 14.78 128.42 9.00 133.35 7.47 Z" />
<path fill="#a0a0a0" opacity="1.00" d=" M 153.31 7.21 C 161.99 7.21 170.67 7.21 179.34 7.21 C 179.41 9.30 179.45 11.40 179.48 13.50 C 176.35 13.50 173.22 13.50 170.09 13.50 C 170.05 21.99 170.12 30.48 170.05 38.98 C 167.61 39.00 165.18 39.00 162.74 39.00 C 162.64 30.52 162.73 22.04 162.69 13.55 C 159.57 13.49 156.44 13.49 153.32 13.50 C 153.32 11.40 153.31 9.31 153.31 7.21 Z" /></g><g id="#ffd700ff"><path fill="#ffd700" opacity="1.00" d=" M 12.02 29.98 C 14.02 27.98 16.02 25.98 18.02 23.98 C 22.01 27.99 26.03 31.97 30.00 35.99 C 34.01 31.99 38.01 27.98 42.02 23.99 C 44.02 25.98 46.02 27.98 48.01 29.98 C 42.29 36.06 35.80 41.46 30.59 48.00 L 29.39 48.00 C 24.26 41.42 17.71 36.08 12.02 29.98 Z" /></g></svg>`;

/**
 * 连接数据库
 */
async function initDB() {
    OJBetter.common.database = new Dexie('OJBetterDB');
    OJBetter.common.database.version(3).stores({
        samplesData: '&url',
        editorCode: '&url',
        translateData: '&url',
        localizeSubsData: '&lang'
    });

    // 等待数据库打开
    await OJBetter.common.database.open();
}

/**
 * 清空数据库
 */
async function clearDatabase() {
    const isConfirmed = await OJB_createDialog(
        i18next.t('isClearDatabase.title', { ns: 'dialog' }),
        i18next.t('isClearDatabase.content', { ns: 'dialog' }),
        [
            i18next.t('isClearDatabase.buttons.0', { ns: 'dialog' }),
            i18next.t('isClearDatabase.buttons.1', { ns: 'dialog' })
        ]
    );
    if (!isConfirmed) {
        try {
            // 开启一个读写事务,包含数据库中的所有表
            await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
                // 遍历所有表
                for (const table of OJBetter.common.database.tables) {
                    // 清空当前表
                    await table.clear();
                }
            });
            console.log("All tables in the database have been cleared.");
            alert("All tables in the database have been cleared.");
        } catch (error) {
            console.error("Error clearing the database:", error);
        }
    }
}

/**
 * 导出数据库
 * @returns {Promise<string>} 数据库的JSON字符串
 */
async function exportDatabase() {
    try {
        // 创建一个存储数据的对象
        const exportData = {};
        // 获取数据库中所有表的名称
        const tableNames = OJBetter.common.database.tables.map(table => table.name);

        // 遍历每一个表,获取数据
        for (const tableName of tableNames) {
            const tableData = await OJBetter.common.database.table(tableName).toArray();
            exportData[tableName] = tableData;
        }

        // 将数据对象转换为JSON字符串
        const jsonData = JSON.stringify(exportData, null, 4);
        return jsonData;
    } catch (error) {
        console.error("Error exporting database:", error);
    }
}

/**
 * 导入数据库
 * @param {string} jsonData 数据库的JSON字符串
 */
async function importDatabase(jsonData) {
    const isConfirmed = await OJB_createDialog(
        i18next.t('isImportDatabase.title', { ns: 'dialog' }),
        i18next.t('isImportDatabase.content', { ns: 'dialog' }),
        [
            i18next.t('isImportDatabase.buttons.0', { ns: 'dialog' }),
            i18next.t('isImportDatabase.buttons.1', { ns: 'dialog' })
        ]
    );
    if (!isConfirmed) {
        try {
            // 将JSON字符串解析为对象
            const importData = JSON.parse(jsonData);

            // 开启一个事务,并清空现有数据
            await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
                // 清空所有表的数据
                for (const tableName of OJBetter.common.database.tables.map(table => table.name)) {
                    await OJBetter.common.database.table(tableName).clear();
                }

                // 插入新数据
                for (const [tableName, rows] of Object.entries(importData)) {
                    await OJBetter.common.database.table(tableName).bulkAdd(rows);
                }
            });
            alert("Data imported successfully");
        } catch (error) {
            console.error("Error importing database:", error);
        }
    }
}

/**
 * 将数据下载为文件
 * @param {string} data 数据
 * @param {string} filename 文件名,默认为'export.json'
 * @param {string} fileType 文件MIME类型,默认为'application/json'
 * @returns {void}
 */
function downloadDataAsFile(data, filename = 'export.json', fileType = 'application/json') {
    // 创建一个blob对象,指定文件类型
    const blob = new Blob([data], { type: fileType });
    const url = URL.createObjectURL(blob);

    // 创建一个隐藏的a标签,模拟点击进行下载
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();

    // 清理
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}


/**
 * 从文件中读取数据
 * @param {Function} callback 回调函数
 * @returns {void}
 */
function readFileInput(callback) {
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = '.json';
    fileInput.style.display = 'none'; // 隐藏input元素

    fileInput.onchange = (e) => {
        const file = e.target.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onload = (e) => {
                const fileContent = e.target.result;
                if (callback && typeof callback === 'function') {
                    callback(fileContent); // 调用回调函数,传入文件内容
                }
            };
            reader.readAsText(file);
        }
    };

    document.body.appendChild(fileInput);
    fileInput.click();
    document.body.removeChild(fileInput);
}

/**
 * 清除所有设置
 */
async function deleteAllConfigSettings() {
    const isConfirmed = await OJB_createDialog(
        i18next.t('isDeleteAllConfigSettings.title', { ns: 'dialog' }),
        i18next.t('isDeleteAllConfigSettings.content', { ns: 'dialog' }),
        [
            i18next.t('isDeleteAllConfigSettings.buttons.0', { ns: 'dialog' }),
            i18next.t('isDeleteAllConfigSettings.buttons.1', { ns: 'dialog' })
        ]
    );
    if (!isConfirmed) {
        const keys = GM_listValues();

        keys.forEach(key => {
            GM_deleteValue(key);
        });

        alert('All settings have been deleted.');
        window.location.reload();
    }
}

/**
 * 导出设置到JSON
 * @returns {string} JSON字符串
 */
function exportSettingsToJSON() {
    const keys = GM_listValues();
    let settings = {};

    keys.forEach(key => {
        settings[key] = GM_getValue(key);
    });

    return JSON.stringify(settings, null, 4);
}

/**
 * 从JSON导入设置
 * @param {string} jsonData JSON字符串
 * @returns {void}
 */
async function importSettingsFromJSON(jsonData) {
    const isConfirmed = await OJB_createDialog(
        i18next.t('isImportSettings.title', { ns: 'dialog' }),
        i18next.t('isImportSettings.content', { ns: 'dialog' }),
        [
            i18next.t('isImportSettings.buttons.0', { ns: 'dialog' }),
            i18next.t('isImportSettings.buttons.1', { ns: 'dialog' })
        ]
    );
    if (!isConfirmed) {
        let settings;
        try {
            settings = JSON.parse(jsonData);
        } catch (e) {
            console.error('JSON parsing error:', e);
            return;
        }

        Object.keys(settings).forEach(key => {
            GM_setValue(key, settings[key]);
        });

        alert('Settings imported successfully!');
        window.location.reload();
    }
}

/**
 * 加载元素本地化语言数据
 * @param {JQuery} element jQuery元素
 * @param {number} [retries=10] 重试次数
 * @param {number} [interval=50] 重试间隔
 */
function elementLocalize(element, retries = 10, interval = 50) {
    if ($.isFunction(element.localize)) {
        element.localize();
    } else if (retries > 0) {
        setTimeout(elementLocalize, interval, element, retries - 1, interval);
    } else {
        console.error('Unable to localize', element);
    }
}

// 切换系统黑暗监听
const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
const changeEventListeners = [];

/**
 * 处理颜色模式变化事件
 * @param {MediaQueryListEvent} event - 媒体查询事件对象
 */
const handleColorSchemeChange = (event) => {
    const theme = event.matches ? 'dark' : 'light';

    // 更新页面主题
    $('html').attr('data-theme', theme);
    OJBetter.common.realDarkMode = theme;

    const updateMonacoTheme = (theme) => {
        const intervalId = setInterval(() => {
            if (OJBetter?.monaco?.editor) {
                monaco.editor.setTheme(theme);
                clearInterval(intervalId);
            }
        }, 100);

        OJBetter.monaco.beautifyEditor.forEach((editor) => {
            editor.updateOptions({ theme });
        });
    };

    if (event.matches) {
        updateMonacoTheme('vs-dark');
    } else {
        const originalColor = $(this).data("original-color");
        $(this).css("background-color", originalColor);
        updateMonacoTheme('vs');
    }
};

// 黑暗模式
(function setDark() {
    // 初始化
    function setDarkTheme() {
        const htmlElement = document.querySelector('html');
        if (htmlElement) {
            htmlElement.setAttribute('data-theme', 'dark');
            const intervalId = setInterval(() => {
                if (OJBetter.monaco && OJBetter.monaco.editor) {
                    monaco.editor.setTheme('vs-dark');
                    clearInterval(intervalId);
                }
            }, 100);
        } else {
            setTimeout(setDarkTheme, 100);
        }
    }

    // 设置黑暗模式和监听器
    OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow");
    if (OJBetter.basic.darkMode == "dark") {
        OJBetter.common.realDarkMode = 'dark';
        setDarkTheme();
    } else if (OJBetter.basic.darkMode == "light") {
        OJBetter.common.realDarkMode = 'light';
    } else if (OJBetter.basic.darkMode == "follow") {
        OJBetter.common.realDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
        // 添加事件监听器
        changeEventListeners.push(handleColorSchemeChange);
        mediaQueryList.addEventListener('change', handleColorSchemeChange);

        if (window.matchMedia('(prefers-color-scheme: dark)').matches) setDarkTheme();
    }

    // 定义全局变量
    GM_addStyle(`
        /* 黑暗支持 */
        html[data-theme=dark]:root {
            color-scheme: light dark;
        }
        /* 颜色 */
        :root {
            /* 文字颜色 */
            --ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
            --ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
            --ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
            --ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
            --ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
            --ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
            --ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
            --ojb-color-text-link: #4b8eda; /* 链接颜色 */

            /* 背景颜色 */
            --ojb-color-bg-primary: #22272e; /* 主背景颜色 */
            --ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
            --ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */

            /* 边框颜色 */
            --ojb-color-border-primary: #48535F; /* 主要边框颜色 */
            --ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
            --ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
            --ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */

            /* 阴影颜色 */
            --ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
            --ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */

            /* 区域遮罩颜色 */
            --ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */

            /* 文字阴影 */
            --ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
        }
        /* 边框样式 */
        :root {
            /* 边框样式 */
            --ojb-border-width: 1px; /* 边框宽度 */
            --ojb-border-style-solid: solid; /* 实线样式 */
            --ojb-border-style-dashed: dashed; /* 虚线样式 */
            --ojb-border-radius-small: 4px; /* 小圆角 */
            --ojb-border-radius-medium: 8px; /* 中圆角 */
            --ojb-border-radius-large: 12px; /* 大圆角 */

            /* 组合边框样式 */
            --ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
            --ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
            --ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
            --ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
        }
    `);

    // OJBetter界面样式
    GM_addStyle(`
        /* 主要文字颜色 */
        html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
        html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
        html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
        html[data-theme=dark] .help_tip .tip_text,
        html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
        html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
        html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
        html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
        html[data-theme=dark] .popup .content{
            color: var(--ojb-color-text-primary);
        }
        /* 次要文字颜色 */
        html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
        html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
        html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
            color: var(--ojb-color-text-secondary);
        }
        /* 文字颜色3 */
        html[data-theme=dark] .ojb_btn{
            color: var(--ojb-color-text-tertiary);
        }
        /* 文字颜色 浅绿 */
        html[data-theme=dark] #SubmitButton{
            color: var(--ojb-color-text-success);
        }
        /* 禁止文字颜色 */
        html[data-theme=dark] .ojb_btn[disabled]{
            color: var(--ojb-color-text-disabled);
        }
        /* 主要背景层次 */
        html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
        html[data-theme=dark] .ojb_btn:hover,
        html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
        html[data-theme=dark] #OJBetter_SubmitForm input,
        html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
        html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
        html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
        html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
        html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
        html[data-theme=dark] .popup .content,
        html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog,
        html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
        html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
        html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
        html[data-theme=dark] .OJBetter_setting_menu select{
            background-color: var(--ojb-color-bg-primary);
      


posted @ 2025-08-16 17:14  MyShiroko  阅读(41)  评论(0)    收藏  举报