import fs from "fs/promises";
import path from "path";
import type { Octokits } from "../api/client";
import { GITHUB_SERVER_URL } from "../api/config";
const escapedUrl = GITHUB_SERVER_URL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const IMAGE_REGEX = new RegExp(
`!\\[[^\\]]*\\]\\((${escapedUrl}\\/user-attachments\\/assets\\/[^)]+)\\)`,
"g",
);
const HTML_IMG_REGEX = new RegExp(
`
]+src=["']([^"']*${escapedUrl}\\/user-attachments\\/assets\\/[^"']+)["'][^>]*>`,
"gi",
);
const SIGNED_URL_REGEX =
/https:\/\/private-user-images\.githubusercontent\.com\/[^"]+\?jwt=[^"]+/g;
// GitHub identifies an uploaded asset by a GUID that appears both in the
// user-attachment URL and in the signed download URL rendered in body_html.
const ASSET_GUID_REGEX =
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
function extractAssetGuid(url: string): string | undefined {
return url.match(ASSET_GUID_REGEX)?.[0]?.toLowerCase();
}
const SIGNED_URL_HOST = "private-user-images.githubusercontent.com";
// Signed download URLs have the shape //-..
// The GUID must come from the resolved filename, not from anywhere in the raw
// string, so text that merely embeds a GUID cannot claim another asset.
const SIGNED_URL_PATH_REGEX =
/^\/[^/]+\/[^/]*-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\.[a-z0-9]+)?$/i;
function extractSignedUrlAssetGuid(signedUrl: string): string | undefined {
let parsed: URL;
try {
parsed = new URL(signedUrl);
} catch {
return undefined;
}
if (parsed.host !== SIGNED_URL_HOST) {
return undefined;
}
return parsed.pathname.match(SIGNED_URL_PATH_REGEX)?.[1]?.toLowerCase();
}
type IssueComment = {
type: "issue_comment";
id: string;
body: string;
};
type ReviewComment = {
type: "review_comment";
id: string;
body: string;
};
type ReviewBody = {
type: "review_body";
id: string;
pullNumber: string;
body: string;
};
type IssueBody = {
type: "issue_body";
issueNumber: string;
body: string;
};
type PullRequestBody = {
type: "pr_body";
pullNumber: string;
body: string;
};
export type CommentWithImages =
| IssueComment
| ReviewComment
| ReviewBody
| IssueBody
| PullRequestBody;
export async function downloadCommentImages(
octokits: Octokits,
owner: string,
repo: string,
comments: CommentWithImages[],
): Promise