diff --git a/src/ingest/adapters/index.ts b/src/ingest/adapters/index.ts index 5abbc82..bbb3258 100644 --- a/src/ingest/adapters/index.ts +++ b/src/ingest/adapters/index.ts @@ -227,6 +227,9 @@ function toAdapter(spec: SourceSpec): Adapter { parserId: spec.parserId, minIntervalMs: spec.minIntervalMs ?? SIX_HOURS_MS, priority: spec.priority ?? 0, + statesNoEvents(html: string): boolean { + return parser.statesNoEvents?.(html) ?? false; + }, parse(html: string, ctx: ParseContext): GachaEvent[] { // A site redesign should fail the run loudly rather than publish an empty // calendar, which would read as "no events" to a user. diff --git a/src/ingest/adapters/types.ts b/src/ingest/adapters/types.ts index bc729f4..7ea2b4c 100644 --- a/src/ingest/adapters/types.ts +++ b/src/ingest/adapters/types.ts @@ -33,6 +33,14 @@ export interface Adapter { priority: number; parse(html: string, ctx: ParseContext): GachaEvent[]; + + /** + * True when the page itself states that it currently lists no events, so an + * empty parse is this source's real answer rather than a source that broke. + * Absent unless the underlying parser implements it — see + * `SourceParser.statesNoEvents`. + */ + statesNoEvents?(html: string): boolean; } export const SIX_HOURS_MS = 6 * 60 * 60 * 1000; diff --git a/src/ingest/parsers/types.ts b/src/ingest/parsers/types.ts index 8d0843b..97bd388 100644 --- a/src/ingest/parsers/types.ts +++ b/src/ingest/parsers/types.ts @@ -26,5 +26,19 @@ export interface SourceParser { */ canParse(html: string): boolean; + /** + * True when the document is one this parser understands *and* the document + * itself says it currently lists no events. + * + * Optional, and absent for every parser whose pages do not say so. It exists + * because `canParse` and a row count cannot together tell a source that broke + * from a game that is simply between versions: both yield nothing. A page + * that states its own emptiness can, and the refresh runner stores that as a + * real answer instead of counting it as a failure — see + * `scripts/refresh-sources.ts` § the parse gate. Anything less explicit than + * the page's own words belongs on the strict side of that gate. + */ + statesNoEvents?(html: string): boolean; + parse(html: string, ctx: ParseContext): GachaEvent[]; }