diff --git a/src/__test__/providerTests.ts b/src/__test__/providerTests.ts index a8c0fa8..551a3ec 100644 --- a/src/__test__/providerTests.ts +++ b/src/__test__/providerTests.ts @@ -15,40 +15,52 @@ export function makeProviderMocks() { const sourceA = { id: 'a', + name: 'A', rank: 1, disabled: false, + flags: [], } as Sourcerer; const sourceB = { id: 'b', + name: 'B', rank: 2, disabled: false, + flags: [], } as Sourcerer; const sourceCDisabled = { id: 'c', + name: 'C', rank: 3, disabled: true, + flags: [], } as Sourcerer; const sourceAHigherRank = { id: 'a', + name: 'A', rank: 100, disabled: false, + flags: [], } as Sourcerer; const sourceGSameRankAsA = { id: 'g', + name: 'G', rank: 1, disabled: false, + flags: [], } as Sourcerer; const fullSourceYMovie = { id: 'y', name: 'Y', rank: 105, scrapeMovie: vi.fn(), + flags: [], } as Sourcerer; const fullSourceYShow = { id: 'y', name: 'Y', rank: 105, scrapeShow: vi.fn(), + flags: [], } as Sourcerer; const fullSourceZBoth = { id: 'z', @@ -56,6 +68,7 @@ const fullSourceZBoth = { rank: 106, scrapeMovie: vi.fn(), scrapeShow: vi.fn(), + flags: [], } as Sourcerer; const embedD = { diff --git a/src/__test__/providers/checks.test.ts b/src/__test__/providers/checks.test.ts index dca4e73..7ffa845 100644 --- a/src/__test__/providers/checks.test.ts +++ b/src/__test__/providers/checks.test.ts @@ -1,12 +1,14 @@ import { mockEmbeds, mockSources } from '@/__test__/providerTests'; +import { FeatureMap } from '@/main/targets.ts'; import { getProviders } from '@/providers/get'; import { vi, describe, it, expect, afterEach } from 'vitest'; const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks()); vi.mock('@/providers/all', () => mocks); -const features = { +const features: FeatureMap = { requires: [], + disallowed: [] } describe('getProviders()', () => { diff --git a/src/__test__/utils/features.test.ts b/src/__test__/utils/features.test.ts new file mode 100644 index 0000000..0cb6a4e --- /dev/null +++ b/src/__test__/utils/features.test.ts @@ -0,0 +1,77 @@ +import { FeatureMap, Flags, flags, flagsAllowedInFeatures } from "@/main/targets"; +import { describe, it, expect } from "vitest"; + +describe('flagsAllowedInFeatures()', () => { + function checkFeatures(featureMap: FeatureMap, flags: Flags[], output: boolean) { + expect(flagsAllowedInFeatures(featureMap, flags)).toEqual(output); + } + + it('should check required correctly', () => { + checkFeatures({ + requires: [], + disallowed: [] + }, [], true); + checkFeatures({ + requires: [flags.CORS_ALLOWED], + disallowed: [] + }, [flags.CORS_ALLOWED], true); + checkFeatures({ + requires: [flags.CORS_ALLOWED], + disallowed: [] + }, [], false); + checkFeatures({ + requires: [flags.CORS_ALLOWED, flags.IP_LOCKED], + disallowed: [] + }, [flags.CORS_ALLOWED, flags.IP_LOCKED], true); + checkFeatures({ + requires: [flags.IP_LOCKED], + disallowed: [] + }, [flags.CORS_ALLOWED], false); + checkFeatures({ + requires: [flags.IP_LOCKED], + disallowed: [] + }, [], false); + }); + + it('should check disallowed correctly', () => { + checkFeatures({ + requires: [], + disallowed: [] + }, [], true); + checkFeatures({ + requires: [], + disallowed: [flags.CORS_ALLOWED] + }, [], true); + checkFeatures({ + requires: [], + disallowed: [flags.CORS_ALLOWED] + }, [flags.CORS_ALLOWED], false); + checkFeatures({ + requires: [], + disallowed: [flags.CORS_ALLOWED] + }, [flags.IP_LOCKED], true); + checkFeatures({ + requires: [], + disallowed: [flags.CORS_ALLOWED, flags.IP_LOCKED] + }, [flags.CORS_ALLOWED], false); + }); + + it('should pass mixed tests', () => { + checkFeatures({ + requires: [flags.CORS_ALLOWED], + disallowed: [flags.IP_LOCKED] + }, [], false); + checkFeatures({ + requires: [flags.CORS_ALLOWED], + disallowed: [flags.IP_LOCKED] + }, [flags.CORS_ALLOWED], true); + checkFeatures({ + requires: [flags.CORS_ALLOWED], + disallowed: [flags.IP_LOCKED] + }, [flags.IP_LOCKED], false); + checkFeatures({ + requires: [flags.CORS_ALLOWED], + disallowed: [flags.IP_LOCKED] + }, [flags.IP_LOCKED, flags.CORS_ALLOWED], false); + }); +}); diff --git a/src/main/builder.ts b/src/main/builder.ts index 0322dbd..2ad6911 100644 --- a/src/main/builder.ts +++ b/src/main/builder.ts @@ -5,7 +5,7 @@ import { scrapeIndividualEmbed, scrapeInvidualSource } from '@/main/individualRu import { ScrapeMedia } from '@/main/media'; import { MetaOutput, getAllEmbedMetaSorted, getAllSourceMetaSorted, getSpecificId } from '@/main/meta'; import { RunOutput, runAllProviders } from '@/main/runner'; -import { Targets, getTargetFeatures } from '@/main/targets'; +import { Targets, flags, getTargetFeatures } from '@/main/targets'; import { EmbedOutput, SourcererOutput } from '@/providers/base'; import { getProviders } from '@/providers/get'; @@ -19,6 +19,10 @@ export interface ProviderBuilderOptions { // target of where the streams will be used target: Targets; + + // Set this to true, if the requests will have the same IP as + // the device that the stream will be played on + consistentIpForRequests?: boolean; } export interface RunnerOptions { @@ -82,6 +86,7 @@ export interface ProviderControls { export function makeProviders(ops: ProviderBuilderOptions): ProviderControls { const features = getTargetFeatures(ops.target); + if (!ops.consistentIpForRequests) features.disallowed.push(flags.IP_LOCKED); const list = getProviders(features); const providerRunnerOps = { features, diff --git a/src/main/targets.ts b/src/main/targets.ts index eb791c2..66d6040 100644 --- a/src/main/targets.ts +++ b/src/main/targets.ts @@ -1,31 +1,51 @@ export const flags = { - NO_CORS: 'no-cors', + // CORS are set to allow any origin + CORS_ALLOWED: 'cors-allowed', + + // the stream is locked on IP, so only works if + // request maker is same as player (not compatible with proxies) IP_LOCKED: 'ip-locked', } as const; export type Flags = (typeof flags)[keyof typeof flags]; export const targets = { + // browser with CORS restrictions BROWSER: 'browser', + + // browser, but no CORS restrictions through a browser extension + BROWSER_EXTENSION: 'browser-extension', + + // native app, so no restrictions in what can be played NATIVE: 'native', - ALL: 'all', + + // any target, no target restrictions + ANY: 'any', } as const; export type Targets = (typeof targets)[keyof typeof targets]; export type FeatureMap = { - requires: readonly Flags[]; + requires: Flags[]; + disallowed: Flags[]; }; export const targetToFeatures: Record = { browser: { - requires: [flags.NO_CORS], + requires: [flags.CORS_ALLOWED], + disallowed: [], + }, + 'browser-extension': { + requires: [], + disallowed: [], }, native: { requires: [], + disallowed: [], }, - all: { + any: { requires: [], + disallowed: [], }, } as const; @@ -36,5 +56,7 @@ export function getTargetFeatures(target: Targets): FeatureMap { export function flagsAllowedInFeatures(features: FeatureMap, inputFlags: Flags[]): boolean { const hasAllFlags = features.requires.every((v) => inputFlags.includes(v)); if (!hasAllFlags) return false; + const hasDisallowedFlag = features.disallowed.some((v) => inputFlags.includes(v)); + if (hasDisallowedFlag) return false; return true; } diff --git a/src/providers/embeds/febbox/hls.ts b/src/providers/embeds/febbox/hls.ts index 58478ca..e0d48e0 100644 --- a/src/providers/embeds/febbox/hls.ts +++ b/src/providers/embeds/febbox/hls.ts @@ -38,7 +38,7 @@ export const febboxHlsScraper = makeEmbed({ return { stream: { type: 'hls', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode), playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`, }, diff --git a/src/providers/embeds/febbox/mp4.ts b/src/providers/embeds/febbox/mp4.ts index 086f191..520a35e 100644 --- a/src/providers/embeds/febbox/mp4.ts +++ b/src/providers/embeds/febbox/mp4.ts @@ -43,7 +43,7 @@ export const febboxMp4Scraper = makeEmbed({ captions: await getSubtitles(ctx, id, fid, type, episode, season), qualities, type: 'file', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], }, }; }, diff --git a/src/providers/embeds/mp4upload.ts b/src/providers/embeds/mp4upload.ts index 81c2f94..72aef8a 100644 --- a/src/providers/embeds/mp4upload.ts +++ b/src/providers/embeds/mp4upload.ts @@ -17,7 +17,7 @@ export const mp4uploadScraper = makeEmbed({ return { stream: { type: 'file', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], captions: [], qualities: { '1080': { diff --git a/src/providers/embeds/smashystream/dued.ts b/src/providers/embeds/smashystream/dued.ts index 89c0814..df45af2 100644 --- a/src/providers/embeds/smashystream/dued.ts +++ b/src/providers/embeds/smashystream/dued.ts @@ -60,7 +60,7 @@ export const smashyStreamDScraper = makeEmbed({ stream: { playlist: playlistRes, type: 'hls', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], captions: [], }, }; diff --git a/src/providers/embeds/smashystream/video1.ts b/src/providers/embeds/smashystream/video1.ts index 3dde571..61172fc 100644 --- a/src/providers/embeds/smashystream/video1.ts +++ b/src/providers/embeds/smashystream/video1.ts @@ -45,7 +45,7 @@ export const smashyStreamFScraper = makeEmbed({ stream: { playlist: res.sourceUrls[0], type: 'hls', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], captions, }, }; diff --git a/src/providers/embeds/streamsb.ts b/src/providers/embeds/streamsb.ts index d15b320..3b0cb7d 100644 --- a/src/providers/embeds/streamsb.ts +++ b/src/providers/embeds/streamsb.ts @@ -157,7 +157,7 @@ export const streamsbScraper = makeEmbed({ return { stream: { type: 'file', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], qualities, captions: [], }, diff --git a/src/providers/embeds/upcloud.ts b/src/providers/embeds/upcloud.ts index d31cf72..514d4f1 100644 --- a/src/providers/embeds/upcloud.ts +++ b/src/providers/embeds/upcloud.ts @@ -121,7 +121,7 @@ export const upcloudScraper = makeEmbed({ stream: { type: 'hls', playlist: sources.file, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], captions, }, }; diff --git a/src/providers/embeds/upstream.ts b/src/providers/embeds/upstream.ts index 852aac7..2b62ee9 100644 --- a/src/providers/embeds/upstream.ts +++ b/src/providers/embeds/upstream.ts @@ -24,7 +24,7 @@ export const upstreamScraper = makeEmbed({ stream: { type: 'hls', playlist: link[1], - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], captions: [], }, }; diff --git a/src/providers/sources/flixhq/index.ts b/src/providers/sources/flixhq/index.ts index 90fe251..b866677 100644 --- a/src/providers/sources/flixhq/index.ts +++ b/src/providers/sources/flixhq/index.ts @@ -9,7 +9,7 @@ export const flixhqScraper = makeSourcerer({ id: 'flixhq', name: 'FlixHQ', rank: 100, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], async scrapeMovie(ctx) { const id = await getFlixhqId(ctx, ctx.media); if (!id) throw new NotFoundError('no search results match'); diff --git a/src/providers/sources/gomovies/index.ts b/src/providers/sources/gomovies/index.ts index ce4a428..ff43411 100644 --- a/src/providers/sources/gomovies/index.ts +++ b/src/providers/sources/gomovies/index.ts @@ -13,7 +13,7 @@ export const goMoviesScraper = makeSourcerer({ id: 'gomovies', name: 'GOmovies', rank: 110, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], async scrapeShow(ctx) { const search = await ctx.proxiedFetcher(`/ajax/search`, { method: 'POST', diff --git a/src/providers/sources/kissasian/index.ts b/src/providers/sources/kissasian/index.ts index 67111ca..d856ea5 100644 --- a/src/providers/sources/kissasian/index.ts +++ b/src/providers/sources/kissasian/index.ts @@ -13,7 +13,7 @@ export const kissAsianScraper = makeSourcerer({ id: 'kissasian', name: 'KissAsian', rank: 130, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], disabled: true, async scrapeShow(ctx) { diff --git a/src/providers/sources/remotestream.ts b/src/providers/sources/remotestream.ts index 696d897..f3ea478 100644 --- a/src/providers/sources/remotestream.ts +++ b/src/providers/sources/remotestream.ts @@ -8,7 +8,7 @@ export const remotestreamScraper = makeSourcerer({ id: 'remotestream', name: 'Remote Stream', rank: 55, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], async scrapeShow(ctx) { const seasonNumber = ctx.media.season.number; const episodeNumber = ctx.media.episode.number; @@ -26,7 +26,7 @@ export const remotestreamScraper = makeSourcerer({ captions: [], playlist: playlistLink, type: 'hls', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], }, }; }, @@ -44,7 +44,7 @@ export const remotestreamScraper = makeSourcerer({ captions: [], playlist: playlistLink, type: 'hls', - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], }, }; }, diff --git a/src/providers/sources/showbox/index.ts b/src/providers/sources/showbox/index.ts index 94164ee..064011e 100644 --- a/src/providers/sources/showbox/index.ts +++ b/src/providers/sources/showbox/index.ts @@ -47,7 +47,7 @@ export const showboxScraper = makeSourcerer({ id: 'showbox', name: 'Showbox', rank: 300, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], scrapeShow: comboScraper, scrapeMovie: comboScraper, }); diff --git a/src/providers/sources/smashystream/index.ts b/src/providers/sources/smashystream/index.ts index c5a8b27..c7601f8 100644 --- a/src/providers/sources/smashystream/index.ts +++ b/src/providers/sources/smashystream/index.ts @@ -58,7 +58,7 @@ export const smashyStreamScraper = makeSourcerer({ id: 'smashystream', name: 'SmashyStream', rank: 70, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], scrapeMovie: universalScraper, scrapeShow: universalScraper, }); diff --git a/src/providers/sources/zoechip/index.ts b/src/providers/sources/zoechip/index.ts index ab4d00f..6b7aae2 100644 --- a/src/providers/sources/zoechip/index.ts +++ b/src/providers/sources/zoechip/index.ts @@ -7,7 +7,7 @@ export const zoechipScraper = makeSourcerer({ id: 'zoechip', name: 'ZoeChip', rank: 200, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], scrapeMovie, scrapeShow, });