import { cachified } from "@epic-web/cachified"; import { cache } from "~/utils/cache.server"; import { IS_E2E_TEST_RUN } from "~/utils/e2e"; import { logger } from "~/utils/logger"; import { twitchFetch } from "./fetch"; import { type RawStream, type StreamsResponse, streamsSchema } from "./schemas"; // const STREAMS_MOCK = [ // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_777_jared-{width}x{height}.jpg", // twitchUserName: "777_jared", // viewerCount: 129, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_ano_rta-{width}x{height}.jpg", // twitchUserName: "ano_rta", // viewerCount: 50, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_mikashita0104-{width}x{height}.jpg", // twitchUserName: "mikashita0104", // viewerCount: 49, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_veenam-{width}x{height}.jpg", // twitchUserName: "veenam", // viewerCount: 42, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_minijutopia-{width}x{height}.jpg", // twitchUserName: "minijutopia", // viewerCount: 25, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_liligravybread-{width}x{height}.jpg", // twitchUserName: "liligravybread", // viewerCount: 25, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_ajx_live-{width}x{height}.jpg", // twitchUserName: "ajx_live", // viewerCount: 19, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_yuzuki729-{width}x{height}.jpg", // twitchUserName: "yuzuki729", // viewerCount: 18, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_noname_nosplat-{width}x{height}.jpg", // twitchUserName: "noname_nosplat", // viewerCount: 14, // }, // { // thumbnailUrl: // "https://static-cdn.jtvnw.net/previews-ttv/live_user_dosankoneet-{width}x{height}.jpg", // twitchUserName: "dosankoneet", // viewerCount: 13, // }, // ]; export async function getStreams() { try { const result = await cachified({ key: "twitch-streams", cache, // 2 minutes ttl: 1000 * 60 * 2, // 10 minutes staleWhileRevalidate: 1000 * 60 * 5 * 2, async getFreshValue() { return (await getAllStreams()) .map(mapRawStream) .sort((a, b) => b.viewerCount - a.viewerCount); }, }); return result; } catch (e) { logger.error(e); return []; } } function mapRawStream(stream: RawStream) { return { thumbnailUrl: stream.thumbnail_url, twitchUserName: stream.user_login.toLowerCase(), viewerCount: stream.viewer_count, }; } const SPLATOON_3_TWITCH_GAME_ID = "1158884259"; async function getAllStreams() { if (process.env.NODE_ENV === "test" || IS_E2E_TEST_RUN) return []; const result: RawStream[] = []; let cursor: string | undefined; let count = 0; while (true) { if (count === 50) { throw new Error("Stuck getting streams"); } const { data, pagination } = await getStreamsChunk(cursor); result.push( // filter to ensure each streamer appears only once ...data.filter( (stream) => !result.some( (existingStream) => existingStream.user_id === stream.user_id, ), ), ); if (!pagination.cursor) { return result; } cursor = pagination.cursor; count++; } } async function getStreamsChunk(cursor?: string): Promise { const res = await twitchFetch( `https://api.twitch.tv/helix/streams?game_id=${SPLATOON_3_TWITCH_GAME_ID}&first=100&after=${ cursor ?? "" }`, ); const parsed = streamsSchema.safeParse(await res.json()); if (!parsed.success) { throw new Error(parsed.error.message); } return parsed.data; }