OJCN Report Gen

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

当前为 2022-11-24 提交的版本,查看 最新版本

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