Sorryops

Collect and reuse ORIOKS test answers

当前为 2024-04-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Sorryops
  3. // @name:ru Сориупс
  4. // @namespace https://git.disroot.org/electromagneticcyclone/sorryops
  5. // @version 20240427.1
  6. // @description Collect and reuse ORIOKS test answers
  7. // @description:ru Скрипт для сбора и переиспользования ответов на тесты ОРИОКС
  8. // @icon https://orioks.miet.ru/favicon.ico
  9. // @author electromagneticcyclone & angelbeautifull
  10. // @license Unlicense
  11. // @supportURL https://git.disroot.org/electromagneticcyclone/sorryops
  12. // @match https://orioks.miet.ru/student/student/test*
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_addStyle
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_setClipboard
  18. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  19. // @run-at document-start
  20. // ==/UserScript==
  21.  
  22. /* Labels */
  23.  
  24. var all_labels = {
  25. en: {
  26. l: "English",
  27. settings_title: "Settings",
  28. script_language: "Language",
  29. auto_answer: "Auto answer",
  30. auto_answer_no: "No",
  31. auto_answer_first: "First",
  32. auto_answer_random: "Random",
  33. display_answer: "Display answer near variant",
  34. stop_timer: "Freeze and hide timer",
  35. register_keyboard_keys: "Register hotkeys",
  36. copy_answers: "Copy results to the clipboard",
  37. append_question_number: "Show question numbers in the final report",
  38. accumulator_enable: "Accumulate test results in one field",
  39. auto_continue: "Auto continue (DANGEROUS!!! Will be disabled after an hour. Press `d` to disable)",
  40. auto_restart: "Auto restart (DANGEROUS!!! Will be disabled after an hour. Press `d` to disable. Make sure you have infinite attempts)",
  41. },
  42. ru: {
  43. l: "Русский",
  44. settings_title: "Настройки",
  45. script_language: "Язык",
  46. auto_answer: "Автовыбор ответа",
  47. auto_answer_no: "Нет",
  48. auto_answer_first: "Первый",
  49. auto_answer_random: "Случайный",
  50. display_answer: "Отображать ответ рядом с вариантом",
  51. stop_timer: "Заморозить и скрыть таймер",
  52. register_keyboard_keys: "Горячие клавиши",
  53. copy_answers: "Копировать результаты в буфер обмена",
  54. append_question_number: "Отображать номер вопроса в финальном отчёте",
  55. accumulator_enable: "Собирать отчёты в одно поле",
  56. auto_continue: "Автопродолжение (ОПАСНО!!! Отключается через час. Нажмите `d`, чтобы остановить)",
  57. auto_restart: "Автоперезапуск (ОПАСНО!!! Отключается через час. Нажмите `d`, чтобы остановить. Убедитесь, что количество попыток неограничено)",
  58. },
  59. };
  60.  
  61. var labels = all_labels[(() => {
  62. var lang = GM_getValue('language', "-");
  63. if (!lang || (lang == "-")) {
  64. lang = navigator.language || navigator.userLanguage;
  65. }
  66. for (var l in all_labels) {
  67. if (lang.includes(l)) {
  68. return l;
  69. }
  70. }
  71. })()];
  72. if (labels == undefined) {
  73. labels = all_labels.ru;
  74. }
  75.  
  76. /* End Labels */
  77.  
  78. /* Config */
  79.  
  80. var config = new GM_config({
  81. id: 'config',
  82. title: labels.settings_title,
  83. fields: {
  84. script_language: {
  85. label: labels.script_language,
  86. type: 'select',
  87. options: [
  88. '-',
  89. all_labels.en.l,
  90. all_labels.ru.l,
  91. ],
  92. default: '-',
  93. },
  94. auto_answer: {
  95. label: labels.auto_answer,
  96. type: 'select',
  97. options: [
  98. labels.auto_answer_no,
  99. labels.auto_answer_first,
  100. labels.auto_answer_random,
  101. ],
  102. default: labels.auto_answer_no,
  103. },
  104. display_answer: {
  105. label: labels.display_answer,
  106. type: 'checkbox',
  107. default: true,
  108. },
  109. stop_timer: {
  110. label: labels.stop_timer,
  111. type: 'checkbox',
  112. default: true,
  113. },
  114. register_keyboard_keys: {
  115. label: labels.register_keyboard_keys,
  116. type: 'checkbox',
  117. default: true,
  118. },
  119. copy_answers: {
  120. label: labels.copy_answers,
  121. type: 'checkbox',
  122. default: false,
  123. },
  124. append_question_number: {
  125. label: labels.append_question_number,
  126. type: 'checkbox',
  127. default: true,
  128. },
  129. accumulator_enable: {
  130. label: labels.accumulator_enable,
  131. type: 'checkbox',
  132. default: false,
  133. },
  134. auto_continue: {
  135. label: labels.auto_continue,
  136. type: 'checkbox',
  137. default: false,
  138. },
  139. auto_continue_time: {
  140. type: 'hidden',
  141. default: 0,
  142. },
  143. auto_restart: {
  144. label: labels.auto_restart,
  145. type: 'checkbox',
  146. default: false,
  147. },
  148. auto_restart_time: {
  149. type: 'hidden',
  150. default: 0,
  151. },
  152. },
  153. events: {
  154. init: function() {
  155. GM_setValue('stop_timer', this.get('stop_timer'));
  156. if (this.get('auto_continue') && (this.get('auto_answer') == "No")) {
  157. this.set('auto_continue', false);
  158. }
  159. if (this.get('accumulator_enable') == false) {
  160. GM_setValue('accumulated_answers', "");
  161. }
  162. switch (this.get('script_language')) {
  163. case all_labels.en.l:
  164. GM_setValue('language', "en");
  165. break;
  166. case all_labels.ru.l:
  167. GM_setValue('language', "ru");
  168. break;
  169. default:
  170. GM_setValue('language', "-");
  171. break;
  172. }
  173. },
  174. save: function(forgotten) {
  175. this.set('auto_continue_time', Date.now());
  176. this.set('auto_restart_time', Date.now());
  177. if (this.isOpen && this.get('auto_continue') && (this.get('auto_answer') == "No")) {
  178. this.set('auto_continue', false);
  179. alert("Can't automatically continue without answer.");
  180. }
  181. this.init();
  182. },
  183. },
  184. });
  185.  
  186. GM_registerMenuCommand(labels.settings_title, () => {
  187. config.open();
  188. });
  189.  
  190. /* End Config */
  191.  
  192. /* Server */
  193.  
  194. // Local
  195. function S_setValue(field, value) {
  196. var v = GM_getValue("server", value);
  197. }
  198.  
  199. // Local
  200. function S_getValue(field, default_value) {
  201. var v = GM_getValue("server", default_value);
  202. }
  203.  
  204. /* End Server */
  205.  
  206. /* Stop timer */
  207.  
  208. if (GM_getValue('stop_timer', true)) {
  209. var i, pbox;
  210. var pboxes = document.getElementsByTagName('p');
  211. for (i = 0; i < pboxes.length; i++) {
  212. pbox = pboxes[i];
  213. if (pbox.textContent.includes("Осталось:")) {
  214. pbox.parentNode.remove();
  215. document.getElementsByTagName('hr')[0].remove();
  216. var injectFakeTimer = function(window) {
  217. window.setInterval = (f, t) => {
  218. return window.setInterval(f, 10^999);
  219. };
  220. }
  221. var scriptFakeTimer = document.createElement('script');
  222. scriptFakeTimer.setAttribute("type", "application/javascript");
  223. scriptFakeTimer.textContent = '(' + injectFakeTimer + ')(window);';
  224. document.body.appendChild(scriptFakeTimer);
  225. break;
  226. }
  227. }
  228. }
  229.  
  230. /* End Stop timer */
  231.  
  232. /* Events */
  233.  
  234. window.addEventListener('load', main);
  235. window.onkeydown = (e) => {
  236. if ((e.key == "Enter") && config.get('register_keyboard_keys')) {
  237. press_continue_btn();
  238. }
  239. if (e.key == "d") {
  240. config.set('auto_continue', false);
  241. config.set('auto_restart', false);
  242. config.save();
  243. }
  244. };
  245.  
  246. /* End Events */
  247.  
  248. /* Page properties */
  249.  
  250. // const success = -1487162948;
  251. var answers = [];
  252. var variant, hash, type;
  253. var testID = (() => {
  254. var url = document.URL;
  255. url = url.slice(url.indexOf("idKM=") + 5);
  256. url = url.slice(0, url.indexOf("&"));
  257. return url;
  258. })();
  259.  
  260. /* End properties */
  261.  
  262. /* Functions */
  263.  
  264. // https://stackoverflow.com/a/15710692
  265. function hashCode(s) {
  266. return s.split("").reduce(function(a, b) {
  267. a = ((a << 5) - a) + b.charCodeAt(0);
  268. return a & a;
  269. }, 0);
  270. }
  271.  
  272. function DB_cleaner() {
  273. var clear = GM_getValue('clear_tests', new Object());
  274. var tests = GM_getValue('tests', new Object());
  275. for (var test in clear) {
  276. delete tests[test];
  277. }
  278. GM_setValue('tests', tests);
  279. GM_setValue('clear_tests', new Object());
  280. }
  281.  
  282. function press_continue_btn() {
  283. var i;
  284. var buttons = document.getElementsByTagName('button');
  285. var button = undefined;
  286. for (i = 0; i < buttons.length; i++) {
  287. var btn = buttons[i];
  288. if (btn.textContent.includes("Пройти") || btn.textContent.includes("Продолжить")) {
  289. button = btn;
  290. break;
  291. }
  292. }
  293. if (button === undefined) {
  294. return;
  295. }
  296. if (button.textContent.includes("Пройти")) {
  297. window.location.replace(button.parentNode.href);
  298. } else if (button.textContent.includes("Продолжить")) {
  299. button.click();
  300. }
  301. }
  302.  
  303. function calculate_variant_hash() {
  304. variant = document.getElementById('w0').parentNode.textContent;
  305. variant = variant.slice(variant.indexOf("Вопрос:"));
  306. hash = hashCode(variant);
  307. }
  308.  
  309. function update_variant() {
  310. var i, pbox;
  311. var chosen_answer = "";
  312. switch (type) {
  313. case 'checkbox':
  314. case 'radio': {
  315. for (i = 0; i < answers.length; i++) {
  316. chosen_answer += answers[i].checked ? answers[i].value : "";
  317. }
  318. chosen_answer = chosen_answer.split('').sort().join('');
  319. } break;
  320. case 'text': {
  321. for (i = 0; i < answers.length; i++) {
  322. chosen_answer += "[" + answers[i].value + "]";
  323. }
  324. }
  325. }
  326. var pboxes = document.getElementsByTagName('p');
  327. const display_answer = config.get('display_answer');
  328. for (i = 0; i < pboxes.length; i++) {
  329. pbox = pboxes[i];
  330. if (pbox.textContent.includes("Вопрос:")) {
  331. pbox.innerHTML = "<i>(Вариант <input onfocus='this.select();' id='variant' value='" + hash + (display_answer == true ? (" " + chosen_answer) : "") + "' readonly>)</i><br>Вопрос:";
  332. break;
  333. }
  334. }
  335. var question_num = undefined;
  336. for (i = 0; i < pboxes.length; i++) {
  337. pbox = pboxes[i];
  338. if (pbox.textContent.includes("Текущий вопрос: ")) {
  339. question_num = pbox.textContent.slice(variant.indexOf("Текущий вопрос: ") + 16);
  340. break;
  341. }
  342. }
  343. var tests = GM_getValue('tests', new Object());
  344. if (tests[testID] === undefined) {
  345. tests[testID] = new Object();
  346. }
  347. tests[testID][hash] = [question_num, chosen_answer];
  348. GM_setValue('tests', tests);
  349. }
  350.  
  351. /* End Functions */
  352.  
  353. /* Handlers */
  354.  
  355. function test_form_handler() {
  356. var i;
  357. var boxes = [];
  358. var objects = new Object();
  359. var form = document.getElementById('testform-answer');
  360. var manual_form = document.getElementById('testform-answer-0');
  361. if (form != null) {
  362. boxes = form.getElementsByTagName('input');
  363. } else if (manual_form != null) {
  364. i = 1;
  365. while (manual_form != null) {
  366. boxes.push(manual_form);
  367. manual_form = document.getElementById('testform-answer-' + i++);
  368. }
  369. console.log(boxes);
  370. }
  371. type = boxes[0].type;
  372. console.log(type);
  373. switch (type) {
  374. case 'checkbox':
  375. case 'radio': {
  376. for (i = 0; i < boxes.length; i++) {
  377. var answerHash = hashCode(boxes[i].parentNode.innerText)
  378. var span = document.createElement('span');
  379. span.innerHTML =
  380. boxes[i].type === 'radio' && boxes[i].value == "1"
  381. ? "<b>" + boxes[i].value + ")</b> "
  382. : boxes[i].value + ") ";
  383. boxes[i].parentNode.insertBefore(span, boxes[i]);
  384. objects[boxes[i].value] = boxes[i];
  385. }
  386. const sorted_objects = Object.keys(objects).sort().reduce(
  387. (obj, key) => {
  388. obj[key] = objects[key];
  389. return obj;
  390. }, {}
  391. );
  392. for (var key in sorted_objects) {
  393. sorted_objects[key].parentNode.remove();
  394. form.appendChild(sorted_objects[key].parentNode);
  395. answers.push(sorted_objects[key]);
  396. }
  397. calculate_variant_hash();
  398. var correct_answer = S_getValue(testID + "." + hash + ".correct", undefined);
  399. var incorrect_answers = S_getValue(testID + "." + hash + ".incorrect", undefined);
  400. const auto_answer = config.get('auto_answer');
  401. if (auto_answer == labels.auto_answer_random) {
  402. if (answers[0].type === 'radio') {
  403. var chosen_answer = Math.floor(Math.random() * answers.length);
  404. answers[chosen_answer].click();
  405. } else {
  406. var pick = Math.floor(Math.random() * (Math.pow(2, answers.length) - 1)) + 1;
  407. for (i = 0; i < answers.length; i++) {
  408. if(pick & Math.pow(2, i)) {
  409. answers[i].click();
  410. }
  411. }
  412. }
  413. } else if (auto_answer == labels.auto_answer_first) {
  414. answers[0].click();
  415. }
  416. } break;
  417. case 'text': {
  418. answers = boxes;
  419. calculate_variant_hash();
  420. } break;
  421. }
  422. update_variant();
  423. for (i = 0; i < answers.length; i++) {
  424. answers[i].addEventListener('change', update_variant);
  425. }
  426. }
  427.  
  428. function result_page_handler() {
  429. var i;
  430. var correct = variant.slice(variant.indexOf("Число верных ответов: ") + 22);
  431. correct = correct.slice(0, correct.indexOf("\n"));
  432. var test = GM_getValue('tests', new Object())[testID];
  433. if (test === undefined) {
  434. return;
  435. }
  436. var printer = "";
  437. var sorted_test = [];
  438. for (var hash in test) {
  439. sorted_test.push([hash].concat(test[hash]));
  440. }
  441. sorted_test.sort((a, b) => {return a[1] - b[1]});
  442. console.log(sorted_test);
  443. for (i = 0; i < sorted_test.length; i++) {
  444. printer += (config.get('append_question_number') ? (sorted_test[i][1] + ") ") : "") + sorted_test[i][0] + " " + sorted_test[i][2] + "\n";
  445. }
  446. printer += correct;
  447. if (config.get('copy_answers')) {
  448. GM_setClipboard(printer);
  449. }
  450. if (config.get('accumulator_enable')) {
  451. var acc = GM_getValue('accumulated_answers', "");
  452. if (acc != "") {
  453. acc += "\n\n";
  454. }
  455. var prefix = testID;
  456. if (prefix != "") {
  457. acc += prefix + "\n";
  458. }
  459. acc += printer;
  460. GM_setValue('accumulated_answers', acc);
  461. printer = acc;
  462. }
  463. printer = "<textarea readonly style='resize:none; width:fit-content; height:fit-content' rows='" + String(Object.keys(test).length + 1) + "' cols='50' onfocus='this.select();' id='answers'>" + printer + "</textarea>";
  464. var pboxes = document.getElementsByTagName('p');
  465. for (i = 0; i < pboxes.length; i++) {
  466. var pbox = pboxes[i];
  467. if (pbox.textContent.includes("Попытка ")) {
  468. pbox.outerHTML += printer;
  469. break;
  470. }
  471. }
  472. var clear = GM_getValue('clear_tests', new Object());
  473. clear[testID] = true;
  474. GM_setValue('clear_tests', clear);
  475. }
  476.  
  477. /* End Handlers */
  478.  
  479. function main() {
  480. var old_time, cur_time;
  481. variant = document.getElementById('w0').parentNode.textContent;
  482. if (variant.includes("Вопрос:")) {
  483. DB_cleaner();
  484. test_form_handler();
  485. if (config.get('auto_continue')) {
  486. old_time = config.get('auto_continue_time');
  487. cur_time = Date.now();
  488. if (cur_time - old_time > 60 * 60 * 1000) {
  489. config.set('auto_continue', false);
  490. } else {
  491. press_continue_btn();
  492. }
  493. }
  494. } else if (variant.includes("Результат прохождения теста:")) {
  495. result_page_handler();
  496. if (config.get('auto_restart')) {
  497. old_time = config.get('auto_restart_time');
  498. cur_time = Date.now();
  499. if (cur_time - old_time > 60 * 60 * 1000) {
  500. config.set('auto_restart', false);
  501. } else {
  502. press_continue_btn();
  503. }
  504. }
  505. }
  506. }