Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions apps/vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CfError, ProxyError, LoginRequiredError, setSessionCookie } from "./too
import { fetchContest, signedUpContest } from "./tools/SignUpContest";
import { translateTextRaw } from "./tools/deepl";
import { runCommand } from "./tools/command";
import { fetchSubmitPage, submitCodeWithRedirect } from "./tools/submit";
import { IncomingMessage } from "./tools/types";

const log = {
Expand Down Expand Up @@ -183,6 +184,43 @@ export async function handleRegistration(contest: string, rated: boolean | undef
}
}

export async function handleFetchSubmitPage(contest: string, send: (payload: Record<string, unknown>) => void) {
send({ type: "loading", text: `正在获取 ${contest} 提交页面信息...` });
try {
const pageData = await fetchSubmitPage(contest);
send({ type: "submitPage", submitTasks: pageData.tasks, languages: pageData.languages, csrfToken: pageData.csrfToken });
send({ type: "update", text: "已获取提交页面信息" });
} catch (error) {
if (!handleErrorWithCfAndLogin(error, send)) {
send({ type: "error", text: error instanceof Error ? error.message : "获取提交页面失败" });
}
}
}
Comment thread
Transparent-fish marked this conversation as resolved.

export async function handleSubmitCode(
Comment thread
Transparent-fish marked this conversation as resolved.
contest: string,
taskScreenName: string | undefined,
languageId: string | undefined,
sourceCode: string | undefined,
send: (payload: Record<string, unknown>) => void,
) {
if (!taskScreenName || !languageId || !sourceCode) {
send({ type: "submitResult", submitResult: { success: false, message: "提交参数不完整" } });
return;
}
send({ type: "loading", text: "正在提交代码..." });
try {
const result = await submitCodeWithRedirect(contest, taskScreenName, languageId, sourceCode);
send({ type: "submitResult", submitResult: result });
if (result.success) send({ type: "update", text: "代码提交成功" });
else send({ type: "error", text: result.message });
} catch (error) {
if (!handleErrorWithCfAndLogin(error, send)) {
send({ type: "submitResult", submitResult: { success: false, message: error instanceof Error ? error.message : "提交失败" } });
}
}
}
Comment thread
Transparent-fish marked this conversation as resolved.

function registerSetDeeplApiKey(context: vscode.ExtensionContext) {
return vscode.commands.registerCommand("extension.setDeeplApiKey", async () => {
const key = await vscode.window.showInputBox({
Expand Down
37 changes: 31 additions & 6 deletions apps/vscode-extension/src/tools/command.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import * as vscode from "vscode";
import { copyMarkdown } from "./copy";
import { IncomingMessage } from "./types";
import { handleContestLoad, handleProblemLoad } from "../extension";
import { handleTranslate, handleGetCookie, handleSetCookie, handleRegistration } from "../extension";
import {
handleContestLoad,
handleProblemLoad,
handleTranslate,
handleGetCookie,
handleSetCookie,
handleRegistration,
handleFetchSubmitPage,
handleSubmitCode,
} from "../extension";

const loadCommands = new Set(["loadContest", "loadProblem", "openBrowser"]);
const deeplCommands = new Set(["translate", "setApiKey"]);
const cookie = new Set(["getCookie", "setCookie"]);
const problem = new Set(["registerContest", "copyMarkdown", "alert"]);
const cookieCommands = new Set(["getCookie", "setCookie"]);
const problemCommands = new Set(["registerContest", "copyMarkdown", "alert"]);
const submitCommands = new Set(["fetchSubmitPage", "submitCode"]);

export async function runCommand(
Comment thread
Transparent-fish marked this conversation as resolved.
message: IncomingMessage,
Expand All @@ -16,11 +25,27 @@ export async function runCommand(
) {
if (loadCommands.has(message.command!)) await runLoadCommand(message, sendToWebview);
else if (deeplCommands.has(message.command!)) await runDeepL(message, context, sendToWebview);
else if (cookie.has(message.command!)) await runCookie(message, context, sendToWebview);
else if (problem.has(message.command!)) await runProblem(message, context, sendToWebview);
else if (cookieCommands.has(message.command!)) await runCookie(message, context, sendToWebview);
Comment thread
Transparent-fish marked this conversation as resolved.
else if (problemCommands.has(message.command!)) await runProblem(message, context, sendToWebview);
Comment thread
Transparent-fish marked this conversation as resolved.
else if (submitCommands.has(message.command!)) await runSubmit(message, context, sendToWebview);
Comment thread
Transparent-fish marked this conversation as resolved.
else throw new Error("unknown command");
}

async function runSubmit(command: IncomingMessage, context: vscode.ExtensionContext, sendToWebview: (payload: Record<string, unknown>) => void,): Promise<boolean> {
switch (command.command) {
case "fetchSubmitPage":
if (!command.contest) return false;
await handleFetchSubmitPage(command.contest, sendToWebview);
return true;
case "submitCode":
if (!command.contest) return false;
await handleSubmitCode(command.contest, command.taskScreenName, command.languageId, command.sourceCode, sendToWebview);
return true;
default:
return false;
}
}
Comment thread
Transparent-fish marked this conversation as resolved.

async function runProblem(
command: IncomingMessage,
context: vscode.ExtensionContext,
Expand Down
90 changes: 90 additions & 0 deletions apps/vscode-extension/src/tools/submit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { fetchText, fetchTextPost } from "./fetch";

export interface LanguageOption {
id: string;
label: string;
}

export interface SubmitPage {
contest: string;
csrfToken: string;
tasks: Array<{ value: string; label: string }>;
languages: LanguageOption[];
}

export interface SubmitStatus {
success: boolean;
message: string;
url?: string;
}

export async function fetchSubmitPage(contest: string): Promise<SubmitPage> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`fetchSubmitPage` has a cyclomatic complexity of 6 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

const url = `https://atcoder.jp/contests/${contest}/submit`;
const html = await fetchText(url);

const csrfMatch = html.match(/name="csrf_token"[^>]*value="([^"]*)"/i);
const csrfToken = csrfMatch ? csrfMatch[1] : "";

const tasks: Array<{ value: string; label: string }> = [];
const taskRegex = /<option[^>]*value="([^"]*)"[^>]*>([^<]*)<\/option>/gi;
let taskMatch: RegExpExecArray | null;
for (;(taskMatch = taskRegex.exec(html)) !== null;) {
const value = taskMatch[1];
if (value) tasks.push({ value, label: taskMatch[2].trim() });
}

const languages: LanguageOption[] = [];
const langRegex = /<option[^>]*value="(\d+)"[^>]*>([^<]*)<\/option>/gi;
let langMatch: RegExpExecArray | null;
for (;(langMatch = langRegex.exec(html)) !== null;) {
const value = langMatch[1];
if (value) languages.push({ id: value, label: langMatch[2].trim() });
}

return { contest, csrfToken, tasks, languages };
}
Comment thread
Transparent-fish marked this conversation as resolved.

export async function submitCode(contest: string, taskScreenName: string, languageId: string, sourceCode: string, csrfToken: string): Promise<SubmitStatus> {
const url = `https://atcoder.jp/contests/${contest}/submit`;
const body =
`csrf_token=${encodeURIComponent(csrfToken)}` +
`&data.TaskScreenName=${encodeURIComponent(taskScreenName)}` +
`&data.LanguageId=${encodeURIComponent(languageId)}` +
`&sourceCode=${encodeURIComponent(sourceCode)}`;

const responseHtml = await fetchTextPost(url, body);

const errMatch = responseHtml.match(
/<div[^>]*class="[^"]*(?:alert-danger|alert-warning|alert-error)[^"]*"[^>]*>([\s\S]*?)<\/div>/i
);
if (errMatch) {
const errMsg = errMatch[1].replace(/<[^>]+>/g, "").trim();
return { success: false, message: errMsg };
}

const successed = responseHtml.match(
/<div[^>]*class="[^"]*alert-success[^"]*"[^>]*>([\s\S]*?)<\/div>/i
);
if (successed) {
const msg = successed[1].replace(/<[^>]+>/g, "").trim();
return { success: true, message: msg, url: `https://atcoder.jp/contests/${contest}/submissions/me` };
}

if (responseHtml.includes("/submissions/me") || responseHtml.includes("Submission")) {
return {
success: true,
message: "代码提交成功",
url: `https://atcoder.jp/contests/${contest}/submissions/me`,
};
}

return { success: false, message: "提交失败,请检查 Cookie 是否有效" };
}
Comment on lines +47 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.


export async function submitCodeWithRedirect(contest: string, taskScreenName: string, languageId: string, sourceCode: string): Promise<SubmitStatus> {
const pageData = await fetchSubmitPage(contest);
if (!pageData.csrfToken) {
return { success: false, message: "无法获取 CSRF Token,请检查 Cookie 是否有效" };
}
return await submitCode(contest, taskScreenName, languageId, sourceCode, pageData.csrfToken);
}
Comment thread
Transparent-fish marked this conversation as resolved.
3 changes: 3 additions & 0 deletions apps/vscode-extension/src/tools/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export interface IncomingMessage {
command?: string;
contest?: string;
task?: string;
taskScreenName?: string;
languageId?: string;
sourceCode?: string;
url?: string;
payload?: Record<string, string>;
targetLang?: string;
Expand Down
Loading