diff --git a/src/providers/all.ts b/src/providers/all.ts index 1908d5a..a3eb05e 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -47,9 +47,14 @@ import { import { FedAPIPrivateScraper, FedDBScraper } from './embeds/fedapi'; import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra'; import { ridooScraper } from './embeds/ridoo'; -import { streamtapeScraper } from './embeds/streamtape'; +import { streamtapeLatinoScraper, streamtapeScraper } from './embeds/streamtape'; import { streamvidScraper } from './embeds/streamvid'; -import { streamwishEnglishScraper, streamwishLatinoScraper, streamwishSpanishScraper } from './embeds/streamwish'; +import { + streamwishEnglishScraper, + streamwishJapaneseScraper, + streamwishLatinoScraper, + streamwishSpanishScraper, +} from './embeds/streamwish'; import { vidCloudScraper } from './embeds/vidcloud'; import { VidsrcsuServer10Scraper, @@ -82,6 +87,7 @@ import { } from './embeds/xprime'; import { oneServerScraper } from './sources/1server'; import { EightStreamScraper } from './sources/8stream'; +import { animeflvScraper } from './sources/animeflv'; import { coitusScraper } from './sources/coitus'; import { ConsumetScraper } from './sources/consumet'; import { cuevana3Scraper } from './sources/cuevana3'; @@ -135,6 +141,7 @@ export function gatherAllSources(): Array { hianimeScraper, oneServerScraper, wecimaScraper, + animeflvScraper, ]; } @@ -204,8 +211,10 @@ export function gatherAllEmbeds(): Array { oneServerHianimeEmbed, oneServerAnimepaheEmbed, oneServerAnizoneEmbed, + streamwishJapaneseScraper, streamwishLatinoScraper, streamwishSpanishScraper, streamwishEnglishScraper, + streamtapeLatinoScraper, ]; } diff --git a/src/providers/embeds/streamtape.ts b/src/providers/embeds/streamtape.ts index db345b0..6e465c3 100644 --- a/src/providers/embeds/streamtape.ts +++ b/src/providers/embeds/streamtape.ts @@ -1,39 +1,56 @@ import { flags } from '@/entrypoint/utils/targets'; import { makeEmbed } from '@/providers/base'; -export const streamtapeScraper = makeEmbed({ - id: 'streamtape', - name: 'Streamtape', - rank: 160, - async scrape(ctx) { - const embed = await ctx.proxiedFetcher(ctx.url); +const providers = [ + { + id: 'streamtape', + name: 'Streamtape', + rank: 160, + }, + { + id: 'streamtape-latino', + name: 'Streamtape (Latino)', + rank: 159, + }, +]; - const match = embed.match(/robotlink'\).innerHTML = (.*)'/); - if (!match) throw new Error('No match found'); +function embed(provider: { id: string; name: string; rank: number }) { + return makeEmbed({ + id: provider.id, + name: provider.name, + rank: provider.rank, + async scrape(ctx) { + const embedHtml = await ctx.proxiedFetcher(ctx.url); - const [fh, sh] = match?.[1]?.split("+ ('") ?? []; - if (!fh || !sh) throw new Error('No match found'); + const match = embedHtml.match(/robotlink'\).innerHTML = (.*)'/); + if (!match) throw new Error('No match found'); - const url = `https:${fh?.replace(/'/g, '').trim()}${sh?.substring(3).trim()}`; + const [fh, sh] = match?.[1]?.split("+ ('") ?? []; + if (!fh || !sh) throw new Error('No match found'); - return { - stream: [ - { - id: 'primary', - type: 'file', - flags: [flags.CORS_ALLOWED, flags.IP_LOCKED], - captions: [], - qualities: { - unknown: { - type: 'mp4', - url, + const url = `https:${fh?.replace(/'/g, '').trim()}${sh?.substring(3).trim()}`; + + return { + stream: [ + { + id: 'primary', + type: 'file', + flags: [flags.CORS_ALLOWED, flags.IP_LOCKED], + captions: [], + qualities: { + unknown: { + type: 'mp4', + url, + }, + }, + headers: { + Referer: 'https://streamtape.com', }, }, - headers: { - Referer: 'https://streamtape.com', - }, - }, - ], - }; - }, -}); + ], + }; + }, + }); +} + +export const [streamtapeScraper, streamtapeLatinoScraper] = providers.map(embed); diff --git a/src/providers/embeds/streamwish.ts b/src/providers/embeds/streamwish.ts index a451dfa..af980eb 100644 --- a/src/providers/embeds/streamwish.ts +++ b/src/providers/embeds/streamwish.ts @@ -4,6 +4,11 @@ import { makeEmbed } from '@/providers/base'; import { NotFoundError } from '@/utils/errors'; const providers = [ + { + id: 'streamwish-japanese', + name: 'StreamWish (Japones Sub EspaƱol)', + rank: 171, + }, { id: 'streamwish-latino', name: 'StreamWish (Latino)', @@ -58,4 +63,5 @@ function embed(provider: { id: string; name: string; rank: number }) { }); } -export const [streamwishLatinoScraper, streamwishSpanishScraper, streamwishEnglishScraper] = providers.map(embed); +export const [streamwishJapaneseScraper, streamwishLatinoScraper, streamwishSpanishScraper, streamwishEnglishScraper] = + providers.map(embed); diff --git a/src/providers/sources/animeflv.ts b/src/providers/sources/animeflv.ts new file mode 100644 index 0000000..9b0562e --- /dev/null +++ b/src/providers/sources/animeflv.ts @@ -0,0 +1,87 @@ +/* eslint-disable no-console */ +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererOutput, makeSourcerer } from '@/providers/base'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +const apiBase = 'https://ws-m3u8.moonpic.qzz.io:3006'; + +async function searchAnimeFlvAPI(title: string): Promise { + const res = await fetch(`${apiBase}/search?title=${encodeURIComponent(title)}`); + if (!res.ok) throw new NotFoundError('Anime not found in API'); + const data = await res.json(); + if (!data.url) throw new NotFoundError('Anime not found in API'); + return data.url; +} + +async function getEpisodesAPI(animeUrl: string): Promise<{ number: number; url: string }[]> { + const res = await fetch(`${apiBase}/episodes?url=${encodeURIComponent(animeUrl)}`); + if (!res.ok) throw new NotFoundError('Episodes not found in API'); + const data = await res.json(); + if (!data.episodes) throw new NotFoundError('Episodes not found in API'); + return data.episodes; +} + +async function getEmbedsAPI(episodeUrl: string): Promise> { + const res = await fetch(`${apiBase}/embeds?episodeUrl=${encodeURIComponent(episodeUrl)}`); + if (!res.ok) throw new NotFoundError('No embed found for this content'); + const data = await res.json(); + return data; +} + +async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { + const title = ctx.media.title; + if (!title) throw new NotFoundError('Missing title'); + + // Search anime/movie by title using the API + const animeUrl = await searchAnimeFlvAPI(title); + + // Get episodes using the API + const episodes = await getEpisodesAPI(animeUrl); + + let episodeUrl = animeUrl; + + if (ctx.media.type === 'show') { + const episode = ctx.media.episode?.number; + if (!episode) throw new NotFoundError('Missing episode data'); + const ep = episodes.find((e) => e.number === episode); + if (!ep) throw new NotFoundError('Episode not found'); + episodeUrl = ep.url; + } else if (ctx.media.type === 'movie') { + // For movies, use the first episode (by convention) + const ep = episodes.find((e) => e.number === 1) || episodes[0]; + if (!ep) throw new NotFoundError('Movie episode not found'); + episodeUrl = ep.url; + } + + // Get all embeds using the API + const embedsData = await getEmbedsAPI(episodeUrl); + + const embeds = []; + if (embedsData['streamwish-japanese']) { + embeds.push({ + embedId: 'streamwish-japanese', + url: embedsData['streamwish-japanese'], + }); + } + if (embedsData['streamtape-latino']) { + embeds.push({ + embedId: 'streamtape-latino', + url: embedsData['streamtape-latino'], + }); + } + + if (embeds.length === 0) throw new NotFoundError('No valid embed found for this content'); + + return { embeds }; +} + +export const animeflvScraper = makeSourcerer({ + id: 'animeflv', + name: 'AnimeFLV', + rank: 90, + disabled: false, + flags: [flags.CORS_ALLOWED], + scrapeShow: comboScraper, + scrapeMovie: comboScraper, +});