Rename flags + rename targets + add disallowed section to feature mapping

This commit is contained in:
mrjvs
2023-12-24 19:18:46 +01:00
parent 0fe2fb40e1
commit a64a80cf12
20 changed files with 143 additions and 24 deletions

View File

@@ -15,40 +15,52 @@ export function makeProviderMocks() {
const sourceA = { const sourceA = {
id: 'a', id: 'a',
name: 'A',
rank: 1, rank: 1,
disabled: false, disabled: false,
flags: [],
} as Sourcerer; } as Sourcerer;
const sourceB = { const sourceB = {
id: 'b', id: 'b',
name: 'B',
rank: 2, rank: 2,
disabled: false, disabled: false,
flags: [],
} as Sourcerer; } as Sourcerer;
const sourceCDisabled = { const sourceCDisabled = {
id: 'c', id: 'c',
name: 'C',
rank: 3, rank: 3,
disabled: true, disabled: true,
flags: [],
} as Sourcerer; } as Sourcerer;
const sourceAHigherRank = { const sourceAHigherRank = {
id: 'a', id: 'a',
name: 'A',
rank: 100, rank: 100,
disabled: false, disabled: false,
flags: [],
} as Sourcerer; } as Sourcerer;
const sourceGSameRankAsA = { const sourceGSameRankAsA = {
id: 'g', id: 'g',
name: 'G',
rank: 1, rank: 1,
disabled: false, disabled: false,
flags: [],
} as Sourcerer; } as Sourcerer;
const fullSourceYMovie = { const fullSourceYMovie = {
id: 'y', id: 'y',
name: 'Y', name: 'Y',
rank: 105, rank: 105,
scrapeMovie: vi.fn(), scrapeMovie: vi.fn(),
flags: [],
} as Sourcerer; } as Sourcerer;
const fullSourceYShow = { const fullSourceYShow = {
id: 'y', id: 'y',
name: 'Y', name: 'Y',
rank: 105, rank: 105,
scrapeShow: vi.fn(), scrapeShow: vi.fn(),
flags: [],
} as Sourcerer; } as Sourcerer;
const fullSourceZBoth = { const fullSourceZBoth = {
id: 'z', id: 'z',
@@ -56,6 +68,7 @@ const fullSourceZBoth = {
rank: 106, rank: 106,
scrapeMovie: vi.fn(), scrapeMovie: vi.fn(),
scrapeShow: vi.fn(), scrapeShow: vi.fn(),
flags: [],
} as Sourcerer; } as Sourcerer;
const embedD = { const embedD = {

View File

@@ -1,12 +1,14 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests'; import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { FeatureMap } from '@/main/targets.ts';
import { getProviders } from '@/providers/get'; import { getProviders } from '@/providers/get';
import { vi, describe, it, expect, afterEach } from 'vitest'; import { vi, describe, it, expect, afterEach } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks()); const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks());
vi.mock('@/providers/all', () => mocks); vi.mock('@/providers/all', () => mocks);
const features = { const features: FeatureMap = {
requires: [], requires: [],
disallowed: []
} }
describe('getProviders()', () => { describe('getProviders()', () => {

View File

@@ -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);
});
});

View File

@@ -5,7 +5,7 @@ import { scrapeIndividualEmbed, scrapeInvidualSource } from '@/main/individualRu
import { ScrapeMedia } from '@/main/media'; import { ScrapeMedia } from '@/main/media';
import { MetaOutput, getAllEmbedMetaSorted, getAllSourceMetaSorted, getSpecificId } from '@/main/meta'; import { MetaOutput, getAllEmbedMetaSorted, getAllSourceMetaSorted, getSpecificId } from '@/main/meta';
import { RunOutput, runAllProviders } from '@/main/runner'; 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 { EmbedOutput, SourcererOutput } from '@/providers/base';
import { getProviders } from '@/providers/get'; import { getProviders } from '@/providers/get';
@@ -19,6 +19,10 @@ export interface ProviderBuilderOptions {
// target of where the streams will be used // target of where the streams will be used
target: Targets; 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 { export interface RunnerOptions {
@@ -82,6 +86,7 @@ export interface ProviderControls {
export function makeProviders(ops: ProviderBuilderOptions): ProviderControls { export function makeProviders(ops: ProviderBuilderOptions): ProviderControls {
const features = getTargetFeatures(ops.target); const features = getTargetFeatures(ops.target);
if (!ops.consistentIpForRequests) features.disallowed.push(flags.IP_LOCKED);
const list = getProviders(features); const list = getProviders(features);
const providerRunnerOps = { const providerRunnerOps = {
features, features,

View File

@@ -1,31 +1,51 @@
export const flags = { 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', IP_LOCKED: 'ip-locked',
} as const; } as const;
export type Flags = (typeof flags)[keyof typeof flags]; export type Flags = (typeof flags)[keyof typeof flags];
export const targets = { export const targets = {
// browser with CORS restrictions
BROWSER: 'browser', 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', NATIVE: 'native',
ALL: 'all',
// any target, no target restrictions
ANY: 'any',
} as const; } as const;
export type Targets = (typeof targets)[keyof typeof targets]; export type Targets = (typeof targets)[keyof typeof targets];
export type FeatureMap = { export type FeatureMap = {
requires: readonly Flags[]; requires: Flags[];
disallowed: Flags[];
}; };
export const targetToFeatures: Record<Targets, FeatureMap> = { export const targetToFeatures: Record<Targets, FeatureMap> = {
browser: { browser: {
requires: [flags.NO_CORS], requires: [flags.CORS_ALLOWED],
disallowed: [],
},
'browser-extension': {
requires: [],
disallowed: [],
}, },
native: { native: {
requires: [], requires: [],
disallowed: [],
}, },
all: { any: {
requires: [], requires: [],
disallowed: [],
}, },
} as const; } as const;
@@ -36,5 +56,7 @@ export function getTargetFeatures(target: Targets): FeatureMap {
export function flagsAllowedInFeatures(features: FeatureMap, inputFlags: Flags[]): boolean { export function flagsAllowedInFeatures(features: FeatureMap, inputFlags: Flags[]): boolean {
const hasAllFlags = features.requires.every((v) => inputFlags.includes(v)); const hasAllFlags = features.requires.every((v) => inputFlags.includes(v));
if (!hasAllFlags) return false; if (!hasAllFlags) return false;
const hasDisallowedFlag = features.disallowed.some((v) => inputFlags.includes(v));
if (hasDisallowedFlag) return false;
return true; return true;
} }

View File

@@ -38,7 +38,7 @@ export const febboxHlsScraper = makeEmbed({
return { return {
stream: { stream: {
type: 'hls', type: 'hls',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode), captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode),
playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`, playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`,
}, },

View File

@@ -43,7 +43,7 @@ export const febboxMp4Scraper = makeEmbed({
captions: await getSubtitles(ctx, id, fid, type, episode, season), captions: await getSubtitles(ctx, id, fid, type, episode, season),
qualities, qualities,
type: 'file', type: 'file',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
}, },
}; };
}, },

View File

@@ -17,7 +17,7 @@ export const mp4uploadScraper = makeEmbed({
return { return {
stream: { stream: {
type: 'file', type: 'file',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
captions: [], captions: [],
qualities: { qualities: {
'1080': { '1080': {

View File

@@ -60,7 +60,7 @@ export const smashyStreamDScraper = makeEmbed({
stream: { stream: {
playlist: playlistRes, playlist: playlistRes,
type: 'hls', type: 'hls',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
captions: [], captions: [],
}, },
}; };

View File

@@ -45,7 +45,7 @@ export const smashyStreamFScraper = makeEmbed({
stream: { stream: {
playlist: res.sourceUrls[0], playlist: res.sourceUrls[0],
type: 'hls', type: 'hls',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
captions, captions,
}, },
}; };

View File

@@ -157,7 +157,7 @@ export const streamsbScraper = makeEmbed({
return { return {
stream: { stream: {
type: 'file', type: 'file',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
qualities, qualities,
captions: [], captions: [],
}, },

View File

@@ -121,7 +121,7 @@ export const upcloudScraper = makeEmbed({
stream: { stream: {
type: 'hls', type: 'hls',
playlist: sources.file, playlist: sources.file,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
captions, captions,
}, },
}; };

View File

@@ -24,7 +24,7 @@ export const upstreamScraper = makeEmbed({
stream: { stream: {
type: 'hls', type: 'hls',
playlist: link[1], playlist: link[1],
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
captions: [], captions: [],
}, },
}; };

View File

@@ -9,7 +9,7 @@ export const flixhqScraper = makeSourcerer({
id: 'flixhq', id: 'flixhq',
name: 'FlixHQ', name: 'FlixHQ',
rank: 100, rank: 100,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
async scrapeMovie(ctx) { async scrapeMovie(ctx) {
const id = await getFlixhqId(ctx, ctx.media); const id = await getFlixhqId(ctx, ctx.media);
if (!id) throw new NotFoundError('no search results match'); if (!id) throw new NotFoundError('no search results match');

View File

@@ -13,7 +13,7 @@ export const goMoviesScraper = makeSourcerer({
id: 'gomovies', id: 'gomovies',
name: 'GOmovies', name: 'GOmovies',
rank: 110, rank: 110,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
async scrapeShow(ctx) { async scrapeShow(ctx) {
const search = await ctx.proxiedFetcher<string>(`/ajax/search`, { const search = await ctx.proxiedFetcher<string>(`/ajax/search`, {
method: 'POST', method: 'POST',

View File

@@ -13,7 +13,7 @@ export const kissAsianScraper = makeSourcerer({
id: 'kissasian', id: 'kissasian',
name: 'KissAsian', name: 'KissAsian',
rank: 130, rank: 130,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
disabled: true, disabled: true,
async scrapeShow(ctx) { async scrapeShow(ctx) {

View File

@@ -8,7 +8,7 @@ export const remotestreamScraper = makeSourcerer({
id: 'remotestream', id: 'remotestream',
name: 'Remote Stream', name: 'Remote Stream',
rank: 55, rank: 55,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
async scrapeShow(ctx) { async scrapeShow(ctx) {
const seasonNumber = ctx.media.season.number; const seasonNumber = ctx.media.season.number;
const episodeNumber = ctx.media.episode.number; const episodeNumber = ctx.media.episode.number;
@@ -26,7 +26,7 @@ export const remotestreamScraper = makeSourcerer({
captions: [], captions: [],
playlist: playlistLink, playlist: playlistLink,
type: 'hls', type: 'hls',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
}, },
}; };
}, },
@@ -44,7 +44,7 @@ export const remotestreamScraper = makeSourcerer({
captions: [], captions: [],
playlist: playlistLink, playlist: playlistLink,
type: 'hls', type: 'hls',
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
}, },
}; };
}, },

View File

@@ -47,7 +47,7 @@ export const showboxScraper = makeSourcerer({
id: 'showbox', id: 'showbox',
name: 'Showbox', name: 'Showbox',
rank: 300, rank: 300,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
scrapeShow: comboScraper, scrapeShow: comboScraper,
scrapeMovie: comboScraper, scrapeMovie: comboScraper,
}); });

View File

@@ -58,7 +58,7 @@ export const smashyStreamScraper = makeSourcerer({
id: 'smashystream', id: 'smashystream',
name: 'SmashyStream', name: 'SmashyStream',
rank: 70, rank: 70,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
scrapeMovie: universalScraper, scrapeMovie: universalScraper,
scrapeShow: universalScraper, scrapeShow: universalScraper,
}); });

View File

@@ -7,7 +7,7 @@ export const zoechipScraper = makeSourcerer({
id: 'zoechip', id: 'zoechip',
name: 'ZoeChip', name: 'ZoeChip',
rank: 200, rank: 200,
flags: [flags.NO_CORS], flags: [flags.CORS_ALLOWED],
scrapeMovie, scrapeMovie,
scrapeShow, scrapeShow,
}); });