games: automatic CS2 / PUBG / R6 match history — games.matches, match_players, seasons, snapshots, accounts; in-app poller (Leetify, FACEIT, Steam share codes, PUBG API, Ubisoft) every 20 min plus scripts/games-ingest.ts for backfills; match history, stats, maps and seasons on the per-game pages
All checks were successful
Deploy production / deploy (push) Successful in 1m6s
All checks were successful
Deploy production / deploy (push) Successful in 1m6s
This commit is contained in:
97
src/lib/games-ingest/run.ts
Normal file
97
src/lib/games-ingest/run.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { pollFaceit } from './faceit';
|
||||
import { pollLeetify } from './leetify';
|
||||
import { pollPubg } from './pubg';
|
||||
import { pollSteamShareCodes } from './steam';
|
||||
import { pollUbisoft } from './ubisoft';
|
||||
import { hoursSince } from './store';
|
||||
import type { Account, Db, GameEnv, RunOptions, SourceResult } from './types';
|
||||
|
||||
// One ingest pass over every enabled games.accounts row. Order matters: Leetify first so the
|
||||
// Steam share-code chain can start from a code Leetify gave us.
|
||||
export const SOURCE_ORDER = ['leetify', 'faceit', 'steam', 'pubg', 'ubisoft'] as const;
|
||||
/** Minimum hours between polls per source (the poller ticks every 20 minutes). */
|
||||
const DUE_HOURS: Record<string, number> = {
|
||||
leetify: 0.3,
|
||||
faceit: 6,
|
||||
steam: 0.3,
|
||||
pubg: 0.3,
|
||||
ubisoft: 0.3
|
||||
};
|
||||
|
||||
/** Which keys each source needs; shown (names only) on the owner's view of the pages. */
|
||||
export function missingKeys(env: GameEnv): Record<string, string[]> {
|
||||
const need = (...k: (keyof GameEnv)[]) => k.filter((x) => !env[x]);
|
||||
return {
|
||||
leetify: [],
|
||||
faceit: [],
|
||||
steam: need('STEAM_WEB_API_KEY', 'CS2_AUTH_CODE'),
|
||||
pubg: need('PUBG_API_KEY'),
|
||||
ubisoft: need('UBI_EMAIL', 'UBI_PASSWORD')
|
||||
};
|
||||
}
|
||||
|
||||
export async function runIngest(
|
||||
sql: Db,
|
||||
env: GameEnv,
|
||||
opts: RunOptions = {}
|
||||
): Promise<SourceResult[]> {
|
||||
const accounts = await sql<Account[]>`
|
||||
select id, game, source, external_id as "externalId", name, state, last_success_at as "lastSuccessAt"
|
||||
from games.accounts where enabled order by id`;
|
||||
const results: SourceResult[] = [];
|
||||
for (const source of SOURCE_ORDER) {
|
||||
if (opts.only?.length && !opts.only.includes(source)) continue;
|
||||
for (const account of accounts.filter((a) => a.source === source)) {
|
||||
const label = account.name ?? account.externalId;
|
||||
if (!opts.backfill && !opts.force && hoursSince(account.lastSuccessAt) < DUE_HOURS[source])
|
||||
continue;
|
||||
if (missingKeys(env)[source]?.length) {
|
||||
results.push({
|
||||
game: account.game,
|
||||
source,
|
||||
account: label,
|
||||
status: 'skipped',
|
||||
added: 0,
|
||||
note: `needs ${missingKeys(env)[source].join(', ')}`
|
||||
});
|
||||
continue;
|
||||
}
|
||||
await sql`update games.accounts set last_polled_at = now() where id = ${account.id}`;
|
||||
try {
|
||||
const r =
|
||||
source === 'leetify'
|
||||
? await pollLeetify(sql, account, env, opts)
|
||||
: source === 'faceit'
|
||||
? await pollFaceit(sql, account, opts)
|
||||
: source === 'steam'
|
||||
? await pollSteamShareCodes(sql, account, env)
|
||||
: source === 'pubg'
|
||||
? await pollPubg(sql, account, env, opts)
|
||||
: await pollUbisoft(sql, account, env, opts);
|
||||
const skipped = 'skipped' in r && r.skipped;
|
||||
if (!skipped)
|
||||
await sql`update games.accounts set last_success_at = now(), last_error = null where id = ${account.id}`;
|
||||
results.push({
|
||||
game: account.game,
|
||||
source,
|
||||
account: label,
|
||||
status: skipped ? 'skipped' : 'ok',
|
||||
added: 'added' in r ? (r.added ?? 0) : 0,
|
||||
note: r.note
|
||||
});
|
||||
} catch (e) {
|
||||
const message = (e as Error).message.slice(0, 300);
|
||||
await sql`update games.accounts set last_error = ${message} where id = ${account.id}`;
|
||||
results.push({
|
||||
game: account.game,
|
||||
source,
|
||||
account: label,
|
||||
status: 'error',
|
||||
added: 0,
|
||||
note: message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
Reference in New Issue
Block a user