Handle unavailable ChatGPT shares

This commit is contained in:
Codex
2026-07-08 08:00:06 +00:00
parent 83c82206c4
commit 37a5450107
3 changed files with 24 additions and 2 deletions
+12 -1
View File
@@ -26,6 +26,9 @@ function publicErrorFor(error: unknown): string {
if (/captcha|verify|robot/i.test(message)) {
return "ChatGPT asked for verification, so this public share could not be captured automatically.";
}
if (/unavailable|no longer public|not public|redirected away/i.test(message)) {
return "This ChatGPT share is not available publicly anymore, so it cannot be captured.";
}
if (/timeout/i.test(message)) {
return "The public ChatGPT share took too long to load.";
}
@@ -271,9 +274,11 @@ async function extractConversation(sourceUrl: string): Promise<ExtractedConversa
if (!page) throw new Error("Chromium CDP page target was not created.");
await page.setViewportSize({ width: 1360, height: 900 }).catch(() => undefined);
page.setDefaultTimeout(config.captureTimeoutMs);
const expectedPath = new URL(sourceUrl).pathname;
await page
.waitForFunction(
() => {
(sharePath) => {
if (!location.pathname.startsWith(sharePath)) return true;
const bodyText = document.body?.innerText || document.body?.textContent || "";
if (/verify you are human|captcha|unusual activity/i.test(bodyText)) return true;
if (document.querySelectorAll("[data-message-author-role]").length > 0) return true;
@@ -281,12 +286,18 @@ async function extractConversation(sourceUrl: string): Promise<ExtractedConversa
if ((markdown?.textContent || "").trim().length > 0) return true;
return /(?:^|\s)(You said:|ChatGPT said:)/.test(bodyText);
},
expectedPath,
{ timeout: config.captureTimeoutMs }
)
.catch(() => undefined);
await page.waitForLoadState("networkidle", { timeout: 5_000 }).catch(() => undefined);
await page.waitForTimeout(1000);
const actualUrl = page.url();
if (!new URL(actualUrl).pathname.startsWith(expectedPath)) {
throw new Error(`ChatGPT share is unavailable or no longer public: page redirected away to ${actualUrl}.`);
}
const extracted = await withTimeout(page.evaluate(() => {
function cleanHtml(root: Element): string {
const clone = root.cloneNode(true) as Element;
+11
View File
@@ -90,6 +90,17 @@ export class Store {
.run(status, detail?.errorPublic ?? null, detail?.errorDetail ?? null, new Date().toISOString(), id);
}
retryCapture(id: string): void {
this.db
.prepare(
`UPDATE captures
SET status = 'queued', title = NULL, error_public = NULL, error_detail = NULL,
data_json = NULL, metadata_json = NULL, captured_at = NULL, updated_at = ?
WHERE id = ?`
)
.run(new Date().toISOString(), id);
}
completeCapture(id: string, input: { slug: string; title: string; dataJson: string; metadataJson: string }): void {
const now = new Date().toISOString();
this.db
+1 -1
View File
@@ -130,7 +130,7 @@ app.post(route("/api/captures/:id/retry"), requireAdmin, (req, res) => {
res.status(409).json({ error: "Successful captures are immutable. Create a new capture instead." });
return;
}
store.updateStatus(row.id, "queued");
store.retryCapture(row.id);
captureQueue.enqueue(row.id);
res.json({ capture: publicCapture(store.getCapture(row.id)) });
});