mirror of
https://github.com/movie-web/providers.git
synced 2025-09-13 12:43:25 +00:00
Add flixhq shows
This commit is contained in:
58
package-lock.json
generated
58
package-lock.json
generated
@@ -12,12 +12,13 @@
|
|||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"nanoid": "^5.0.1",
|
"node-fetch": "^2.7.0",
|
||||||
"node-fetch": "^2.7.0"
|
"randombytes": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/node-fetch": "^2.6.6",
|
"@types/node-fetch": "^2.6.6",
|
||||||
|
"@types/randombytes": "^2.0.1",
|
||||||
"@types/spinnies": "^0.5.1",
|
"@types/spinnies": "^0.5.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
||||||
"@typescript-eslint/parser": "^5.60.0",
|
"@typescript-eslint/parser": "^5.60.0",
|
||||||
@@ -942,6 +943,15 @@
|
|||||||
"form-data": "^4.0.0"
|
"form-data": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/randombytes": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-kWMqPyxpTUTofwbGN47MWddBFiJnWJlfLBdDg2NvmZSKHOmKY9ujVA3PIfBgXcIHTCpsqoQqYudBwanFXzGD9A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.0",
|
"version": "7.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
|
||||||
@@ -4698,23 +4708,6 @@
|
|||||||
"url": "https://github.com/sponsors/raouldeheer"
|
"url": "https://github.com/sponsors/raouldeheer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-vWeVtV5Cw68aML/QaZvqN/3QQXc6fBfIieAlu05m7FZW2Dgb+3f0xc0TTxuJW+7u30t7iSDTV/j3kVI0oJqIfQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/ai"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bin": {
|
|
||||||
"nanoid": "bin/nanoid.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^18 || >=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
@@ -5258,6 +5251,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/randombytes": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
@@ -5428,6 +5429,25 @@
|
|||||||
"queue-microtask": "^1.2.2"
|
"queue-microtask": "^1.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/safe-regex-test": {
|
"node_modules/safe-regex-test": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
|
||||||
|
@@ -48,6 +48,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/node-fetch": "^2.6.6",
|
"@types/node-fetch": "^2.6.6",
|
||||||
|
"@types/randombytes": "^2.0.1",
|
||||||
"@types/spinnies": "^0.5.1",
|
"@types/spinnies": "^0.5.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
||||||
"@typescript-eslint/parser": "^5.60.0",
|
"@typescript-eslint/parser": "^5.60.0",
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"nanoid": "^5.0.1",
|
"node-fetch": "^2.7.0",
|
||||||
"node-fetch": "^2.7.0"
|
"randombytes": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -183,7 +183,7 @@ async function runScraper(providers: ProviderControls, source: MetaOutput, optio
|
|||||||
id: source.id,
|
id: source.id,
|
||||||
});
|
});
|
||||||
spinnies.succeed('scrape', { text: 'Done!' });
|
spinnies.succeed('scrape', { text: 'Done!' });
|
||||||
console.log(result);
|
console.log(JSON.stringify(result, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Unknown error';
|
let message = 'Unknown error';
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -208,7 +208,7 @@ async function runScraper(providers: ProviderControls, source: MetaOutput, optio
|
|||||||
id: source.id,
|
id: source.id,
|
||||||
});
|
});
|
||||||
spinnies.succeed('scrape', { text: 'Done!' });
|
spinnies.succeed('scrape', { text: 'Done!' });
|
||||||
console.log(result);
|
console.log(JSON.stringify(result, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Unknown error';
|
let message = 'Unknown error';
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
@@ -15,10 +15,27 @@ export const flixhqScraper = makeSourcerer({
|
|||||||
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');
|
||||||
|
|
||||||
const sources = await getFlixhqSources(ctx, id);
|
const sources = await getFlixhqSources(ctx, ctx.media, id);
|
||||||
const upcloudStream = sources.find((v) => v.embed.toLowerCase() === 'upcloud');
|
const upcloudStream = sources.find((v) => v.embed.toLowerCase() === 'upcloud');
|
||||||
if (!upcloudStream) throw new NotFoundError('upcloud stream not found for flixhq');
|
if (!upcloudStream) throw new NotFoundError('upcloud stream not found for flixhq');
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
embedId: upcloudScraper.id,
|
||||||
|
url: await getFlixhqSourceDetails(ctx, upcloudStream.episodeId),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async scrapeShow(ctx) {
|
||||||
|
const id = await getFlixhqId(ctx, ctx.media);
|
||||||
|
if (!id) throw new NotFoundError('no search results match');
|
||||||
|
|
||||||
|
const sources = await getFlixhqSources(ctx, ctx.media, id);
|
||||||
|
const upcloudStream = sources.find((v) => v.embed.toLowerCase() === 'server upcloud');
|
||||||
|
if (!upcloudStream) throw new NotFoundError('upcloud stream not found for flixhq');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
|
@@ -1,16 +1,68 @@
|
|||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
|
|
||||||
|
import { MovieMedia, ShowMedia } from '@/main/media';
|
||||||
import { flixHqBase } from '@/providers/sources/flixhq/common';
|
import { flixHqBase } from '@/providers/sources/flixhq/common';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
export async function getFlixhqSources(ctx: ScrapeContext, id: string) {
|
export async function getFlixhqSources(ctx: ScrapeContext, media: MovieMedia | ShowMedia, id: string) {
|
||||||
const type = id.split('/')[0];
|
|
||||||
const episodeParts = id.split('-');
|
const episodeParts = id.split('-');
|
||||||
const episodeId = episodeParts[episodeParts.length - 1];
|
const episodeId = episodeParts[episodeParts.length - 1];
|
||||||
|
|
||||||
const data = await ctx.proxiedFetcher<string>(`/ajax/${type}/episodes/${episodeId}`, {
|
if (media.type === 'show') {
|
||||||
|
const seasonsListData = await ctx.proxiedFetcher<string>(`/ajax/season/list/${episodeId}`, {
|
||||||
|
baseUrl: flixHqBase,
|
||||||
|
});
|
||||||
|
|
||||||
|
const seasonsDoc = load(seasonsListData);
|
||||||
|
const season = seasonsDoc('.dropdown-item')
|
||||||
|
.toArray()
|
||||||
|
.find((el) => seasonsDoc(el).text() === `Season ${media.season.number}`)?.attribs['data-id'];
|
||||||
|
|
||||||
|
if (!season) throw new NotFoundError('season not found');
|
||||||
|
|
||||||
|
const seasonData = await ctx.proxiedFetcher<string>(`/ajax/season/episodes/${season}`, {
|
||||||
|
baseUrl: flixHqBase,
|
||||||
|
});
|
||||||
|
const seasonDoc = load(seasonData);
|
||||||
|
const episode = seasonDoc('.nav-item > a')
|
||||||
|
.toArray()
|
||||||
|
.map((el) => {
|
||||||
|
return {
|
||||||
|
id: seasonDoc(el).attr('data-id'),
|
||||||
|
title: seasonDoc(el).attr('title'),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.find((e) => e.title?.startsWith(`Eps ${media.episode.number}`))?.id;
|
||||||
|
|
||||||
|
if (!episode) throw new NotFoundError('episode not found');
|
||||||
|
|
||||||
|
const data = await ctx.proxiedFetcher<string>(`/ajax/episode/servers/${episode}`, {
|
||||||
|
baseUrl: flixHqBase,
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc = load(data);
|
||||||
|
|
||||||
|
const sourceLinks = doc('.nav-item > a')
|
||||||
|
.toArray()
|
||||||
|
.map((el) => {
|
||||||
|
const query = doc(el);
|
||||||
|
const embedTitle = query.attr('title');
|
||||||
|
const linkId = query.attr('data-id');
|
||||||
|
if (!embedTitle || !linkId) throw new Error('invalid sources');
|
||||||
|
return {
|
||||||
|
embed: embedTitle,
|
||||||
|
episodeId: linkId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return sourceLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await ctx.proxiedFetcher<string>(`/ajax/movie/episodes/${episodeId}`, {
|
||||||
baseUrl: flixHqBase,
|
baseUrl: flixHqBase,
|
||||||
});
|
});
|
||||||
|
|
||||||
const doc = load(data);
|
const doc = load(data);
|
||||||
const sourceLinks = doc('.nav-item > a')
|
const sourceLinks = doc('.nav-item > a')
|
||||||
.toArray()
|
.toArray()
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
|
|
||||||
import { MovieMedia } from '@/main/media';
|
import { MovieMedia, ShowMedia } from '@/main/media';
|
||||||
import { flixHqBase } from '@/providers/sources/flixhq/common';
|
import { flixHqBase } from '@/providers/sources/flixhq/common';
|
||||||
import { compareMedia } from '@/utils/compare';
|
import { compareMedia } from '@/utils/compare';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
export async function getFlixhqId(ctx: ScrapeContext, media: MovieMedia): Promise<string | null> {
|
export async function getFlixhqId(ctx: ScrapeContext, media: MovieMedia | ShowMedia): Promise<string | null> {
|
||||||
const searchResults = await ctx.proxiedFetcher<string>(`/search/${media.title.replaceAll(/[^a-z0-9A-Z]/g, '-')}`, {
|
const searchResults = await ctx.proxiedFetcher<string>(`/search/${media.title.replaceAll(/[^a-z0-9A-Z]/g, '-')}`, {
|
||||||
baseUrl: flixHqBase,
|
baseUrl: flixHqBase,
|
||||||
});
|
});
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import CryptoJS from 'crypto-js';
|
import CryptoJS from 'crypto-js';
|
||||||
import { customAlphabet } from 'nanoid';
|
import randomBytes from 'randombytes';
|
||||||
|
|
||||||
import type { ScrapeContext } from '@/utils/context';
|
import type { ScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
import { apiUrls, appId, appKey, key } from './common';
|
import { apiUrls, appId, appKey, key } from './common';
|
||||||
import { encrypt, getVerify } from './crypto';
|
import { encrypt, getVerify } from './crypto';
|
||||||
|
|
||||||
const nanoid = customAlphabet('0123456789abcdef', 32);
|
|
||||||
const expiry = () => Math.floor(Date.now() / 1000 + 60 * 60 * 12);
|
const expiry = () => Math.floor(Date.now() / 1000 + 60 * 60 * 12);
|
||||||
|
|
||||||
export const sendRequest = async (ctx: ScrapeContext, data: object, altApi = false) => {
|
export const sendRequest = async (ctx: ScrapeContext, data: object, altApi = false) => {
|
||||||
@@ -40,7 +39,7 @@ export const sendRequest = async (ctx: ScrapeContext, data: object, altApi = fal
|
|||||||
formatted.append('platform', 'android');
|
formatted.append('platform', 'android');
|
||||||
formatted.append('version', '129');
|
formatted.append('version', '129');
|
||||||
formatted.append('medium', 'Website');
|
formatted.append('medium', 'Website');
|
||||||
formatted.append('token', nanoid());
|
formatted.append('token', randomBytes(16).toString('hex'));
|
||||||
|
|
||||||
const requestUrl = altApi ? apiUrls[1] : apiUrls[0];
|
const requestUrl = altApi ? apiUrls[1] : apiUrls[0];
|
||||||
|
|
||||||
|
@@ -12,8 +12,14 @@ export function compareTitle(a: string, b: string): boolean {
|
|||||||
return normalizeTitle(a) === normalizeTitle(b);
|
return normalizeTitle(a) === normalizeTitle(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compareMedia(media: CommonMedia, title: string, releaseYear?: number): boolean {
|
export function compareMedia(media: CommonMedia, title: string, releaseYear?: number, compareYear?: boolean): boolean {
|
||||||
// if no year is provided, count as if its the correct year
|
// if no year is provided, count as if its the correct year
|
||||||
const isSameYear = releaseYear === undefined ? true : media.releaseYear === releaseYear;
|
let isSameYear: boolean;
|
||||||
|
if (!compareYear) {
|
||||||
|
isSameYear = true;
|
||||||
|
} else {
|
||||||
|
isSameYear = releaseYear === undefined ? true : media.releaseYear === releaseYear;
|
||||||
|
}
|
||||||
|
|
||||||
return compareTitle(media.title, title) && isSameYear;
|
return compareTitle(media.title, title) && isSameYear;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user