Add flixhq shows

This commit is contained in:
2023-09-27 22:49:22 -05:00
parent 2799708992
commit 3c292baf26
8 changed files with 129 additions and 34 deletions

58
package-lock.json generated
View File

@@ -12,12 +12,13 @@
"cheerio": "^1.0.0-rc.12",
"crypto-js": "^4.1.1",
"form-data": "^4.0.0",
"nanoid": "^5.0.1",
"node-fetch": "^2.7.0"
"node-fetch": "^2.7.0",
"randombytes": "^2.1.0"
},
"devDependencies": {
"@types/crypto-js": "^4.1.1",
"@types/node-fetch": "^2.6.6",
"@types/randombytes": "^2.0.1",
"@types/spinnies": "^0.5.1",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
@@ -942,6 +943,15 @@
"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": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
@@ -4698,23 +4708,6 @@
"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": {
"version": "1.4.0",
"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": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -5428,6 +5429,25 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",

View File

@@ -48,6 +48,7 @@
"devDependencies": {
"@types/crypto-js": "^4.1.1",
"@types/node-fetch": "^2.6.6",
"@types/randombytes": "^2.0.1",
"@types/spinnies": "^0.5.1",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
@@ -76,7 +77,7 @@
"cheerio": "^1.0.0-rc.12",
"crypto-js": "^4.1.1",
"form-data": "^4.0.0",
"nanoid": "^5.0.1",
"node-fetch": "^2.7.0"
"node-fetch": "^2.7.0",
"randombytes": "^2.1.0"
}
}

View File

@@ -183,7 +183,7 @@ async function runScraper(providers: ProviderControls, source: MetaOutput, optio
id: source.id,
});
spinnies.succeed('scrape', { text: 'Done!' });
console.log(result);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
let message = 'Unknown error';
if (error instanceof Error) {
@@ -208,7 +208,7 @@ async function runScraper(providers: ProviderControls, source: MetaOutput, optio
id: source.id,
});
spinnies.succeed('scrape', { text: 'Done!' });
console.log(result);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
let message = 'Unknown error';
if (error instanceof Error) {

View File

@@ -15,10 +15,27 @@ export const flixhqScraper = makeSourcerer({
const id = await getFlixhqId(ctx, ctx.media);
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');
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 {
embeds: [
{

View File

@@ -1,16 +1,68 @@
import { load } from 'cheerio';
import { MovieMedia, ShowMedia } from '@/main/media';
import { flixHqBase } from '@/providers/sources/flixhq/common';
import { ScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
export async function getFlixhqSources(ctx: ScrapeContext, id: string) {
const type = id.split('/')[0];
export async function getFlixhqSources(ctx: ScrapeContext, media: MovieMedia | ShowMedia, id: string) {
const episodeParts = id.split('-');
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,
});
const doc = load(data);
const sourceLinks = doc('.nav-item > a')
.toArray()

View File

@@ -1,11 +1,11 @@
import { load } from 'cheerio';
import { MovieMedia } from '@/main/media';
import { MovieMedia, ShowMedia } from '@/main/media';
import { flixHqBase } from '@/providers/sources/flixhq/common';
import { compareMedia } from '@/utils/compare';
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, '-')}`, {
baseUrl: flixHqBase,
});

View File

@@ -1,12 +1,11 @@
import CryptoJS from 'crypto-js';
import { customAlphabet } from 'nanoid';
import randomBytes from 'randombytes';
import type { ScrapeContext } from '@/utils/context';
import { apiUrls, appId, appKey, key } from './common';
import { encrypt, getVerify } from './crypto';
const nanoid = customAlphabet('0123456789abcdef', 32);
const expiry = () => Math.floor(Date.now() / 1000 + 60 * 60 * 12);
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('version', '129');
formatted.append('medium', 'Website');
formatted.append('token', nanoid());
formatted.append('token', randomBytes(16).toString('hex'));
const requestUrl = altApi ? apiUrls[1] : apiUrls[0];

View File

@@ -12,8 +12,14 @@ export function compareTitle(a: string, b: string): boolean {
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
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;
}