CF notification

Send a system notification when your friend solves a problem on codeforces.

  1. // ==UserScript==
  2. // @name CF notification
  3. // @name:zh-CN CF notification
  4. // @namespace https://github.com/platelett/script
  5. // @version 0.2.2
  6. // @description Send a system notification when your friend solves a problem on codeforces.
  7. // @description:zh-CN 看看是谁在卷题
  8. // @author platelet
  9. // @match https://codeforces.com/problemset/status?friends=on
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=codeforces.com
  11. // @license MIT
  12. // @source https://github.com/platelett/script/blob/main/CF%20notification.js
  13. // @grant none
  14. // ==/UserScript==
  15.  
  16. // Open the page https://codeforces.com/problemset/status?friends=on script takes effect.
  17.  
  18. (function() {
  19. 'use strict';
  20. onload = () => {
  21. const interval = 1000 * 12;
  22. var parser = new DOMParser();
  23. const query = async (url, path) => parser.parseFromString(await (await fetch(url)).text(), "text/html").querySelectorAll(path);
  24. Notification.requestPermission(() => {
  25. if(document.querySelector("#header > div.lang-chooser > div:nth-child(2) > a:nth-child(2)").textContent == "Register") {
  26. alert("You are not logged in and cannot receive notifications.");
  27. return;
  28. }
  29. var last = document.querySelector("#pageContent > div.datatable > div:nth-child(6) > table > tbody > tr > td.id-cell.dark.left > a").textContent;
  30. var running = new Set();
  31. var ID = setInterval(async () => {
  32. try {
  33. var now, result = [];
  34. await (async () => {
  35. for(var page = 1; page <= 5; page++) {
  36. var list = await query("https://codeforces.com/problemset/status/page/" + page + "?friends=on",
  37. "#pageContent > div.datatable > div:nth-child(6) > table > tbody > tr:not(.first-row)");
  38. if(!list.length) throw "Invalid page";
  39. if(page == 1) now = list[0].children[0].textContent.trim();
  40. for(var submission of list) {
  41. var info = [];
  42. for(var i of submission.children) {
  43. if(i.children[0]) i = i.children[0];
  44. info.push(i.textContent.trim());
  45. }
  46. if(info[0] <= last && (!running.size || info[0] < Math.min.apply(null, [...running]))) return;
  47. var visited = info[0] <= last;
  48. if(running.has(info[0])) running.delete(info[0]), visited = false;
  49. if(visited) continue;
  50. if(info[5] == "Accepted" || info[5] == "Happy New Year!" ||
  51. info[5].startsWith("Pretests passed") || info[5].startsWith("Perfect result")) {
  52. var parts = info[3].split(" ");
  53. result.push({
  54. title: info[2] + " has solved " + parts[0],
  55. user: info[2],
  56. problem: submission.children[3].children[0].href,
  57. name: parts[0].slice(parts[0].match(/[A-Z]/).index) + ". " + parts.slice(2).join("")
  58. });
  59. }
  60. if(info[5] == "In queue" || info[5].startsWith("Running on test")) running.add(info[0]);
  61. }
  62. }
  63. running.clear();
  64. })();
  65. last = now;
  66. var all = [];
  67. for(var i of result) all.push(new Promise(async resolve => {
  68. var tmp = i;
  69. for(var node of await query(tmp.problem, "span.tag-box")) {
  70. var tag = node.textContent.trim();
  71. if(tag[0] == '*') tmp.title += " (" + parseInt(tag.slice(1)) + ")";
  72. }
  73. tmp.title += ".", resolve();
  74. }));
  75. await Promise.all(all);
  76. while(result.length) {
  77. var i = result.pop();
  78. if(!i.duplicate) new Notification(i.title, { body: i.name }).onclick
  79. = () => window.open(i.problem, '_blank');
  80. }
  81. } catch(err) {
  82. new Notification("Error happened! Please check the console.");
  83. console.log(err), clearInterval(ID);
  84. }
  85. }, interval);
  86. });
  87. }
  88. })();