From 3c292baf2629895f785e78e2532ec0a907b88898 Mon Sep 17 00:00:00 2001 From: Isra Date: Wed, 27 Sep 2023 22:49:22 -0500 Subject: [PATCH] Add flixhq shows --- package-lock.json | 58 +++++++++++++------ package.json | 5 +- src/dev-cli.ts | 4 +- src/providers/sources/flixhq/index.ts | 19 +++++- src/providers/sources/flixhq/scrape.ts | 58 ++++++++++++++++++- src/providers/sources/flixhq/search.ts | 4 +- .../sources/superstream/sendRequest.ts | 5 +- src/utils/compare.ts | 10 +++- 8 files changed, 129 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9ada68..ab52e74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 9c8964d..0db14f9 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/dev-cli.ts b/src/dev-cli.ts index 2e691da..b2161b3 100644 --- a/src/dev-cli.ts +++ b/src/dev-cli.ts @@ -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) { diff --git a/src/providers/sources/flixhq/index.ts b/src/providers/sources/flixhq/index.ts index fcd504d..c8a519a 100644 --- a/src/providers/sources/flixhq/index.ts +++ b/src/providers/sources/flixhq/index.ts @@ -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: [ { diff --git a/src/providers/sources/flixhq/scrape.ts b/src/providers/sources/flixhq/scrape.ts index 5c25682..e5707eb 100644 --- a/src/providers/sources/flixhq/scrape.ts +++ b/src/providers/sources/flixhq/scrape.ts @@ -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(`/ajax/${type}/episodes/${episodeId}`, { + if (media.type === 'show') { + const seasonsListData = await ctx.proxiedFetcher(`/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(`/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(`/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(`/ajax/movie/episodes/${episodeId}`, { baseUrl: flixHqBase, }); + const doc = load(data); const sourceLinks = doc('.nav-item > a') .toArray() diff --git a/src/providers/sources/flixhq/search.ts b/src/providers/sources/flixhq/search.ts index 569d2d1..cea1b14 100644 --- a/src/providers/sources/flixhq/search.ts +++ b/src/providers/sources/flixhq/search.ts @@ -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 { +export async function getFlixhqId(ctx: ScrapeContext, media: MovieMedia | ShowMedia): Promise { const searchResults = await ctx.proxiedFetcher(`/search/${media.title.replaceAll(/[^a-z0-9A-Z]/g, '-')}`, { baseUrl: flixHqBase, }); diff --git a/src/providers/sources/superstream/sendRequest.ts b/src/providers/sources/superstream/sendRequest.ts index ed67a35..7c0122c 100644 --- a/src/providers/sources/superstream/sendRequest.ts +++ b/src/providers/sources/superstream/sendRequest.ts @@ -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]; diff --git a/src/utils/compare.ts b/src/utils/compare.ts index 8cce7da..ba1b5e4 100644 --- a/src/utils/compare.ts +++ b/src/utils/compare.ts @@ -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; }