")
.addClass("html2md-panel")
.attr("id", "CF2luoguPanel")
.insertBefore('.problemindexholder');
const url = `https://www.luogu.com.cn/problem/CF${getProblemId()}`;
const result = await checkLinkExistence(url);
if (getProblemId() && result) {
const problemLink = $("
")
.attr("id", "problemLink")
.attr("href", url)
.attr("target", "_blank")
.html(``);
panelElement.append(problemLink);
}
}
// 等待Latex渲染队列全部完成
function waitUntilIdleThenDo(callback) {
var intervalId = setInterval(function () {
var queue = MathJax.Hub.queue;
if (queue.pending === 0 && queue.running === 0) {
clearInterval(intervalId);
callback();
}
}, 100);
}
// 字数超限确认
function showWordsExceededDialog(button) {
return new Promise(resolve => {
const styleElement = GM_addStyle(darkenPageStyle);
$(button).removeClass("translated");
$(button).text("字数超限");
$(button).css("cursor", "not-allowed");
$(button).prop("disabled", true);
let htmlString = `
字数超限!
注意,即将翻译的内容字数超过了4950个字符,您可能选择了错误的翻译按钮
`+ helpCircleHTML + `
由于实现方式,区域中会出现多个翻译按钮,请点击更小的子区域中的翻译按钮,
或者在设置面板中开启 分段翻译 后重试。
对于免费的接口,大量请求可能导致你的IP被暂时禁止访问,对于GPT,会消耗大量的token
您确定要继续翻译吗?
`;
$('body').before(htmlString);
$("#continueButton").click(function () {
$(styleElement).remove();
$('.wordsExceeded').remove();
resolve(true);
});
$("#cancelButton").click(function () {
$(styleElement).remove();
$('.wordsExceeded').remove();
resolve(false);
});
});
}
//跳过折叠块确认
function skiFoldingBlocks() {
return new Promise(resolve => {
const styleElement = GM_addStyle(darkenPageStyle);
let htmlString = `
是否跳过折叠块?
即将翻译的区域中包含折叠块,折叠块可能是代码,通常不需要翻译,现在您需要选择是否跳过这些折叠块,
如果其中有您需要翻译的折叠块,可以稍后再单独点击这些折叠块内的翻译按钮进行翻译
要跳过折叠块吗?(建议选择跳过)
`;
$('body').before(htmlString);
$("#skipButton").click(function () {
$(styleElement).remove();
$('.wordsExceeded').remove();
resolve(true);
});
$("#cancelButton").click(function () {
$(styleElement).remove();
$('.wordsExceeded').remove();
resolve(false);
});
});
}
// 翻译框/翻译处理器
var translatedText = "";
async function translateProblemStatement(text, element_node, button) {
let status = 0;
let id = getRandomNumber(8);
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) {
//去除开头结尾的标签
text = text.replace(/^
/i, "");
text = text.replace(/<\/p>$/i, "");
//
let i = 0;
let regex = /.*?<\/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 (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) { }
}
if (text.length > 4950) {
const shouldContinue = await showWordsExceededDialog(button);
if (!shouldContinue) {
status = 1;
return {
translateDiv: translateDiv,
status: status
};
}
}
// 翻译
if (translation == "deepl") {
translateDiv.innerHTML = "正在使用 deepl 翻译中……请稍等";
translatedText = await translate_deepl(text);
} else if (translation == "youdao") {
translateDiv.innerHTML = "正在使用 有道 翻译中……请稍等";
translatedText = await translate_youdao_mobile(text);
} else if (translation == "google") {
translateDiv.innerHTML = "正在使用 google 翻译中……请稍等";
translatedText = await translate_gg(text);
} else if (translation == "openai") {
try {
translateDiv.innerHTML = "正在使用 ChatGPT 翻译中……" +
"
应用的配置:" + opneaiConfig.configurations[opneaiConfig.choice].note +
"
使用 ChatGPT 翻译需要很长的时间,请耐心等待";
translatedText = await translate_openai(text);
} catch (error) {
status = 2;
translatedText = error;
}
}
if (/^翻译出错/.test(translatedText)) status = 2;
// 还原latex公式
translatedText = translatedText.replace(/】【/g, '】 【');
if (is_oldLatex) {
translatedText = "" + translatedText;
translatedText += "
";
try {
for (let i = 0; i < matches.length; i++) {
let match = matches[i];
let replacement = replacements[`【${i + 1}】`];
let regex;
regex = new RegExp(`【\\s*${i + 1}\\s*】`, 'g');
translatedText = translatedText.replace(regex, replacement);
regex = new RegExp(`\\[\\s*${i + 1}\\s*\\]`, 'g');
translatedText = translatedText.replace(regex, replacement);
regex = new RegExp(`【\\s*${i + 1}(?![】\\d])`, 'g');
translatedText = translatedText.replace(regex, replacement);
regex = new RegExp(`(? {
$(this).removeClass("copied");
$(this).text("Copy");
}, 2000);
});
translateDiv.parentNode.insertBefore(wrapperDiv, translateDiv);
}
// 转义LaTex中的特殊符号
if (!is_oldLatex) {
const escapeRules = [
{ pattern: /(?(?!\s)/g, replacement: " > " }, // >符号
{ pattern: /(? {
translatedText = translatedText.replace(pattern, replacement);
});
// 更新
if (is_oldLatex) {
// oldlatex
translatedText = $.parseHTML(translatedText);
$(translateDiv).empty().append($(translatedText));
return {
translateDiv: translateDiv,
status: status
};
} else {
// 渲染MarkDown
var md = window.markdownit();
var html = md.render(translatedText);
translateDiv.innerHTML = html;
// 渲染Latex
MathJax.Hub.Queue(["Typeset", MathJax.Hub, translateDiv]);
return {
translateDiv: translateDiv,
status: status,
copyDiv: textElement,
copyButton: copyButton
};
}
}
// ChatGPT
async function translate_openai(raw) {
var openai_retext = "";
var data;
if (is_oldLatex) {
data = {
model: (openai_model !== null && openai_model !== "") ? openai_model : 'gpt-3.5-turbo',
messages: [{
role: "user",
content: "请将下面的文本翻译为中文,这是一道编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的【】、HTML标签本身以及其中的内容不翻译不变动,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n" + raw
}],
temperature: 0.7,
...Object.assign({}, ...openai_data)
};
} else {
data = {
model: (openai_model !== null && openai_model !== "") ? openai_model : 'gpt-3.5-turbo',
messages: [{
role: "user",
content: "请将下面的文本翻译为中文,这是一道编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的latex公式不翻译,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n" + raw
}],
temperature: 0.7
};
};
return new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: 'POST',
url: (openai_proxy !== null && openai_proxy !== "") ? openai_proxy : 'https://api.openai.com/v1/chat/completions',
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + openai_key,
...Object.assign({}, ...openai_header)
},
responseType: 'json',
onload: function (response) {
if (!response.response) {
reject("发生了未知的错误,如果你启用了代理API,请确认是否填写正确,并确保代理能够正常工作。\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/465777/feedback 反馈 请注意打码响应报文的敏感部分\n\n响应报文:" + JSON.stringify(response));
}
else if (!response.response.choices || response.response.choices.length < 1 || !response.response.choices[0].message) {
resolve("翻译出错,请重试\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/465777/feedback 反馈 \n\n报错信息:" + JSON.stringify(response.response, null, '\n'));
} else {
openai_retext = response.response.choices[0].message.content;
resolve(openai_retext);
}
},
onerror: function (response) {
reject("发生了未知的错误,请确认你是否能正常访问OpenAi的接口,如果使用代理API,请检查是否正常工作\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/465777/feedback 反馈 请注意打码响应报文的敏感部分\n\n响应报文:" + JSON.stringify(response));
},
});
});
}
//--谷歌翻译--start
async function translate_gg(raw) {
return new Promise((resolve, reject) => {
const url = 'https://translate.google.com/m';
const params = `tl=zh-CN&q=${encodeURIComponent(raw)}`;
GM_xmlhttpRequest({
method: 'GET',
url: `${url}?${params}`,
onload: function (response) {
const html = response.responseText;
const translatedText = $(html).find('.result-container').text();
resolve(translatedText);
},
onerror: function (error) {
console.error('Error:', error);
reject(error);
}
});
});
}
//--谷歌翻译--end
//--有道翻译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",
'Host': 'm.youdao.com',
'Origin': 'http://m.youdao.com',
'Referer': 'http://m.youdao.com/translate',
}
}
return await BaseTranslate('有道翻译mobile', raw, options, res => /id="translateResult">\s*?([\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': 'www2.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
//--异步请求包装工具--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) {
let errtext;
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) {
errtext = tmp;
throw {
responseText: tmp,
err: err
}
}
}
return await PromiseRetryWrap(toDo, { RetryTimes: 3, ErrProcesser: () => "翻译出错,请重试或更换翻译接口\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/465777/feedback 反馈 请注意打码报错信息的敏感部分\n\n报错信息:" + errtext })
}
function Request(options) {
return new Promise((reslove, reject) => GM_xmlhttpRequest({ ...options, onload: reslove, onerror: reject }))
}
//--异步请求包装工具--end
// 开始
document.addEventListener("DOMContentLoaded", function () {
function checkJQuery(retryDelay) {
if (typeof jQuery === 'undefined') {
console.warn("JQuery未加载," + retryDelay + "毫秒后重试");
setTimeout(function () {
var newRetryDelay = Math.min(retryDelay * 2, 2000);
checkJQuery(newRetryDelay);
}, retryDelay);
} else {
init();
settingPanel();
checkScriptVersion();
toZH_CN();
var newElement = $("").addClass("alert alert-info CFBetter_alert")
.html(`Codeforces Better! —— 正在等待页面资源加载……`)
.css({
"margin": "1em",
"text-align": "center",
"font-weight": "600",
"position": "relative"
});
var tip_SegmentedTranslation = $("").addClass("alert alert-error CFBetter_alert")
.html(`Codeforces Better! —— 注意!分段翻译已开启,这会造成负面效果,
除非你现在需要翻译超长篇的博客或者题目,否则请前往设置关闭分段翻译
`)
.css({
"margin": "1em",
"text-align": "center",
"font-weight": "600",
"position": "relative"
});
async function processPage() {
if (showLoading) newElement.html('Codeforces Better! —— 正在等待Latex渲染队列全部完成……');
await waitUntilIdleThenDo(async function () {
if (enableSegmentedTranslation) $(".menu-box:first").next().after(tip_SegmentedTranslation); //显示分段翻译警告
if (showJumpToLuogu) CF2luogu();
Promise.resolve()
.then(() => {
if (showLoading && expandFoldingblocks) newElement.html('Codeforces Better! —— 正在展开折叠块……');
return delay(100).then(() => { if (expandFoldingblocks) ExpandFoldingblocks() });
})
.then(() => {
if (showLoading && commentPaging) newElement.html('Codeforces Better! —— 正在对评论区分页……');
return delay(100).then(() => { if (commentPaging) CommentPagination() });
})
.then(() => {
if (showLoading) newElement.html('Codeforces Better! —— 正在加载按钮……');
return delay(100).then(() => addConversionButton());
})
.then(async () => {
if (showLoading && renderPerfOpt) newElement.html('Codeforces Better! —— 正在优化折叠块渲染……');
await delay(100);
if (renderPerfOpt) await RenderPerfOpt();
})
.then(() => {
alertZh();
if (showLoading) {
newElement.html('Codeforces Better! —— 加载已完成');
newElement.removeClass('alert-info').addClass('alert-success');
setTimeout(function () {
newElement.remove();
}, 3000);
}
})
.catch((error) => {
console.log(error);
});
});
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
if (showLoading) {
if (is_mSite) $("header").after(newElement);
else $(".menu-box:first").next().after(newElement);
}
if (loaded) {
processPage();
} else {
// 页面完全加载完成后执行
window.onload = function () {
processPage();
};
}
}
}
checkJQuery(50);
});
// 配置自动迁移代码(将在10个小版本后移除)
if (GM_getValue("openai_key") || GM_getValue("api2d_key")) {
const newConfig = { "choice": -1, "configurations": [] };
if (GM_getValue("openai_key")) {
let config1 = {
"note": "我的配置1",
"model": GM_getValue("openai_model") || "",
"key": GM_getValue("openai_key"),
"proxy": GM_getValue("openai_proxy") || "",
"_header": "",
"_data": ""
}
if (GM_getValue("translation") === "openai") newConfig.choice = 0;
newConfig.configurations.push(config1);
}
if (GM_getValue("api2d_key")) {
let config2 = {
"note": "api2d",
"model": GM_getValue("api2d_model"),
"key": GM_getValue("api2d_key"),
"proxy": GM_getValue("api2d_request_entry") + '/v1/chat/completions',
"_header": GM_getValue("x_api2d_no_cache") ? "" : " x-api2d-no-cache : 1",
"_data": ""
}
if (GM_getValue("translation") === "api2d") {
if (GM_getValue("openai_key")) newConfig.choice = 1;
else newConfig.choice = 0;
}
newConfig.configurations.push(config2);
}
GM_setValue("chatgpt-config", newConfig);
const keysToDelete = ["openai_key", "openai_model", "openai_proxy", "api2d_key", "api2d_model", "api2d_request_entry", "x_api2d_no_cache", "showOpneAiAdvanced"];
keysToDelete.forEach(key => {
if (GM_getValue(key) != undefined) GM_deleteValue(key);
});
if (GM_getValue("translation") === "api2d") GM_setValue("translation", "openai");
location.reload();
}