mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-16 23:16:26 +00:00
95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
/**
|
|
* GitHub REST + GraphQL client on GH_PAT. Hand-rolled fetch, no SDK —
|
|
* matching the repo's dependency-free automation rule.
|
|
*/
|
|
|
|
export interface GitHub {
|
|
repo: string;
|
|
rest(path: string, init?: RequestInit): Promise<unknown>;
|
|
graphql(query: string, variables?: Record<string, unknown>): Promise<unknown>;
|
|
list(path: string): AsyncGenerator<unknown, void, void>;
|
|
/** Comment first, then close — a failed close still leaves a visible note. */
|
|
closeIssue(issue: number, comment: string): Promise<void>;
|
|
comment(issue: number, body: string): Promise<void>;
|
|
react(commentId: number, content: string): Promise<void>;
|
|
}
|
|
|
|
const PER_PAGE = 100;
|
|
|
|
export function github(token: string, repo: string): GitHub {
|
|
async function rest(path: string, init: RequestInit = {}): Promise<unknown> {
|
|
const res = await fetch(`https://api.github.com${path}`, {
|
|
...init,
|
|
headers: {
|
|
accept: "application/vnd.github+json",
|
|
authorization: `Bearer ${token}`,
|
|
"user-agent": "srs-api",
|
|
"x-github-api-version": "2022-11-28",
|
|
...(init.body ? { "content-type": "application/json" } : {}),
|
|
},
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`${init.method ?? "GET"} ${path} -> ${res.status} ${await res.text()}`);
|
|
}
|
|
return res.status === 204 ? null : res.json();
|
|
}
|
|
|
|
async function graphql(
|
|
query: string,
|
|
variables: Record<string, unknown> = {},
|
|
): Promise<unknown> {
|
|
const res = await fetch("https://api.github.com/graphql", {
|
|
method: "POST",
|
|
headers: {
|
|
authorization: `Bearer ${token}`,
|
|
"content-type": "application/json",
|
|
"user-agent": "srs-api",
|
|
},
|
|
body: JSON.stringify({ query, variables }),
|
|
});
|
|
if (!res.ok) throw new Error(`graphql -> ${res.status} ${await res.text()}`);
|
|
const payload = (await res.json()) as { data?: unknown; errors?: { message: string }[] };
|
|
if (payload.errors?.length) throw new Error(payload.errors.map((e) => e.message).join("; "));
|
|
return payload.data;
|
|
}
|
|
|
|
async function* list(path: string): AsyncGenerator<unknown, void, void> {
|
|
const sep = path.includes("?") ? "&" : "?";
|
|
for (let page = 1; ; page++) {
|
|
const batch = await rest(`${path}${sep}per_page=${PER_PAGE}&page=${page}`);
|
|
if (!Array.isArray(batch)) throw new Error(`unexpected ${path} payload: not an array`);
|
|
yield* batch;
|
|
if (batch.length < PER_PAGE) return;
|
|
}
|
|
}
|
|
|
|
return {
|
|
repo,
|
|
rest,
|
|
graphql,
|
|
list,
|
|
async closeIssue(issue, comment) {
|
|
await rest(`/repos/${repo}/issues/${issue}/comments`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ body: comment }),
|
|
});
|
|
await rest(`/repos/${repo}/issues/${issue}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify({ state: "closed", state_reason: "completed" }),
|
|
});
|
|
},
|
|
async comment(issue, body) {
|
|
await rest(`/repos/${repo}/issues/${issue}/comments`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ body }),
|
|
});
|
|
},
|
|
async react(commentId, content) {
|
|
await rest(`/repos/${repo}/issues/comments/${commentId}/reactions`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ content }),
|
|
});
|
|
},
|
|
};
|
|
}
|