MENDO.MK Enhancement

Adds dark mode, search in tasks and other stuff to MENDO.MK

当前为 2025-01-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MENDO.MK Enhancement
  3. // @version 39
  4. // @namespace mendo-mk-enhancement
  5. // @description Adds dark mode, search in tasks and other stuff to MENDO.MK
  6. // @author EntityPlantt
  7. // @match *://mendo.mk/*
  8. // @noframes
  9. // @icon https://mendo.mk/img/favicon.ico
  10. // @grant none
  11. // @license CC-BY-ND
  12. // ==/UserScript==
  13.  
  14. const VERSION = 39, AprilFools = new Date().getMonth() == 3 && new Date().getDate() < 3;
  15. console.log("%cMENDO.MK Enhancement%c loaded", "color:magenta;text-decoration:underline", "");
  16. var loadingSuccess = 0;
  17. setTimeout(() => {
  18. if (loadingSuccess == 1) console.log("Loading %csuccessful", "color:#4f4");
  19. else if (loadingSuccess == 2) console.log("Loading %cwith errors", "color:#ff0");
  20. else console.log("Loading %cunsuccessful", "color:red");
  21. }, 1000);
  22. async function MendoMkEnhancement() {
  23. try {
  24. function logFinish(taskName) {
  25. console.log("%cFinished task:%c " + taskName, "color:#0f0", "");
  26. }
  27. console.groupCollapsed("Start log");
  28. var style = document.createElement("style");
  29. style.innerHTML = `
  30. ${ // Dark mode
  31. localStorage.getItem("mendo-mk-enhancement-theme") == "dark" ? `
  32. html, img, svg, #cboxOverlay, .copy-io-btn span {
  33. filter: invert(1) hue-rotate(180deg);
  34. }
  35. body, img, svg {
  36. background: white;
  37. }
  38. ::-webkit-scrollbar {
  39. width: initial;
  40. }
  41. ::-webkit-scrollbar-track {
  42. background: #eee;
  43. }
  44. body::-webkit-scrollbar-track {
  45. background: #111;
  46. }
  47. ::-webkit-scrollbar-thumb {
  48. background: #ddd;
  49. }
  50. ` : ""}
  51. body::-webkit-scrollbar-thumb {
  52. background: #222;
  53. }
  54. ${AprilFools ? `
  55. td.solved, td.wrong {
  56. background: #bfb !important;
  57. }
  58. td.correct {
  59. background: #fbb !important;
  60. }` : `
  61. td.solved, td.correct {
  62. background: #bfb !important;
  63. }
  64. td.wrong {
  65. background: #fbb !important;
  66. }
  67. `}
  68. .copy-io-btn span:before {
  69. content: "📃";
  70. }
  71. #search {
  72. font-family: consolas;
  73. }
  74. #search, #search-submit {
  75. border: solid 2px black;
  76. transition: box-shadow .5s;
  77. margin-bottom: 20px;
  78. }
  79. #search-submit:hover {
  80. cursor: pointer;
  81. }
  82. #search:focus {
  83. background: #eee;
  84. }
  85. #search-submit:hover, #search:hover {
  86. background: #ddd;
  87. }
  88. .copy-io-btn {
  89. float: right;
  90. background: #ddd;
  91. padding: 5px;
  92. cursor: pointer;
  93. border-radius: 5px;
  94. user-select: none;
  95. }
  96. .copy-io-btn:hover {
  97. background: #e8e8e8;
  98. }
  99. #search:active, #search:focus, #search-submit:active, #search-submit:focus {
  100. box-shadow: 0 0 2.5px 2.5px black;
  101. }
  102. @keyframes gta-cinematic-image {
  103. 0%, 50% {filter: blur(500px); opacity: 0;}
  104. 10%, 40% {filter: blur(0px); opacity: 1;}
  105. }
  106. td.share-solved {
  107. background: #ac0 !important;
  108. }
  109. .update-available {
  110. animation: update-available .5s infinite linear;
  111. }
  112. @keyframes update-available {
  113. from, to {color: red}
  114. 50% {color: white}
  115. }
  116. .ojtxt {
  117. animation: ojtxt 3s 1 linear;
  118. color: white;
  119. display: block;
  120. }
  121. @keyframes ojtxt {
  122. from {color: #4f4}
  123. to {color: white}
  124. }
  125. .progbar {
  126. background: #0004;
  127. margin-top: .5em;
  128. border-radius: 5px;
  129. padding: 2.5px;
  130. width: 100%;
  131. }
  132. .progbar > div {
  133. background: #bfb;
  134. border: solid 1px gray;
  135. border-radius: 2.5px;
  136. height: 100%;
  137. position: relative;
  138. padding: 5px;
  139. box-sizing: border-box;
  140. }
  141. .hidden {
  142. display: none;
  143. }
  144. .sorttask {
  145. font: inherit;
  146. background: transparent;
  147. border: none;
  148. display: inline-block;
  149. float: right;
  150. width: 1rem;
  151. }
  152. /* April Fools! */
  153. html.mirrored {
  154. transform: rotateY(1620deg) rotateX(-10deg);z
  155. }
  156. html {
  157. transition: transform 3s cubic-bezier(0.45, 0, 0.55, 1);
  158. }
  159. `;
  160. document.head.appendChild(style);
  161. logFinish("inject style sheet");
  162. if (document.querySelector(".sitename h1 a")) {
  163. document.querySelector(".sitename h1").innerHTML += " <a href='https://entityplantt.cyclic.app/r/mendo-mk-enhancement' id=enhancement-logo><em><b>Enhanced</b></em></a>";
  164. }
  165. logFinish("complete site logo");
  166. fetch("https://greasyfork.org/scripts/450985-mendo-mk-enhancement/code/MENDOMK%20Enhancement.user.js").then(x => x.text()).then(cfUpdt => {
  167. if (parseInt(/@version *?(\d+)/.exec(cfUpdt)[1]) > VERSION) {
  168. if (document.getElementById("enhancement-logo")) {
  169. document.getElementById("enhancement-logo").classList.add("update-available");
  170. document.querySelector("#enhancement-logo b").innerText = "Update to v" + /@version *?(\d+)/.exec(cfUpdt)[1];
  171. }
  172. }
  173. logFinish("check for updates");
  174. });
  175. /* if (document.querySelector(".main-navigation > ul") && !document.URL.includes("Help.do")) {
  176. let elm = document.createElement("li");
  177. elm.innerHTML = `<a href="/simple_jsp/report_bug.jsp" class="cbrbm cboxElement">${true ? "Пријави Грешка" : "Report Bug"}</a>`;
  178. document.querySelector(".main-navigation > ul").appendChild(elm);
  179. logFinish("add report bug form");
  180. } */
  181. if (document.URL.includes("/Training.do") || document.URL.includes("/User_Competition.do")) {
  182. var search = document.createElement("form");
  183. search.className = "content-search";
  184. search.action = "#";
  185. search.innerHTML = `
  186. <input type=text id=search autocomplete=off>
  187. <input type=submit id=search-submit value=Search>
  188. `;
  189. search.onsubmit = e => {
  190. e.preventDefault();
  191. location.hash = "#" + escape(search.querySelector("#search").value);
  192. search.querySelector("#search").blur();
  193. hashChange();
  194. }
  195. function hashChange() {
  196. // if (document.activeElement == search.querySelector("#search")) {
  197. // return;
  198. // }
  199. let kw = unescape(location.hash.substr(1));
  200. search.querySelector("#search").value = kw;
  201. kw = kw.toLowerCase();
  202. if (kw.includes("mirror")) document.body.parentElement.classList.add("mirrored");
  203. else document.body.parentElement.classList.remove("mirrored");
  204. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div > div > table > tbody > tr").forEach(elm => {
  205. if (!elm.querySelector("td:nth-child(2) > a")) {
  206. return;
  207. }
  208. if (elm.innerText.toLowerCase().includes(kw) || elm.querySelector("td:nth-child(2) > a").href.toLowerCase().includes(kw)) {
  209. elm.style.display = "";
  210. }
  211. else {
  212. elm.style.display = "none";
  213. }
  214. });
  215. if (AprilFools) {
  216. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div > div > table > tbody").forEach(elm => {
  217. let afa = Array.from(elm.querySelectorAll("tr")).slice(1);
  218. function shuffle(array) {
  219. let currentIndex = array.length;
  220. while (currentIndex != 0) {
  221. let randomIndex = Math.floor(Math.random() * currentIndex);
  222. currentIndex--;
  223. [array[currentIndex], array[randomIndex]] = [
  224. array[randomIndex], array[currentIndex]];
  225. }
  226. }
  227. shuffle(afa);
  228. afa.forEach(x => elm.appendChild(x));
  229. });
  230. }
  231. }
  232. window.onhashchange = hashChange;
  233. hashChange();
  234. document.querySelector(".main-content").prepend(search);
  235. logFinish("add task search bar");
  236. document.querySelector("body > div.page-container > div.main > div.main-content > div:nth-child(3)").innerHTML += `<a href="./Training.do?cid=5">[ ${document.cookie.includes("mkjudge_language=en") ? "Other tasks" : "Други задачи"} ]</a>&nbsp;&nbsp;`;
  237. document.querySelector("body > div.page-container > div.main > div.main-content > div:last-child").innerHTML =
  238. document.querySelector("body > div.page-container > div.main > div.main-content > div:nth-child(3)").innerHTML;
  239. logFinish("add secret tasks");
  240. if (/(Статистика|Success rate)/.test(document.querySelector(".main-content > .column1-unit > .training-content table tr th:last-child").textContent)) {
  241. let elm = document.querySelector(".main-content > .column1-unit > .training-content table tr th:last-child");
  242. let select = document.createElement("select");
  243. select.innerHTML = [["normal", "indexmin"], ["latest", "indexmax"], ["min %", "percmin"], ["max %", "percmax"], ["least tried", "submin"],
  244. ["most tried", "submax"], ["least solved", "solvmin"], ["most solved", "solvmax"]].map(x => `<option value="${x[1]}">${x[0]}</option>`).join("");
  245. select.className = "sorttask";
  246. let sortcriteria = {
  247. index: tr => parseInt(tr.querySelector("td").textContent),
  248. perc: tr => parseInt(/\((\d+)%\)/.exec(tr.querySelector("td:last-child").textContent)[1]),
  249. sub: tr => parseInt(/\/(\d+)\b/.exec(tr.querySelector("td:last-child").textContent)[1]),
  250. solv: tr => parseInt(/^(\d+)\//.exec(tr.querySelector("td:last-child").textContent)[1])
  251. };
  252. select.oninput = event => {
  253. let [, criteria, rev] = /^([a-z]+)(min|max)$/.exec(select.value);
  254. rev = rev == "max" ? -1 : 1;
  255. let f = sortcriteria[criteria];
  256. let rows = Array.from(document.querySelectorAll(".main-content > .column1-unit > .training-content table tr")).slice(1).sort((a, b) => (f(a) - f(b)) * rev);
  257. rows.forEach(x => document.querySelector(".main-content > .column1-unit > .training-content tbody").appendChild(x));
  258. };
  259. elm.appendChild(select);
  260. logFinish("add statistics sorting");
  261. }
  262. }
  263. if (document.querySelector("body > div.page-container > div.header > div.header-bottom > div")) {
  264. document.querySelector("body > div.page-container > div.header > div.header-bottom > div").innerHTML += `<ul><li><a style="
  265. background-image: url(https://evolveyoursuccess.com/wp-content/uploads/2019/12/lightbulb-icon-png-icon-transparent-light-bulb-png.png);
  266. background-size: 12.5px;
  267. " href='/algoritmi'>${document.cookie.includes("mkjudge_language=en") ? "II Algorithms" : "ИИ Алгоритми"}</a></li></ul>`;
  268. logFinish("add ii algorithms button");
  269. document.querySelector("body > div.page-container > div.header > div.header-bottom > div > ul:nth-child(1) > li > a").href = "/";
  270. document.querySelector("body > div.page-container > div.header > div.header-bottom > div > ul:nth-child(2) > li > a").href = "/Training.do";
  271. document.querySelector("body > div.page-container > div.header > div.header-bottom > div > ul:nth-child(2) > li > a").className = "";
  272. document.querySelectorAll("div.main-content > div > div > table > tbody > tr > td:nth-child(2) > a").forEach(e => void(e.target = "_blank"));
  273. logFinish("make task links open in another window");
  274. if (document.URL.includes("/Training.do")) {
  275. var solved = 0, total = 0, progbar = document.createElement("div");
  276. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div > div > table > tbody > tr").forEach(elm => {
  277. if (elm.children.length < 2 || elm.children[0].nodeName.toUpperCase() == "TH") return;
  278. if (elm.querySelector("td.solved")) solved++;
  279. total++;
  280. });
  281. progbar.className = "progbar";
  282. progbar.innerHTML = `<div style="width:${solved / total * 100}%">${solved} / ${total} (${Math.floor(solved / total * 100)}%)</div>`;
  283. document.querySelector("body > div.page-container > div.main > div.main-content > div > div > table > caption").appendChild(progbar);
  284. logFinish("task solved percentage");
  285. var taskShare = document.createElement("div");
  286. taskShare.style.marginBottom = "10px";
  287. taskShare.innerHTML = `
  288. <button id=solved-tasks-save>${document.cookie.includes("mkjudge_language=en") ? "Share solved tasks" : "Сподели решени задачи"}</button>
  289. <button id=solved-tasks-load>${document.cookie.includes("mkjudge_language=en") ? "Load shared solved tasks" : "Лоадирај споделени решени задачи"}</button>
  290. <button id=hide-solved-tasks>${document.cookie.includes("mkjudge_language=en") ? "Hide/Show solved tasks" : "Скриј/Откриј решени задачи"}</button>`;
  291. let taskshcode = "mendo-reseni-zadaci" + /^https?(.*)$/.exec(document.URL)[1];
  292. if (taskshcode.includes("#")) taskshcode = taskshcode.slice(0, taskshcode.indexOf("#"));
  293. taskShare.querySelector("#hide-solved-tasks").onclick = () => {
  294. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div:nth-child(5) > div > table > tbody > tr").forEach(td => {
  295. if (td.childNodes[0].classList.contains("solved")) td.classList.toggle("hidden");
  296. });
  297. };
  298. taskShare.querySelector("#solved-tasks-save").onclick = () => {
  299. var array = [];
  300. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div:nth-child(5) > div > table > tbody > tr > td:first-child").forEach(td => {
  301. if (td.classList.contains("solved")) array.push(td.innerText.substr(0, td.innerText.length - 1));
  302. });
  303. array.unshift(taskshcode);
  304. navigator.clipboard.writeText(array.join(","));
  305. };
  306. taskShare.querySelector("#solved-tasks-load").onclick = async() => {
  307. var array = prompt(document.cookie.includes("mkjudge_language=en") ? "Enter code..." : "Внеси код...").split(",");
  308. if (array[0] != taskshcode) {
  309. alert(document.cookie.includes("mkjudge_language=en") ? "Invalid task solve share schema! / Invalid site!" : "Невалидна шема на споделени решени задачи! / Невалидна страна!");
  310. return;
  311. }
  312. array.shift();
  313. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div:nth-child(5) > div > table > tbody > tr > td:first-child").forEach(td => {
  314. if (array.includes(td.innerText.substr(0, td.innerText.length - 1))) td.classList.add("share-solved");
  315. else td.classList.remove("share-solved");
  316. });
  317. };
  318. document.querySelector(".main-content").prepend(taskShare);
  319. logFinish("add task share");
  320. }
  321. }
  322. if (document.querySelector("body > div.page-container > div.header > div.header-breadcrumbs > ul > li:last-child > a")) {
  323. window.name = document.querySelector("body > div.page-container > div.header > div.header-breadcrumbs > ul > li:last-child > a").innerText;
  324. }
  325. else if (document.querySelector(".pagetitle")) {
  326. window.name = document.querySelector(".pagetitle").innerText;
  327. }
  328. document.title = (document.querySelector("body > div.page-container > div.header > div.header-breadcrumbs > ul > li:last-child > a")
  329. ?? document.querySelector(".pagetitle")
  330. ?? document.querySelector(".pagename")
  331. ?? {innerText: document.URL.substr(document.URL.indexOf("/", 8) + 1)}
  332. ).innerText + " – МЕНДО";
  333. logFinish("document title set");
  334. if (document.URL.includes("/Task.do")) {
  335. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div.column1-unit.taskContentView > table pre").forEach(pre => {
  336. var text = pre.innerText.substr(pre.innerText.indexOf("\n") + 1);
  337. var copyIoBtn = document.createElement("span");
  338. copyIoBtn.innerHTML = "<span></span>";
  339. copyIoBtn.setAttribute("onclick", `navigator.clipboard.writeText(${JSON.stringify(text)})`);
  340. copyIoBtn.className = "copy-io-btn";
  341. pre.parentElement.appendChild(copyIoBtn);
  342. });
  343. logFinish("copy io buttons");
  344. var navArrows = document.createElement("div");
  345. navArrows.innerHTML = `
  346. <a href="${document.URL.substr(0, document.URL.lastIndexOf("=") + 1) + (parseInt(document.URL.substr(document.URL.lastIndexOf("=") + 1)) - 1)}">&lt;</a>
  347. <a href="${document.URL.substr(0, document.URL.lastIndexOf("=") + 1) + (parseInt(document.URL.substr(document.URL.lastIndexOf("=") + 1)) + 1)}" style="float:right">&gt;</a>
  348. `;
  349. navArrows.style.fontSize = "40px";
  350. navArrows.style.marginBottom = "20px";
  351. document.querySelector(".main-content").prepend(navArrows);
  352. logFinish("add nav buttons");
  353. document.querySelector(".pagetitle").style.textAlign = "center";
  354. logFinish("center title text");
  355. setInterval(() => {
  356. var scode = document.getElementById("solutionCode");
  357. if (!scode.value.includes("// online judge") && !scode.value.includes("#define ONLINE_JUDGE") && scode.value.length) {
  358. scode.value = "#define ONLINE_JUDGE // online judge\n" + scode.value;
  359. document.querySelector("label[for=solutionCode]").innerHTML += `<a href="https://entityplantt.cyclic.app/r/mendo-mk-enhancement" class=ojtxt>${document.cookie.includes("mkjudge_language=en") ? "This macro was automatically added" : "Ова макро беше автоматски додадено"}: <code>ONLINE_JUDGE</code></a>`;
  360. setTimeout(() => document.querySelector(".ojtxt:last-child").remove(), 3000);
  361. }
  362. }, 500);
  363. logFinish("#define ONLINE_JUDGE");
  364. }
  365. (document.querySelector(".footer") ?? {}).innerHTML += `<p class="credits"><a href="https://entityplantt.cyclic.app/r/mendo-mk-enhancement">MENDO.MK Enhancement</a> <a href="javascript:toggleTheme()">🎨</a></p>`;
  366. window.toggleTheme = () => {
  367. localStorage.setItem("mendo-mk-enhancement-theme", localStorage.getItem("mendo-mk-enhancement-theme") == "dark" ? "light" : "dark");
  368. location.reload();
  369. };
  370. logFinish("dark mode button");
  371. loadingSuccess = 1;
  372. if (/^https?:\/\/mendo\.mk\/.*?User_Submission.do\?/.test(document.URL)) {
  373. let ok = false;
  374. for (let elm of document.querySelectorAll("img")) {
  375. if (elm.src.includes("loadingAnimation")) {
  376. ok = true;
  377. break;
  378. }
  379. }
  380. function checkForCinematic() {
  381. let usubTBody = document.querySelector("div.main-content > div > div > table:nth-child(6) > tbody");
  382. if (!ok) {
  383. taskSolveCinematic(0);
  384. return;
  385. }
  386. if (!usubTBody) {
  387. requestAnimationFrame(checkForCinematic);
  388. }
  389. else if (usubTBody.querySelectorAll("tr td.correct:first-child").length + 1 >= usubTBody.querySelectorAll("tr").length) taskSolveCinematic(1);
  390. else taskSolveCinematic(2);
  391. }
  392. checkForCinematic();
  393. logFinish("task solve cinematic setup");
  394. }
  395. }
  396. catch (_) {
  397. console.error(_);
  398. loadingSuccess = 2;
  399. }
  400. console.groupEnd();
  401. }
  402. function taskSolveCinematic(showType) {
  403. if (AprilFools) {
  404. let congrattd = document.querySelector(".submission-content tr[align=right] td");
  405. if (congrattd && congrattd.innerText.includes("Congratulations!")) congrattd.innerHTML = "April Fools!";
  406. if (congrattd && congrattd.innerText.includes("Честитки!")) congrattd.innerHTML = "Априлилили!";
  407. }
  408. let tcs = document.querySelector("div.main-content > div > div > table:nth-child(6) > tbody");
  409. let tclist = Array.from(tcs.children);
  410. let tr;
  411. for (let tc of tclist) {
  412. if (!tr || tr.children.length == 5) {
  413. tr = document.createElement("tr");
  414. tcs.appendChild(tr);
  415. }
  416. let td = tc.querySelector("td");
  417. if (td) {
  418. if (tc.innerText.includes("Runtime Error")) {
  419. td.innerText += " RE";
  420. td.classList.add("verdict-re");
  421. }
  422. if (tc.innerText.includes("Wrong") || tc.innerText.includes("Погрешен")) {
  423. td.innerText += " WA";
  424. td.classList.add("verdict-wa");
  425. }
  426. if (tc.innerText.includes("Time") || tc.innerText.includes("време")) {
  427. td.innerText += " TLE";
  428. td.classList.add("verdict-tle");
  429. }
  430. if (tc.innerText.includes("Точен") || tc.innerText.includes("Correct")) {
  431. td.innerText += " AC (" + /[\d.]+/.exec(tc.children[1].innerText)[0] + ")";
  432. td.classList.add("verdict-ac");
  433. }
  434. tr.appendChild(td);
  435. }
  436. tc.remove();
  437. }
  438. if (!showType) return;
  439. var preCinematicScreen = document.createElement("div");
  440. preCinematicScreen.style = `
  441. top: 0px; left: 0px; position: fixed; width: 100vw; height: 100vh;
  442. background: white; font-size: 20px; cursor: pointer; z-index: 99999;
  443. `;
  444. preCinematicScreen.innerHTML = `
  445. <div style="color: black; position: fixed; top: 50vh; left: 50vw; transform: translate(-50%, -50%);">[ ${document.cookie.includes("mkjudge_language=en") ? "Reveal" : "Откриј"} ]</div>
  446. <div style="color: black; position: fixed; top: 10px; right: 10px;" id=skip-cinematic>${document.cookie.includes("mkjudge_language=en") ? "Skip" : "Скокни"} &gt;&gt;</div>
  447. `;
  448. preCinematicScreen.onclick = () => {
  449. preCinematicScreen.remove();
  450. if (window.event.target.id == "skip-cinematic") return;
  451. const cinematics = [() => {
  452. let img = document.createElement("img");
  453. img.src = "https://i.ibb.co/b7WW8Q3/mission-passed.png";
  454. img.style = "animation: gta-cinematic-image 15s 1 linear; position: fixed; top: 0; left: 0; width: 100vw; background: transparent !important";
  455. let audio = document.createElement("audio");
  456. audio.src = "https://dl.sndup.net/fmjm/mission%20passed%20audio.mp3";
  457. audio.play();
  458. document.body.appendChild(img);
  459. setTimeout(() => img.remove(), 10000);
  460. }], failCinematics = [() => {
  461. let audio = document.createElement("audio");
  462. audio.src = "https://www.myinstants.com/media/sounds/erro.mp3";
  463. audio.play();
  464. let img = document.createElement("img");
  465. img.src = "https://i.kym-cdn.com/photos/images/original/000/918/810/a22.jpg";
  466. img.style = "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(.5); background: transparent !important; box-shadow: black 10px 10px 5px";
  467. document.body.appendChild(img);
  468. img.onclick = () => img.remove();
  469. }];
  470. if (showType == 1) cinematics[Math.floor(Math.random() * cinematics.length)]();
  471. else failCinematics[Math.floor(Math.random() * failCinematics.length)]();
  472. };
  473. document.body.appendChild(preCinematicScreen);
  474. }
  475. window.MendoMkEnhancement = MendoMkEnhancement;
  476. window.taskSolveCinematic = taskSolveCinematic;
  477. MendoMkEnhancement();