diff --git a/src/providers/all.ts b/src/providers/all.ts index 22ce371..4001735 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -19,6 +19,17 @@ import { mp4hydraScraper } from '@/providers/sources/mp4hydra'; import { tugaflixScraper } from '@/providers/sources/tugaflix'; import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu'; +import { + oneServerAnimepaheEmbed, + oneServerAnizoneEmbed, + oneServerAutoembedEmbed, + oneServerFlixhqEmbed, + oneServerFoxstreamEmbed, + oneServerGokuEmbed, + oneServerHianimeEmbed, + oneServerPrimeboxEmbed, + oneServerVidsrcsuEmbed, +} from './embeds/1server'; import { autoembedBengaliScraper, autoembedEnglishScraper, @@ -66,6 +77,7 @@ import { xprimePrimenetEmbed, xprimeStreamboxEmbed, } from './embeds/xprime'; +import { oneServerScraper } from './sources/1server'; import { EightStreamScraper } from './sources/8stream'; import { coitusScraper } from './sources/coitus'; import { ConsumetScraper } from './sources/consumet'; @@ -115,6 +127,7 @@ export function gatherAllSources(): Array { xprimeScraper, ConsumetScraper, hianimeScraper, + oneServerScraper, ]; } @@ -173,5 +186,14 @@ export function gatherAllEmbeds(): Array { hianimeHd2DubEmbed, hianimeHd1SubEmbed, hianimeHd2SubEmbed, + oneServerAutoembedEmbed, + oneServerVidsrcsuEmbed, + oneServerPrimeboxEmbed, + oneServerFoxstreamEmbed, + oneServerFlixhqEmbed, + oneServerGokuEmbed, + oneServerHianimeEmbed, + oneServerAnimepaheEmbed, + oneServerAnizoneEmbed, ]; } diff --git a/src/providers/embeds/1server.ts b/src/providers/embeds/1server.ts new file mode 100644 index 0000000..3d600bc --- /dev/null +++ b/src/providers/embeds/1server.ts @@ -0,0 +1,295 @@ +/* eslint-disable no-console */ +import { flags } from '@/entrypoint/utils/targets'; +import { EmbedOutput, makeEmbed } from '@/providers/base'; +import { NotFoundError } from '@/utils/errors'; + +const baseUrl = 'https://flix.1anime.app'; + +const languageMap: Record = { + 'chinese - hong kong': 'zh', + 'chinese - traditional': 'zh', + czech: 'cs', + danish: 'da', + dutch: 'nl', + english: 'en', + 'english - sdh': 'en', + finnish: 'fi', + french: 'fr', + german: 'de', + greek: 'el', + hungarian: 'hu', + italian: 'it', + korean: 'ko', + norwegian: 'no', + polish: 'pl', + portuguese: 'pt', + 'portuguese - brazilian': 'pt', + romanian: 'ro', + 'spanish - european': 'es', + 'spanish - latin american': 'es', + swedish: 'sv', + turkish: 'tr', + اَلْعَرَبِيَّةُ: 'ar', + বাংলা: 'bn', + filipino: 'tl', + indonesia: 'id', + اردو: 'ur', +}; + +function createProxyUrl(originalUrl: string, referer?: string): string { + const encodedUrl = encodeURIComponent(originalUrl); + const encodedHeaders = encodeURIComponent( + JSON.stringify({ + referer, + }), + ); + + return `https://proxy.fifthwit.net/m3u8-proxy?url=${encodedUrl}&headers=${encodedHeaders}`; +} + +function processProxiedURL(url: string): string { + // Handle orbitproxy URLs + if (url.includes('orbitproxy')) { + try { + const urlParts = url.split(/orbitproxy\.[^/]+\//); + if (urlParts.length >= 2) { + const encryptedPart = urlParts[1].split('.m3u8')[0]; + try { + const decodedData = + typeof window !== 'undefined' + ? atob(encryptedPart) + : Buffer.from(encryptedPart, 'base64').toString('utf-8'); + + const jsonData = JSON.parse(decodedData); + const originalUrl = jsonData.u; + const referer = jsonData.r || ''; + + return createProxyUrl(originalUrl, referer); + } catch (jsonError) { + console.error('Error decoding/parsing orbitproxy data:', jsonError); + } + } + } catch (error) { + console.error('Error processing orbitproxy URL:', error); + } + } + + // Handle other proxied URLs + if (url.includes('/m3u8-proxy?url=')) { + return url.replace(/https:\/\/[^/]+\/m3u8-proxy/, 'https://proxy.fifthwit.net/m3u8-proxy'); + } + + return url; +} + +function isOnionflixerUrl(url: string): boolean { + return url.includes('onionflixer'); +} + +function processSubtitles(subtitles: any[] | undefined): any[] { + const captions = []; + + if (subtitles && Array.isArray(subtitles)) { + for (const sub of subtitles) { + const url = sub.url || sub.file; + const lang = sub.lang || sub.label || 'unknown'; + + if (url) { + captions.push({ + type: sub.type || 'vtt', + url, + language: languageMap[lang.toLowerCase()] || lang.toLowerCase() || 'unknown', + }); + } + } + } + + return captions; +} + +function processApiResponse(response: any, ctx: any): EmbedOutput { + if (!response) { + throw new NotFoundError('No response received'); + } + + if (response.error) { + throw new NotFoundError(`${response.error}${response.hint ? ` - ${response.hint}` : ''}`); + } + + // Handle array responses (multiple provider options) + if (Array.isArray(response)) { + for (const item of response) { + // Handle format with headers and sources array + if (item.headers && item.sources && Array.isArray(item.sources)) { + const bestSource = item.sources.find((s: any) => s.isM3U8); + if (bestSource && bestSource.url) { + const playlistUrl = processProxiedURL(bestSource.url); + const captions = processSubtitles(item.subtitles); + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: playlistUrl, + flags: [flags.CORS_ALLOWED], + captions, + }, + ], + }; + } + } + + // Handle format with source.provider and source.files + if (item.source && item.source.files && Array.isArray(item.source.files)) { + const bestFile = item.source.files.find((f: any) => f.type === 'hls' || f.file.includes('.m3u8')); + if (bestFile && bestFile.file) { + const playlistUrl = processProxiedURL(bestFile.file); + const captions = processSubtitles(item.source.subtitles); + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: playlistUrl, + flags: [flags.CORS_ALLOWED], + captions, + }, + ], + }; + } + } + } + } + + // Handle original format with sources object + const sourcesObj = response.sources; + if (sourcesObj) { + let bestSource = null; + + // Look through all providers in the sources object + for (const provider in sourcesObj) { + if (Object.prototype.hasOwnProperty.call(sourcesObj, provider)) { + const providerSources = sourcesObj[provider]; + if (providerSources && providerSources.length > 0) { + // Find the highest quality non-onionflixer source + for (const source of providerSources) { + if (source.url && source.isM3U8 && !isOnionflixerUrl(source.url)) { + bestSource = source; + break; + } + } + // If we found a good source, no need to check other providers + if (bestSource) break; + } + } + } + + // If no non-onionflixer source was found, use the first available one + if (!bestSource) { + for (const provider in sourcesObj) { + if (Object.prototype.hasOwnProperty.call(sourcesObj, provider)) { + const providerSources = sourcesObj[provider]; + if (providerSources && providerSources.length > 0) { + bestSource = providerSources[0]; + break; + } + } + } + } + + if (bestSource && bestSource.url) { + const playlistUrl = processProxiedURL(bestSource.url); + const captions = processSubtitles(response.subtitles); + + ctx.progress(100); + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: createProxyUrl(playlistUrl, ctx.referer), + flags: [flags.CORS_ALLOWED], + captions, + }, + ], + }; + } + } + + throw new NotFoundError('No valid stream URL found in response'); +} + +/* MOVIE & TV PROVIDERS */ +const movieTvProviders = [ + { id: 'autoembed', name: 'Autoembed', rank: 165 }, + { id: 'vidsrcsu', name: 'vidsrc.su', rank: 164 }, + { id: 'primebox', name: 'Primebox', rank: 162, disabled: true }, + { id: 'foxstream', name: 'Foxstream', rank: 161, disabled: true }, + { id: 'flixhq', name: 'FlixHQ', rank: 166 }, + { id: 'goku', name: 'Goku', rank: 163, disabled: true }, +]; + +const createMovieTvEmbed = (provider: { id: string; name: string; rank: number; disabled?: boolean }) => + makeEmbed({ + id: `oneserver-${provider.id}`, + name: provider.name, + rank: provider.rank, + disabled: provider.disabled, + async scrape(ctx) { + const query = JSON.parse(ctx.url); + const apiUrl = + query.type === 'movie' + ? `${baseUrl}/movie/${provider.id}/${query.tmdbId}` + : `${baseUrl}/tv/${provider.id}/${query.tmdbId}/${query.season}/${query.episode}`; + + try { + const response = await ctx.fetcher(apiUrl); + ctx.progress(50); + return processApiResponse(response, ctx); + } catch (error) { + if (error instanceof NotFoundError) throw error; + throw new NotFoundError(`Failed to fetch from ${provider.id}: ${error}`); + } + }, + }); + +export const [ + oneServerAutoembedEmbed, + oneServerVidsrcsuEmbed, + oneServerPrimeboxEmbed, + oneServerFoxstreamEmbed, + oneServerFlixhqEmbed, + oneServerGokuEmbed, +] = movieTvProviders.map(createMovieTvEmbed); + +/* ANIME PROVIDERS */ +const animeProviders = [ + { id: 'hianime', name: 'Hianime', rank: 269 }, + { id: 'animepahe', name: 'Animepahe', rank: 268 }, + { id: 'anizone', name: 'Anizone', rank: 267 }, +]; + +const createAnimeEmbed = (provider: { id: string; name: string; rank: number }) => + makeEmbed({ + id: `oneserver-${provider.id}`, + name: provider.name, + rank: provider.rank, + async scrape(ctx) { + const query = JSON.parse(ctx.url); + const apiUrl = `${baseUrl}/anime/${provider.id}/${query.anilistId}${query.episode ? `/${query.episode}` : ''}`; + + try { + const response = await ctx.fetcher(apiUrl); + return processApiResponse(response, ctx); + } catch (error) { + if (error instanceof NotFoundError) throw error; + throw new NotFoundError(`Failed to fetch from ${provider.id}: ${error}`); + } + }, + }); + +export const [oneServerHianimeEmbed, oneServerAnimepaheEmbed, oneServerAnizoneEmbed] = + animeProviders.map(createAnimeEmbed); diff --git a/src/providers/sources/1server.ts b/src/providers/sources/1server.ts new file mode 100644 index 0000000..3fe2b90 --- /dev/null +++ b/src/providers/sources/1server.ts @@ -0,0 +1,66 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererOutput, makeSourcerer } from '@/providers/base'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; + +async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { + const query = { + type: ctx.media.type, + title: ctx.media.title, + tmdbId: ctx.media.tmdbId.toString(), + ...(ctx.media.type === 'show' && { + season: ctx.media.season.number, + episode: ctx.media.episode.number, + }), + }; + + const embeds = [ + { + embedId: 'oneserver-autoembed', + url: JSON.stringify(query), + }, + { + embedId: 'oneserver-vidsrcsu', + url: JSON.stringify(query), + }, + { + embedId: 'oneserver-primebox', + url: JSON.stringify(query), + }, + { + embedId: 'oneserver-foxstream', + url: JSON.stringify(query), + }, + { + embedId: 'oneserver-flixhq', + url: JSON.stringify(query), + }, + { + embedId: 'oneserver-goku', + url: JSON.stringify(query), + }, + // { + // embedId: 'oneserver-hianime', + // url: JSON.stringify(query), + // }, + // { + // embedId: 'oneserver-animepahe', + // url: JSON.stringify(query), + // }, + // { + // embedId: 'oneserver-anizone', + // url: JSON.stringify(query), + // }, + ]; + + return { embeds }; +} + +export const oneServerScraper = makeSourcerer({ + id: '1server', + name: '1Server', + rank: 165, + disabled: false, + flags: [flags.CORS_ALLOWED], + scrapeMovie: comboScraper, + scrapeShow: comboScraper, +}); diff --git a/src/utils/valid.ts b/src/utils/valid.ts index 9b67854..f37c1e6 100644 --- a/src/utils/valid.ts +++ b/src/utils/valid.ts @@ -1,5 +1,16 @@ // import { alphaScraper, deltaScraper } from '@/providers/embeds/nsbx'; // import { astraScraper, novaScraper, orionScraper } from '@/providers/embeds/whvx'; +import { + oneServerAnimepaheEmbed, + oneServerAnizoneEmbed, + oneServerAutoembedEmbed, + oneServerFlixhqEmbed, + oneServerFoxstreamEmbed, + oneServerGokuEmbed, + oneServerHianimeEmbed, + oneServerPrimeboxEmbed, + oneServerVidsrcsuEmbed, +} from '@/providers/embeds/1server'; import { ConsumetStreamSBScraper, ConsumetStreamTapeScraper, @@ -51,6 +62,15 @@ const SKIP_VALIDATION_CHECK_IDS = [ hianimeHd1SubEmbed.id, hianimeHd2DubEmbed.id, hianimeHd2SubEmbed.id, + oneServerAutoembedEmbed.id, + oneServerVidsrcsuEmbed.id, + oneServerPrimeboxEmbed.id, + oneServerFoxstreamEmbed.id, + oneServerFlixhqEmbed.id, + oneServerGokuEmbed.id, + oneServerHianimeEmbed.id, + oneServerAnimepaheEmbed.id, + oneServerAnizoneEmbed.id, ]; export function isValidStream(stream: Stream | undefined): boolean {