mirror of
https://github.com/movie-web/providers.git
synced 2025-09-13 18:13:25 +00:00
Rename flags + rename targets + add disallowed section to feature mapping
This commit is contained in:
@@ -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 = {
|
||||||
|
@@ -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()', () => {
|
||||||
|
77
src/__test__/utils/features.test.ts
Normal file
77
src/__test__/utils/features.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
@@ -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,
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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`,
|
||||||
},
|
},
|
||||||
|
@@ -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],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@@ -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': {
|
||||||
|
@@ -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: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -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: [],
|
||||||
},
|
},
|
||||||
|
@@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -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: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -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');
|
||||||
|
@@ -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',
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@@ -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,
|
||||||
});
|
});
|
||||||
|
@@ -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,
|
||||||
});
|
});
|
||||||
|
@@ -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,
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user