OJCN Report Gen

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

// ==UserScript==
// @name        OJCN Report Gen
// @description 自动生成作业报告 (docx)。虽然大家并不想理我。
// @namespace   https://greasyfork.org/users/197529
// @version     0.2.16
// @author      kkocdko
// @license     Unlicense
// @match       *://noi.openjudge.cn/*
// ==/UserScript==
"use strict";

const cfg = {
  studentName: "无名氏", // 姓名
  homeworkId: 4, // 作业序号
  userId: document.querySelector("#userToolbar>li")?.textContent,
};
cfg.problems = {
  // 9: [
  //   "ch0107/01",
  //   "ch0107/03",
  // ],
  8: [
    "ch0110/05",
    "ch0110/09",
    "ch0112/10",
    "ch0113/14",
    "ch0107/22",
    "ch0107/26",
    // https://leetcode.cn/problems/string-to-integer-atoi/
    // https://leetcode.cn/problems/valid-palindrome/
    // https://leetcode.cn/problems/ugly-number/
    // https://leetcode.cn/problems/power-of-two/
  ],
  7: [
    "ch0107/19",
    "ch0107/20",
    "ch0107/21",
    "ch0107/23",
    "ch0107/25",
    // just them
  ],
  6: [
    "ch0107/01",
    "ch0107/03",
    "ch0107/05",
    "ch0107/08",
    "ch0107/09",
    "ch0107/10",
    "ch0107/12",
    "ch0107/17",
    "ch0107/33",
    "ch0109/11",
  ],
  5: [
    "ch0105/15",
    "ch0105/22",
    "ch0105/35",
    "ch0106/08",
    "ch0106/09",
    "ch0106/10",
    "ch0106/11",
    "ch0110/03",
    "ch0110/04",
    "ch0107/02",
  ],
  4: [
    "ch0106/01",
    "ch0106/02",
    "ch0106/04",
    "ch0106/05",
    "ch0106/06",
    "ch0106/07",
    "ch0109/01",
    "ch0109/05",
    "ch0109/07",
    "ch0110/01",
  ],
}[cfg.homeworkId];
if (!document.querySelector(".account-link")) throw alert("login required");
if (!cfg.studentName === "无名氏") throw alert("please modify the config");
document.lastChild.appendChild(document.createElement("style")).textContent = `
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; }
@keyframes spin { 100% { transform: rotate(3600deg) } }
`;
const results = cfg.problems.map(() => null);
const tasks = cfg.problems.map(async (path, idx) => {
  const [ch, subId] = path.split("/");
  const queryUrl = `/${ch}/status/?problemNumber=${subId}&userName=${cfg.userId}`;
  const queryPage = await fetch(queryUrl).then((r) => r.text());
  const table = queryPage.split(/<\/?table>/g)[1];
  const entry = table.split(/<\/?tr>/).find((v) => v.includes("Accepted"));
  const record = [];
  for (let s = entry; s !== ""; ) {
    if (s.startsWith("<")) s = s.slice(s.indexOf(">"));
    let idx = s.indexOf("<");
    if (idx === -1) break;
    let v = s.slice(1, idx).trim();
    if (v) record.push(v);
    s = s.slice(idx).trim();
  }
  record[1] = { text: record[1], target: location.origin + queryUrl };
  const solutionUrl = entry.match(/(?<=language"><a href=")[^"]+/)[0];
  const solutionPage = await fetch(solutionUrl).then((r) => r.text());
  const codeExactor = document.createElement("p");
  codeExactor.innerHTML = solutionPage.match(/<pre(.|\n)+?<\/pre>/)[0];
  results[idx] = { path, code: codeExactor.textContent, record };
});
tasks.push(
  import(`https://cdn.jsdelivr.net/npm/docx@7.7.0/build/index.min.js`)
);
Promise.all(tasks).then(async () => {
  const {
    AlignmentType,
    BorderStyle,
    Document,
    ExternalHyperlink,
    Footer,
    Packer,
    PageNumber,
    PageNumberSeparator,
    Paragraph,
    Table,
    TableCell,
    TableRow,
    TextRun,
    UnderlineType,
    WidthType,
  } = docx;
  const genProblemPart = ({ num, path, code, record }) => [
    new Paragraph({
      spacing: { before: 500, after: 200 },
      children: [
        new TextRun({
          text: `Problem ${num.toString().padStart(2, "0")}`,
          bold: true,
          size: 24,
          font: "Arial",
        }),
      ],
    }),
    new Paragraph({
      spacing: { before: 200, line: 300 },
      children: [
        new TextRun({
          text: "Description: ",
          font: "Times New Roman",
          size: 21,
          bold: true,
        }),
        new TextRun({
          text: "Read the problem at ",
          font: "Times New Roman",
          size: 21,
        }),
        new TextRun({
          text: `http://noi.openjudge.cn/${path}/`,
          font: "Times New Roman",
          size: 21,
          italics: true,
        }),
        new TextRun({
          text: ", try to make your program ",
          font: "Times New Roman",
          size: 21,
        }),
        new TextRun({
          text: "accepted",
          font: "Times New Roman",
          size: 21,
          italics: true,
        }),
        new TextRun({
          text: " by the OJ system.",
          font: "Times New Roman",
          size: 21,
        }),
      ],
    }),
    new Paragraph({
      spacing: { before: 150, after: 150 },
      children: [
        new TextRun({
          text: "My Program:",
          font: "Segoe UI Semibold",
          size: 21,
          underline: { type: UnderlineType.DOUBLE },
        }),
      ],
    }),
    ...code.split("\n").map(
      (text) =>
        new Paragraph({
          spacing: { line: 280 },
          indent: { left: 400 },
          alignment: AlignmentType.LEFT,
          children: [new TextRun({ text, font: "Consolas", size: 21 })],
        })
    ),
    new Paragraph({
      spacing: { before: 150, after: 150 },
      children: [
        new TextRun({
          text: "My Result:",
          font: "Segoe UI Semibold",
          size: 21,
          underline: { type: UnderlineType.DOUBLE },
        }),
      ],
    }),
    new Table({
      rows: [
        new TableRow({
          children: "提交人|题目|结果|分数|内存|时间|代码长度|语言"
            .split("|")
            .map(
              (field, i) =>
                new TableCell({
                  width: {
                    size: [1500, 3500, 800, 600, 800, 800, 900, 600][i],
                    type: WidthType.DXA,
                  },
                  margins: { top: 0, bottom: 0, left: 100, right: 100 },
                  borders: {
                    top: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                    right: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                    bottom: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                    left: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                  },
                  shading: { fill: "E0EAF1" },
                  children: [
                    new Paragraph({
                      children: [
                        new TextRun({
                          text: field,
                          font: "Microsoft YaHei",
                          size: 16,
                        }),
                      ],
                    }),
                  ],
                })
            ),
        }),
        new TableRow({
          children: record.slice(0, 8).map(
            (entry, i) =>
              new TableCell({
                width: {
                  size: [1500, 3500, 800, 600, 800, 800, 900, 600][i], // sync with upper code
                  type: WidthType.DXA,
                },
                margins: { top: 0, bottom: 0, left: 100, right: 100 },
                borders: {
                  top: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                  right: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                  bottom: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                  left: { style: BorderStyle.SINGLE, color: "DDDDDD" },
                },
                children: [
                  new Paragraph({
                    children: [
                      entry.target
                        ? new ExternalHyperlink({
                            children: [
                              new TextRun({
                                text: entry.text,
                                font: "Microsoft YaHei",
                                size: 16,
                                color: "3070B0",
                              }),
                            ],
                            link: entry.target,
                          })
                        : new TextRun({
                            text: entry,
                            font: "Microsoft YaHei",
                            size: 16,
                          }),
                    ],
                  }),
                ],
              })
          ),
        }),
      ],
    }),
  ];
  const doc = new Document({
    sections: [
      {
        properties: {
          page: {
            margin: { top: "2cm", right: "2cm", bottom: "2cm", left: "2cm" },
            pageNumbers: { start: 1, separator: PageNumberSeparator.COLON },
          },
        },
        footers: {
          default: new Footer({
            children: [
              new Paragraph({
                alignment: AlignmentType.CENTER,
                children: [
                  new TextRun({
                    children: [
                      PageNumber.CURRENT,
                      " / ",
                      PageNumber.TOTAL_PAGES,
                    ],
                    font: "Microsoft YaHei",
                    size: 18,
                  }),
                ],
              }),
            ],
          }),
        },
        children: [
          new Paragraph({
            spacing: { before: 200, after: 200 },
            alignment: AlignmentType.CENTER,
            children: [
              new TextRun({
                text: `Homework ${cfg.homeworkId.toString().padStart(2, "0")}`,
                bold: true,
                size: 32,
                font: "Microsoft YaHei",
              }),
            ],
          }),
          new Paragraph({
            spacing: { before: 400, after: 720 },
            children: [
              new TextRun({
                text: "Student ID:   ",
                bold: true,
                size: 28,
                font: "Calibri",
              }),
              new TextRun({
                text: `\t\t ${cfg.userId}\t\t`,
                size: 28,
                font: "Times New Roman",
                underline: { type: UnderlineType.SINGLE },
              }),
              new TextRun({
                text: "   Name:   ",
                bold: true,
                size: 28,
                font: "Calibri",
              }),
              new TextRun({
                text: `\t\t  ${cfg.studentName} \t\t`,
                size: 28,
                font: "宋体",
                underline: { type: UnderlineType.SINGLE },
              }),
            ],
            alignment: AlignmentType.CENTER,
          }),
          ...results.flatMap((v, i) => genProblemPart({ num: i + 1, ...v })),
        ],
      },
    ],
  });
  const saveLink = document.createElement("a");
  saveLink.download = `${cfg.userId}.docx`;
  saveLink.href = URL.createObjectURL(await Packer.toBlob(doc));
  saveLink.click();
});

// document.lastChild.appendChild(document.createElement("style")).textContent = `
// table{ width: 100vw; background: #fff; position: fixed; top: 0; left: 0; z-index: 99999; box-shadow:0 0 0 20px #fff; }
// tr:not(:first-child){ opacity:0; }
// #footer { display:none; }
// `.replace(/;/g, "!important;");
// ~/misc/apps/miniserve -p 9973 --header cache-control:max-age=3 /home/kkocdko/misc/code/user-scripts/scripts/ojcn-report-gen
// http://127.0.0.1:9973/index.html