feat:JS new sourcces AniWorld(DE) & AllAnime(EN)

This commit is contained in:
kodjomoustapha
2024-03-28 11:15:21 +01:00
parent 67109cdbbc
commit 65b86799e5
4 changed files with 734 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
const mangayomiSources = [{
"name": "AniWorld",
"lang": "de",
"baseUrl": "https://aniworld.to",
"apiUrl": "",
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/de.aniworld.png",
"typeSource": "single",
"isManga": false,
"isNsfw": false,
"version": "0.0.1",
"dateFormat": "",
"dateFormatLocale": "",
"pkgPath": "anime/src/de/aniworld.js"
}];
class DefaultExtension extends MProvider {
async getPopular(page) {
const baseUrl = this.source.baseUrl;
const res = await new Client().get(`${baseUrl}/beliebte-animes`);
const elements = new Document(res.body).select("div.seriesListContainer div");
const list = [];
for (const element of elements) {
const linkElement = element.selectFirst("a");
const name = element.selectFirst("h3").text;
const imageUrl = baseUrl + linkElement.selectFirst("img").attr("data-src");
const link = linkElement.attr("href");
list.push({ name, imageUrl, link });
}
return {
list: list,
hasNextPage: false
}
}
async getLatestUpdates(page) {
const baseUrl = this.source.baseUrl;
const res = await new Client().get(`${baseUrl}/neu`);
const elements = new Document(res.body).select("div.seriesListContainer div");
const list = [];
for (const element of elements) {
const linkElement = element.selectFirst("a");
const name = element.selectFirst("h3").text;
const imageUrl = baseUrl + linkElement.selectFirst("img").attr("data-src");
const link = linkElement.attr("href");
list.push({ name, imageUrl, link });
}
return {
list: list,
hasNextPage: false
}
}
async search(query, page, filters) {
const baseUrl = this.source.baseUrl;
const res = await new Client().get(`${baseUrl}/animes`);
const elements = new Document(res.body).select("#seriesContainer > div > ul > li > a").filter(e => e.attr("title").toLowerCase().includes(query.toLowerCase()));
const list = [];
for (const element of elements) {
const name = element.text;
const link = element.attr("href");
const img = new Document((await new Client().get(baseUrl + link)).body).selectFirst("div.seriesCoverBox img").attr("data-src");
const imageUrl = baseUrl + img;
list.push({ name, imageUrl, link });
}
return {
list: list,
hasNextPage: false
}
}
async getDetail(url) {
const baseUrl = this.source.baseUrl;
const res = await new Client().get(baseUrl + url);
const document = new Document(res.body);
const imageUrl = baseUrl +
document.selectFirst("div.seriesCoverBox img").attr("data-src");
const name = document.selectFirst("div.series-title h1 span").text;
const genre = document.select("div.genres ul li").map(e => e.text);
const description = document.selectFirst("p.seri_des").attr("data-full-description");
const produzent = document.select("div.cast li")
.filter(e => e.outerHtml.includes("Produzent:"));
let author = "";
if (produzent.length > 0) {
author = produzent[0].select("li").map(e => e.text).join(", ");
}
const seasonsElements = document.select("#stream > ul:nth-child(1) > li > a");
let episodes = [];
for (const element of seasonsElements) {
const eps = await this.parseEpisodesFromSeries(element);
for (const ep of eps) {
episodes.push(ep);
}
}
episodes.reverse();
return {
name, imageUrl, description, author, status: 5, genre, episodes
};
}
async parseEpisodesFromSeries(element) {
const seasonId = element.getHref;
const res = await new Client().get(this.source.baseUrl + seasonId);
const episodeElements = new Document(res.body).select("table.seasonEpisodesList tbody tr");
const list = [];
for (const episodeElement of episodeElements) {
list.push(this.episodeFromElement(episodeElement));
}
return list;
}
episodeFromElement(element) {
let name = "";
let url = "";
if (element.selectFirst("td.seasonEpisodeTitle a").attr("href").includes("/film")) {
const num = element.attr("data-episode-season-id");
name = `Film ${num}` + " : " + element.selectFirst("td.seasonEpisodeTitle a span").text;
url = element.selectFirst("td.seasonEpisodeTitle a").attr("href");
} else {
const season =
element.selectFirst("td.seasonEpisodeTitle a").attr("href").substringAfter("staffel-").substringBefore("/episode");;
const num = element.attr("data-episode-season-id");
name = `Staffel ${season} Folge ${num}` + " : " + element.selectFirst("td.seasonEpisodeTitle a span").text;
url = element.selectFirst("td.seasonEpisodeTitle a").attr("href");
}
if (name.length > 0 && url.length > 0) {
return { name, url }
}
return {}
}
async getVideoList(url) {
const baseUrl = this.source.baseUrl;
const res = await new Client().get(baseUrl + url);
const document = new Document(res.body);
const redirectlink = document.select("ul.row li");
const preference = new SharedPreferences();
const hosterSelection = preference.get("hoster_selection");
const videos = [];
for (const element of redirectlink) {
const langkey = element.attr("data-lang-key");
let language = "";
if (langkey.includes("3")) {
language = "Deutscher Sub";
} else if (langkey.includes("1")) {
language = "Deutscher Dub";
} else if (langkey.includes("2")) {
language = "Englischer Sub";
}
const redirectgs = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
const hoster = element.selectFirst("a h4").text;
if (hoster == "Streamtape" && hosterSelection.includes("Streamtape")) {
const body = (await new Client().get(redirectgs)).body;
const quality = `Streamtape ${language}`;
const vids = await streamTapeExtractor(body.match(/https:\/\/streamtape\.com\/e\/[a-zA-Z0-9]+/g)[0], quality);
for (const vid of vids) {
videos.push(vid);
}
} else if (hoster == "VOE" && hosterSelection.includes("VOE")) {
const body = (await new Client().get(redirectgs)).body;
const quality = `VOE ${language}`;
const vids = await voeExtractor(body.match(/https:\/\/voe\.sx\/e\/[a-zA-Z0-9]+/g)[0], quality);
for (const vid of vids) {
videos.push(vid);
}
} else if (hoster == "Vidoza" && hosterSelection.includes("Vidoza")) {
const body = (await new Client().get(redirectgs)).body;
const quality = `Vidoza ${language}`;
const match = body.match(/https:\/\/[^\s]*\.vidoza\.net\/[^\s]*\.mp4/g);
if (match.length > 0) {
videos.push({ url: match[0], originalUrl: match[0], quality });
}
}
}
return this.sortVideos(videos);
}
sortVideos(videos) {
const preference = new SharedPreferences();
const hoster = preference.get("preferred_hoster");
const subPreference = preference.get("preferred_lang");
videos.sort((a, b) => {
let qualityMatchA = 0;
if (a.quality.includes(hoster) &&
a.quality.includes(subPreference)) {
qualityMatchA = 1;
}
let qualityMatchB = 0;
if (b.quality.includes(hoster) &&
b.quality.includes(subPreference)) {
qualityMatchB = 1;
}
return qualityMatchB - qualityMatchA;
});
return videos;
}
getSourcePreferences() {
return [
{
"key": "preferred_lang",
"listPreference": {
"title": "Bevorzugte Sprache",
"summary": "",
"valueIndex": 0,
"entries": [
"Deutscher Sub",
"Deutscher Dub",
"Englischer Sub"
],
"entryValues": [
"Deutscher Sub",
"Deutscher Dub",
"Englischer Sub"
]
}
},
{
"key": "preferred_hoster",
"listPreference": {
"title": "Standard-Hoster",
"summary": "",
"valueIndex": 0,
"entries": [
"Streamtape",
"VOE",
"Vidoza"
],
"entryValues": [
"Streamtape",
"VOE",
"Vidoza"
]
}
},
{
"key": "hoster_selection",
"multiSelectListPreference": {
"title": "Hoster auswählen",
"summary": "",
"entries": [
"Streamtape",
"VOE",
"Vidoza"
],
"entryValues": [
"Streamtape",
"VOE",
"Vidoza"
],
"values": [
"Streamtape",
"VOE",
"Vidoza"
]
}
}
];
}
}

View File

@@ -0,0 +1,482 @@
const mangayomiSources = [{
"name": "AllAnime",
"lang": "en",
"baseUrl": "https://allanime.to",
"apiUrl": "https://api.allanime.day/api",
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/en.allanime.png",
"typeSource": "single",
"isManga": false,
"isNsfw": false,
"version": "0.0.1",
"apiUrl": "",
"dateFormat": "",
"dateFormatLocale": "",
"pkgName": "anime/src/en/allanime.js"
}];
class DefaultExtension extends MProvider {
async request(body) {
const apiUrl = this.source.apiUrl;
const baseUrl = this.source.baseUrl;
return (await new Client().get(apiUrl + body, { "Referer": baseUrl })).body
}
async getPopular(page) {
const encodedGql = `?variables=%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22type%22:%20%22anime%22,%0A%20%20%20%20%20%20%20%20%20%20%22size%22:%2026,%0A%20%20%20%20%20%20%20%20%20%20%22dateRange%22:%201,%0A%20%20%20%20%20%20%20%20%20%20%22page%22:%20${page}%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&query=%0A%20%20%20%20%20%20%20%20query($type:%20VaildPopularTypeEnumType!,%20$size:%20Int!,%20$dateRange:%20Int,%20$page:%20Int)%20%7B%0A%20%20%20%20%20%20%20%20%20%20queryPopular(type:%20$type,%20size:%20$size,%20dateRange:%20$dateRange,%20page:%20$page)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20recommendations%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20anyCard%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_id%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20englishName%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nativeName%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20thumbnail%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20slugTime%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20`
const resList = JSON.parse(await this.request(encodedGql)).data.queryPopular.recommendations.filter(e => e.anyCard !== null);
const preferences = new SharedPreferences();
const titleStyle = preferences.get("preferred_title_style");
const list = [];
for (const anime of resList) {
let title;
if (titleStyle === "romaji") {
title = anime.anyCard.name;
} else if (titleStyle === "eng") {
title = anime.anyCard.englishName || anime.anyCard.name;
} else {
title = anime.anyCard.nativeName || anime.anyCard.name;
}
const name = title;
const imageUrl = anime.anyCard.thumbnail;
const link = `/bangumi/${anime.anyCard._id}/${anime.anyCard.name.replace(/[^a-zA-Z0-9]/g, "-")
.replace(/-{2,}/g, "-")
.toLowerCase()}`;
list.push({ name, imageUrl, link });
}
return {
list: list,
hasNextPage: list.length === 26
}
}
async getLatestUpdates(page) {
return await this.search("", page, []);
}
async search(query, page, filters) {
query = query.replace(" ", "%20");
const encodedGql = `?variables=%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22search%22:%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22query%22:%20%22${query}%22,%0A%20%20%20%20%20%20%20%20%20%20%20%20%22allowAdult%22:%20false,%0A%20%20%20%20%20%20%20%20%20%20%20%20%22allowUnknown%22:%20false%0A%20%20%20%20%20%20%20%20%20%20%7D,%0A%20%20%20%20%20%20%20%20%20%20%22countryOrigin%22:%20%22ALL%22,%0A%20%20%20%20%20%20%20%20%20%20%22limit%22:%2026,%0A%20%20%20%20%20%20%20%20%20%20%22page%22:%20${page}%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&query=%0A%20%20%20%20%20%20%20%20query($search:%20SearchInput,%20$limit:%20Int,%20$countryOrigin:%20VaildCountryOriginEnumType,%20$page:%20Int)%20%7B%0A%20%20%20%20%20%20%20%20%20%20shows(search:%20$search,%20limit:%20$limit,%20countryOrigin:%20$countryOrigin,%20page:%20$page)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20edges%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20_id%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20nativeName%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20englishName%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20thumbnail%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20slugTime%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20`;
const resList = JSON.parse(await this.request(encodedGql)).data.shows.edges;
const preferences = new SharedPreferences();
const titleStyle = preferences.get("preferred_title_style");
const list = [];
for (const anime of resList) {
let title;
if (titleStyle === "romaji") {
title = anime.name;
} else if (titleStyle === "eng") {
title = anime.englishName || anime.name;
} else {
title = anime.nativeName || anime.name;
}
const name = title;
const imageUrl = anime.thumbnail;
const link = `/bangumi/${anime._id}/${anime.name.replace(/[^a-zA-Z0-9]/g, "-")
.replace(/-{2,}/g, "-")
.toLowerCase()}`;
list.push({ name, imageUrl, link });
}
return {
list: list,
hasNextPage: list.length === 26
}
}
async getDetail(url) {
const id = url.substringAfter('bangumi/').substringBefore('/');
const encodedGql = `?variables=%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22id%22:%20%22${id}%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&query=%0A%20%20%20%20%20%20%20%20query($id:%20String!)%20%7B%0A%20%20%20%20%20%20%20%20%20%20show(_id:%20$id)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20thumbnail%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20description%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20type%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20season%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20score%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20genres%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20status%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20studios%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20availableEpisodesDetail%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20`;
const show = JSON.parse(await this.request(encodedGql)).data.show;
const genre = show.genres || [];
const status = this.parseStatus(show.status);
const author = show.studios.length > 0 ? show.studios[0] : "";
let description = "";
description = description.concat(show.description, "\n\n", `Type: ${show.type || "Unknown"}`, `\nAired: ${show.season?.quarter || "-"} ${show.season?.year || "-"}`, `\nScore: ${show.score || "-"}`);
let episodesSub = [];
for (const episode of show.availableEpisodesDetail.sub) {
const num = parseInt(episode) || 1;
const name = `Episode ${num}`;
const url = JSON.stringify({
showId: id,
translationType: ["sub"],
episodeString: episode
});
const scanlator = "sub";
episodesSub.push({ num, name, url, scanlator });
}
let episodesDub = [];
for (const episode of show.availableEpisodesDetail.dub) {
const num = parseInt(episode) || 1;
const name = `Episode ${num}`;
const url = JSON.stringify({
showId: id,
translationType: ["dub"],
episodeString: episode
});
const scanlator = "dub";
episodesDub.push({ num, name, url, scanlator });
}
let episodes = [];
if (episodesSub.length > 0 && episodesSub.length) {
episodes = episodesSub.map(ep => {
const f = episodesDub.filter(e => e.num === ep.num);
if (f.length > 0) {
const url = JSON.parse(ep.url);
return {
"name": ep.name, "url": JSON.stringify({
showId: url.showId,
translationType: ['sub', 'dub'],
episodeString: url.episodeString
}), scanlator: `sub, dub`
}
}
else {
return ep;
}
})
} else {
episodes = episodesDub;
}
return {
description, author, status, genre, episodes
}
}
parseStatus(string) {
switch (string) {
case "Releasing":
return 0;
case "Finished":
return 1;
case "Not Yet Released":
return 0;
default:
return 5;
}
}
async getVideoList(url) {
const baseUrl = this.source.baseUrl;
const preferences = new SharedPreferences();
const subPref = preferences.get("preferred_sub");
const ep = JSON.parse(url);
const translationType = ep.translationType.filter(t => t === subPref);
if (translationType.length == 0) {
return [];
}
const encodedGql = `?variables=%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22showId%22:%20%22${ep.showId}%22,%0A%20%20%20%20%20%20%20%20%20%20%22episodeString%22:%20%22${ep.episodeString}%22,%0A%20%20%20%20%20%20%20%20%20%20%22translationType%22:%20%22${translationType[0]}%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&query=%0A%20%20%20%20%20%20%20%20query(%0A%20%20%20%20%20%20%20%20%20%20$showId:%20String!%0A%20%20%20%20%20%20%20%20%20%20$episodeString:%20String!%0A%20%20%20%20%20%20%20%20%20%20$translationType:%20VaildTranslationTypeEnumType!%0A%20%20%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20episode(%0A%20%20%20%20%20%20%20%20%20%20%20%20showId:%20$showId%0A%20%20%20%20%20%20%20%20%20%20%20%20episodeString:%20$episodeString%0A%20%20%20%20%20%20%20%20%20%20%20%20translationType:%20$translationType%0A%20%20%20%20%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrls%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20`;
const videoJson = JSON.parse(await this.request(encodedGql));
const videos = [];
const altHosterSelection = preferences.get('alt_hoster_selection');
for (const video of videoJson.data.episode.sourceUrls) {
const videoUrl = this.decryptSource(video.sourceUrl);
let quality = "";
if (videoUrl.includes("/apivtwo/") && altHosterSelection.some(element => 'player' === element)) {
quality = `internal ${video.sourceName}`;
const vids = await new AllAnimeExtractor({ "Referer": baseUrl }, "https://allanime.to").videoFromUrl(videoUrl, quality);
for (const vid of vids) {
videos.push(vid);
}
} else if (["vidstreaming", "https://gogo", "playgo1.cc", "playtaku"].some(element => videoUrl.includes(element)) && altHosterSelection.some(element => 'vidstreaming' === element)) {
const vids = await gogoCdnExtractor(videoUrl);
for (const vid of vids) {
videos.push(vid);
}
} else if (["dood", "d0"].some(element => videoUrl.includes(element)) && altHosterSelection.some(element => 'dood' === element)) {
const vids = await doodExtractor(videoUrl);
for (const vid of vids) {
videos.push(vid);
}
} else if (videoUrl.includes("ok.ru") && altHosterSelection.some(element => 'okru' === element)) {
const vids = await okruExtractor(videoUrl);
for (const vid of vids) {
videos.push(vid);
}
} else if (videoUrl.includes("mp4upload.com") && altHosterSelection.some(element => 'mp4upload' === element)) {
const vids = await mp4UploadExtractor(videoUrl);
for (const vid of vids) {
videos.push(vid);
}
} else if (videoUrl.includes("streamlare.com") && altHosterSelection.some(element => 'streamlare' === element)) {
const vids = await streamlareExtractor(videoUrl);
for (const vid of vids) {
videos.push(vid);
}
}
}
return this.sortVideos(videos);
}
sortVideos(videos) {
const preferences = new SharedPreferences();
const hoster = preferences.get("preferred_hoster");
const quality = preferences.get("preferred_quality");
videos.sort((a, b) => {
let qualityMatchA = 0;
if (a.quality.includes(hoster) &&
a.quality.includes(quality)) {
qualityMatchA = 1;
}
let qualityMatchB = 0;
if (b.quality.includes(hoster) &&
b.quality.includes(quality)) {
qualityMatchB = 1;
}
return qualityMatchB - qualityMatchA;
});
return videos;
}
decryptSource(str) {
if (str.startsWith("-")) {
return str.substring(str.lastIndexOf('-') + 1)
.match(/.{1,2}/g)
.map(hex => parseInt(hex, 16))
.map(byte => String.fromCharCode(byte ^ 56))
.join("");
} else {
return str;
}
}
getSourcePreferences() {
return [
{
"key": "preferred_title_style",
"listPreference": {
"title": "Preferred Title Style",
"summary": "",
"valueIndex": 0,
"entries": ["Romaji", "English", "Native"],
"entryValues": ["romaji", "eng", "native"]
}
},
{
"key": "preferred_quality",
"listPreference": {
"title": "Preferred quality",
"summary": "",
"valueIndex": 0,
"entries": [
"2160p",
"1440p",
"1080p",
"720p",
"480p",
"360p",
"240p",
"80p"],
"entryValues": [
"2160",
"1440",
"1080",
"720",
"480",
"360",
"240",
"80"]
}
},
{
"key": "preferred_sub",
"listPreference": {
"title": "Prefer subs or dubs?",
"summary": "",
"valueIndex": 0,
"entries": ["Subs", "Dubs"],
"entryValues": ["sub", "dub"]
}
},
{
"key": "preferred_hoster",
"listPreference": {
"title": "Preferred Video Server",
"summary": "",
"valueIndex": 0,
"entries": [
"Ac", "Ak", "Kir", "Rab", "Luf-mp4",
"Si-Hls", "S-mp4", "Ac-Hls", "Uv-mp4", "Pn-Hls",
"vidstreaming", "okru", "mp4upload", "streamlare", "doodstream"
],
"entryValues": [
"Ac", "Ak", "Kir", "Rab", "Luf-mp4",
"Si-Hls", "S-mp4", "Ac-Hls", "Uv-mp4", "Pn-Hls",
"vidstreaming", "okru", "mp4upload", "streamlare", "doodstream"
]
}
},
{
"key": "alt_hoster_selection",
"multiSelectListPreference": {
"title": "Enable/Disable Alternative Hosts",
"summary": "",
"entries": [
"player",
"vidstreaming",
"okru",
"mp4upload",
"streamlare",
"doodstream"
],
"entryValues": [
"player",
"vidstreaming",
"okru",
"mp4upload",
"streamlare",
"doodstream"
],
"values": [
"player",
"vidstreaming",
"okru",
"mp4upload",
"streamlare",
"doodstream"
]
}
}
];
}
}
class AllAnimeExtractor {
constructor(headers, baseUrl) {
this.headers = headers;
this.baseUrl = baseUrl;
}
bytesIntoHumanReadable(bytes) {
const kilobyte = 1000;
const megabyte = kilobyte * 1000;
const gigabyte = megabyte * 1000;
const terabyte = gigabyte * 1000;
if (bytes >= 0 && bytes < kilobyte) {
return `${bytes} b/s`;
} else if (bytes >= kilobyte && bytes < megabyte) {
return `${Math.floor(bytes / kilobyte)} kb/s`;
} else if (bytes >= megabyte && bytes < gigabyte) {
return `${Math.floor(bytes / megabyte)} mb/s`;
} else if (bytes >= gigabyte && bytes < terabyte) {
return `${Math.floor(bytes / gigabyte)} gb/s`;
} else if (bytes >= terabyte) {
return `${Math.floor(bytes / terabyte)} tb/s`;
} else {
return `${bytes} bits/s`;
}
}
async videoFromUrl(url, name) {
const videoList = [];
const endPointResponse = JSON.parse((await new Client().get(`${this.baseUrl}/getVersion`, this.headers)).body);
const endPoint = endPointResponse.episodeIframeHead;
const resp = await new Client().get(endPoint + url.replace("/clock?", "/clock.json?"), this.headers);
if (resp.statusCode !== 200) {
return [];
}
const linkJson = JSON.parse(resp.body);
for (const link of linkJson.links) {
const subtitles = [];
if (link.subtitles && link.subtitles.length > 0) {
subtitles.push(...link.subtitles.map(sub => {
const label = sub.label ? ` - ${sub.label}` : '';
return { file: sub.src, label: `${sub.lang}${label}` };
}));
}
if (link.mp4) {
videoList.push({
url:
link.link,
quality: `Original (${name} - ${link.resolutionStr})`,
originalUrl: link.link,
subtitles,
});
} else if (link.hls) {
const headers =
{
'Host': link.link.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1],
'Origin': endPoint,
'Referer': `${endPoint}/`
};
const resp = await new Client().get(link.link, headers);
if (resp.statusCode === 200) {
const masterPlaylist = resp.body;
const audios = [];
if (masterPlaylist.includes('#EXT-X-MEDIA:TYPE=AUDIO')) {
const audioInfo = masterPlaylist.substringAfter('#EXT-X-MEDIA:TYPE=AUDIO').substringBefore('\n');
const language = audioInfo.substringAfter('NAME="').substringBefore('"');
const url = audioInfo.substringAfter('URI="').substringBefore('"');
audios.push({ file: url, label: language });
}
if (!masterPlaylist.includes('#EXT-X-STREAM-INF:')) {
if (audios.length === 0) {
videoList.push({ url: link.link, quality: `${name} - ${link.resolutionStr}`, originalUrl: link.link, subtitles, headers });
} else {
videoList.push({ url: link.link, quality: `${name} - ${link.resolutionStr}`, originalUrl: link.link, subtitles, audios, headers });
}
} else {
masterPlaylist.substringAfter('#EXT-X-STREAM-INF:').split('#EXT-X-STREAM-INF:').forEach(it => {
let bandwidth = '';
if (it.includes('AVERAGE-BANDWIDTH')) {
bandwidth = ` ${this.bytesIntoHumanReadable(it.substringAfter('AVERAGE-BANDWIDTH=').substringBefore(','))}`;
}
const quality = `${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p${bandwidth} (${name} - ${link.resolutionStr})`;
let videoUrl = it.substringAfter('\n').substringBefore('\n');
if (!videoUrl.startsWith('http')) {
videoUrl = resp.request.url.substringBeforeLast('/') + `/${videoUrl}`;
}
const headers =
{
'Host': videoUrl.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1],
'Origin': endPoint,
'Referer': `${endPoint}/`
};
if (audios.length === 0) {
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, subtitles, headers });
} else {
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, subtitles, audios, headers });
}
});
}
}
} else if (link.crIframe) {
for (const stream of link.portData.streams) {
if (stream.format === 'adaptive_dash') {
videoList.push({
url:
stream.url,
quality: `Original (AC - Dash${stream.hardsub_lang.length === 0 ? '' : ` - Hardsub: ${stream.hardsub_lang}`})`,
originalUrl: stream.url,
subtitles,
});
} else if (stream.format === 'adaptive_hls') {
const resp = await new Client().get(stream.url, { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0' })
if (resp.statusCode === 200) {
const masterPlaylist = resp.body;
masterPlaylist.substringAfter('#EXT-X-STREAM-INF:').split('#EXT-X-STREAM-INF:').forEach(t => {
const quality = `${t.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p (AC - HLS${stream.hardsub_lang.length === 0 ? '' : ` - Hardsub: ${stream.hardsub_lang}`})`;
const videoUrl = t.substringAfter('\n').substringBefore('\n');
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, subtitles });
});
}
}
}
} else if (link.dash) {
const audios = link.rawUrls && link.rawUrls.audios ? link.rawUrls.audios.map(it => { return { file: it.url, label: this.bytesIntoHumanReadable(it.bandwidth) }; }) : [];
const videos = link.rawUrls && link.rawUrls.vids ? link.rawUrls.vids.map
(it => {
if (!audios) {
return { url: it.url, quality: `${name} - ${it.height} ${this.bytesIntoHumanReadable(it.bandwidth)}`, originalUrl: it.url, subtitles };
} else {
return { url: it.url, quality: `${name} - ${it.height} ${this.bytesIntoHumanReadable(it.bandwidth)}`, originalUrl: it.url, audios, subtitles };
}
}) : [];
if (videos.length > 0) {
videoList.push(...videos);
}
}
}
return videoList;
}
}