From 9b9ce253a077b5c1d8a08e38c42d5c8cb07041d4 Mon Sep 17 00:00:00 2001 From: StableAgOH Date: Mon, 9 May 2022 14:21:58 +0800 Subject: [PATCH] feat: add support for luogu --- .gitignore | 1 + .vscode/launch.json | 22 ++++++++++++++++++++ .vscode/tasks.json | 14 +++++++++++++ cli.ts | 6 +++--- src/contest/codeforces.ts | 9 +++++++-- src/contest/contest.ts | 29 +++++++++++++++++---------- src/contest/luogu.ts | 42 +++++++++++++++++++++++++++++++++++++++ src/list.ts | 4 ++-- tsconfig.json | 4 +++- 9 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 src/contest/luogu.ts diff --git a/.gitignore b/.gitignore index 44d93db..ebbcbb5 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ dist .tern-port bin +output diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1739e5c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\cli.ts", + "preLaunchTask": "tsc: 构建 - tsconfig.json", + "outFiles": [ + "${workspaceFolder}/output/**/*.js" + ], + "args": ["-d 7"] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b2340df --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,14 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": "build", + "label": "tsc: 构建 - tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/cli.ts b/cli.ts index e57d2b4..74ee1d7 100644 --- a/cli.ts +++ b/cli.ts @@ -6,15 +6,15 @@ import list from "./src/list"; program .name("lsct") .version(version) - .option("-d, --days", "Number of days to get contests information", "3") + .option("-d, --days, ", "Number of days to get contests information", "3") .option("-l, --list", "List all supported OJ") - .addOption(new Option("-o, --oj ", "OJs to get contests information").choices(alloj)) + .addOption(new Option("-o, --oj ", "OJs to get contests information").choices(Object.keys(alloj))) .parse(); const opts = program.opts(); async function main() { - if (opts.list) console.log(alloj); + if (opts.list) console.log(Object.values(alloj).map((ojd) => ojd.name)); else console.log(await list(opts.oj, opts.days as number)); } diff --git a/src/contest/codeforces.ts b/src/contest/codeforces.ts index 6de454e..c7a0c20 100644 --- a/src/contest/codeforces.ts +++ b/src/contest/codeforces.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { Contest } from "./contest"; +import { Contest, contestRule } from "./contest"; interface CodeforcesResult { id: string; @@ -12,13 +12,18 @@ interface CodeforcesResult { relativeTimeSeconds: number; } +const ruleRecord: Record = { + "CF": "Codeforces", + "ICPC": "ICPC" +}; + export default async function get() { const result: CodeforcesResult[] = (await axios.get("https://codeforces.com/api/contest.list")).data.result; return result.map((contest): Contest => { return { oj: "Codeforces", name: contest.name, - rule: contest.type, + rule: ruleRecord[contest.type], startTime: new Date(contest.startTimeSeconds * 1000), durationHours: contest.durationSeconds / 60 / 60, url: `https://codeforces.com/contests/${contest.id}` diff --git a/src/contest/contest.ts b/src/contest/contest.ts index 7257ab6..c87f267 100644 --- a/src/contest/contest.ts +++ b/src/contest/contest.ts @@ -1,23 +1,30 @@ import codeforces from "./codeforces"; +import luogu from "./luogu"; -export interface Contest { - oj: string; +export type oj = "cf" | "lg"; +type ojName = "Codeforces" | "Luogu"; +type ojDetail = { + name: ojName, + getter: () => Promise +} +type ojRecord = Record +export type contestRule = "OI" | "IOI" | "ICPC" | "LeDuo" | "Codeforces" + +export type Contest = { + oj: ojName; name: string; - rule: string; + rule: contestRule; startTime: Date; durationHours: number; url: string; } -export type oj = "cf"; - -export const alloj: oj[] = ["cf"]; - -export const getters: Record Promise> = { - "cf": codeforces, +export const alloj: ojRecord = { + "cf": { name: "Codeforces", getter: codeforces }, + "lg": { name: "Luogu", getter: luogu } }; export function getGetterList(ojs: oj[]) { - if (!ojs) ojs = alloj; - return ojs.map((oj) => getters[oj]); + if (!ojs) ojs = Object.keys(alloj) as oj[]; + return ojs.map((oj) => alloj[oj].getter); } diff --git a/src/contest/luogu.ts b/src/contest/luogu.ts new file mode 100644 index 0000000..b33dbf8 --- /dev/null +++ b/src/contest/luogu.ts @@ -0,0 +1,42 @@ +import axios from "axios"; +import { Contest, contestRule } from "./contest"; + +type LuoguResult = { + ruleType: number; + visibilityType: number; + invitationCodeType: number; + rated: boolean; + host: { id: number, name: string, isPremium: boolean }; + problemCount: number; + id: number; + name: string; + startTime: number; + endTime: number; +} + +const ruleRecord: Record = { + 1: "OI", + 2: "ICPC", + 3: "LeDuo", + 4: "IOI", + 5: "Codeforces" +}; + +const headers = { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", + "x-luogu-type": "content-only" +}; + +export default async function get() { + const result: LuoguResult[] = (await axios.get("https://www.luogu.com.cn/contest/list", { headers })).data.currentData.contests.result; + return result.map((contest): Contest => { + return { + oj: "Luogu", + name: contest.name, + rule: ruleRecord[contest.ruleType], + startTime: new Date(contest.startTime * 1000), + durationHours: (contest.endTime - contest.startTime) / 60 / 60, + url: `https://www.luogu.com.cn/contest/${contest.id}` + }; + }).filter((contest) => contest.startTime >= new Date()); +} diff --git a/src/list.ts b/src/list.ts index 715c9fa..78f7821 100644 --- a/src/list.ts +++ b/src/list.ts @@ -1,11 +1,11 @@ import { getGetterList, oj } from "./contest/contest"; export default async function list(ojs: oj[], days: number) { - return Promise.all( + return (await Promise.all( getGetterList(ojs).map( async (get) => (await get()).filter( (ct) => ct.startTime <= new Date(Date.now() + (days as number) * 86400000) ) ) - ); + )).reduce((ls1, ls2) => ls1.concat(ls2)); } diff --git a/tsconfig.json b/tsconfig.json index f852e3d..afc8b6b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,8 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "outDir": "output", + "sourceMap": true } } \ No newline at end of file