diff --git a/src/providers/all.ts b/src/providers/all.ts index d91af17..9cddacd 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -14,6 +14,7 @@ import { vidsrcembedScraper } from '@/providers/embeds/vidsrc'; import { vTubeScraper } from '@/providers/embeds/vtube'; import { flixhqScraper } from '@/providers/sources/flixhq/index'; import { goMoviesScraper } from '@/providers/sources/gomovies/index'; +import { insertunitScraper } from '@/providers/sources/insertunit'; import { kissAsianScraper } from '@/providers/sources/kissasian/index'; import { lookmovieScraper } from '@/providers/sources/lookmovie'; import { remotestreamScraper } from '@/providers/sources/remotestream'; @@ -59,6 +60,7 @@ export function gatherAllSources(): Array { goojaraScraper, hdRezkaScraper, primewireScraper, + insertunitScraper, soaperTvScraper, ]; } diff --git a/src/providers/sources/insertunit/captions.ts b/src/providers/sources/insertunit/captions.ts new file mode 100644 index 0000000..881c9c2 --- /dev/null +++ b/src/providers/sources/insertunit/captions.ts @@ -0,0 +1,30 @@ +import { Caption, removeDuplicatedLanguages } from '@/providers/captions'; + +import { Subtitle } from './types'; + +export async function getCaptions(data: Subtitle[]) { + let captions: Caption[] = []; + for (const subtitle of data) { + let language = ''; + + if (subtitle.name.includes('Рус')) { + language = 'ru'; + } else if (subtitle.name.includes('Укр')) { + language = 'uk'; + } else if (subtitle.name.includes('Eng')) { + language = 'en'; + } else { + continue; + } + + captions.push({ + id: subtitle.url, + url: subtitle.url, + language, + type: 'vtt', + hasCorsRestrictions: false, + }); + } + captions = removeDuplicatedLanguages(captions); + return captions; +} diff --git a/src/providers/sources/insertunit/index.ts b/src/providers/sources/insertunit/index.ts new file mode 100644 index 0000000..9a54866 --- /dev/null +++ b/src/providers/sources/insertunit/index.ts @@ -0,0 +1,103 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { makeSourcerer } from '@/providers/base'; +import { Caption } from '@/providers/captions'; +import { NotFoundError } from '@/utils/errors'; + +import { getCaptions } from './captions'; +import { Season } from './types'; + +const insertUnitBase = 'https://api.insertunit.ws/'; + +export const insertunitScraper = makeSourcerer({ + id: 'insertunit', + name: 'Insertunit', + disabled: false, + rank: 60, + flags: [flags.CORS_ALLOWED], + async scrapeShow(ctx) { + const playerData = await ctx.fetcher(`/embed/imdb/${ctx.media.imdbId}`, { + baseUrl: insertUnitBase, + }); + ctx.progress(30); + + const seasonDataJSONregex = /seasons:(.*)/; + const seasonData = seasonDataJSONregex.exec(playerData); + + if (seasonData === null || seasonData[1] === null) { + throw new NotFoundError('No result found'); + } + ctx.progress(60); + + const seasonTable: Season[] = JSON.parse(seasonData[1]) as Season[]; + + const currentSeason = seasonTable.find( + (seasonElement) => seasonElement.season === ctx.media.season.number && !seasonElement.blocked, + ); + + const currentEpisode = currentSeason?.episodes.find((episodeElement) => + episodeElement.episode.includes(ctx.media.episode.number.toString()), + ); + + if (!currentEpisode?.hls) throw new NotFoundError('No result found'); + + let captions: Caption[] = []; + + if (currentEpisode.cc != null) { + captions = await getCaptions(currentEpisode.cc); + } + + ctx.progress(95); + + return { + embeds: [], + stream: [ + { + id: 'primary', + playlist: currentEpisode.hls, + type: 'hls', + flags: [flags.CORS_ALLOWED], + captions, + }, + ], + }; + }, + async scrapeMovie(ctx) { + const playerData = await ctx.fetcher(`/embed/imdb/${ctx.media.imdbId}`, { + baseUrl: insertUnitBase, + }); + ctx.progress(35); + + const streamRegex = /hls: "([^"]*)/; + const streamData = streamRegex.exec(playerData); + + if (streamData === null || streamData[1] === null) { + throw new NotFoundError('No result found'); + } + ctx.progress(75); + + const subtitleRegex = /cc: (.*)/; + const subtitleJSONData = subtitleRegex.exec(playerData); + + let captions: Caption[] = []; + + if (subtitleJSONData != null && subtitleJSONData[1] != null) { + const subtitleData = JSON.parse(subtitleJSONData[1]); + captions = await getCaptions(subtitleData); + } + + ctx.progress(90); + + return { + embeds: [], + stream: [ + { + id: 'primary', + type: 'hls', + playlist: streamData[1], + flags: [flags.CORS_ALLOWED], + captions, + }, + ], + }; + }, +}); diff --git a/src/providers/sources/insertunit/types.ts b/src/providers/sources/insertunit/types.ts new file mode 100644 index 0000000..587ae36 --- /dev/null +++ b/src/providers/sources/insertunit/types.ts @@ -0,0 +1,30 @@ +export interface Subtitle { + url: string; + name: string; +} + +export interface Episode { + episode: string; + id: number; + videoKey: string; + hls: string; + audio: { + names: string[]; + order: number[]; + }; + cc: Subtitle[]; + duration: number; + title: string; + download: string; + sections: string[]; + poster: string; + preview: { + src: string; + }; +} + +export interface Season { + season: number; + blocked: boolean; + episodes: Episode[]; +}