import type { FetchUrlResponse } from "~/types/episode/fetch-url-response"; import { type SkipTime, convertSkipTime } from "./convertSkipTime"; export async function getSourcesFromAniwatch( watchId: string, ): Promise { console.log(`Fetching sources from aniwatch for ${watchId}`); const url = await getEpisodeUrl(watchId); if (url) { return { success: true, result: url }; } const servers = await getEpisodeServers(watchId); if (servers.length === 0) { return { success: false }; } for (const server of servers) { const url = await getEpisodeUrl(watchId, server.serverName); if (url) { return { success: true, result: url }; } } return { success: false }; } async function getEpisodeUrl(watchId: string, server?: string) { let url = `https://aniwatch.up.railway.app/api/v2/hianime/episode/sources?animeEpisodeId=${encodeURIComponent(watchId)}`; if (server) { url += `&server=${encodeURIComponent(server)}`; } const { source, intro, outro, subtitles, headers } = await fetch(url) .then( (res) => res.json() as Promise<{ status: number; data: AniwatchEpisodeUrlResponse; }>, ) .then(({ status, data }) => { if (status >= 300 || !data.sources || data.sources.length === 0) { return { source: null }; } const { intro, outro, sources, tracks, headers } = data; return { intro: convertSkipTime(intro), outro: convertSkipTime(outro), source: sources[0].url, subtitles: tracks.map(({ url, lang }) => ({ url, lang, })), headers, }; }); if (!source) { return null; } return { source, headers, intro, outro, subtitles, audio: [], }; } async function getEpisodeServers(watchId: string) { const { data } = await fetch( `https://aniwatch.up.railway.app/api/v2/hianime/episode/servers?animeEpisodeId=${encodeURIComponent( watchId, )}`, ) .then((res) => res.json() as Promise) .then((res) => { if (res.status >= 300 || !res.data) { throw new Error("Failed to fetch episode servers"); } return res; }); return data.sub; } interface AniwatchEpisodeUrlResponse { headers?: Record; tracks: Track[]; intro: SkipTime; outro: SkipTime; sources: Source[]; anilistID: number; malID: number; } interface Source { url: string; type: string; } interface Track { url: string; lang?: string; kind: string; default?: boolean; } interface AniwatchEpisodeServersResponse { status: number; data: AniwatchEpisodeServers; } interface AniwatchEpisodeServers { sub: AniwatchEpisodeServer[]; dub: AniwatchEpisodeServer[]; raw: AniwatchEpisodeServer[]; episodeID: string; episodeNo: number; } interface AniwatchEpisodeServer { serverName: string; serverID: number; }