diff --git a/src/providers/all.ts b/src/providers/all.ts index 9cb3585..2056faa 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -19,6 +19,7 @@ import { kissAsianScraper } from '@/providers/sources/kissasian/index'; import { lookmovieScraper } from '@/providers/sources/lookmovie'; import { remotestreamScraper } from '@/providers/sources/remotestream'; import { showboxScraper } from '@/providers/sources/showbox/index'; +import { tugaflixScraper } from '@/providers/sources/tugaflix'; import { vidsrcScraper } from '@/providers/sources/vidsrc/index'; import { zoechipScraper } from '@/providers/sources/zoechip'; @@ -66,6 +67,7 @@ export function gatherAllSources(): Array { warezcdnScraper, insertunitScraper, soaperTvScraper, + tugaflixScraper, ]; } diff --git a/src/providers/sources/tugaflix/common.ts b/src/providers/sources/tugaflix/common.ts new file mode 100644 index 0000000..d7f58f7 --- /dev/null +++ b/src/providers/sources/tugaflix/common.ts @@ -0,0 +1,19 @@ +import { load } from 'cheerio'; + +export const baseUrl = 'https://tugaflix.best/'; + +export function parseSearch(page: string): { title: string; year?: number; url: string }[] { + const results: { title: string; year?: number; url: string }[] = []; + const $ = load(page); + + $('.items .poster').each((_, element) => { + const $link = $(element).find('a'); + const url = $link.attr('href'); + const [, title, year] = $link.attr('title')?.match(/^(.*?)\s*(?:\((\d{4})\))?\s*$/) || []; + if (!title || !url) return; + + results.push({ title, year: year ? parseInt(year, 10) : undefined, url }); + }); + + return results; +} diff --git a/src/providers/sources/tugaflix/index.ts b/src/providers/sources/tugaflix/index.ts new file mode 100644 index 0000000..ee7242f --- /dev/null +++ b/src/providers/sources/tugaflix/index.ts @@ -0,0 +1,114 @@ +import { load } from 'cheerio'; + +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererEmbed, makeSourcerer } from '@/providers/base'; +import { compareMedia } from '@/utils/compare'; +import { NotFoundError } from '@/utils/errors'; + +import { baseUrl, parseSearch } from './common'; + +export const tugaflixScraper = makeSourcerer({ + id: 'tugaflix', + name: 'Tugaflix', + rank: 112, + flags: [flags.IP_LOCKED], + scrapeMovie: async (ctx) => { + const searchResults = parseSearch( + await ctx.proxiedFetcher('/filmes/', { + baseUrl, + query: { + s: ctx.media.title, + }, + }), + ); + if (searchResults.length === 0) throw new NotFoundError('No watchable item found'); + + const url = searchResults.find((x) => x && compareMedia(ctx.media, x.title, x.year))?.url; + if (!url) throw new NotFoundError('No watchable item found'); + + const videoPage = await ctx.proxiedFetcher(url, { + method: 'POST', + body: new URLSearchParams({ play: '' }), + }); + const $ = load(videoPage); + + const embeds: SourcererEmbed[] = []; + + for (const element of $('.play a')) { + const embedUrl = $(element).attr('href'); + if (!embedUrl) continue; + + const embedPage = await ctx.proxiedFetcher.full( + embedUrl.startsWith('https://') ? embedUrl : `https://${embedUrl}`, + ); + + const finalUrl = load(embedPage.body)('a:contains("Download Filme")').attr('href'); + if (!finalUrl) continue; + + if (finalUrl.includes('streamtape')) { + embeds.push({ + embedId: 'streamtape', + url: finalUrl, + }); + } else if (finalUrl.includes('dood')) { + embeds.push({ + embedId: 'dood', + url: finalUrl, + }); + } + } + + return { + embeds, + }; + }, + scrapeShow: async (ctx) => { + const searchResults = parseSearch( + await ctx.proxiedFetcher('/series/', { + baseUrl, + query: { + s: ctx.media.title, + }, + }), + ); + if (searchResults.length === 0) throw new NotFoundError('No watchable item found'); + + const url = searchResults.find((x) => x && compareMedia(ctx.media, x.title, x.year))?.url; + if (!url) throw new NotFoundError('No watchable item found'); + + const s = ctx.media.season.number < 10 ? `0${ctx.media.season.number}` : ctx.media.season.number.toString(); + const e = ctx.media.episode.number < 10 ? `0${ctx.media.episode.number}` : ctx.media.episode.number.toString(); + const videoPage = await ctx.proxiedFetcher(url, { + method: 'POST', + body: new URLSearchParams({ [`S${s}E${e}`]: '' }), + }); + + const embedUrl = load(videoPage)('iframe[name="player"]').attr('src'); + if (!embedUrl) throw new Error('Failed to find taprUrl'); + + const playerPage = await ctx.proxiedFetcher(embedUrl.startsWith('https:') ? embedUrl : `https:${embedUrl}`, { + method: 'POST', + body: new URLSearchParams({ submit: '' }), + }); + + const embeds: SourcererEmbed[] = []; + + const finalUrl = load(playerPage)('a:contains("Download Episodio")').attr('href'); + + if (finalUrl?.includes('streamtape')) { + embeds.push({ + embedId: 'streamtape', + url: finalUrl, + }); + } else if (finalUrl?.includes('dood')) { + embeds.push({ + embedId: 'dood', + url: finalUrl, + }); + } + + return { + embeds, + }; + }, +});