Codeforces Better!

Codeforces界面汉化、题目翻译,markdown视图,一键复制题目

目前為 2023-05-17 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Codeforces Better!
// @namespace    https://greasyfork.org/users/747162
// @version      1.24
// @description  Codeforces界面汉化、题目翻译,markdown视图,一键复制题目
// @author       北极小狐
// @match        https://codeforces.com/*
// @connect      www2.deepl.com
// @connect      m.youdao.com
// @connect      openai.api2d.net
// @connect      api.openai.com
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @connect      greasyfork.org
// @icon         https://aowuucdn.oss-cn-beijing.aliyuncs.com/codeforces.png
// @require      https://cdn.bootcdn.net/ajax/libs/turndown/7.1.1/turndown.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.1/markdown-it.min.js
// @license      MIT
// ==/UserScript==
// 样式
function loadCssCode(code) {
    var style = document.createElement('style');
    style.type = 'text/css';
    style.rel = 'stylesheet';
    style.appendChild(document.createTextNode(code));
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(style);
}
loadCssCode(`
span.mdViewContent {
    white-space: pre-wrap;
}
/*翻译div*/
.translate-problem-statement {
    display: grid;
    justify-items: start;
    white-space: pre-wrap;
    border: 1px dashed #00aeeccc;
    border-radius: 0.3rem;
    padding: 5px;
    margin: 10px 0px;
    font-size: 13px;
}
.translate-problem-statement a {
    color: #4d87c7;
    background: 0 0;
    text-decoration: none;
}
.translate-problem-statement a:hover {
    background-color: #800;
    color: #fff;
    text-decoration: none;
}
.html2md-panel {
    display: flex;
    justify-content: flex-end;
}
button.html2mdButton {
    cursor: pointer;
    background-color: #e6e6e6;
    color: #727378;
    height: 22px;
    width: auto;
    font-size: 13px;
    border-radius: 0.3rem;
    border: none;
    padding: 1px 5px;
    margin: 5px;
    box-shadow: 0 0 1px #0000004d;
}
button.html2mdButton.copied {
    background-color: #07e65196;
    color: #104f2b;
}
button.html2mdButton.mdViewed {
    background-color: #ff980057;
    color: #5a3a0c;
}
button.translated {
    cursor: not-allowed;
    background-color: #2a6dc296;
    color: #fffdfd;
}
button.html2mdButton.reTranslation {
    background-color: #faebd7;
}
.translate-problem-statement table {
    border: 1px #ccc solid;
    border-collapse: collapse;
    margin: 1.3571em 0 0;
    color: #222;
}
.translate-problem-statement table td {
    border-right: 1px solid #ccc;
    border-top: 1px solid #ccc;
    padding: 0.7143em 0.5em;
}
.translate-problem-statement table th {
    padding: 0.7143em 0.5em;
}
.translate-problem-statement p:not(:first-child) {
    margin: 1.5em 0 0;
}
/*设置面板*/
button.html2mdButton.CFBetter_setting {
    float: right;
    height: 30px;
    background: #60a5fa;
    color: white;
    box-shadow: 0px 0px 1px 1px #0000004d;
    margin: 10px;
}
#CFBetter_setting_menu {
    z-index: 9999;
    border-radius: 0.5rem;
    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
    display: grid;
    position: fixed;
    top: 50%;
    left: 50%;
    width: 270px;
    transform: translate(-50%, -50%);
    border-radius: 16px;
    background-color: #ecf0ff;
    border: 6px solid #ffffff;
    color: #697e91;
    padding: 10px 20px 20px 20px;
}

/*设置面板-关闭按钮*/
#CFBetter_setting_menu .tool-box {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  top: 3px;
  right: 3px;
}

#CFBetter_setting_menu .btn-close {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 2rem;
  height: 2rem;
  color: transparent;
  font-size: 0;
  cursor: pointer;
  background-color: #ff000080;
  border: none;
  border-radius: 10px;
  transition: .2s ease all;
}

#CFBetter_setting_menu .btn-close:hover {
  width: 2rem;
  height: 2rem;
  font-size: 1rem;
  color: #ffffff;
  background-color: #ff0000cc;
  box-shadow: 0 5px 5px 0 #00000026;
}

#CFBetter_setting_menu .btn-close:active {
  width: .9rem;
  height: .9rem;
  font-size: .9rem;
  color: #ffffffde;
  --shadow-btn-close: 0 3px 3px 0 #00000026;
  box-shadow: var(--shadow-btn-close);
}

/*设置面板-checkbox*/
#CFBetter_setting_menu input[type=checkbox]:focus {
    outline: 0px;
}

#CFBetter_setting_menu input[type="checkbox"] {
    margin: 0px;
	appearance: none;
	width: 48px;
	height: 24px;
	border: 2px solid #6b8092;
	border-radius: 20px;
	background: #f1e1e1;
	position: relative;
	box-sizing: border-box;
}

#CFBetter_setting_menu input[type="checkbox"]::before {
	content: "";
	width: 16px;
	height: 16px;
	background: #6b80927a;
	border: 2px solid #6b8092;
	border-radius: 50%;
	position: absolute;
	top: 0;
	left: 0;
	transform: translate(0%, 0%);
	transition: all 0.3s ease-in-out;
}

#CFBetter_setting_menu input[type="checkbox"]::after {
	content: url("data:image/svg+xml,%3Csvg xmlns='://www.w3.org/2000/svg' width='23' height='23' viewBox='0 0 23 23' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.55021 5.84315L17.1568 16.4498L16.4497 17.1569L5.84311 6.55026L6.55021 5.84315Z' fill='%23EA0707' fill-opacity='0.89'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M17.1567 6.55021L6.55012 17.1568L5.84302 16.4497L16.4496 5.84311L17.1567 6.55021Z' fill='%23EA0707' fill-opacity='0.89'/%3E%3C/svg%3E");
	position: absolute;
	top: 0;
	left: 24px;
}

#CFBetter_setting_menu input[type="checkbox"]:checked {
	border: 2px solid #02c202;
	background: #e2f1e1;
}

#CFBetter_setting_menu input[type="checkbox"]:checked::before {
	background: rgba(2, 194, 2, 0.5);
	border: 2px solid #02c202;
	transform: translate(119%, 0%);
	transition: all 0.3s ease-in-out;
}

#CFBetter_setting_menu input[type="checkbox"]:checked::after {
	content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='13' viewBox='0 0 15 13' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.8185 0.114533C15.0314 0.290403 15.0614 0.605559 14.8855 0.818454L5.00187 12.5L0.113036 6.81663C-0.0618274 6.60291 -0.0303263 6.2879 0.183396 6.11304C0.397119 5.93817 0.71213 5.96967 0.886994 6.18339L5.00187 11L14.1145 0.181573C14.2904 -0.0313222 14.6056 -0.0613371 14.8185 0.114533Z' fill='%2302C202' fill-opacity='0.9'/%3E%3C/svg%3E");
	position: absolute;
	top: 3px;
	left: 4px;
}

#CFBetter_setting_menu input[type="checkbox"] + label {
    margin: 0px 0px 0px 10px !important;
    font-size: 16px;
}

.CFBetter_setting_list {
	display: flex;
	align-items: center;
	margin-bottom: 18px;
}

/*设置面板-radio*/
#CFBetter_setting_menu>label {
    display: flex;
    list-style-type: none;
    padding-inline-start: 0px;
    overflow-x: auto;
    max-width: 100%;
    margin: 0px;
    align-items: center;
    margin: 3px 0px;
}

.CFBetter_setting_menu_label_text {
    border: 1px dashed #00aeeccc;
    display: block;
    height: 20px;
    width: 100%;
    color: gray;
    font-weight: 300;
    font-size: 14px;
    letter-spacing: 2px;
    padding: 7px;
}

input[type="radio"]:checked+.CFBetter_setting_menu_label_text {
    background: #41e49930;
    border: 1px solid green;
    color: green;
    font-weight: 500;
}

#CFBetter_setting_menu>label input[type="radio"] {
    -webkit-appearance: none;
    appearance: none;
    list-style: none;
    margin: 0px;
}

#CFBetter_setting_menu input[type="text"] {
    display: block;
    height: 25px;
    background-color: #ffffff;
    color: #727378;
    font-size: 10px;
    border-radius: 0.3rem;
    padding: 1px 5px;
    margin: 5px 0px 5px 0px;
    border: 1px solid #00aeeccc;
    box-shadow: 0 0 1px #0000004d;
}

.CFBetter_setting_menu_input {
    width: 100%;
    display: grid;
    justify-content: start;
}

#CFBetter_setting_menu #save {
    cursor: pointer;
	display: inline-flex;
	padding: 0.5rem 1rem;
	background-color: #1aa06d;
	color: #ffffff;
	font-size: 1rem;
	line-height: 1.5rem;
	font-weight: 500;
	justify-content: center;
	width: 100%;
	border-radius: 0.375rem;
	border: none;
	box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}

#CFBetter_setting_menu span.tip {
    color: red;
    font-size: 2px;
    font-weight: 500;
    padding: 0px 0px 10px 0px;
}
/*更新检查*/
div#update_panel {
    position: fixed;
    top: 50%;
    left: 50%;
    width: 240px;
    transform: translate(-50%, -50%);
    background-color: #fdfdfd;
    border: 1px solid #00aeeccc;
    border-radius: 5px;
    box-shadow: 2px 2px 3px 1px #0000004d;
    padding: 10px 20px 20px 20px;
    color: #444242;
    background-color: #ecf0ff;
    border: 6px solid #ffffff;
    border-radius: 16px;
}
div#update_panel #updating {
    cursor: pointer;
	display: inline-flex;
	padding: 0px;
	background-color: #1aa06d;
	color: #ffffff;
	font-size: 1rem;
	line-height: 1.5rem;
	font-weight: 500;
	justify-content: center;
	width: 100%;
	border-radius: 0.375rem;
	border: none;
	box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
div#update_panel #updating a {
    text-decoration: none;
    color: white;
    display: flex;
    position: inherit;
    top: 0;
    left: 0;
    width: 100%;
    height: 22px;
    font-size: 14px;
    justify-content: center;
    align-items: center;
}
`);

// 更新检查
(function checkScriptVersion() {
    function compareVersions(version1 = "0", version2 = "0") {
        const v1Array = String(version1).split(".");
        const v2Array = String(version2).split(".");
        const minLength = Math.min(v1Array.length, v2Array.length);
        let result = 0;
        for (let i = 0; i < minLength; i++) {
            const curV1 = Number(v1Array[i]);
            const curV2 = Number(v2Array[i]);
            if (curV1 > curV2) {
                result = 1;
                break;
            } else if (curV1 < curV2) {
                result = -1;
                break;
            }
        }
        if (result === 0 && v1Array.length !== v2Array.length) {
            const v1IsBigger = v1Array.length > v2Array.length;
            const maxLenArray = v1IsBigger ? v1Array : v2Array;
            for (let i = minLength; i < maxLenArray.length; i++) {
                const curVersion = Number(maxLenArray[i]);
                if (curVersion > 0) {
                    v1IsBigger ? result = 1 : result = -1;
                    break;
                }
            }
        }
        return result;
    }

    GM_xmlhttpRequest({
        method: "GET",
        url: "https://greasyfork.org/zh-CN/scripts/465777.json",
        timeout: 10 * 1e3,
        onload: function (response) {
            const scriptData = JSON.parse(response.responseText);
            if (scriptData.name === GM_info.script.name && compareVersions(scriptData.version, GM_info.script.version) === 1) {
                $("body").append(`
					<div id='update_panel'>
						<h3>${GM_info.script.name}有新版本!</h3>
						<hr>
						<div class='update_panel_menu'>
                            <span class ='tip'>版本信息:${GM_info.script.version} → ${scriptData.version}</span>
						</div>
						<br>
						<button id='updating'><a target="_blank" href="${scriptData.url}">更新</a></button>
					</div>
				`);
            }
        }
    });
})();

// 汉化替换
(function () {
    if (getCookie("bottomZh_CN") === '') setCookie("bottomZh_CN", "true", 3650);
    if (getCookie("bottomZh_CN") != "true") return;
    // 设置语言为zh
    var htmlTag = document.getElementsByTagName("html")[0];
    htmlTag.setAttribute("lang", "zh-CN");

    // 暴力html替换
    const classData = {
        '.menu-list.main-menu-list': [
            { match: 'Help', replace: '帮助' },
            { match: 'Calendar', replace: '日历' },
            { match: 'Edu', replace: '培训' },
            { match: 'Rating', replace: '积分榜' },
            { match: 'Groups', replace: '团体' },
            { match: 'Problemset', replace: '题单' },
            { match: 'Gym', replace: '训练营(过去的大型比赛)' },
            { match: 'Contests', replace: '比赛' },
            { match: 'Catalog', replace: '指南目录' },
            { match: 'Top', replace: '热门' },
            { match: 'Home', replace: '主页' },
        ],
        '.personal-sidebar ': [
            { match: 'Contribution', replace: '贡献' },
            { match: 'Settings', replace: '设置' },
            { match: 'Blog', replace: '博客' },
            { match: 'Teams', replace: '队伍' },
            { match: 'Submissions', replace: '提交' },
            { match: 'Talks', replace: '私信' },
            { match: 'Contests', replace: '比赛' },
        ],
        '.contest-state-phase': [
            { match: 'Before contest', replace: '即将进行的比赛' },
        ],
        '.notice': [
            { match: 'has extra registration', replace: '有额外的报名时期' },
            { match: 'If you are late to register in 5 minutes before the start, you can register later during the extra registration. Extra registration opens 10 minutes after the contest starts and lasts 25 minutes.', replace: '如果您在比赛开始前5分钟前还未报名,您可以在额外的报名期间稍后报名。额外的报名将在比赛开始后10分钟开放,并持续25分钟。' },
        ],
        '.act-item': [
            { match: 'Add to favourites', replace: '添加到收藏' },
            { match: 'Submit', replace: '提交' },
        ],
        '.datatable': [
            { match: 'Virtual participation', replace: '参加虚拟重现赛' },
            { match: 'Enter', replace: '进入' },
            { match: 'Final standings', replace: '榜单' },
            { match: 'School/University/City/Region Championship', replace: '学校/大学/城市/区域比赛' },
            { match: 'Official School Contest', replace: '学校官方比赛' },
            { match: 'Training Contest', replace: '训练赛' },
            { match: 'Training Camp Contest', replace: '训练营比赛' },
            { match: 'Official ICPC Contest', replace: 'ICPC官方比赛' },
            { match: 'Official International Personal Contest', replace: '官方国际个人赛' },
            { match: 'China', replace: '中国' },
            { match: 'Statements', replace: '题目描述' },
            { match: 'in Chinese', replace: '中文' },
            { match: 'Trainings', replace: '训练' },
            { match: 'Prepared by', replace: '编写人' },
            { match: 'Current or upcoming contests', replace: '当前或即将举行的比赛' },
            { match: 'Past contests', replace: '过去的比赛' },
            { match: 'Exclusions', replace: '排除' },
            { match: 'Before start', replace: '还有' },
            { match: 'Before registration', replace: '报名还有' },
            { match: 'Until closing ', replace: '结束报名' },
            { match: 'Register', replace: '报名' },
            { match: 'Registration completed', replace: '已报名' },
            { match: 'Questions about problems', replace: '关于题目的提问' },
        ],
        '.ask-question-link': [
            { match: 'Ask a question', replace: '提一个问题' },
        ],
        '.contests-table': [
            { match: 'Contest history', replace: '比赛历史' },
        ],
        '.roundbox.sidebox.borderTopRound ': [
            { match: 'Register now', replace: '现在报名' },
        ],
        '.icon-eye-close.icon-large': [
            { match: 'Add to exclusions', replace: '添加到排除列表' },
        ],
        '._ContestFilterExclusionsManageFrame_addExclusionLink': [
            { match: 'Add to exclusions for gym contests filter', replace: '添加训练营过滤器的排除项' },
        ],
        '.roundbox.sidebox.sidebar-menu.borderTopRound ': [
            { match: 'Announcement', replace: '公告' },
            { match: 'Statements', replace: '统计报表' },
            { match: 'Tutorial', replace: '题解' },
        ],
        '.second-level-menu ': [
            { match: 'Problems', replace: '问题' },
            { match: 'Submit Code', replace: '提交代码' },
            { match: 'My Submissions', replace: '我的提交' },
            { match: 'Status', replace: '状态' },
            { match: 'Standings', replace: '榜单' },
            { match: 'Custom Invocation', replace: '自定义调试' },
            { match: 'Common standings', replace: '全部排行' },
            { match: 'Friends standings', replace: '只看朋友' },
            { match: 'Submit', replace: '提交' },
            { match: 'Custom test', replace: '自定义测试' },
            { match: 'Blog', replace: '博客' },
            { match: 'Teams', replace: '队伍' },
            { match: 'Submissions', replace: '提交' },
            { match: 'Groups', replace: '团体' },
            { match: 'Contests', replace: '比赛' },
            { match: '问题etting', replace: '参与编写的问题' },
            { match: 'Gym', replace: '训练营' },
            { match: 'Mashups', replace: '组合混搭' },
            { match: 'Posts', replace: '帖子' },
            { match: '>Comments<', replace: '>回复<' },
            { match: 'Main', replace: '主要' },
            { match: 'Settings', replace: '设置' },
            { match: 'Lists', replace: '列表' },
        ],
        '.topic-toggle-collapse': [
            { match: 'Expand', replace: '展开' },
        ],
        '.topic-read-more': [
            { match: 'Full text and comments', replace: '阅读全文/评论' },
        ],
        '.toggleEditorCheckboxLabel': [
            { match: 'Switch off editor', replace: '关闭编辑器语法高亮' },
        ],
        '.submit': [
            { match: 'Registration for the contest', replace: '比赛报名' },
        ],
    };
    for (const className in classData) {
        const elements = document.querySelectorAll(className);
        elements.forEach((element) => {
            let html = element.outerHTML;
            const parent = element.parentNode;
            const childIndex = Array.from(parent.children).indexOf(element);
            let matched = false;
            classData[className].forEach((rule) => {
                if (html.match(new RegExp(rule.match, 'g'))) {
                    matched = true;
                    html = html.replace(new RegExp(rule.match, 'g'), rule.replace);
                    const temp = document.createElement('div');
                    temp.innerHTML = html.trim();
                    const newElement = element.cloneNode(true);
                    newElement.innerHTML = temp.firstChild.innerHTML;
                    parent.replaceChild(newElement, parent.children[childIndex]);
                }
            });
        });
    }

    // 元素选择替换
    // 侧栏titled汉化
    (function () {
        var translations = {
            "Pay attention": "→ 注意",
            "Top rated": "→ 积分排行",
            "Top contributors": "→ 贡献者排行",
            "Find user": "→ 查找用户",
            "Recent actions": "→ 最近热帖",
            "Training filter": "→ 过滤筛选",
            "Find training": "→ 搜索比赛/问题",
            "Virtual participation": "→ 什么是虚拟参赛",
            "Contest materials": "→ 比赛相关资料",
            "Settings": "→ 设置",
            "Clone Contest to Mashup": "→ Clone比赛到组合混搭",
            "Submit": "→ 提交",
            "Practice": "→ 练习",
            "Problem tags": "→ 问题标签",
            "Filter Problems": "→ 过滤问题",
            "Attention": "→ 注意",
            "About Contest": "→ 关于比赛",
        };

        $(".caption.titled").each(function () {
            var tag = $(this).text();
            for (var property in translations) {
                if (tag.match(property)) {
                    $(this).addClass(property);
                    $(this).text(translations[property]);
                    break;
                }
            }
        });
    })();
    // 题目Tag汉化
    (function () {
        var parentElement = $('._FilterByTagsFrame_addTagLabel');
        var selectElement = parentElement.find('select');
        var translations = {
            "*combine tags by OR": "*按逻辑或组合我选择的标签",
            "combine-tags-by-or": "*按逻辑或组合我选择的标签(combine-tags-by-or)",
            "2-sat": "二分图可满足性问题(2-sat)",
            "binary search": "二分搜索(binary search)",
            "bitmasks": "位掩码(bitmasks)",
            "brute force": "暴力枚举(brute force)",
            "chinese remainder theorem": "中国剩余定理(chinese remainder theorem)",
            "combinatorics": "组合数学(combinatorics)",
            "constructive algorithms": "构造算法(constructive algorithms)",
            "data structures": "数据结构(data structures)",
            "dfs and similar": "深度优先搜索及其变种(dfs and similar)",
            "divide and conquer": "分治算法(divide and conquer)",
            "dp": "动态规划(dp)",
            "dsu": "并查集(dsu)",
            "expression parsing": "表达式解析(expression parsing)",
            "fft": "快速傅里叶变换(fft)",
            "flows": "流(flows)",
            "games": "博弈论(games)",
            "geometry": "计算几何(geometry)",
            "graph matchings": "图匹配(graph matchings)",
            "graphs": "图论(graphs)",
            "greedy": "贪心策略(greedy)",
            "hashing": "哈希表(hashing)",
            "implementation": "实现问题,编程技巧,模拟(implementation)",
            "interactive": "交互性问题(interactive)",
            "math": "数学(math)",
            "matrices": "矩阵(matrices)",
            "meet-in-the-middle": "meet-in-the-middle算法(meet-in-the-middle)",
            "number theory": "数论(number theory)",
            "probabilities": "概率论(probabilities)",
            "schedules": "调度算法(schedules)",
            "shortest paths": "最短路算法(shortest paths)",
            "sortings": "排序算法(sortings)",
            "string suffix structures": "字符串后缀结构(string suffix structures)",
            "strings": "字符串处理(strings)",
            "ternary search": "三分搜索(ternary search)",
            "trees": "树形结构(trees)",
            "two pointers": "双指针算法(two pointers)"
        };
        selectElement.find("option").each(function () {
            var optionValue = $(this).val();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
        $("._FilterByTagsFrame_tagBoxCaption").each(function () {
            var tag = $(this).text();
            if (tag in translations) {
                $(this).text(translations[tag]);
            }
        });
        $(".notice").each(function () {
            var tag = $(this).text();
            if (tag in translations) {
                $(this).text(translations[tag]);
            }
        });
        $(".tag-box").each(function () {
            var tag = $(this).text();
            for (var property in translations) {
                property = property.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
                if (tag.match(property)) {
                    $(this).text(translations[property]);
                    break;
                }
            }
        });
    })();
    // 题目过滤器选项汉化
    (function () {
        var parentElement = $('#gym-filter-form');
        var selectElement = parentElement.find('div');
        var translations = {
            "Contest type:": "比赛类型:",
            "ICPC region:": "ICPC地区:",
            "Contest format:": "比赛形式:",
            "Order by:": "排序方式:",
            "Secondary order by:": "次要排序方式:",
            "Hide, if participated:": "隐藏我参加过的:",
        };
        selectElement.find("label").each(function () {
            var optionValue = $(this).text();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
        translations = {
            "Season:": "时间范围(年度)",
            "Duration, hours:": "持续时间(小时):",
            "Difficulty:": "难度:"
        };
        selectElement.each(function () {
            var optionValue = $(this).text();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
    })();
    (function () {
        var parentElement = $('.setting-value');
        var selectElement = parentElement.find('select');
        var translations = {
            "Official ACM-ICPC Contest": "ICPC官方比赛",
            "Official School Contest": "学校官方比赛",
            "Opencup Contest": "Opencup比赛",
            "School/University/City/Region Championship": "学校/大学/城市/地区锦标赛",
            "Training Camp Contest": "训练营比赛",
            "Official International Personal Contest": "官方国际个人赛",
            "Training Contest": "训练比赛",
            "ID_ASC": "创建时间(升序)",
            "ID_DESC": "创建时间(降序)",
            "RATING_ASC": "评分(升序)",
            "RATING_DESC": "评分(降序)",
            "DIFFICULTY_ASC": "难度(升序)",
            "DIFFICULTY_DESC": "难度(降序)",
            "START_TIME_ASC": "开始时间(升序)",
            "START_TIME_DESC": "开始时间(降序)",
            "DURATION_ASC": "持续时间(升序)",
            "DURATION_DESC": "持续时间(降序)",
            "POPULARITY_ASC": "热度(升序)",
            "POPULARITY_DESC": "热度(降序)",
            "UPDATE_TIME_ASC": "更新时间(升序)",
            "UPDATE_TIME_DESC": "更新时间(降序)"
        };
        selectElement.find("option").each(function () {
            var optionValue = $(this).val();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
        parentElement = $('.setting-last-value');
        selectElement = parentElement.find('select');
        selectElement.find("option").each(function () {
            var optionValue = $(this).val();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
    })();
    // 右侧sidebox通用汉化
    (function () {
        var parentElement = $('.sidebox');
        var selectElement = parentElement.find('div');
        var translations = {
            "Show tags for unsolved problems": "显示未解决问题的标签",
            "Hide solved problems": "隐藏已解决的问题",
        };
        selectElement.find("label").each(function () {
            var optionValue = $(this).text();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
    })();
    // 题目提交/报名页汉化
    (function () {
        var translations = {
            "Problem:": "题目:",
            "Language:": "语言:",
            "Source code:": "源代码:",
            "Or choose file:": "或者选择文件:",
            "Choose file:": "选择文件:",
            "Notice:": "注意:",
            "virtual participation:": "虚拟参与:",
            "Registration for the contest:": "比赛报名:",
            "Terms of agreement:": "协议条款:",
            "Take part:": "参与:",
            "as individual participant:": "作为个人参与者:",
            "as a team member:": "作为团队成员:",
            "Virtual start time:": "虚拟开始时间:",
            "Complete problemset:": "完整的问题集:"
        };
        $(".field-name").each(function () {
            var optionValue = $(this).text();
            if (translations[optionValue]) {
                $(this).text(translations[optionValue]);
            }
        });
    })();
    // 按钮汉化
    (function () {
        var translations = {
            "Register for virtual participation": "报名虚拟参赛"
        };
        $(".submit").each(function () {
            var optionValue = $(this).val();
            if (translations[optionValue]) {
                $(this).val(translations[optionValue]);
            }
        });
    })();
    // 杂项汉化
    (function () {
        var translations = {
            "(standard input\/output)": "标准输入/输出",
        };
        $("div.notice").each(function () {
            var tag = $(this).children().eq(0).text();
            for (var property in translations) {
                if (tag.match(property)) {
                    $(this).children().eq(0).text(translations[property]);
                    break;
                }
            }
        });
    })();

})();

// **设置面板**
$("div[class='lang-chooser']").each(function () {
    $(this).before(
        "<button class='html2mdButton CFBetter_setting'>CodeforcesBetter设置</button>"
    );
});
$(document).ready(function () {
    $(".CFBetter_setting").click(function () {
        $(".CFBetter_setting").attr("disabled", true);
        $(".CFBetter_setting").css("background-color", "#e6e6e6");
        $(".CFBetter_setting").css("color", "#727378");
        $(".CFBetter_setting").css("cursor", "not-allowed");
        $("body").append(`
					<div id='CFBetter_setting_menu'>
                        <div class="tool-box">
		                    <button class="btn-close">×</button>
	                    </div>
                        <h3>基本设置</h3>
						<hr>
						<div class='CFBetter_setting_list'>
							<input type="checkbox" id="bottomZh_CN" name="bottomZh_CN" checked>
							<label for="bottomZh_CN">界面汉化</label>
						</div>
						<h3>翻译设置</h3>
						<hr>
						<label>
							<input type='radio' name='translation' value='deepl'>
							<span class='CFBetter_setting_menu_label_text'>deepl翻译</span>
						</label>
						<label>
							<input type='radio' name='translation' value='youdao'>
							<span class='CFBetter_setting_menu_label_text'>有道翻译</span>
						</label>
						<label>
							<input type='radio' name='translation' value='openai'>
							<span class='CFBetter_setting_menu_label_text'>使用ChatGPT翻译(API)</span>
						</label>
						<label>
							<input type='radio' name='translation' value='api2d'>
							<span class='CFBetter_setting_menu_label_text'>使用api2d翻译(API)</span>
							</label><br>
						<div class='CFBetter_setting_menu_input' id='baidu' style='display: none;'>
							<label for='baidu_uid'>APP ID:</label><input type='text' id='baidu_uid'>
							<label for='baidu_key'>KEY:</label><input type='text' id='baidu_key'>
						</div>
						<div class='CFBetter_setting_menu_input' id='openai' style='display: none;'>
                            <span class ='tip'>提示:<br>请确保你能够正常访问OpenAI的api<br></span>
                            <span class ='tip'>使用ChatGPT-3.5进行翻译,脚本的所有请求均在本地完成,</span>
                            <span class ='tip'>你需要输入自己的OpenAI KEY,<a target="_blank" href="https://platform.openai.com/account/usage">官网</a></span>
							<label for='openai_key'>KEY:</label><input type='text' id='openai_key'>
						</div>
						<div class='CFBetter_setting_menu_input' id='api2d' style='display: none;'>
                            <span class ='tip'>提示:<br>api2d是国内的一家提供代理直连访问OpenAI的api的服务商,相当于OpenAI的api的套壳<br></span>
                            <span class ='tip'>使用ChatGPT-3.5进行翻译,脚本的所有请求均在本地完成<br></span>
                            <span class ='tip'>你需要输入自己的api2d KEY,<a target="_blank" href="https://api2d.com/profile">官网</a></span>
							<label for='api2d_key'>KEY:</label><input type='text' id='api2d_key'>
						</div>
						<br>
						<button id='save'>保存</button>
					</div>
				`);
        var translation = getCookie("translation");
        $("#bottomZh_CN").prop("checked", getCookie("bottomZh_CN") === "true");
        if (translation === 'undefined' || translation === '') {
            setCookie("translation", "deepl", 3650);
            $("input[name='translation'][value='deepl']").prop("checked", true);
        } else {
            $("input[name='translation'][value='" + translation + "']").prop("checked", true);
            $("input[name='translation']").css("color", "gray");
            if (translation == "baidu") {
                $("#baidu").show();
                $("#baidu_uid").val(getCookie("baidu_uid"));
                $("#baidu_key").val(getCookie("baidu_key"));
                $("#baidu_uid").css("color", "gray");
                $("#baidu_key").css("color", "gray");
            } else if (translation == "openai") {
                $("#openai").show();
                $("#openai_key").val(getCookie("openai_key"));
                $("#openai_key").css("color", "gray");
            } else if (translation == "api2d") {
                $("#api2d").show();
                $("#api2d_key").val(getCookie("api2d_key"));
                $("#api2d_key").css("color", "gray");
            }
        }

        // 当单选框被选中时,显示对应的输入框,同时隐藏其他输入框
        $("input[name='translation']").change(function () {
            var selected = $(this).val(); // 获取当前选中的值
            if (selected === 'baidu') {
                $("#baidu").show();
                $("#baidu_uid").val(getCookie("baidu_uid"));
                $("#baidu_key").val(getCookie("baidu_key"));
                $("#openai, #api2d").hide();
            } else if (selected === 'openai') {
                $("#openai").show();
                $("#openai_key").val(getCookie("openai_key"));
                $("#baidu, #api2d").hide();
            } else if (selected === 'api2d') {
                $("#api2d").show();
                $("#api2d_key").val(getCookie("api2d_key"));
                $("#baidu, #openai").hide();
            } else {
                $("#baidu, #openai, #api2d").hide();
            }
        });

        $("#save").click(function () {
            setCookie("bottomZh_CN", $("#bottomZh_CN").prop("checked"), 3650);
            var translation = $("input[name='translation']:checked").val();
            var baidu_uid = $("#baidu_uid").val();
            var baidu_key = $("#baidu_key").val();
            var openai_key = $("#openai_key").val();
            var api2d_key = $("#api2d_key").val();
            setCookie("translation", translation, 3650);
            if (translation == "baidu") {
                setCookie("baidu_uid", baidu_uid, 3650);
                setCookie("baidu_key", baidu_key, 3650);
            } else if (translation == "openai") {
                setCookie("openai_key", openai_key, 3650);
            } else if (translation == "api2d") {
                setCookie("api2d_key", api2d_key, 3650);
            }
            location.reload();
        });
        // 关闭
        $("#CFBetter_setting_menu .btn-close").click(function () {
            $("#CFBetter_setting_menu").remove();
            $(".CFBetter_setting").attr("disabled", false);
            $(".CFBetter_setting").css("background-color", "#60a5fa");
            $(".CFBetter_setting").css("color", "white");
            $(".CFBetter_setting").css("cursor", "pointer");
        });
    });
});

// 是否为旧的latex渲染
var is_oldLatex = false;
if ($('.tex-span').length) {
    is_oldLatex = true;
    var newElement = $("<div></div>").addClass("alert alert-warning").html(`
    注意:当前页面存在使用非 MathJax 库渲染为 HTML 的 Latex 公式(这通常是一道古老的题目),这导致 CodeforcesBetter 无法将其还原回 Latex,因此当前页面部分功能不适用。
    <br>此外当前页面的翻译功能采用了特别的实现方式,因此可能会出现排版错位的情况。
    `).css({
        "margin": "1em",
        "text-align": "center",
        "position": "relative"
    });
    $(".menu-box:first").next().after(newElement);
}

// 题目markdown转换/翻译面板
function addButtonPanel(parent, suffix, type, is_simple = false) {
    if (type === "upper_level_2" || type === "problem") {
        $(parent).children(':eq(1)').before(
            "<div class='html2md-panel'> <button class='html2mdButton html2md-view" + suffix + "'>MarkDown视图</button> <button class='html2mdButton html2md-cb" + suffix + "'>Copy</button> <button class='html2mdButton translateButton" + suffix + "'>翻译</button> </div>");
    } else if (is_simple) {
        $(parent).before(
            "<div class='html2md-panel'><button class='html2mdButton translateButton" + suffix + "'>翻译</button> </div>");
    } else {
        $(parent).before(
            "<div class='html2md-panel'> <button class='html2mdButton html2md-view" + suffix + "'>MarkDown视图</button> <button class='html2mdButton html2md-cb" + suffix + "'>Copy</button> <button class='html2mdButton translateButton" + suffix + "'>翻译</button> </div>");
    }
}
function addButtonWithHTML2MD(parent, suffix, type) {
    if(is_oldLatex){
        $(".html2md-view" + suffix).css({
            "cursor": "not-allowed",
            "background-color": "#e6e6e638",
            "color": "#72737896",
        });
        $(".html2md-view" + suffix).prop("disabled", true);
    }
    $(".html2md-view" + suffix).click(function () {
        var target, removedChildren;
        if (type === "problem") {
            target = $(parent).children().next().next().get(0);
        } else if (type === "problem_QA") {
            target = $(this).parent().parent().get(0);
            removedChildren = $(this).parent().parent().children().eq(0).detach();
        } else if (type === "upper_level_2") {
            target = parent;
            removedChildren = $(parent).children().slice(0, 2).detach();
        } else {
            target = parent;
        }
        if (target.viewmd) {
            target.viewmd = false;
            $(this).text("MarkDown视图");
            $(this).removeClass("mdViewed");
            $(target).html(target.original_html);
        } else {
            target.viewmd = true;
            if (!target.original_html) {
                target.original_html = $(target).html();
            }
            if (!target.markdown) {
                target.markdown = turndownService.turndown($(target).html());
            }
            $(this).text("原始内容");
            $(this).addClass("mdViewed");
            $(target).html(`<span class="mdViewContent" oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto;">${target.markdown}</span>`);
        }
        // 恢复删除的元素
        if (type === "problem_QA" || type === "upper_level_2") $(target).prepend(removedChildren);
    });
}

function addButtonWithCopy(parent, suffix, type) {
    if(is_oldLatex){
        $(".html2md-cb" + suffix).css({
            "cursor": "not-allowed",
            "background-color": "#e6e6e638",
            "color": "#72737896",
        });
        $(".html2md-cb" + suffix).prop("disabled", true);
    }
    $(".html2md-cb" + suffix).click(function () {
        var target, removedChildren;
        if (type === "problem") {
            target = $($(parent).children().next().next().get(0)).clone();
        } else if (type === "problem_QA") {
            target = $(this).parent().parent().eq(0).clone();
            $(target).children(':first').remove();
        } else if (type === "upper_level_2") {
            target = $(parent).clone();
            $(target).children(':first').remove(); // 删除前两个元素(标题和按钮面板)
            $(target).children(':first').remove();
        } else {
            target = $(parent).clone();
        }
        if (!target.markdown) {
            target.markdown = turndownService.turndown($(target).html());
        }
        const textarea = document.createElement('textarea');
        textarea.value = target.markdown;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        $(this).addClass("copied");
        $(this).text("Copied");
        // 更新复制按钮文本
        setTimeout(() => {
            $(this).removeClass("copied");
            $(this).text("Copy");
        }, 2000);
        $(target).remove();
    });
}

function addButtonWithTranslation(parent, suffix, type) {
    $(".translateButton" + suffix).click(async function () {
        var target, removedChildren;
        if (type === "problem") {
            target = $($(parent).children().next().next().get(0)).clone();
        } else if (type === "problem_QA") {
            target = $(this).parent().parent().eq(0).clone();
            $(target).children(':first').remove();
        } else if (type === "upper_level_2") {
            target = $(parent).clone();
            $(target).children(':first').remove(); // 删除前两个元素(标题和按钮面板)
            $(target).children(':first').remove();
        } else {
            target = $(parent).clone();
        }
        if (is_oldLatex) {
            target.markdown = $(target).html();
        } else if (!target.markdown) {
            target.markdown = turndownService.turndown($(target).html());
        }
        const textarea = document.createElement('textarea');
        textarea.value = target.markdown;
        // 翻译处理
        $(this).removeClass("translated");
        $(this).text("翻译中");
        $(this).css("cursor", "not-allowed");
        var element_node;
        if (type === "problem") {
            element_node = $(parent).children()[2]; // 题面特殊处理
        } else if (type === "problem_QA") {
            element_node = $(parent).children().last().get(0);
        } else {
            element_node = parent;
        }
        var translateDiv = await translateProblemStatement(textarea.value, element_node);
        //
        $(this).addClass("translated");
        $(this).text("已翻译");
        $(this).prop("disabled", true);
        $(target).remove();
        // 重新翻译
        if ($(this).next('.reTranslation').length === 0) {
            const reTranslateBtn = $('<button>').addClass('html2mdButton reTranslation').html('&circlearrowright;').attr('title', '重新翻译');
            reTranslateBtn.on('click', function() {
                $(translateDiv).remove();
                $(this).prev().click();
            });
            $(this).after(reTranslateBtn);
        }else {
            const reTranslateBtn = $(this).next('.reTranslation');
            reTranslateBtn.off('click').on('click', function() {
                $(translateDiv).remove();
                $(this).prev().click();
            });
        }
    });
}

var turndownService = new TurndownService();
window.onload = function () {
    turndownService.keep(['del']);

    // 处理规则
    // 忽略
    turndownService.addRule('ignore-sample-tests', {
        filter: function (node) {
            return node.classList.contains('sample-tests') || node.classList.contains('header');
        },
        replacement: function (content, node) {
            return "";
        }
    });

    // remove <script> math
    turndownService.addRule('remove-script', {
        filter: function (node, options) {
            return node.tagName.toLowerCase() == "script" && node.type.startsWith("math/tex");
        },
        replacement: function (content, node) {
            return "";
        }
    });

    // inline math
    turndownService.addRule('inline-math', {
        filter: function (node, options) {
            return node.tagName.toLowerCase() == "span" && node.className == "MathJax";
        },
        replacement: function (content, node) {
            return "$ " + $(node).next().text() + " $";
        }
    });

    // block math
    turndownService.addRule('block-math', {
        filter: function (node, options) {
            return node.tagName.toLowerCase() == "div" && node.className == "MathJax_Display";
        },
        replacement: function (content, node) {
            return "\n$$\n" + $(node).next().text() + "\n$$\n";
        }
    });

    // bordertable
    turndownService.addRule('bordertable', {
        filter: 'table',
        replacement: function (content, node) {
            if (node.classList.contains('bordertable')) {
                var output = [],
                    thead = '',
                    trs = node.querySelectorAll('tr');
                if (trs.length > 0) {
                    var ths = trs[0].querySelectorAll('th');
                    if (ths.length > 0) {
                        thead = '| ' + Array.from(ths).map(th => turndownService.turndown(th.innerHTML.trim())).join(' | ') + ' |\n'
                            + '| ' + Array.from(ths).map(() => ' --- ').join('|') + ' |\n';
                    }
                }
                var rows = node.querySelectorAll('tr');
                Array.from(rows).forEach(function (row, i) {
                    if (i > 0) {
                        var cells = row.querySelectorAll('td,th');
                        var trow = '| ' + Array.from(cells).map(cell => turndownService.turndown(cell.innerHTML.trim())).join(' | ') + ' |';
                        output.push(trow);
                    }
                });
                return thead + output.join('\n');
            } else {
                return content;
            }
        }
    });


    // 添加按钮到题面部分
    $("div[class='problem-statement']").each(function () {
        var problem_id = "_comment_" + getRandomNumber(8);
        addButtonPanel(this, problem_id, "problem");
        addButtonWithHTML2MD(this, problem_id, "problem");
        addButtonWithCopy(this, problem_id, "problem");
        addButtonWithTranslation(this, problem_id, "problem");
    });

    // 添加按钮到input部分
    $("div[class='input-specification']").each(function () {
        var id = "_" + getRandomNumber(8);
        addButtonPanel(this, id, "upper_level_2");
        addButtonWithHTML2MD(this, id, "upper_level_2");
        addButtonWithCopy(this, id, "upper_level_2");
        addButtonWithTranslation(this, id, "upper_level_2");
    });

    // 添加按钮到output部分
    $("div[class='output-specification']").each(function () {
        var id = "_" + getRandomNumber(8);
        addButtonPanel(this, id, "upper_level_2");
        addButtonWithHTML2MD(this, id, "upper_level_2");
        addButtonWithCopy(this, id, "upper_level_2");
        addButtonWithTranslation(this, id, "upper_level_2");
    });

    // 添加按钮到note部分
    $("div[class='note']").each(function () {
        var id = "_" + getRandomNumber(8);
        addButtonPanel(this, id, "upper_level_2");
        addButtonWithHTML2MD(this, id, "upper_level_2");
        addButtonWithCopy(this, id, "upper_level_2");
        addButtonWithTranslation(this, id, "upper_level_2");
    });

    // 添加按钮到ttypography部分
    $(".ttypography").each(function () {
        // 题目页特判
        if (!$(this).parent().hasClass('problemindexholder')) {
            var comment_id = "_comment_" + getRandomNumber(8);
            addButtonPanel(this, comment_id, "this_level");
            addButtonWithHTML2MD(this, comment_id, "this_level");
            addButtonWithCopy(this, comment_id, "this_level");
            addButtonWithTranslation(this, comment_id, "this_level");
        }
    });

    // 添加按钮到titled部分
    (function () {
        var elements = [".Virtual.participation", ".Attention", ".Practice"];//只为部分titled添加
        $.each(elements, function (index, value) {
            $(value).each(function () {
                var titled_id = "_titled_" + getRandomNumber(8);
                var $nextDiv = $(this).next().get(0);
                addButtonPanel($nextDiv, titled_id, "this_level", true);
                addButtonWithTranslation($nextDiv, titled_id, "this_level");
            });
        });
    })();

    // 添加按钮到比赛QA部分
    $(".question-requestText-td").each(function () {
        var $nextDiv = $(this).children().get(0);
        var question_id = "_question_" + getRandomNumber(8);
        addButtonPanel($nextDiv, question_id, "problem_QA", true);
        addButtonWithTranslation(this, question_id, "problem_QA");
    });
};

// 翻译框/翻译处理器
var translatedText = "";
async function translateProblemStatement(text, element_node) {
    let id = Math.floor(Date.now() / 1000);
    let matches = [];
    let replacements = {};
    // 创建元素并放在element_node的后面
    const translateDiv = document.createElement('div');
    translateDiv.setAttribute('id', id);
    translateDiv.classList.add('translate-problem-statement');
    const spanElement = document.createElement('span');
    translateDiv.appendChild(spanElement);
    element_node.insertAdjacentElement('afterend', translateDiv);
    // 替换latex公式
    if (is_oldLatex) {
        //去除开头结尾的<p>标签
        text = text.replace(/^<p>/i, "");
        text = text.replace(/<\/p>$/i, "");
        //
        let i = 0;
        let regex = /<span\s+class="tex-span">.*?<\/span>/gi;
        matches = text.match(regex);
        try {
            for (i; i < matches.length; i++) {
                let match = matches[i];
                text = text.replace(match, `【${i + 1}】`);
                replacements[`【${i + 1}】`] = match;
            }
        } catch (e) { }
    } else if (getCookie("translation") != "api2d" && getCookie("translation") != "openai") {
        // 使用GPT翻译时不必替换latex公式
        let i = 0;
        // 块公式
        matches = matches.concat(text.match(/\$\$([\s\S]*?)\$\$/g));
        try {
            for (i; i < matches.length; i++) {
                let match = matches[i];
                text = text.replace(match, `【${i + 1}】`);
                replacements[`【${i + 1}】`] = match;
            }
        } catch (e) { }
        // 行内公式
        matches = matches.concat(text.match(/\$(.*?)\$/g));
        try {
            for (i; i < matches.length; i++) {
                let match = matches[i];
                text = text.replace(match, `【${i + 1}】`);
                replacements[`【${i + 1}】`] = match;
            }
        } catch (e) { }
    }
    // 翻译
    var translation = getCookie("translation");
    if (translation === 'undefined' || translation === '') {
        setCookie("translation", "deepl", 3650);
        translation = "deepl";
    }
    if (translation == "deepl") {
        translateDiv.textContent = "正在翻译中……请耐心等待,\n\n如果长时间无变化,请尝试刷新页面重试或者查看控制台的报错信息";
        translatedText = await translate_deepl(text);
    } else if (translation == "youdao") {
        translateDiv.textContent = "正在翻译中……请耐心等待,\n\n如果长时间无变化,请尝试刷新页面重试或者查看控制台的报错信息";
        translatedText = await translate_youdao_mobile(text);
    } else if (translation == "baidu") {
        var baidu_appid = getCookie("baidu_uid");
        var baidu_key = getCookie("baidu_key");
    } else if (translation == "openai") {
        translateDiv.textContent = "正在翻译中……请稍等\n\n使用GPT(ChatGPT/api2d)进行翻译通常需要很长的时间,请耐心等待\n\n如果长时间无变化,请尝试刷新页面重试或者查看控制台的报错信息";
        translatedText = await translate_openai(text);
    } else if (translation == "api2d") {
        translateDiv.textContent = "正在翻译中……请稍等\n\n使用GPT(ChatGPT/api2d)进行翻译通常需要很长的时间,请耐心等待\n\n如果长时间无变化,请尝试刷新页面重试或者查看控制台的报错信息";
        translatedText = await translate_api2d(text);
    }
    // 还原latex公式
    if (is_oldLatex) {
        translatedText = "<p>" + translatedText;
        translatedText += "</p>";
        try {
            for (let i = 0; i < matches.length; i++) {
                let match = matches[i];
                let replacement = replacements[`【${i + 1}】`];
                translatedText = translatedText.replace(`【${i + 1}】`, replacement);
            }
        } catch (e) { }
    }
    else if (getCookie("translation") != "api2d" && getCookie("translation") != "openai") {
        try {
            for (let i = 0; i < matches.length; i++) {
                let match = matches[i];
                let replacement = replacements[`【${i + 1}】`];
                translatedText = translatedText.replace(`【${i + 1}】`, replacement);
            }
        } catch (e) { }
    }
    // 使符合mathjx的转换语法
    translatedText = translatedText.replace(/(\$\$)/g, "");
    translatedText = translatedText.replace(/(?<!\$)\$(?!\$)/g, "$$$$$");
    // 更新
    if (is_oldLatex) {
        // oldlatex
        translatedText = $.parseHTML(translatedText);
        $(translateDiv).empty().append($(translatedText));
    } else {
        translateDiv.innerHTML = translatedText;
        // 渲染MarkDown
        var md = window.markdownit();
        var html = md.render(translateDiv.innerText);
        translateDiv.innerHTML = html;
        // 渲染Latex
        MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById(id)]);
    }
    return translateDiv;
}

// ChatGPT
async function translate_openai(raw) {
    var openai_key = getCookie("openai_key");
    var openai_retext = "";
    var data;
    if (is_oldLatex) {
        data = {
            prompt: "(You:请帮我将下面的文本翻译为中文,这是一个编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的【】、HTML标签本身以及其中的内容不翻译不变动,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n" + raw + ")",
            max_tokens: 2048,
            model: "text-davinci-003",
        };
    } else {
        data = {
            prompt: "(You:请帮我将下面的文本翻译为中文,这是一个编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的latex公式不翻译,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n" + raw + ")",
            max_tokens: 2048,
            model: "text-davinci-003",
        };
    };
    return new Promise(function (resolve, reject) {
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://api.openai.com/v1/completions',
            data: JSON.stringify(data),
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + getCookie("openai_key") + ''
            },
            responseType: 'json',
            onload: function (res) {
                if (res.status === 200) {
                    openai_retext = res.response.choices[0].text;
                    openai_retext = openai_retext.replace(/^\s+/, '');
                    resolve(openai_retext);
                }
                else {
                    console.error(res.statusText);
                    reject(res.statusText);
                }
            }
        });
    });
}

// api2d
async function translate_api2d(raw) {
    var api2d_key = getCookie("api2d_key");
    var api2d_retext = "";
    var postData;
    if (is_oldLatex) {
        postData = JSON.stringify({
            model: 'gpt-3.5-turbo',
            messages: [{ role: 'user', content: '请帮我将下面的文本翻译为中文,这是一个编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的【】、HTML标签本身以及其中的内容不翻译不变动,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n' + raw }],
        });
    } else {
        postData = JSON.stringify({
            model: 'gpt-3.5-turbo',
            messages: [{ role: 'user', content: '请帮我将下面的文本翻译为中文,这是一个编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的latex公式不翻译,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n' + raw }],
        });
    }
    const options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Authorization: 'Bearer ' + api2d_key,
        },
        data: postData,
    };

    return new Promise(function (resolve, reject) {
        GM_xmlhttpRequest({
            method: options.method,
            url: `https://openai.api2d.net/v1/chat/completions`,
            headers: options.headers,
            data: options.data,
            responseType: 'json',
            onload: function (response) {
                api2d_retext = response.response.choices[0].message.content;
                resolve(api2d_retext);
            },
            onerror: function (response) {
                console.error(response.statusText);
                reject(response.statusText);
            },
        });
    });
}
//

//--有道翻译m--start
async function translate_youdao_mobile(raw) {
    const options = {
        method: "POST",
        url: 'http://m.youdao.com/translate',
        data: "inputtext=" + encodeURIComponent(raw) + "&type=AUTO",
        anonymous: true,
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        }
    }
    return await BaseTranslate('有道翻译mobile', raw, options, res => /id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res)[1])
}
//--有道翻译m--end

//--Deepl翻译--start
function getTimeStamp(iCount) {
    const ts = Date.now();
    if (iCount !== 0) {
        iCount = iCount + 1;
        return ts - (ts % iCount) + iCount;
    } else {
        return ts;
    }
}

async function translate_deepl(raw) {
    const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
    const data = {
        jsonrpc: '2.0',
        method: 'LMT_handle_texts',
        id,
        params: {
            splitting: 'newlines',
            lang: {
                source_lang_user_selected: 'auto',
                target_lang: 'ZH',
            },
            texts: [{
                text: raw,
                requestAlternatives: 3
            }],
            timestamp: getTimeStamp(raw.split('i').length - 1)
        }
    }
    let postData = JSON.stringify(data);
    if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
        postData = postData.replace('"method":"', '"method" : "');
    } else {
        postData = postData.replace('"method":"', '"method": "');
    }
    const options = {
        method: 'POST',
        url: 'https://www2.deepl.com/jsonrpc',
        data: postData,
        headers: {
            'Content-Type': 'application/json',
            'Host': 'www.deepl.com',
            'Origin': 'https://www.deepl.com',
            'Referer': 'https://www.deepl.com/'
        },
        anonymous: true,
        nocache: true,
    }
    return await BaseTranslate('Deepl翻译', raw, options, res => JSON.parse(res).result.texts[0].text)
}

//--Deepl翻译--end

// Cookie Get/Set
function setCookie(cname, cvalue, exdays) {
    var d = new Date();
    d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
    var expires = "expires=" + d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}


//--异步请求包装工具--start
async function PromiseRetryWrap(task, options, ...values) {
    const { RetryTimes, ErrProcesser } = options || {};
    let retryTimes = RetryTimes || 5;
    const usedErrProcesser = ErrProcesser || (err => { throw err });
    if (!task) return;
    while (true) {
        try {
            return await task(...values);
        } catch (err) {
            if (!--retryTimes) {
                console.log(err);
                return usedErrProcesser(err);
            }
        }
    }
}

async function BaseTranslate(name, raw, options, processer) {
    const toDo = async () => {
        var tmp;
        try {
            const data = await Request(options);
            tmp = data.responseText;
            const result = await processer(tmp);
            if (result) sessionStorage.setItem(name + '-' + raw, result);
            return result
        } catch (err) {
            throw {
                responseText: tmp,
                err: err
            }
        }
    }
    return await PromiseRetryWrap(toDo, { RetryTimes: 3, ErrProcesser: () => "翻译出错" })
}

function Request(options) {
    return new Promise((reslove, reject) => GM_xmlhttpRequest({ ...options, onload: reslove, onerror: reject }))
}

//--异步请求包装工具--end

// 随机数生成
function getRandomNumber(numDigits) {
    let min = Math.pow(10, numDigits - 1);
    let max = Math.pow(10, numDigits) - 1;
    return Math.floor(Math.random() * (max - min + 1)) + min;
}