OJCN Report Gen

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

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

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