commit
20229a4667
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@movie-web/providers",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.0",
|
||||
"description": "Package that contains all the providers of movie-web",
|
||||
"main": "./lib/index.umd.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
@ -79,6 +79,7 @@
|
|||
"cheerio": "^1.0.0-rc.12",
|
||||
"crypto-js": "^4.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"iso-639-1": "^3.1.0",
|
||||
"nanoid": "^3.3.6",
|
||||
"node-fetch": "^2.7.0",
|
||||
"unpacker": "^1.0.1"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import ISO6391 from 'iso-639-1';
|
||||
|
||||
export const captionTypes = {
|
||||
srt: 'srt',
|
||||
vtt: 'vtt',
|
||||
};
|
||||
export type CaptionType = keyof typeof captionTypes;
|
||||
|
||||
export type Caption = {
|
||||
type: CaptionType;
|
||||
url: string;
|
||||
hasCorsRestrictions: boolean;
|
||||
language: string;
|
||||
};
|
||||
|
||||
export function getCaptionTypeFromUrl(url: string): CaptionType | null {
|
||||
const extensions = Object.keys(captionTypes) as CaptionType[];
|
||||
const type = extensions.find((v) => url.endsWith(`.${v}`));
|
||||
if (!type) return null;
|
||||
return type;
|
||||
}
|
||||
|
||||
export function labelToLanguageCode(label: string): string | null {
|
||||
const code = ISO6391.getCode(label);
|
||||
if (code.length === 0) return null;
|
||||
return code;
|
||||
}
|
||||
|
||||
export function isValidLanguageCode(code: string | null): boolean {
|
||||
if (!code) return false;
|
||||
return ISO6391.validate(code);
|
||||
}
|
|
@ -65,6 +65,7 @@ export const febBoxScraper = makeEmbed({
|
|||
return {
|
||||
stream: {
|
||||
type: 'file',
|
||||
captions: [],
|
||||
flags: [flags.NO_CORS],
|
||||
qualities: embedQualities,
|
||||
},
|
||||
|
|
|
@ -36,6 +36,7 @@ export const mixdropScraper = makeEmbed({
|
|||
stream: {
|
||||
type: 'file',
|
||||
flags: [],
|
||||
captions: [],
|
||||
qualities: {
|
||||
unknown: {
|
||||
type: 'mp4',
|
||||
|
|
|
@ -18,6 +18,7 @@ export const mp4uploadScraper = makeEmbed({
|
|||
stream: {
|
||||
type: 'file',
|
||||
flags: [flags.NO_CORS],
|
||||
captions: [],
|
||||
qualities: {
|
||||
'1080': {
|
||||
type: 'mp4',
|
||||
|
|
|
@ -159,6 +159,7 @@ export const streamsbScraper = makeEmbed({
|
|||
type: 'file',
|
||||
flags: [flags.NO_CORS],
|
||||
qualities,
|
||||
captions: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@ import crypto from 'crypto-js';
|
|||
|
||||
import { flags } from '@/main/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
|
||||
|
||||
const { AES, enc } = crypto;
|
||||
|
||||
|
@ -96,11 +97,27 @@ export const upcloudScraper = makeEmbed({
|
|||
|
||||
if (!sources) throw new Error('upcloud source not found');
|
||||
|
||||
const captions: Caption[] = [];
|
||||
streamRes.tracks.forEach((track) => {
|
||||
if (track.kind !== 'captions') return;
|
||||
const type = getCaptionTypeFromUrl(track.file);
|
||||
if (!type) return;
|
||||
const language = labelToLanguageCode(track.label);
|
||||
if (!language) return;
|
||||
captions.push({
|
||||
language,
|
||||
hasCorsRestrictions: false,
|
||||
type,
|
||||
url: track.file,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
stream: {
|
||||
type: 'hls',
|
||||
playlist: sources.file,
|
||||
flags: [flags.NO_CORS],
|
||||
captions,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ export const upstreamScraper = makeEmbed({
|
|||
type: 'hls',
|
||||
playlist: link[1],
|
||||
flags: [flags.NO_CORS],
|
||||
captions: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ export const goMoviesScraper = makeSourcerer({
|
|||
async scrapeShow(ctx) {
|
||||
const search = await ctx.proxiedFetcher<string>(`/ajax/search`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
keyword: ctx.media.title,
|
||||
}),
|
||||
body: new URLSearchParams({ keyword: ctx.media.title }),
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
|
@ -104,9 +102,7 @@ export const goMoviesScraper = makeSourcerer({
|
|||
async scrapeMovie(ctx) {
|
||||
const search = await ctx.proxiedFetcher<string>(`ajax/search`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
keyword: ctx.media.title,
|
||||
}),
|
||||
body: new URLSearchParams({ keyword: ctx.media.title }),
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@ export const remotestreamScraper = makeSourcerer({
|
|||
return {
|
||||
embeds: [],
|
||||
stream: {
|
||||
captions: [],
|
||||
playlist: playlistLink,
|
||||
type: 'hls',
|
||||
flags: [flags.NO_CORS],
|
||||
|
@ -40,6 +41,7 @@ export const remotestreamScraper = makeSourcerer({
|
|||
return {
|
||||
embeds: [],
|
||||
stream: {
|
||||
captions: [],
|
||||
playlist: playlistLink,
|
||||
type: 'hls',
|
||||
flags: [flags.NO_CORS],
|
||||
|
|
|
@ -6,14 +6,16 @@ import { sendRequest } from './sendRequest';
|
|||
const allowedQualities = ['360', '480', '720', '1080'];
|
||||
|
||||
export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {
|
||||
const mediaRes: { list: { path: string; real_quality: string }[] } = (await sendRequest(ctx, apiQuery)).data;
|
||||
const mediaRes: { list: { path: string; quality: string; fid?: number }[] } = (await sendRequest(ctx, apiQuery)).data;
|
||||
ctx.progress(66);
|
||||
|
||||
console.log(mediaRes);
|
||||
|
||||
const qualityMap = mediaRes.list
|
||||
.filter((file) => allowedQualities.includes(file.real_quality.replace('p', '')))
|
||||
.filter((file) => allowedQualities.includes(file.quality.replace('p', '')))
|
||||
.map((file) => ({
|
||||
url: file.path,
|
||||
quality: file.real_quality.replace('p', ''),
|
||||
quality: file.quality.replace('p', ''),
|
||||
}));
|
||||
|
||||
const qualities: Record<string, StreamFile> = {};
|
||||
|
@ -28,5 +30,8 @@ export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {
|
|||
}
|
||||
});
|
||||
|
||||
return qualities;
|
||||
return {
|
||||
qualities,
|
||||
fid: mediaRes.list[0]?.fid,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { flags } from '@/main/targets';
|
||||
import { makeSourcerer } from '@/providers/base';
|
||||
import { getSubtitles } from '@/providers/sources/superstream/subtitles';
|
||||
import { compareTitle } from '@/utils/compare';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
|
@ -41,11 +42,19 @@ export const superStreamScraper = makeSourcerer({
|
|||
group: '',
|
||||
};
|
||||
|
||||
const qualities = await getStreamQualities(ctx, apiQuery);
|
||||
const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
|
||||
|
||||
return {
|
||||
embeds: [],
|
||||
stream: {
|
||||
captions: await getSubtitles(
|
||||
ctx,
|
||||
superstreamId,
|
||||
fid,
|
||||
'show',
|
||||
ctx.media.episode.number,
|
||||
ctx.media.season.number,
|
||||
),
|
||||
qualities,
|
||||
type: 'file',
|
||||
flags: [flags.NO_CORS],
|
||||
|
@ -80,11 +89,12 @@ export const superStreamScraper = makeSourcerer({
|
|||
group: '',
|
||||
};
|
||||
|
||||
const qualities = await getStreamQualities(ctx, apiQuery);
|
||||
const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
|
||||
|
||||
return {
|
||||
embeds: [],
|
||||
stream: {
|
||||
captions: await getSubtitles(ctx, superstreamId, fid, 'movie'),
|
||||
qualities,
|
||||
type: 'file',
|
||||
flags: [flags.NO_CORS],
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { Caption, getCaptionTypeFromUrl, isValidLanguageCode } from '@/providers/captions';
|
||||
import { sendRequest } from '@/providers/sources/superstream/sendRequest';
|
||||
import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
interface CaptionApiResponse {
|
||||
data: {
|
||||
list: {
|
||||
subtitles: {
|
||||
order: number;
|
||||
lang: string;
|
||||
file_path: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSubtitles(
|
||||
ctx: ScrapeContext,
|
||||
id: string,
|
||||
fid: number | undefined,
|
||||
type: 'show' | 'movie',
|
||||
episodeId?: number,
|
||||
seasonId?: number,
|
||||
): Promise<Caption[]> {
|
||||
const module = type === 'movie' ? 'Movie_srt_list_v2' : 'TV_srt_list_v2';
|
||||
const subtitleApiQuery = {
|
||||
fid,
|
||||
uid: '',
|
||||
module,
|
||||
mid: id,
|
||||
episode: episodeId?.toString(),
|
||||
season: seasonId?.toString(),
|
||||
group: episodeId ? '' : undefined,
|
||||
};
|
||||
|
||||
const subtitleList = ((await sendRequest(ctx, subtitleApiQuery)) as CaptionApiResponse).data.list;
|
||||
const output: Caption[] = [];
|
||||
|
||||
subtitleList.forEach((sub) => {
|
||||
const subtitle = sub.subtitles.sort((a, b) => a.order - b.order)[0];
|
||||
if (!subtitle) return;
|
||||
const subtitleType = getCaptionTypeFromUrl(subtitle.file_path);
|
||||
if (!subtitleType) return;
|
||||
|
||||
const validCode = isValidLanguageCode(subtitle.lang);
|
||||
if (!validCode) return;
|
||||
|
||||
output.push({
|
||||
language: subtitle.lang,
|
||||
hasCorsRestrictions: true,
|
||||
type: subtitleType,
|
||||
url: subtitle.file_path,
|
||||
});
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { Flags } from '@/main/targets';
|
||||
import { Caption } from '@/providers/captions';
|
||||
|
||||
export type StreamFile = {
|
||||
type: 'mp4';
|
||||
|
@ -18,6 +19,7 @@ export type HlsBasedStream = {
|
|||
type: 'hls';
|
||||
flags: Flags[];
|
||||
playlist: string;
|
||||
captions: Caption[];
|
||||
};
|
||||
|
||||
export type Stream = FileBasedStream | HlsBasedStream;
|
||||
|
|
Loading…
Reference in New Issue