Backfill YouTube titles through the queue
This commit is contained in:
@@ -171,6 +171,8 @@ export interface MediaRow {
|
||||
preview_attempts: number;
|
||||
/** Embed title from Discord (YouTube and friends). */
|
||||
title: string | null;
|
||||
title_attempts: number;
|
||||
title_error: string | null;
|
||||
/** Lowercased, punctuation-collapsed title for token-prefix search. */
|
||||
title_key: string | null;
|
||||
created_at: string;
|
||||
@@ -224,6 +226,8 @@ CREATE TABLE IF NOT EXISTS media (
|
||||
preview_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
title TEXT,
|
||||
title_key TEXT,
|
||||
title_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
title_error TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
@@ -273,6 +277,10 @@ export class ScrapeStore {
|
||||
this.db.exec("ALTER TABLE media ADD COLUMN title TEXT");
|
||||
this.db.exec("ALTER TABLE media ADD COLUMN title_key TEXT");
|
||||
}
|
||||
if (!names.has("title_attempts")) {
|
||||
this.db.exec("ALTER TABLE media ADD COLUMN title_attempts INTEGER NOT NULL DEFAULT 0");
|
||||
this.db.exec("ALTER TABLE media ADD COLUMN title_error TEXT");
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
@@ -434,6 +442,8 @@ export class ScrapeStore {
|
||||
pending: number;
|
||||
cached: number;
|
||||
failed: number;
|
||||
/** YouTube-like rows still missing a title. */
|
||||
titlePending: number;
|
||||
byKind: Record<MediaKind, { total: number; pending: number; cached: number; failed: number }>;
|
||||
} {
|
||||
const empty = () => ({ total: 0, pending: 0, cached: 0, failed: 0 });
|
||||
@@ -477,10 +487,58 @@ export class ScrapeStore {
|
||||
totals.failed += row.failed ?? 0;
|
||||
}
|
||||
|
||||
return { ...totals, byKind };
|
||||
const titles = this.db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) AS count FROM media
|
||||
WHERE title IS NULL AND title_error IS NULL
|
||||
AND (lower(COALESCE(provider, '')) LIKE '%youtube%'
|
||||
OR lower(url) LIKE '%youtube.com/%'
|
||||
OR lower(url) LIKE '%youtu.be/%')`
|
||||
)
|
||||
.get() as { count: number | null } | undefined;
|
||||
|
||||
return { ...totals, titlePending: titles?.count ?? 0, byKind };
|
||||
}
|
||||
|
||||
/** Count one transient preview failure and return the new total. */
|
||||
/**
|
||||
* YouTube-like rows whose title Discord did not provide, oldest first. Resolved through the
|
||||
* provider oEmbed in the backfill queue.
|
||||
*/
|
||||
pendingTitles(limit = 500): MediaRow[] {
|
||||
return this.db
|
||||
.prepare(
|
||||
`SELECT * FROM media
|
||||
WHERE title IS NULL AND title_error IS NULL
|
||||
AND (lower(COALESCE(provider, '')) LIKE '%youtube%'
|
||||
OR lower(url) LIKE '%youtube.com/%'
|
||||
OR lower(url) LIKE '%youtu.be/%')
|
||||
ORDER BY id LIMIT ?`
|
||||
)
|
||||
.all(limit) as unknown as MediaRow[];
|
||||
}
|
||||
|
||||
/** Store a resolved title and its search key. */
|
||||
markTitle(id: number, title: string): void {
|
||||
this.db
|
||||
.prepare("UPDATE media SET title = ?, title_key = ?, title_error = NULL WHERE id = ?")
|
||||
.run(title.slice(0, 300), normalizeTitleKey(title), id);
|
||||
}
|
||||
|
||||
/** Count one transient title failure and return the new total. */
|
||||
bumpTitleAttempt(id: number): number {
|
||||
this.db.prepare("UPDATE media SET title_attempts = title_attempts + 1 WHERE id = ?").run(id);
|
||||
const row = this.db.prepare("SELECT title_attempts FROM media WHERE id = ?").get(id) as
|
||||
| { title_attempts: number | null }
|
||||
| undefined;
|
||||
|
||||
return row?.title_attempts ?? 0;
|
||||
}
|
||||
|
||||
markTitleError(id: number, error: string): void {
|
||||
this.db.prepare("UPDATE media SET title_error = ? WHERE id = ?").run(error.slice(0, 200), id);
|
||||
}
|
||||
|
||||
/** Count one transient preview failure and return the new total. */
|
||||
bumpPreviewAttempt(id: number): number {
|
||||
this.db.prepare("UPDATE media SET preview_attempts = preview_attempts + 1 WHERE id = ?").run(id);
|
||||
|
||||
Reference in New Issue
Block a user