// ==UserScript==
// @name USACO Better
// @namespace http://tampermonkey.net/
// @version 1.2.2
// @description USACO 优化插件
// @author ZnPdCo
// @match https://usaco.org/*
// @icon https://usaco.guide/favicon-32x32.png
// @grant unsafeWindow
// @connect www2.deepl.com
// @connect www.iflyrec.com
// @connect m.youdao.com
// @connect api.interpreter.caiyunai.com
// @connect translate.google.com
// @connect greasyfork.org
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js
// @license MIT
// ==/UserScript==
(function() {
// 常量
const translates = {
'gg': {'name': '谷歌翻译', 'func': translate_gg},
'youdao_mobile': {'name': '有道翻译', 'func': translate_youdao_mobile},
'deepl': {'name': 'deepl翻译', 'func': translate_deepl},
'iflyrec': {'name': '讯飞听见翻译', 'func': translate_iflyrec},
};
// 设置页内容
if(location.search == '?page=settings') {
$('.content').html(`
<a href="index.php">
<img src="current/images/usaco_logo.png" height="130px" border="0px">
</a>
<div class="navbar" align="left" border="10px">
<ul>
<li>
<a href="index.php">Overview</a>
</li>
<li>
<a href="index.php?page=training">Training</a>
</li>
<li>
<a href="index.php?page=contests">Contests</a>
</li>
<li>
<a href="index.php?page=history">History</a>
</li>
<li>
<a href="index.php?page=staff">Staff</a>
</li>
<li>
<a href="index.php?page=resources">Resources</a>
</li>
</ul>
</div>
<br>
<div style="position:relative; float:left; left:10px; top:-50px; width:880px;">
<div class="panel">
<h2>USACO Better 设置</h2>
</div>
</div>
<br style="clear:both">`);
// 题面翻译设置
if(GM_getValue('translate') == undefined) {
GM_setValue('translate', 'iflyrec');
}
$('.panel').append(`翻译引擎设置:<select id="translate"></select><br><br>`)
for (var [key, value] of Object.entries(translates)) {
$('#translate').append(`<option value="${key}" ${GM_getValue('translate') == key ? 'selected' : ''}>${value['name']}</option>`);
}
$('#translate').change(function() {
GM_setValue('translate', $('#translate').val());
})
// 提交代码框设置
if(GM_getValue('code_box') == undefined) {
GM_setValue('code_box', true);
}
$('.panel').append(`提交代码时使用代码框:<input id="code_box" type="checkbox" ${GM_getValue('code_box') == true ? 'checked' : ''}><br><br>`)
$('#code_box').change(function() {
GM_setValue('code_box', $('#code_box').is(':checked'));
})
}
// 菜单栏展示设置页
$('.navbar ul').append(`<li><a href="index.php?page=settings">Settings</a></li>`)
$('.navbar ul li a').css({'width': '90px'})
// 界面翻译
$('.navbar').html($('.navbar').html().
replaceAll('Overview', '主页').
replaceAll('Training', '训练').
replaceAll('Contests', '比赛').
replaceAll('History', '历史').
replaceAll('Staff', '成员').
replaceAll('Resources', '资源').
replaceAll('Settings', '设置'))
// 主页内容
if(location.search == '') {
$('.panel a:contains("rules")').text(`比赛规则`)
}
// 训练页内容
if(location.search == '?page=training') {
$('.panel').html(`
<h2>训练</h2>
<p><b>新资源:</b> 一群敬业的学长们已经准备了一个新的培训平台: <a href="https://usaco.guide/">USACO Guide</a>。
<p> USACO的<a href="https://usaco.training">培训页面</a> 提供了数百个小时的免费指导和练习问题,帮助提高编程和问题解决能力。
(注:我们正在迁移培训页面至新系统;与此同时,它们托管在我们的旧系统上,在这段时间内,全新的usaco.org用户账户无法被识别 —— 您可能需要创建一个单独的账户来访问培训页面。)
</p>
<p> 通过在线培训页面展现出实质性进步,并在我们的<a href="index.php?page=contests">在线编程比赛</a>中表现出色的学生,有资格被选为受邀参加USACO夏季<a href="index.php?page=camp">培训营</a>的决赛选手,进一步接受指导,并有可能被考虑为参加国际信息学奥林匹克竞赛(IOI)的美国代表团成员。
</p>
`)
}
// 历史页内容
if(location.search == '?page=history') {
$('.panel').html(`该页由于篇幅原因,不进行翻译` + $('.panel').html())
}
// 成员页内容
if(location.search == '?page=staff') {
$('.panel').html(`该页由于篇幅原因,不进行翻译` + $('.panel').html())
}
// 资源页内容
if(location.search == '?page=resources') {
$('.panel').html(`该页由于篇幅原因,不进行翻译` + $('.panel').html())
}
// 规则页内容
if(location.search == '?page=instructions') {
$('.panel').html(`一般情况下,C 和 C++ 时限 2 秒,Java 和 Python 时限 4 秒,代码长度不超过 100,000 bytes,编译不超过 30 秒,空间限制 256 MB,C 和 C++ 开 O2,Python 开 O 优化\n` + $('.panel').html())
}
// 比赛页
if(location.search == '?page=contests') {
$('.panel:eq(0)').html($('.panel:eq(0)').html().
replaceAll('Previous Contests', '往届比赛').
replaceAll('Season', '季度'))
}
//--谷歌翻译--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 (response) {
reject("发生了未知的错误,请确认你是否能正常访问Google翻译,\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/471106/feedback 反馈 请注意打码报错信息的敏感部分\n\n响应报文:" + JSON.stringify(response))
}
});
});
}
//--谷歌翻译--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*?<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': '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
//--讯飞听见翻译--end
async function translate_iflyrec(text) {
const options = {
method: "POST",
url: 'https://www.iflyrec.com/TranslationService/v1/textTranslation',
data: JSON.stringify({
"from": "2",
"to": "1",
"contents": [{
"text": text,
"frontBlankLine": 0
}]
}),
anonymous: true,
headers: {
'Content-Type': 'application/json',
'Origin': 'https://www.iflyrec.com',
},
responseType: "json",
};
return await BaseTranslate('讯飞翻译', text, options, res => JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n"));
}
//--讯飞听见翻译--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.warn(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;
let result = await processer(tmp);
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/471106/feedback 反馈 请注意打码报错信息的敏感部分\n\n报错信息:" + errtext })
}
function Request(options) {
return new Promise((reslove, reject) => GM_xmlhttpRequest({ ...options, onload: reslove, onerror: reject }))
}
async function show_translate_btn() {
if($('#probtext-text').length) {
$.ajax({
type: "GET",
url: location.href,
async: false,
success: function(data) {
window.data = data
},
error: function(xhr, statusText, error) {}
});
var origin_html = {};
$('#probtext-text').html($(data).find('#probtext-text').html())
var ele1 = $('#probtext-text').children()
var ele2 = $('#probtext-text').contents().filter(function() {
return this.nodeType === 3;
})
var ele = $.merge(ele1, ele2)
for(let i = 0; i < ele.length; i++) {
if(ele.eq(i).text().replaceAll('\n', '').replaceAll(' ', '') == '' || ele.get(i).tagName == 'PRE' || ele.eq(i).text().includes('SAMPLE INPUT:') || ele.eq(i).text().includes('SAMPLE OUTPUT:')) continue
$(`
<div style="text-align: right">
<button style="margin-bottom:6px;
background-color: transparent;
color: #08c;
margin-left: 4px;
border: 1px solid #08c;
border-radius: 3px;" class="fanyi" id="fanyi-${i}" type="button">翻译</button>
</div>`).insertBefore(ele.eq(i))
if(ele.get(i).nodeType == 3)origin_html[i] = ele.eq(i).text()
else origin_html[i] = ele.eq(i).html()
}
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
$(`.fanyi`).click(async function(e) {
var fanyi_id = parseInt($(e.target).attr("id").replace('fanyi-', ''));
var text = origin_html[fanyi_id]
var texts = text.split('$$')
var res = ""
var tex = {}
var cnt = 0;
for(let i = 0; i < texts.length; i++) {
if(i % 2 == 0) res += texts[i];
else {
cnt++;
tex[cnt] = '$$' + texts[i] + '$$';
res += '{' + cnt + '}';
}
}
text = res
texts = text.split('$')
res = ""
for(let i = 0; i < texts.length; i++) {
if(i % 2 == 0) res += texts[i];
else {
cnt++;
tex[cnt] = '$' + texts[i] + '$';
res += '{' + cnt + '}';
}
}
text = res
if($(`#result-${fanyi_id}`).length == 0) {
$(`<div align="left" id="result-${fanyi_id}" class="problem-text mathjax" style="width:750px; padding-top:10px;"></div>`).insertAfter(e.target)
$(`
<button style="margin-bottom:6px;
background-color: transparent;
color: #08c;
margin-left: 4px;
border: 1px solid #08c;
border-radius: 3px;" type="button" onclick="
function run(){
if($('#result-${fanyi_id}').is(':hidden')) {
$('#result-${fanyi_id}').show()
} else {
$('#result-${fanyi_id}').hide()
}
}
run()">折叠、展开</button>
`).insertBefore(e.target)
}
var timer = setInterval(function() {
var tip = `正在使用 ${translates[GM_getValue('translate')]['name']} 翻译,稍安勿躁`;
var tip1 = tip + '.';
var tip2 = tip + '..';
var tip3 = tip + '...';
if($(`#result-${fanyi_id}`).html() == tip1) $(`#result-${fanyi_id}`).html(tip2);
else if($(`#result-${fanyi_id}`).html() == tip2) $(`#result-${fanyi_id}`).html(tip3);
else if($(`#result-${fanyi_id}`).html() == tip3) $(`#result-${fanyi_id}`).html(tip1);
}, 100);
$(`#result-${fanyi_id}`).html(`正在使用 ${translates[GM_getValue('translate')]['name']} 翻译,稍安勿躁.`);
text = await translates[GM_getValue('translate')]['func'](text)
texts = text.split(/{|}/)
res = ""
for(let i = 0; i < texts.length; i++) {
if(i % 2 == 0) res += texts[i]
else res += tex[parseInt(texts[i])]
}
text = res
clearInterval(timer);
$(`#result-${fanyi_id}`).html(text);
$(e.target).text('重新翻译');
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
})
}
}
show_translate_btn();
function show_code_box() {
if($('#probtext-text').length && GM_getValue('code_box') == true) {
$('#solution .field2:eq(2)').remove()
$('#solution .field2:eq(1)').remove()
$(`
<div class="field2">
<label for="sourcefile">Your Code:</label>
<textarea id="code" style="width: 800px; height: 200px;"></textarea></div>
<div class="field2">
<button id="solution-submit" type="button">Submit Solution</button></div>
`).insertAfter('#solution .field2:eq(0)')
$('#solution-submit').click(function() {
var form = document.getElementsByClassName('submission')[0];
var text = document.getElementById('code').value;
var fileData = new File([text], 'foo.cpp', {
type: 'multipart/form-data',
});
var formData = new FormData(form);
formData.set('sourcefile', fileData)
$.ajax({
url: "current/tpcm/submit-solution.php",
type: "POST",
async: false,
data: formData,
processData: false, // 不处理数据
contentType: false // 不设置内容类型
});
location.reload();
})
}
}
show_code_box();
})();