// ==UserScript==
// @name CFCodereviewer
// @namespace http://tampermonkey.net/
// @version 2.2.0
// @description Codereview codeforces
// @author kdzestelov
// @license MIT
// @match *://*codeforces.com/*
// @grant GM_xmlhttpRequest
// @updateUrl https://gist.githubusercontent.com/KhetagAb/000670a28c8a37b917ece858b142b747/raw/script.js
// ==/UserScript==
const SHEET_URL = 'https://script.google.com/macros/s/AKfycbxGoJrsWBtwNFcYcXBNrk-PUTIOWrsRvPqDxBzBqT_TFpfaurt8IB5Dc9dEQU97-Eoyug/exec';
const SUB_RJ_BUTTON_CLASS = "submission-rj-button";
const SUB_AC_BUTTON_CLASS = "submission-ac-button";
const SUB_RG_BUTTON_CLASS = "submission-rejudge-form";
const SUB_COMMENT_SEND_BUTTON_CLASS = "submission-comment-send-form";
function getLast(href) {
return href.split("/").pop()
}
function getGroupUrl() {
const url = window.location.href
return url.substring(0, url.indexOf("contest"))
}
const getContestUrl = () => {
const url = window.location.href
return url.substring(0, url.indexOf("status"))
}
function getContestId() {
const url = window.location.href
return url.substr(url.indexOf("contest") + 8, 6);
}
function wrap(text) {
return '[' + text + ']';
}
function getSubRjButton(subId) {
return $("[submissionid=" + subId + "] ." + SUB_RJ_BUTTON_CLASS);
}
function getSubAcButton(subId) {
return $("[submissionid=" + subId + "] ." + SUB_AC_BUTTON_CLASS);
}
function getCommentSubAcButton(subId) {
return $("." + SUB_COMMENT_SEND_BUTTON_CLASS)
}
function getProblemIndex(subId) {
return getLast($("tr[data-submission-id=" + subId + "] .status-small a").attr('href'));
}
function getSubRow(subId) {
return $('tr[data-submission-id="' + subId + '"]')
}
function getHandle(subId) {
return $("tr[data-submission-id=" + subId + "] .status-party-cell").text().trim();
}
function getAllSubmissionsRow() {
return $(".status-frame-datatable tbody tr")
}
function getSubmissionRow(subId) {
return $(".status-frame-datatable tbody tr[data-submission-id=" + subId + "]")
}
function getSubsId() {
return $(".information-box-link")
}
function getCorrectSubs() {
return $(".information-box-link .verdict-accepted").parent();
}
function getSubButtons() {
return $(".submission-action-form");
}
function getSideBar() {
return $("div[id=sidebar]")
}
function getFilterBox() {
return $(".status-filter-box");
}
const getSheetSubmissions = () => {
const fullUrl = SHEET_URL + "?type=get"
GM_xmlhttpRequest({
method: 'GET',
url: fullUrl,
onload: (response) => {
if (response.status === 200) {
try {
console.log("Going to parse response: " + response.responseText);
var submissions = JSON.parse(response.responseText)
submissions = JSON.parse(response.responseText)
localStorage.setItem("c_status", JSON.stringify(submissions));
console.log("Submissions loaded from sheet: " + submissions);
} catch (e) {
console.error("Unable to parse submissions: " + e);
}
} else {
console.error("Unable to load submissions from sheet: " + response.responseText);
}
},
onerror: (error) => {
console.error(error);
}
});
}
const acceptSheetSubmission = (subId, button, type) => {
const full_url = SHEET_URL + "?type=" + type + "&value=" + subId;
GM_xmlhttpRequest({
method: 'GET',
url: full_url,
timeout: 5000,
onload: (response) => {
if (response.status === 200) {
const submissions = getSubmissions();
submissions.push(subId);
localStorage.setItem("c_status", JSON.stringify(submissions));
button.style.backgroundColor = "#81D718";
button.style.borderColor = "#81D718";
if (showed_codes[subId] != null && showed_codes[subId].showed) {
showed_codes[subId]["showButton"].click();
}
button.innerText = (type === "star" ? "🔥" : "Похвалить");
} else {
button.innerText = "Ошибка!"
console.error(response)
}
},
onerror: (error) => {
console.error(error);
}
});
}
const getSubmissions = () => {
return JSON.parse(localStorage.getItem("c_status") ?? "[]");
};
const isSubAccepted = (subId) => {
return getSubmissions().includes(subId)
}
const acceptSubmission = (subId, button) => {
const type = isSubAccepted(subId) ? "star" : "accept";
acceptSheetSubmission(subId, button, type);
}
const showed_codes = {};
function createSubShowButton(subId, lang) {
const button = document.createElement("button");
button.className = "submission-show";
button.style.marginTop = "4px";
button.style.marginBottom = "4px";
button.style.backgroundColor = "#176F95";
button.style.border = "1px solid #176F95";
button.style.borderRadius = "8px"
button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
button.style.color = "#fff"
button.style.cursor = "pointer"
button.style.padding = "10px"
button.style.width = "100%";
button.innerText = "Показать " + lang;
button.onclick = (_) => showButtonClick(subId, button);
return button;
}
function patchCodeSection(subId) {
const patchLine = (i, line) => {
line.addEventListener('click', () => {
if(window.getSelection().toString() != "")
return;
const text = $("[data-submission-id=" + subId + "] textarea")
text.val((text.val().length === 0 ? "" : text.val() + "\n") + "Строка " + (i + 1) + ": ")
var x = window.scrollX, y = window.scrollY;
text.focus();
window.scrollTo(x, y);
});
return line;
};
let pretty_code = $('[data-submission-id=' + subId + '] .program-source li');
const code_lines_count = pretty_code.length
pretty_code.each((i, line) => patchLine(i, line))
pretty_code = pretty_code.parent().parent();
pretty_code.before((_) => {
const lines = document.createElement("pre");
lines.style.width = '4%';
lines.style.padding = "0.5em";
lines.style.display = 'inline-block';
const lineNs = [...Array(code_lines_count).keys()].map((i) => {
const line = document.createElement("span");
line.style.color = 'rgb(153, 153, 153)';
line.innerText = "[" + (i + 1) + "]";
line.style.display = "block";
line.style.textAlign = "right";
line.style.userSelect = "none";
line.style.cursor = "pointer";
return patchLine(i, line);
})
lines.append(...lineNs)
return lines
})
pretty_code.css({'display': 'inline-block', 'width': '90%'})
}
function showButtonClick(subId, button) {
if (showed_codes[subId] != null) {
if (showed_codes[subId].showed == true) {
$(showed_codes[subId].commentSection).hide();
$(showed_codes[subId].codeSection).hide();
button.innerText = "Показать код";
showed_codes[subId].showed = false;
} else if (showed_codes[subId].showed == false) {
$(showed_codes[subId].commentSection).show();
$(showed_codes[subId].codeSection).show();
button.innerText = "Скрыть код";
showed_codes[subId].showed = true;
}
} else {
button.innerText = showed_codes[subId] = "Загружаю код..."
const requestUrl = getContestUrl() + 'submission/' + subId;
console.log(requestUrl);
$.get(requestUrl, function (html) {
const codeHtml = $(html).find(".SubmissionDetailsFrameRoundBox-" + subId).html()
if (codeHtml == undefined) {
button.innerText = "Ошибка!";
//location.reload();
return;
}
const commentSection = createCommentSection(subId)
const subCodeSection = createSubCodeSection(subId, codeHtml);
const subRow = getSubRow(subId);
subRow.after(commentSection, subCodeSection)
prettyPrint(subId);
patchCodeSection(subId);
showed_codes[subId] = {
"showed": true,
"showButton": button,
"commentSection": commentSection,
"codeSection": subCodeSection
}
button.innerText = "Скрыть код"
});
}
}
function createSubCodeSection(subId, codeHtml) {
const trSubCode = document.createElement("tr");
trSubCode.setAttribute('data-submission-id', subId);
const tdSubCode = document.createElement("td");
tdSubCode.setAttribute('colspan', '8');
tdSubCode.innerHTML = codeHtml;
tdSubCode.style.textAlign = "start"
trSubCode.append(tdSubCode);
return trSubCode;
}
const createCommentSection = (subId) => {
const subAcButton = getSubAcButton(subId)[0];
const isAccepted = isSubAccepted(subId);
const commentTextfield = createCommentTextfield()
const commentAcButton = commentSendButtonTemplate(subId, (isAccepted ? "Похвалить" : "Принять") + " с комментарием", (isAccepted ? "#81D718" : "#13aa52"), (subId, button) => {
const text = $(commentTextfield).val();
if (text.length === 0) {
button.innerText = "Принимаю...";
acceptSubmission(subId, subAcButton);
} else {
button.innerText = "Отправляю...";
$.post(getGroupUrl() + 'data/newAnnouncement', {
contestId: getContestId(),
englishText: "",
russianText: text,
submittedProblemIndex: getProblemIndex(subId),
targetUserHandle: getHandle(subId),
announceInPairContest: true,
}, () => {
acceptSubmission(subId, subAcButton);
})
}
})
const commentRjButton = commentSendButtonTemplate(subId, "Отклонить с комментарием", "#EC431A", (subId, button) => {
const text = $(commentTextfield).val();
if (text.length > 0) {
button.innerText = "Отклоняю...";
$.post(getGroupUrl() + 'data/newAnnouncement', {
contestId: getContestId(),
englishText: "",
russianText: text,
submittedProblemIndex: getProblemIndex(subId),
targetUserHandle: getHandle(subId),
announceInPairContest: true,
}, () => {
rejectSub(subId);
if (showed_codes[subId] != null) {
$(showed_codes[subId]["codeSection"]).hide();
}
button.innerText = "Отклонено";
})
}
});
commentTextfield.addEventListener("keyup", (event) => {
event.preventDefault();
if (event.keyCode === 13) {
commentRjButton.click();
}
});
const trSection = document.createElement("tr");
trSection.setAttribute('data-submission-id', subId);
const tdSection = document.createElement("td");
tdSection.setAttribute('colspan', '8');
const tdSectionTitle = document.createElement("div");
tdSectionTitle.style.textAlign = "left";
tdSectionTitle.className = "caption titled"
tdSectionTitle.innerText = "→ Комментарий"
tdSection.append(tdSectionTitle, commentTextfield, commentAcButton, commentRjButton);
trSection.append(tdSection)
return trSection;
}
function createCommentTextfield() {
const textField = document.createElement("textarea");
textField.name = "russianText"
textField.className = "bottom-space-small monospaced"
textField.style.width = "80rem";
textField.style.height = "5rem";
textField.style.margin = "4px";
return textField;
}
const commentSendButtonTemplate = (subId, text, color, action) => {
const button = document.createElement("button");
button.className = SUB_COMMENT_SEND_BUTTON_CLASS;
button.style.margin = "4px";
button.style.width = "40%";
button.style.backgroundColor = color;
button.style.border = "1px solid " + color;
button.style.borderRadius = "8px"
button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
button.style.color = "#fff"
button.style.cursor = "pointer"
button.style.padding = "8px 0.5em"
button.innerText = text;
button.onclick = () => action(subId, button);
return button
}
const acButtonTemplate = (subId, action, text) => {
const acButton = document.createElement("button");
acButton.className = SUB_AC_BUTTON_CLASS;
const color = (isSubAccepted(subId) ? "#81D718" : "#13aa52");
acButton.style.backgroundColor = color;
acButton.style.border = "1px solid " + color;
acButton.style.borderRadius = "8px"
acButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
acButton.style.color = "#fff"
acButton.style.cursor = "pointer"
acButton.style.padding = "8px 0.5em"
acButton.style.margin = "5px 5px 0 0";
acButton.style.width = "59%";
acButton.innerText = text !== undefined ? text : (isSubAccepted(subId) ? "Похвалить" : "AC");
acButton.onclick = (_) => action(subId, acButton);
return acButton;
}
const createAcButton = (template, subId, ...args) => {
return template(subId, (subId, button) => {
button.innerText = "Подтверждаю...";
button.style.borderColor = "gray";
button.style.backgroundColor = "gray";
acceptSubmission(subId, button);
}, ...args);
}
const createRjButton = (subId, text, action) => {
const rjButton = document.createElement("button");
rjButton.className = SUB_RJ_BUTTON_CLASS;
rjButton.style.width = "38%";
rjButton.style.backgroundColor = "#EC431A";
rjButton.style.border = "1px solid #EC431A"
rjButton.style.borderRadius = "8px"
rjButton.style.padding = "8px 0.5em"
rjButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
rjButton.style.color = "#fff"
rjButton.style.cursor = "pointer"
rjButton.innerText = text;
rjButton.onclick = (_) => action(subId, rjButton);
return rjButton
}
const createRgButton = (subId) => {
const button = document.createElement("button");
button.className = SUB_RG_BUTTON_CLASS;
button.style.margin = "5px 0";
button.style.padding = "4px"
button.style.width = "100%";
button.style.backgroundColor = "#176F95";
button.style.border = "1px solid #176F95"
button.style.borderRadius = "15px"
button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
button.style.color = "#fff"
button.style.cursor = "pointer"
button.innerText = "Перетестировать";
button.onclick = (_) => {
const requestUrl = getContestUrl() + 'submission/' + subId
const data = {action: "rejudge", submissionId: subId}
$.post(requestUrl, data, (_) => location.reload());
button.innerText = "Тестирую...";
};
return button;
}
const rejectSub = (subId) => {
const subRjButton = getSubRjButton(subId);
subRjButton.innerText = "Отклоняю...";
const subAcButton = getSubAcButton(subId);
const commentSubAcButton = getCommentSubAcButton(subId);
const requestUrl = getContestUrl() + 'submission/' + subId
const data = {action: "reject", submissionId: subId}
$.post(requestUrl, data, function (_) {
$("[submissionid=" + subId + "] .verdict-accepted").remove()
subAcButton.remove()
subRjButton.remove()
commentSubAcButton.remove();
})
}
const patchSubmissions = () => {
const subsId = getSubsId();
const languages = subsId.parent().prev()
languages.append((i, e) => {
const subId = Number($(subsId[i])[0].getAttribute('submissionid'))
const language = e.split('(')[0]
$(languages[i]).empty()
return createSubShowButton(subId, language);
})
}
const patchCorrectSubmissions = () => {
const correctSubs = getCorrectSubs();
correctSubs.parent().append((i) => {
const subId = Number($(correctSubs[i]).attr('submissionid'))
const acButton = createAcButton(acButtonTemplate, subId)
const rgButton = createRgButton(subId);
const rjButton = createRjButton(subId, "RJ", (subId, _) => {
rejectSub(subId);
});
return [acButton, rjButton, rgButton]
})
}
const patchContestSidebar = () => {
const contestsSidebar = $(".GroupContestsSidebarFrame ul a")
contestsSidebar.before((i) => {
const contestHref = $(contestsSidebar[i]).attr('href');
return document.createTextNode(wrap(getLast(contestHref)));
});
}
const patchSubmission = () => {
const buttons = getSubButtons()
if (buttons.length > 0) {
const subId = Number(getLast(location.pathname));
const acButton = createAcButton(acButtonTemplate, subId);
buttons[0].before(acButton);
}
}
const patchFilterBox = () => {
const filterBox = getFilterBox();
const sidebar = getSideBar();
filterBox.detach().prependTo(sidebar);
const filterBoxPart = filterBox.find(".status-filter-form-part")[0];
const correctSubsId = getCorrectSubs().map((i, e) => Number($(e).attr("submissionid"))).toArray();
const filter = (checkbox) => {
localStorage.setItem("filterPendingSubs", checkbox.checked);
const filtered = correctSubsId.filter(subId => {
console.log(subId + " " + !isSubAccepted(subId))
return !isSubAccepted(subId)
});
console.log(filtered)
getAllSubmissionsRow().each((i, e) => {
if (checkbox.checked) {
if (!filtered.includes(Number($(e).attr('data-submission-id')))) {
$(e).hide();
}
} else {
$(e).show()
}
});
};
const template = createFilterPendingCheckboxTemplate(filter);
const label = template[0]
const checkbox = template[1]
checkbox.checked = ('true' === localStorage.getItem("filterPendingSubs") ?? false);
filter(checkbox);
filterBoxPart.before(label);
}
function createFilterPendingCheckboxTemplate(action) {
const label = document.createElement("label");
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.onclick = (_) => action(checkbox);
const title = document.createElement("span");
title.style.padding = "5px";
title.className = "smaller";
title.innerText = "Только непроверенные посылки";
label.append(checkbox, title);
return [label, checkbox];
}
(function () {
getSheetSubmissions();
try {
patchContestSidebar();
} catch (e) {
console.error(e);
}
try {
patchFilterBox();
} catch (e) {
console.error(e);
}
try {
patchCorrectSubmissions();
} catch (e) {
console.error(e);
}
try {
patchSubmissions();
} catch (e) {
console.error(e);
}
try {
patchSubmission();
} catch (e) {
console.error(e);
}
})();