OJCN Report Gen

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址