Merge pull request #4 from ztpn/catflix

Add catflix source and turbovid embed
This commit is contained in:
TPN 2024-07-16 10:54:50 +05:30 committed by GitHub
commit 7345b98579
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 164 additions and 6 deletions

View File

@ -8,12 +8,14 @@ import { mixdropScraper } from '@/providers/embeds/mixdrop';
import { mp4uploadScraper } from '@/providers/embeds/mp4upload';
import { streambucketScraper } from '@/providers/embeds/streambucket';
import { streamsbScraper } from '@/providers/embeds/streamsb';
import { turbovidScraper } from '@/providers/embeds/turbovid';
import { upcloudScraper } from '@/providers/embeds/upcloud';
import { upstreamScraper } from '@/providers/embeds/upstream';
import { vidsrcembedScraper } from '@/providers/embeds/vidsrc';
import { vTubeScraper } from '@/providers/embeds/vtube';
import { astraScraper, novaScraper } from '@/providers/embeds/whvx';
import { autoembedScraper } from '@/providers/sources/autoembed';
import { catflixScraper } from '@/providers/sources/catflix';
import { flixhqScraper } from '@/providers/sources/flixhq/index';
import { goMoviesScraper } from '@/providers/sources/gomovies/index';
import { insertunitScraper } from '@/providers/sources/insertunit';
@ -67,6 +69,7 @@ import { warezcdnScraper } from './sources/warezcdn';
export function gatherAllSources(): Array<Sourcerer> {
// all sources are gathered here
return [
catflixScraper,
flixhqScraper,
remotestreamScraper,
kissAsianScraper,
@ -134,6 +137,7 @@ export function gatherAllEmbeds(): Array<Embed> {
autoembedBengaliScraper,
autoembedTamilScraper,
autoembedTeluguScraper,
turbovidScraper,
novaScraper,
astraScraper,
];

View File

@ -0,0 +1,79 @@
import { makeEmbed } from '@/providers/base';
// Thanks to Paradox_77 for helping with the decryption
function hexToChar(hex: string): string {
return String.fromCharCode(parseInt(hex, 16));
}
function decrypt(data: string, key: string): string {
const formatedData = data.match(/../g)?.map(hexToChar).join('') || '';
return formatedData
.split('')
.map((char, i) => String.fromCharCode(char.charCodeAt(0) ^ key.charCodeAt(i % key.length)))
.join('');
}
export const turbovidScraper = makeEmbed({
id: 'turbovid',
name: 'Turbovid',
rank: 122,
async scrape(ctx) {
const baseUrl = new URL(ctx.url).origin;
const embedPage = await ctx.proxiedFetcher(ctx.url);
ctx.progress(30);
// the whitespace is for future-proofing the regex a bit
const apkey = embedPage.match(/const\s+apkey\s*=\s*"(.*?)";/)?.[1];
const xxid = embedPage.match(/const\s+xxid\s*=\s*"(.*?)";/)?.[1];
if (!apkey || !xxid) throw new Error('Failed to get required values');
// json isn't parsed by proxiedFetcher due to content-type being text/html
const juiceKey = JSON.parse(
await ctx.proxiedFetcher('/api/cucked/juice_key', {
baseUrl,
headers: {
referer: ctx.url,
},
}),
).juice;
if (!juiceKey) throw new Error('Failed to fetch the key');
ctx.progress(60);
const data = JSON.parse(
await ctx.proxiedFetcher('/api/cucked/the_juice/', {
baseUrl,
query: {
[apkey]: xxid,
},
headers: {
referer: ctx.url,
},
}),
).data;
if (!data) throw new Error('Failed to fetch required data');
ctx.progress(90);
const playlist = decrypt(data, juiceKey);
return {
stream: [
{
type: 'hls',
id: 'primary',
playlist,
headers: {
referer: baseUrl,
},
flags: [],
captions: [],
},
],
};
},
});

View File

@ -0,0 +1,74 @@
import { load } from 'cheerio';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { compareMedia } from '@/utils/compare';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
const baseUrl = 'https://catflix.su';
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
const searchPage = await ctx.proxiedFetcher('/', {
baseUrl,
query: {
s: ctx.media.title,
},
});
ctx.progress(40);
const $search = load(searchPage);
const searchResults: { title: string; year?: number | undefined; url: string }[] = [];
$search('li').each((_, element) => {
const title = $search(element).find('h2').first().text().trim();
// the year is always present, but I sitll decided to make it nullable since the impl isn't as future-proof
const year = Number($search(element).find('.text-xs > span').eq(1).text().trim()) || undefined;
const url = $search(element).find('a').attr('href');
if (!title || !url) return;
searchResults.push({ title, year, url });
});
let watchPageUrl = searchResults.find((x) => x && compareMedia(ctx.media, x.title, x.year))?.url;
if (!watchPageUrl) throw new NotFoundError('No watchable item found');
ctx.progress(60);
if (ctx.media.type === 'show') {
const match = watchPageUrl.match(/\/series\/([^/]+)\/?/);
if (!match) throw new Error('Failed to parse watch page url');
watchPageUrl = watchPageUrl.replace(
`/series/${match[1]}`,
`/episode/${match[1]}-${ctx.media.season.number}x${ctx.media.episode.number}`,
);
}
const watchPage = load(await ctx.proxiedFetcher(watchPageUrl));
ctx.progress(80);
const url = watchPage('iframe').first().attr('src'); // I couldn't think of a better way
if (!url) throw new Error('Failed to find embed url');
ctx.progress(90);
return {
embeds: [
{
embedId: 'turbovid',
url,
},
],
};
}
export const catflixScraper = makeSourcerer({
id: 'catflix',
name: 'Catflix',
rank: 122,
flags: [],
scrapeMovie: comboScraper,
scrapeShow: comboScraper,
});

View File

@ -1,5 +1,6 @@
import { load } from 'cheerio';
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { compareTitle } from '@/utils/compare';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
@ -7,8 +8,8 @@ import { NotFoundError } from '@/utils/errors';
import { SearchResults } from './types';
const nepuBase = 'https://nepu.to';
const nepuReferer = `${nepuBase}/`;
const nepuBase = 'https://nepu.io';
const nepuReferer = 'https://nepu.to';
const universalScraper = async (ctx: MovieScrapeContext | ShowScrapeContext) => {
const searchResultRequest = await ctx.proxiedFetcher<string>('/ajax/posts', {
@ -63,11 +64,11 @@ const universalScraper = async (ctx: MovieScrapeContext | ShowScrapeContext) =>
captions: [],
playlist: streamUrl[1],
type: 'hls',
flags: [],
headers: {
Origin: nepuBase,
Referer: nepuReferer,
Origin: nepuReferer,
Referer: `${nepuReferer}/`,
},
flags: [],
},
],
} as SourcererOutput;
@ -77,8 +78,8 @@ export const nepuScraper = makeSourcerer({
id: 'nepu',
name: 'Nepu',
rank: 80,
flags: [],
disabled: true,
flags: [],
scrapeMovie: universalScraper,
scrapeShow: universalScraper,
});