1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-20 14:37:43 +01:00

start on jons providers

Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
Co-authored-by: Jonathan Barrow <jonbarrow@users.noreply.github.com>
This commit is contained in:
mrjvs 2023-02-18 22:42:24 +01:00
parent b43f39b007
commit 4a35287975
6 changed files with 313 additions and 11 deletions

View file

@ -0,0 +1,20 @@
import { MWEmbedType } from "@/backend/helpers/embed";
import { MWMediaType } from "../metadata/types";
import { registerEmbedScraper } from "@/backend/helpers/register";
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
registerEmbedScraper({
id: "playm4u",
displayName: "playm4u",
for: MWEmbedType.PLAYM4U,
rank: 0,
async getStream(ctx) {
throw new Error("Oh well 2")
return {
streamUrl: '',
quality: MWStreamQuality.Q1080P,
captions: [],
type: MWStreamType.MP4,
};
},
})

View file

@ -0,0 +1,71 @@
import { MWEmbedType } from "@/backend/helpers/embed";
import { MWMediaType } from "../metadata/types";
import { registerEmbedScraper } from "@/backend/helpers/register";
import { MWStreamQuality, MWStreamType, MWStream } from "@/backend/helpers/streams";
import { proxiedFetch } from "@/backend/helpers/fetch";
const HOST = 'streamm4u.club';
const URL_BASE = `https://${HOST}`;
const URL_API = `${URL_BASE}/api`;
const URL_API_SOURCE = `${URL_API}/source`;
// TODO check out 403 / 404 on successfully returned video stream URLs
registerEmbedScraper({
id: "streamm4u",
displayName: "streamm4u",
for: MWEmbedType.STREAMM4U,
rank: 100,
async getStream({ progress, url }) {
const scrapingThreads = [];
let streams = [];
const sources = (await scrape(url)).sort((a, b) => Number(b.quality.replace("p", "")) - Number(a.quality.replace("p", "")));
let preferredSourceIndex = 0;
let preferredSource;
while (!preferredSource && sources[preferredSourceIndex]) {
console.log('Testing', preferredSourceIndex)
console.log(sources[preferredSourceIndex]?.streamUrl)
// try {
// await proxiedFetch(sources[preferredSourceIndex]?.streamUrl)
// } catch (err) { }
preferredSource = sources[0]
preferredSourceIndex++
}
console.log(preferredSource)
if (!preferredSource) throw new Error("No source found")
progress(100)
return preferredSource
},
})
async function scrape(embed: string) {
const sources: MWStream[] = [];
const embedID = embed.split('/').pop();
console.log(`${URL_API_SOURCE}/${embedID}`)
const json = await proxiedFetch<any>(`${URL_API_SOURCE}/${embedID}`, {
method: 'POST',
body: `r=&d=${HOST}`
});
if (json.success) {
const streams = json.data;
for (const stream of streams) {
sources.push({
streamUrl: stream.file as string,
quality: stream.label as MWStreamQuality,
type: stream.type as MWStreamType,
captions: []
});
}
}
return sources;
}

View file

@ -1,7 +1,9 @@
import { MWStream } from "./streams";
export enum MWEmbedType {
OPENLOAD = "openload",
M4UFREE = "m4ufree",
STREAMM4U = "streamm4u",
PLAYM4U = "playm4u"
}
export type MWEmbed = {

View file

@ -25,15 +25,15 @@ type MWProviderRunContextBase = {
};
type MWProviderRunContextTypeSpecific =
| {
type: MWMediaType.MOVIE | MWMediaType.ANIME;
episode: undefined;
season: undefined;
}
type: MWMediaType.MOVIE | MWMediaType.ANIME;
episode: undefined;
season: undefined;
}
| {
type: MWMediaType.SERIES;
episode: string;
season: string;
};
type: MWMediaType.SERIES;
episode: string;
season: string;
};
export type MWProviderRunContext = MWProviderRunContextBase &
MWProviderRunContextTypeSpecific;
@ -50,7 +50,7 @@ async function findBestEmbedStream(
embedNum += 1;
if (!embed.type) continue;
const scraper = getEmbedScraperByType(embed.type);
if (!scraper) throw new Error("Type for embed not found");
if (!scraper) throw new Error("Type for embed not found: " + embed.type);
const eventId = [providerId, scraper.id, embedNum].join("|");

View file

@ -5,8 +5,10 @@ import "./providers/gdriveplayer";
import "./providers/flixhq";
import "./providers/superstream";
import "./providers/netfilm";
import "./providers/m4ufree";
// embeds
// -- nothing here yet
import "./embeds/streamm4u"
import "./embeds/playm4u"
initializeScraperStore();

View file

@ -0,0 +1,207 @@
import { compareTitle } from "@/utils/titleMatch";
import { MWEmbedType } from "../helpers/embed";
import { proxiedFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
import { MWMediaType } from "../metadata/types";
import { MWEmbed } from "@/backend/helpers/embed";
const HOST = 'm4ufree.com';
const URL_BASE = `https://${HOST}`;
const URL_SEARCH = `${URL_BASE}/search`;
const URL_AJAX = `${URL_BASE}/ajax`;
const URL_AJAX_TV = `${URL_BASE}/ajaxtv`;
// * Years can be in one of 4 formats:
// * - "startyear" (for movies, EX: 2022)
// * - "startyear-" (for TV series which has not ended, EX: 2022-)
// * - "startyear-endyear" (for TV series which has ended, EX: 2022-2023)
// * - "startyearendyear" (for TV series which has ended, EX: 20222023)
const REGEX_TITLE_AND_YEAR = /(.*) \(?(\d*|\d*-|\d*-\d*)\)?$/;
const REGEX_TYPE = /.*-(movie|tvshow)-online-free-m4ufree\.html/;
const REGEX_COOKIES = /XSRF-TOKEN=(.*?);.*laravel_session=(.*?);/;
const REGEX_SEASON_EPISODE = /S(\d*)-E(\d*)/;
function toDom(html: string) {
return new DOMParser().parseFromString(html, "text/html")
}
registerProvider({
id: "m4ufree",
displayName: "m4ufree",
rank: -1,
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
async scrape({ media, progress, type, episode: episodeId, season: seasonId }) {
const season = media.meta.seasons?.find(s => s.id === seasonId)?.number || 1
const episode = media.meta.type === MWMediaType.SERIES ? media.meta.seasonData.episodes.find(ep => ep.id === episodeId)?.number || 1 : undefined
const embeds: MWEmbed[] = [];
/*
, {
responseType: "text" as any,
}
*/
let responseText = await proxiedFetch<string>(`${URL_SEARCH}/${encodeURIComponent(media.meta.title)}.html`);
let dom = toDom(responseText);
const searchResults = [...dom.querySelectorAll('.item')].map(element => {
const tooltipText = element.querySelector('.tiptitle p')?.innerHTML;
if (!tooltipText) return;
let regexResult = REGEX_TITLE_AND_YEAR.exec(tooltipText);
if (!regexResult || !regexResult[1] || !regexResult[2]) {
return;
}
const title = regexResult[1];
const year = Number(regexResult[2].slice(0, 4)); // * Some media stores the start AND end year. Only need start year
const a = element.querySelector('a');
if (!a) return;
const href = a.href;
regexResult = REGEX_TYPE.exec(href);
if (!regexResult || !regexResult[1]) {
return;
}
let scraperDeterminedType = regexResult[1];
scraperDeterminedType = scraperDeterminedType === 'tvshow' ? 'show' : 'movie'; // * Map to Trakt type
return { type: scraperDeterminedType, title, year, href };
}).filter(item => item);
const mediaInResults = searchResults.find(item => item && item.title === media.meta.title && item.year.toString() === media.meta.year);
if (!mediaInResults) {
// * Nothing found
return {
embeds,
};
}
let cookies: string | null = '';
const responseTextFromMedia = await proxiedFetch<string>(mediaInResults.href, {
onResponse(context) {
cookies = context.response.headers.get('X-Set-Cookie')
},
});
dom = toDom(responseTextFromMedia);
let regexResult = REGEX_COOKIES.exec(cookies);
if (!regexResult || !regexResult[1] || !regexResult[2]) {
// * DO SOMETHING?
throw new Error("No regexResults, yikesssssss kinda gross idk")
}
const cookieHeader = `XSRF-TOKEN=${regexResult[1]}; laravel_session=${regexResult[2]}`;
const token = dom.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
if (!token) return { embeds };
if (type === MWMediaType.SERIES) {
// * Get the season/episode data
const episodes = [...dom.querySelectorAll('.episode')].map(element => {
regexResult = REGEX_SEASON_EPISODE.exec(element.innerHTML);
if (!regexResult || !regexResult[1] || !regexResult[2]) {
return;
}
const episode = Number(regexResult[1]);
const season = Number(regexResult[2]);
return {
id: element.getAttribute('idepisode'),
episode: episode,
season: season
};
}).filter(item => item);
const ep = episodes.find(ep => ep && ep.episode === episode && ep.season === season);
if (!ep) return { embeds }
const form = `idepisode=${ep.id}&_token=${token}`;
let response = await proxiedFetch<string>(URL_AJAX_TV, {
method: 'POST',
headers: {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': "en-US,en;q=0.9",
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Sec-CH-UA': '"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"',
'Sec-CH-UA-Mobile': '?0',
'Sec-CH-UA-Platform': '"Linux"',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'X-Cookie': cookieHeader,
'X-Origin': URL_BASE,
'X-Referer': mediaInResults.href
},
body: form
});
dom = toDom(response);
}
const servers = [...dom.querySelectorAll('.singlemv')].map(element => element.getAttribute('data'));
for (const server of servers) {
const form = `m4u=${server}&_token=${token}`;
const response = await proxiedFetch<string>(URL_AJAX, {
method: 'POST',
headers: {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': "en-US,en;q=0.9",
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Sec-CH-UA': '"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"',
'Sec-CH-UA-Mobile': '?0',
'Sec-CH-UA-Platform': '"Linux"',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'X-Cookie': cookieHeader,
'X-Origin': URL_BASE,
'X-Referer': mediaInResults.href
},
body: form
});
const dom = toDom(response);
const link = dom.querySelector('iframe')?.src;
const getEmbedType = (url: string) => {
if (url.startsWith("https://streamm4u.club")) return MWEmbedType.STREAMM4U
if (url.startsWith("https://play.playm4u.xyz")) return MWEmbedType.PLAYM4U
return null;
}
if (!link) continue;
const embedType = getEmbedType(link);
if (embedType) {
embeds.push({
url: link,
type: embedType
})
};
}
console.log(embeds);
return {
embeds,
}
}
});