mirror of
https://github.com/movie-web/providers.git
synced 2025-09-13 13:03:25 +00:00
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -1,3 +1 @@
|
|||||||
* @movie-web/core
|
* @movie-web/project-leads
|
||||||
|
|
||||||
.github @binaryoverload
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@movie-web/providers",
|
"name": "@movie-web/providers",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"description": "Package that contains all the providers of movie-web",
|
"description": "Package that contains all the providers of movie-web",
|
||||||
"main": "./lib/index.umd.js",
|
"main": "./lib/index.umd.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
@@ -81,6 +81,7 @@ export async function processOptions(sources: Array<Embed | Sourcerer>, options:
|
|||||||
const providerOptions: ProviderMakerOptions = {
|
const providerOptions: ProviderMakerOptions = {
|
||||||
fetcher,
|
fetcher,
|
||||||
target: targets.ANY,
|
target: targets.ANY,
|
||||||
|
consistentIpForRequests: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -2,9 +2,9 @@ import { gatherAllEmbeds, gatherAllSources } from '@/providers/all';
|
|||||||
import { Embed, Sourcerer } from '@/providers/base';
|
import { Embed, Sourcerer } from '@/providers/base';
|
||||||
|
|
||||||
export function getBuiltinSources(): Sourcerer[] {
|
export function getBuiltinSources(): Sourcerer[] {
|
||||||
return gatherAllSources();
|
return gatherAllSources().filter((v) => !v.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBuiltinEmbeds(): Embed[] {
|
export function getBuiltinEmbeds(): Embed[] {
|
||||||
return gatherAllEmbeds();
|
return gatherAllEmbeds().filter((v) => !v.disabled);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { MediaTypes } from '@/entrypoint/utils/media';
|
import { MediaTypes } from '@/entrypoint/utils/media';
|
||||||
import { flags } from '@/entrypoint/utils/targets';
|
|
||||||
import { makeEmbed } from '@/providers/base';
|
import { makeEmbed } from '@/providers/base';
|
||||||
import { parseInputUrl } from '@/providers/embeds/febbox/common';
|
import { parseInputUrl } from '@/providers/embeds/febbox/common';
|
||||||
import { getStreams } from '@/providers/embeds/febbox/fileList';
|
import { getStreams } from '@/providers/embeds/febbox/fileList';
|
||||||
@@ -16,6 +15,7 @@ export const febboxHlsScraper = makeEmbed({
|
|||||||
id: 'febbox-hls',
|
id: 'febbox-hls',
|
||||||
name: 'Febbox (HLS)',
|
name: 'Febbox (HLS)',
|
||||||
rank: 160,
|
rank: 160,
|
||||||
|
disabled: true,
|
||||||
async scrape(ctx) {
|
async scrape(ctx) {
|
||||||
const { type, id, season, episode } = parseInputUrl(ctx.url);
|
const { type, id, season, episode } = parseInputUrl(ctx.url);
|
||||||
const sharelinkResult = await ctx.proxiedFetcher<{
|
const sharelinkResult = await ctx.proxiedFetcher<{
|
||||||
@@ -40,7 +40,7 @@ export const febboxHlsScraper = makeEmbed({
|
|||||||
{
|
{
|
||||||
id: 'primary',
|
id: 'primary',
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
flags: [flags.CORS_ALLOWED],
|
flags: [],
|
||||||
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`,
|
||||||
},
|
},
|
||||||
|
@@ -37,22 +37,28 @@ export async function getSubtitles(
|
|||||||
const subResult = (await sendRequest(ctx, subtitleApiQuery)) as CaptionApiResponse;
|
const subResult = (await sendRequest(ctx, subtitleApiQuery)) as CaptionApiResponse;
|
||||||
const subtitleList = subResult.data.list;
|
const subtitleList = subResult.data.list;
|
||||||
const output: Caption[] = [];
|
const output: Caption[] = [];
|
||||||
|
const languagesAdded: Record<string, true> = {};
|
||||||
|
|
||||||
subtitleList.forEach((sub) => {
|
subtitleList.forEach((sub) => {
|
||||||
const subtitle = sub.subtitles.sort((a, b) => b.order - a.order)[0];
|
const subtitle = sub.subtitles.sort((a, b) => b.order - a.order)[0];
|
||||||
if (!subtitle) return;
|
if (!subtitle) return;
|
||||||
|
|
||||||
const subtitleFilePath = subtitle.file_path
|
const subtitleFilePath = subtitle.file_path
|
||||||
.replace(captionsDomains[0], captionsDomains[1])
|
.replace(captionsDomains[0], captionsDomains[1])
|
||||||
.replace(/\s/g, '+')
|
.replace(/\s/g, '+')
|
||||||
.replace(/[()]/g, (c) => {
|
.replace(/[()]/g, (c) => {
|
||||||
return `%${c.charCodeAt(0).toString(16)}`;
|
return `%${c.charCodeAt(0).toString(16)}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const subtitleType = getCaptionTypeFromUrl(subtitleFilePath);
|
const subtitleType = getCaptionTypeFromUrl(subtitleFilePath);
|
||||||
if (!subtitleType) return;
|
if (!subtitleType) return;
|
||||||
|
|
||||||
const validCode = isValidLanguageCode(subtitle.lang);
|
const validCode = isValidLanguageCode(subtitle.lang);
|
||||||
if (!validCode) return;
|
if (!validCode) return;
|
||||||
|
|
||||||
|
if (languagesAdded[subtitle.lang]) return;
|
||||||
|
languagesAdded[subtitle.lang] = true;
|
||||||
|
|
||||||
output.push({
|
output.push({
|
||||||
id: subtitleFilePath,
|
id: subtitleFilePath,
|
||||||
language: subtitle.lang,
|
language: subtitle.lang,
|
||||||
|
@@ -10,8 +10,8 @@ async function universalScraper(ctx: MovieScrapeContext | ShowScrapeContext): Pr
|
|||||||
if (!lookmovieData) throw new NotFoundError('Media not found');
|
if (!lookmovieData) throw new NotFoundError('Media not found');
|
||||||
|
|
||||||
ctx.progress(30);
|
ctx.progress(30);
|
||||||
const videoUrl = await scrape(ctx, ctx.media, lookmovieData);
|
const video = await scrape(ctx, ctx.media, lookmovieData);
|
||||||
if (!videoUrl) throw new NotFoundError('No video found');
|
if (!video.playlist) throw new NotFoundError('No video found');
|
||||||
|
|
||||||
ctx.progress(60);
|
ctx.progress(60);
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ async function universalScraper(ctx: MovieScrapeContext | ShowScrapeContext): Pr
|
|||||||
stream: [
|
stream: [
|
||||||
{
|
{
|
||||||
id: 'primary',
|
id: 'primary',
|
||||||
playlist: videoUrl,
|
playlist: video.playlist,
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
flags: [flags.IP_LOCKED],
|
flags: [flags.IP_LOCKED],
|
||||||
captions: [],
|
captions: video.captions,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -33,6 +33,7 @@ export const lookmovieScraper = makeSourcerer({
|
|||||||
id: 'lookmovie',
|
id: 'lookmovie',
|
||||||
name: 'LookMovie',
|
name: 'LookMovie',
|
||||||
rank: 1,
|
rank: 1,
|
||||||
|
disabled: true,
|
||||||
flags: [flags.IP_LOCKED],
|
flags: [flags.IP_LOCKED],
|
||||||
scrapeShow: universalScraper,
|
scrapeShow: universalScraper,
|
||||||
scrapeMovie: universalScraper,
|
scrapeMovie: universalScraper,
|
||||||
|
@@ -39,8 +39,17 @@ interface VideoSources {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface VideoSubtitles {
|
||||||
|
id?: number;
|
||||||
|
id_movie?: number;
|
||||||
|
url: string;
|
||||||
|
language: string;
|
||||||
|
shard?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StreamsDataResult {
|
export interface StreamsDataResult {
|
||||||
streams: VideoSources;
|
streams: VideoSources;
|
||||||
|
subtitles: VideoSubtitles[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResultItem {
|
export interface ResultItem {
|
||||||
|
@@ -4,7 +4,9 @@ import { ScrapeContext } from '@/utils/context';
|
|||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
import { Result, ResultItem, ShowDataResult, episodeObj } from './type';
|
import { Result, ResultItem, ShowDataResult, episodeObj } from './type';
|
||||||
import { getVideoUrl } from './video';
|
import { getVideo } from './video';
|
||||||
|
|
||||||
|
export const baseUrl = 'https://lmscript.xyz';
|
||||||
|
|
||||||
export async function searchAndFindMedia(
|
export async function searchAndFindMedia(
|
||||||
ctx: ScrapeContext,
|
ctx: ScrapeContext,
|
||||||
@@ -12,7 +14,7 @@ export async function searchAndFindMedia(
|
|||||||
): Promise<ResultItem | undefined> {
|
): Promise<ResultItem | undefined> {
|
||||||
if (media.type === 'show') {
|
if (media.type === 'show') {
|
||||||
const searchRes = await ctx.fetcher<Result>(`/v1/shows`, {
|
const searchRes = await ctx.fetcher<Result>(`/v1/shows`, {
|
||||||
baseUrl: 'https://lmscript.xyz',
|
baseUrl,
|
||||||
query: { 'filters[q]': media.title },
|
query: { 'filters[q]': media.title },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ export async function searchAndFindMedia(
|
|||||||
}
|
}
|
||||||
if (media.type === 'movie') {
|
if (media.type === 'movie') {
|
||||||
const searchRes = await ctx.fetcher<Result>(`/v1/movies`, {
|
const searchRes = await ctx.fetcher<Result>(`/v1/movies`, {
|
||||||
baseUrl: 'https://lmscript.xyz',
|
baseUrl,
|
||||||
query: { 'filters[q]': media.title },
|
query: { 'filters[q]': media.title },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ export async function scrape(ctx: ScrapeContext, media: MovieMedia | ShowMedia,
|
|||||||
id = result.id_movie;
|
id = result.id_movie;
|
||||||
} else if (media.type === 'show') {
|
} else if (media.type === 'show') {
|
||||||
const data = await ctx.fetcher<ShowDataResult>(`/v1/shows`, {
|
const data = await ctx.fetcher<ShowDataResult>(`/v1/shows`, {
|
||||||
baseUrl: 'https://lmscript.xyz',
|
baseUrl,
|
||||||
query: { expand: 'episodes', id: result.id_show },
|
query: { expand: 'episodes', id: result.id_show },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,6 +56,6 @@ export async function scrape(ctx: ScrapeContext, media: MovieMedia | ShowMedia,
|
|||||||
// Check ID
|
// Check ID
|
||||||
if (id === null) throw new NotFoundError('Not found');
|
if (id === null) throw new NotFoundError('Not found');
|
||||||
|
|
||||||
const videoUrl = await getVideoUrl(ctx, id, media);
|
const video = await getVideo(ctx, id, media);
|
||||||
return videoUrl;
|
return video;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media';
|
import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media';
|
||||||
|
import { Caption } from '@/providers/captions';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
import { StreamsDataResult } from './type';
|
import { StreamsDataResult } from './type';
|
||||||
|
import { baseUrl } from './util';
|
||||||
|
|
||||||
export async function getVideoSources(
|
export async function getVideoSources(
|
||||||
ctx: ScrapeContext,
|
ctx: ScrapeContext,
|
||||||
@@ -17,17 +19,17 @@ export async function getVideoSources(
|
|||||||
path = `/v1/movies/view`;
|
path = `/v1/movies/view`;
|
||||||
}
|
}
|
||||||
const data = await ctx.fetcher<StreamsDataResult>(path, {
|
const data = await ctx.fetcher<StreamsDataResult>(path, {
|
||||||
baseUrl: 'https://lmscript.xyz',
|
baseUrl,
|
||||||
query: { expand: 'streams', id },
|
query: { expand: 'streams,subtitles', id },
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVideoUrl(
|
export async function getVideo(
|
||||||
ctx: ScrapeContext,
|
ctx: ScrapeContext,
|
||||||
id: string,
|
id: string,
|
||||||
media: MovieMedia | ShowMedia,
|
media: MovieMedia | ShowMedia,
|
||||||
): Promise<string | null> {
|
): Promise<{ playlist: string | null; captions: Caption[] }> {
|
||||||
// Get sources
|
// Get sources
|
||||||
const data = await getVideoSources(ctx, id, media);
|
const data = await getVideoSources(ctx, id, media);
|
||||||
const videoSources = data.streams;
|
const videoSources = data.streams;
|
||||||
@@ -42,5 +44,16 @@ export async function getVideoUrl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoUrl;
|
const captions: Caption[] = data.subtitles.map((sub) => ({
|
||||||
|
id: sub.url,
|
||||||
|
type: 'vtt',
|
||||||
|
url: `${baseUrl}${sub.url}`,
|
||||||
|
hasCorsRestrictions: false,
|
||||||
|
language: sub.language,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
playlist: videoUrl,
|
||||||
|
captions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user