OJCN Report Gen

自动生成作业报告 (docx)。虽然大家并不想理我。

目前为 2022-10-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name OJCN Report Gen
  3. // @description 自动生成作业报告 (docx)。虽然大家并不想理我。
  4. // @namespace https://greasyfork.org/users/197529
  5. // @version 0.2.4
  6. // @author kkocdko
  7. // @license Unlicense
  8. // @match *://noi.openjudge.cn/*
  9. // ==/UserScript==
  10. "use strict";
  11.  
  12. const cfg = {
  13. homeworkId: 2,
  14. studentName: "Ninja",
  15. problems: [
  16. "ch0104/01",
  17. "ch0104/03",
  18. "ch0104/07",
  19. "ch0104/08",
  20. "ch0104/09",
  21. "ch0104/14",
  22. "ch0104/15",
  23. "ch0104/16",
  24. "ch0104/18",
  25. "ch0104/19",
  26. ],
  27. userId: document.querySelector("#userToolbar>li")?.textContent,
  28. };
  29. if (!document.querySelector(".account-link")) throw alert("login required");
  30. if (!cfg.studentName === "Ninja")
  31. throw alert("please modify the script to fit your personal info");
  32. document.lastChild.appendChild(document.createElement("style")).textContent = `
  33. body::before { content: ""; position: fixed; left: 40px; top: 40px; padding: 20px; border: 8px solid #37b; border-radius: 25%; z-index: 2000; animation: spin 12s linear; }
  34. @keyframes spin { 100% { transform: rotate(3600deg) } }
  35. `;
  36. const tasks = [];
  37. const tasksExtend = (a, f = (v) => v) => a.map((v, i) => tasks.push(f(v, i)));
  38. tasksExtend([
  39. new Promise((r) => {
  40. const el = document.body.appendChild(document.createElement("script"));
  41. el.src = `https://cdn.jsdelivr.net/npm/docx@7.6.0/build/index.min.js`;
  42. el.onload = r; // import(`https://cdn.jsdelivr.net/npm/docx@7.6.0/build/index.min.js`)
  43. }),
  44. ]);
  45. const results = cfg.problems.map(() => null);
  46. tasksExtend(cfg.problems, async (path, idx) => {
  47. const [ch, subId] = path.split("/");
  48. const queryUrl = `/${ch}/status/?problemNumber=${subId}&userName=${cfg.userId}`;
  49. const queryPage = await fetch(queryUrl).then((r) => r.text());
  50. const table = queryPage.split(/<\/?table>/g)[1];
  51. const entry = table.split(/<\/?tr>/).find((v) => v.includes("Accepted"));
  52. const record = [];
  53. for (let s = entry; s !== ""; ) {
  54. if (s.startsWith("<")) s = s.slice(s.indexOf(">"));
  55. let idx = s.indexOf("<");
  56. if (idx === -1) break;
  57. let v = s.slice(1, idx).trim();
  58. if (v) record.push(v);
  59. s = s.slice(idx).trim();
  60. }
  61. record[1] = { text: record[1], link: location.origin + queryUrl };
  62. const solutionUrl = entry.match(/(?<=language"><a href=")[^"]+/)[0];
  63. const solutionPage = await fetch(solutionUrl).then((r) => r.text());
  64. const codeExactor = document.createElement("p");
  65. codeExactor.innerHTML = solutionPage.match(/<pre(.|\n)+?<\/pre>/)[0];
  66. const code = codeExactor.textContent;
  67. results[idx] = { path, code, record };
  68. });
  69. Promise.all(tasks).then(async () => {
  70. const {
  71. Document,
  72. Packer,
  73. Paragraph,
  74. ExternalHyperlink,
  75. TextRun,
  76. AlignmentType,
  77. TableCell,
  78. Footer,
  79. PageNumber,
  80. Table,
  81. WidthType,
  82. PageNumberSeparator,
  83. UnderlineType,
  84. BorderStyle,
  85. TableRow,
  86. } = docx;
  87. const genProblemPart = ({ num, path, code, record }) => [
  88. new Paragraph({
  89. spacing: { before: 500, after: 200 },
  90. children: [
  91. new TextRun({
  92. text: `Problem ${num.toString().padStart(2, "0")}`,
  93. bold: true,
  94. size: 24,
  95. font: "Arial",
  96. }),
  97. ],
  98. }),
  99. new Paragraph({
  100. spacing: { before: 200, line: 300 },
  101. children: [
  102. new TextRun({
  103. text: "Description: ",
  104. font: "Times New Roman",
  105. size: 21,
  106. bold: true,
  107. }),
  108. new TextRun({
  109. text: "Read the problem at ",
  110. font: "Times New Roman",
  111. size: 21,
  112. }),
  113. new TextRun({
  114. text: `http://noi.openjudge.cn/${path}/`,
  115. font: "Times New Roman",
  116. size: 21,
  117. italics: true,
  118. }),
  119. new TextRun({
  120. text: ", try to make your program ",
  121. font: "Times New Roman",
  122. size: 21,
  123. }),
  124. new TextRun({
  125. text: "accepted",
  126. font: "Times New Roman",
  127. size: 21,
  128. italics: true,
  129. }),
  130. new TextRun({
  131. text: " by the OJ system.",
  132. font: "Times New Roman",
  133. size: 21,
  134. }),
  135. ],
  136. }),
  137. new Paragraph({
  138. spacing: { before: 150, after: 150 },
  139. children: [
  140. new TextRun({
  141. text: "My Program:",
  142. font: "Segoe UI Semibold",
  143. size: 21,
  144. underline: { type: UnderlineType.DOUBLE },
  145. }),
  146. ],
  147. }),
  148. ...code.split("\n").map(
  149. (text) =>
  150. new Paragraph({
  151. spacing: { line: 280 },
  152. indent: { left: 400 },
  153. alignment: AlignmentType.LEFT,
  154. children: [new TextRun({ text, font: "Consolas", size: 21 })],
  155. })
  156. ),
  157. new Paragraph({
  158. spacing: { before: 150, after: 150 },
  159. children: [
  160. new TextRun({
  161. text: "My Result:",
  162. font: "Segoe UI Semibold",
  163. size: 21,
  164. underline: { type: UnderlineType.DOUBLE },
  165. }),
  166. ],
  167. }),
  168. new Table({
  169. rows: [
  170. new TableRow({
  171. children: "提交人|题目|结果|分数|内存|时间|代码长度|语言"
  172. .split("|")
  173. .map(
  174. (field, i) =>
  175. new TableCell({
  176. width: {
  177. size: [1500, 3300, 800, 600, 800, 800, 1100, 600][i],
  178. type: WidthType.DXA,
  179. },
  180. shading: { fill: "E0EAF1" },
  181. margins: { top: 0, bottom: 0, left: 100, right: 100 },
  182. borders: {
  183. top: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  184. right: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  185. bottom: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  186. left: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  187. },
  188. children: [
  189. new Paragraph({
  190. children: [
  191. new TextRun({
  192. text: field.split(",")[0],
  193. font: "Microsoft YaHei",
  194. size: 16,
  195. }),
  196. ],
  197. }),
  198. ],
  199. })
  200. ),
  201. }),
  202. new TableRow({
  203. children: record.slice(0, 8).map(
  204. (entry, i) =>
  205. new TableCell({
  206. width: {
  207. size: [1500, 3300, 800, 600, 800, 800, 1100, 600][i],
  208. type: WidthType.DXA,
  209. },
  210. margins: { top: 0, bottom: 0, left: 100, right: 100 },
  211. borders: {
  212. top: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  213. right: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  214. bottom: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  215. left: { style: BorderStyle.SINGLE, color: "DDDDDD" },
  216. },
  217. children: [
  218. new Paragraph({
  219. children: [
  220. entry.text
  221. ? new ExternalHyperlink({
  222. children: [
  223. new TextRun({
  224. text: entry.text,
  225. font: "Microsoft YaHei",
  226. size: 16,
  227. }),
  228. ],
  229. link: entry.link,
  230. })
  231. : new TextRun({
  232. text: entry,
  233. font: "Microsoft YaHei",
  234. size: 16,
  235. }),
  236. ],
  237. }),
  238. ],
  239. })
  240. ),
  241. }),
  242. ],
  243. }),
  244. ];
  245. const doc = new Document({
  246. sections: [
  247. {
  248. properties: {
  249. page: {
  250. margin: { top: "2cm", right: "2cm", bottom: "2cm", left: "2cm" },
  251. pageNumbers: { start: 1, separator: PageNumberSeparator.COLON },
  252. },
  253. },
  254. footers: {
  255. default: new Footer({
  256. children: [
  257. new Paragraph({
  258. alignment: AlignmentType.CENTER,
  259. children: [
  260. new TextRun({
  261. children: [
  262. PageNumber.CURRENT,
  263. " / ",
  264. PageNumber.TOTAL_PAGES,
  265. ],
  266. font: "Microsoft YaHei",
  267. size: 18,
  268. }),
  269. ],
  270. }),
  271. ],
  272. }),
  273. },
  274. children: [
  275. new Paragraph({
  276. spacing: { before: 200, after: 200 },
  277. alignment: AlignmentType.CENTER,
  278. children: [
  279. new TextRun({
  280. text: `Homework ${cfg.homeworkId.toString().padStart(2, "0")}`,
  281. bold: true,
  282. size: 32,
  283. font: "Microsoft YaHei",
  284. }),
  285. ],
  286. }),
  287. new Paragraph({
  288. spacing: { before: 400, after: 720 },
  289. children: [
  290. new TextRun({
  291. text: "Student ID: ",
  292. bold: true,
  293. size: 28,
  294. font: "Calibri",
  295. }),
  296. new TextRun({
  297. text: `\t\t212210711114`.padEnd(16, "\t"),
  298. bold: true,
  299. size: 28,
  300. font: "Times New Roman",
  301. underline: { type: UnderlineType.SINGLE },
  302. }),
  303. new TextRun({
  304. text: " Name: ",
  305. bold: true,
  306. size: 28,
  307. font: "Calibri",
  308. }),
  309. new TextRun({
  310. text: `\t\t ${cfg.studentName}`.padEnd(8, "\t"),
  311. bold: true,
  312. size: 28,
  313. font: "宋体",
  314. underline: { type: UnderlineType.SINGLE },
  315. }),
  316. ],
  317. alignment: AlignmentType.CENTER,
  318. }),
  319. ...results.flatMap((v, i) => genProblemPart({ num: i + 1, ...v })),
  320. ],
  321. },
  322. ],
  323. });
  324. const blob = await Packer.toBlob(doc);
  325. const saveLink = document.createElement("a");
  326. saveLink.download = `${cfg.userId}.docx`;
  327. saveLink.href = URL.createObjectURL(blob);
  328. saveLink.click();
  329. });
  330.  
  331. // document.lastChild.appendChild(document.createElement("style")).textContent = `
  332. // table{ width: 100vw; background: #fff; position: fixed; top: 0; left: 0; z-index: 99999; box-shadow:0 0 0 20px #fff; }
  333. // tr:not(:first-child){ opacity:0; }
  334. // #footer { display:none; }
  335. // `.replace(/;/g, "!important;");
  336. // ~/misc/apps/miniserve -p 9973 --header cache-control:max-age=3 /home/kkocdko/misc/code/user-scripts/scripts/ojcn-report-gen
  337. // http://127.0.0.1:9973/index.html