mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
Merge branch 'main' into main
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -14,9 +14,13 @@ const mangayomiSources = [{
|
||||
}];
|
||||
|
||||
class DefaultExtension extends MProvider {
|
||||
constructor () {
|
||||
super();
|
||||
this.client = new Client();
|
||||
}
|
||||
async getPopular(page) {
|
||||
const baseUrl = this.source.baseUrl;
|
||||
const res = await new Client().get(`${baseUrl}/beliebte-animes`);
|
||||
const res = await this.client.get(`${baseUrl}/beliebte-animes`);
|
||||
const elements = new Document(res.body).select("div.seriesListContainer div");
|
||||
const list = [];
|
||||
for (const element of elements) {
|
||||
@@ -33,7 +37,7 @@ class DefaultExtension extends MProvider {
|
||||
}
|
||||
async getLatestUpdates(page) {
|
||||
const baseUrl = this.source.baseUrl;
|
||||
const res = await new Client().get(`${baseUrl}/neu`);
|
||||
const res = await this.client.get(`${baseUrl}/neu`);
|
||||
const elements = new Document(res.body).select("div.seriesListContainer div");
|
||||
const list = [];
|
||||
for (const element of elements) {
|
||||
@@ -50,13 +54,13 @@ class DefaultExtension extends MProvider {
|
||||
}
|
||||
async search(query, page, filters) {
|
||||
const baseUrl = this.source.baseUrl;
|
||||
const res = await new Client().get(`${baseUrl}/animes`);
|
||||
const res = await this.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 img = new Document((await this.client.get(baseUrl + link)).body).selectFirst("div.seriesCoverBox img").attr("data-src");
|
||||
const imageUrl = baseUrl + img;
|
||||
list.push({ name, imageUrl, link });
|
||||
}
|
||||
@@ -67,8 +71,7 @@ class DefaultExtension extends MProvider {
|
||||
}
|
||||
async getDetail(url) {
|
||||
const baseUrl = this.source.baseUrl;
|
||||
const client = new Client();
|
||||
const res = await client.get(baseUrl + url);
|
||||
const res = await this.client.get(baseUrl + url);
|
||||
const document = new Document(res.body);
|
||||
const imageUrl = baseUrl +
|
||||
document.selectFirst("div.seriesCoverBox img").attr("data-src");
|
||||
@@ -82,18 +85,29 @@ class DefaultExtension extends MProvider {
|
||||
author = produzent[0].select("li").map(e => e.text).join(", ");
|
||||
}
|
||||
const seasonsElements = document.select("#stream > ul:nth-child(1) > li > a");
|
||||
const episodes = (await Promise.all(seasonsElements.map(element => this.parseEpisodesFromSeries(element, client)))).flat();
|
||||
|
||||
const promises = [];
|
||||
const episodes = [];
|
||||
for (const element of seasonsElements) {
|
||||
promises.push(this.parseEpisodesFromSeries(element));
|
||||
}
|
||||
for (const p of (await Promise.allSettled(promises))) {
|
||||
if (p.status == 'fulfilled') {
|
||||
episodes.push(...p.value);
|
||||
}
|
||||
}
|
||||
episodes.reverse();
|
||||
return {
|
||||
name, imageUrl, description, author, status: 5, genre, episodes
|
||||
};
|
||||
return { name, imageUrl, description, author, status: 5, genre, episodes };
|
||||
}
|
||||
async parseEpisodesFromSeries(seriesElement, client) {
|
||||
const seasonId = seriesElement.getHref;
|
||||
const response = await client.get(`${this.source.baseUrl}${seasonId}`);
|
||||
const episodeElements = new Document(response.body).select("table.seasonEpisodesList tbody tr");
|
||||
const episodes = Array.from(episodeElements).map((episodeElement) => this.episodeFromElement(episodeElement));
|
||||
return episodes.filter(ep => Object.keys(ep).length > 0);
|
||||
async parseEpisodesFromSeries(element) {
|
||||
const seasonId = element.getHref;
|
||||
const res = await this.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) {
|
||||
const titleAnchor = element.selectFirst("td.seasonEpisodeTitle a");
|
||||
@@ -110,109 +124,50 @@ class DefaultExtension extends MProvider {
|
||||
}
|
||||
return name && url ? { name, url } : {};
|
||||
}
|
||||
getRandomString(length) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const random = Math.floor(Math.random() * 61);
|
||||
result += chars[random];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async doodExtractor(url, quality) {
|
||||
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(url);
|
||||
while("location" in response.headers) {
|
||||
response = await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(response.headers.location);
|
||||
}
|
||||
const newUrl = response.request.url;
|
||||
const doodhost = newUrl.match(/https:\/\/(.*?)\//, newUrl)[0].slice(8, -1);
|
||||
const md5 = response.body.match(/'\/pass_md5\/(.*?)',/, newUrl)[0].slice(11, -2);
|
||||
const token = md5.substring(md5.lastIndexOf("/") + 1);
|
||||
const expiry = new Date().valueOf();
|
||||
const randomString = this.getRandomString(10);
|
||||
|
||||
response = await new Client().get(`https://${doodhost}/pass_md5/${md5}`, {"Referer": newUrl});
|
||||
const videoUrl = `${response.body}${randomString}?token=${token}&expiry=${expiry}`;
|
||||
const headers = { "User-Agent": "Mangayomi", "Referer": doodhost };
|
||||
return [{ url: videoUrl, originalUrl: videoUrl, headers: headers, quality }];
|
||||
}
|
||||
async vidozaExtractor(url, quality) {
|
||||
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": true }).get(url);
|
||||
const videoUrl = response.body.match(/https:\/\/\S*\.mp4/)[0];
|
||||
return [{ url: videoUrl, originalUrl: videoUrl, quality }];
|
||||
}
|
||||
async getVideoList(url) {
|
||||
const baseUrl = this.source.baseUrl;
|
||||
const res = await new Client().get(baseUrl + url);
|
||||
const res = await this.client.get(baseUrl + url, {
|
||||
'Accept': '*/*',
|
||||
'Referer': baseUrl + url,
|
||||
'Priority': 'u=0, i',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
|
||||
});
|
||||
const document = new Document(res.body);
|
||||
const redirectlink = document.select("ul.row li");
|
||||
const preference = new SharedPreferences();
|
||||
const hosterSelection = preference.get("hoster_selection_new");
|
||||
let promises = [];
|
||||
const videos = [];
|
||||
for (const element of redirectlink) {
|
||||
try {
|
||||
|
||||
const redirectsElements = document.select("ul.row li");
|
||||
const hosterSelection = new SharedPreferences().get("hoster_selection_new");
|
||||
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||
|
||||
for (const element of redirectsElements) {
|
||||
const host = element.selectFirst("a h4").text;
|
||||
|
||||
if (hosterSelection.includes(host)) {
|
||||
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 location = (await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(redirectgs)).headers.location;
|
||||
const quality = `${language} - Streamtape`;
|
||||
const vids = await streamTapeExtractor(location, quality);
|
||||
for (const vid of vids) {
|
||||
videos.push(vid);
|
||||
}
|
||||
} else if (hoster == "VOE" && hosterSelection.includes("VOE")) {
|
||||
const location = (await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(redirectgs)).headers.location;
|
||||
const quality = `${language} - `;
|
||||
const vids = await voeExtractor(location, quality);
|
||||
for (const vid of vids) {
|
||||
videos.push(vid);
|
||||
}
|
||||
} else if (hoster == "Vidoza" && hosterSelection.includes("Vidoza")) {
|
||||
const location = (await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(redirectgs)).headers.location;
|
||||
const quality = `${language} - Vidoza`;
|
||||
const vids = await this.vidozaExtractor(location, quality);
|
||||
for (const vid of vids) {
|
||||
videos.push(vid);
|
||||
}
|
||||
} else if (hoster == "Doodstream" && hosterSelection.includes("Doodstream")) {
|
||||
const location = (await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(redirectgs)).headers.location;
|
||||
const quality = `${language} - Doodstream`;
|
||||
const vids = await this.doodExtractor(location, quality);
|
||||
for (const vid of vids) {
|
||||
videos.push(vid);
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
|
||||
const lang = (langkey == 1 || langkey == 3) ? 'Deutscher' : 'Englischer';
|
||||
const type = (langkey == 1) ? 'Dub' : 'Sub';
|
||||
const redirect = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
|
||||
promises.push((async (redirect, lang, type, host) => {
|
||||
const location = (await dartClient.get(redirect)).headers.location;
|
||||
return await extractAny(location, host.toLowerCase(), lang, type, host);
|
||||
})(redirect, lang, type, host));
|
||||
}
|
||||
}
|
||||
for (const p of (await Promise.allSettled(promises))) {
|
||||
if (p.status == 'fulfilled') {
|
||||
videos.push.apply(videos, p.value);
|
||||
}
|
||||
}
|
||||
return this.sortVideos(videos);
|
||||
}
|
||||
sortVideos(videos) {
|
||||
const preference = new SharedPreferences();
|
||||
const hoster = preference.get("preferred_hoster_new");
|
||||
const subPreference = preference.get("preferred_lang");
|
||||
const hoster = RegExp(preference.get("preferred_hoster_new"));
|
||||
const lang = RegExp(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;
|
||||
}
|
||||
let qualityMatchA = hoster.test(a.quality) * lang.test(a.quality);
|
||||
let qualityMatchB = hoster.test(b.quality) * lang.test(b.quality);
|
||||
return qualityMatchB - qualityMatchA;
|
||||
});
|
||||
return videos;
|
||||
@@ -246,12 +201,14 @@ class DefaultExtension extends MProvider {
|
||||
"entries": [
|
||||
"Streamtape",
|
||||
"VOE",
|
||||
"Vidoza", "Doodstream"
|
||||
"Vidoza",
|
||||
"Doodstream"
|
||||
],
|
||||
"entryValues": [
|
||||
"Streamtape",
|
||||
"VOE",
|
||||
"Vidoza", "Doodstream"
|
||||
"Voe",
|
||||
"Vidoza",
|
||||
"Doodstream"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -263,20 +220,86 @@ class DefaultExtension extends MProvider {
|
||||
"entries": [
|
||||
"Streamtape",
|
||||
"VOE",
|
||||
"Vidoza", "Doodstream"
|
||||
"Vidoza",
|
||||
"Doodstream"
|
||||
],
|
||||
"entryValues": [
|
||||
"Streamtape",
|
||||
"VOE",
|
||||
"Vidoza", "Doodstream"
|
||||
"Vidoza",
|
||||
"Doodstream"
|
||||
],
|
||||
"values": [
|
||||
"Streamtape",
|
||||
"VOE",
|
||||
"Vidoza", "Doodstream"
|
||||
"Vidoza",
|
||||
"Doodstream"
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
async function doodExtractor(url) {
|
||||
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||
let response = await dartClient.get(url);
|
||||
while ("location" in response.headers) {
|
||||
response = await dartClient.get(response.headers.location);
|
||||
}
|
||||
const newUrl = response.request.url;
|
||||
const doodhost = newUrl.match(/https:\/\/(.*?)\//, newUrl)[0].slice(8, -1);
|
||||
const md5 = response.body.match(/'\/pass_md5\/(.*?)',/, newUrl)[0].slice(11, -2);
|
||||
const token = md5.substring(md5.lastIndexOf("/") + 1);
|
||||
const expiry = new Date().valueOf();
|
||||
const randomString = getRandomString(10);
|
||||
|
||||
response = await new Client().get(`https://${doodhost}/pass_md5/${md5}`, { "Referer": newUrl });
|
||||
const videoUrl = `${response.body}${randomString}?token=${token}&expiry=${expiry}`;
|
||||
const headers = { "User-Agent": "Mangayomi", "Referer": doodhost };
|
||||
return [{ url: videoUrl, originalUrl: videoUrl, headers: headers, quality: '' }];
|
||||
}
|
||||
|
||||
async function vidozaExtractor(url) {
|
||||
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": true }).get(url);
|
||||
const videoUrl = response.body.match(/https:\/\/\S*\.mp4/)[0];
|
||||
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
||||
}
|
||||
|
||||
_streamTapeExtractor = streamTapeExtractor;
|
||||
streamTapeExtractor = async (url) => {
|
||||
return await _streamTapeExtractor(url, '');
|
||||
}
|
||||
|
||||
_voeExtractor = voeExtractor;
|
||||
voeExtractor = async (url) => {
|
||||
return (await _voeExtractor(url, '')).map(v => {
|
||||
v.quality = v.quality.replace(/Voe: (\d+p?)/i, '$1');
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
async function extractAny(link, method, lang, type, host) {
|
||||
const m = extractAny.methods[method];
|
||||
return (!m) ? [] : (await m(link)).map(v => {
|
||||
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||
return v;
|
||||
});
|
||||
};
|
||||
|
||||
extractAny.methods = {
|
||||
'doodstream': doodExtractor,
|
||||
'streamtape': streamTapeExtractor,
|
||||
'vidoza': vidozaExtractor,
|
||||
'voe': voeExtractor
|
||||
};
|
||||
|
||||
function getRandomString(length) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const random = Math.floor(Math.random() * 61);
|
||||
result += chars[random];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
1128
javascript/anime/src/es/animefenix.js
Normal file
1128
javascript/anime/src/es/animefenix.js
Normal file
File diff suppressed because it is too large
Load Diff
1398
javascript/anime/src/es/jkanime.js
Normal file
1398
javascript/anime/src/es/jkanime.js
Normal file
File diff suppressed because it is too large
Load Diff
717
javascript/anime/src/es/tioanime.js
Normal file
717
javascript/anime/src/es/tioanime.js
Normal file
@@ -0,0 +1,717 @@
|
||||
const mangayomiSources = [{
|
||||
"name": "TioAnime",
|
||||
"lang": "es",
|
||||
"baseUrl": "https://tioanime.com",
|
||||
"apiUrl": "",
|
||||
"iconUrl": "https://tioanime.com/assets/img/tio_fb.jpg",
|
||||
"typeSource": "single",
|
||||
"isManga": false,
|
||||
"version": "0.1.0",
|
||||
"dateFormat": "",
|
||||
"dateFormatLocale": "",
|
||||
"pkgPath": "anime/src/es/tioanime.js"
|
||||
}];
|
||||
|
||||
class DefaultExtension extends MProvider {
|
||||
constructor () {
|
||||
super();
|
||||
this.client = new Client();
|
||||
}
|
||||
getHeaders(url) {
|
||||
throw new Error("getHeaders not implemented");
|
||||
}
|
||||
async parseAnimeList(url) {
|
||||
const res = await this.client.get(url);
|
||||
const doc = new Document(res.body);
|
||||
const elements = doc.select("ul.animes > li");
|
||||
const list = [];
|
||||
|
||||
for (const element of elements) {
|
||||
const name = element.selectFirst(".title").text;
|
||||
const imageUrl = this.source.baseUrl + element.selectFirst("img").getSrc;
|
||||
const link = element.selectFirst("a").getHref;
|
||||
list.push({ name, imageUrl, link });
|
||||
}
|
||||
const hasNextPage = doc.selectFirst("li.page-item.active + li").text == "»";
|
||||
return { "list": list, "hasNextPage": hasNextPage };
|
||||
}
|
||||
statusFromString(status) {
|
||||
return {
|
||||
"En emision": 0,
|
||||
"Finalizado": 1
|
||||
}[status] ?? 5;
|
||||
}
|
||||
async getPopular(page) {
|
||||
return this.parseAnimeList(`${this.source.baseUrl}/directorio?p=${page}`);
|
||||
}
|
||||
async getLatestUpdates(page) {
|
||||
return this.parseAnimeList(`${this.source.baseUrl}/directorio?p=${page}`);
|
||||
}
|
||||
async search(query, page, filters) {
|
||||
query = query.trim().replaceAll(/\ +/g, "+");
|
||||
let url = `${this.source.baseUrl}/directorio?q=${query}&p=${page}`;
|
||||
return this.parseAnimeList(url);
|
||||
}
|
||||
async getDetail(url) {
|
||||
const res = await this.client.get(this.source.baseUrl + url);
|
||||
const doc = new Document(res.body);
|
||||
const detail = {};
|
||||
|
||||
const info = doc.selectFirst("article.anime-single");
|
||||
const episodeCount = parseInt(/episodes = \[(\d+)/.exec(doc.select("script").pop().innerHtml)[1]);
|
||||
const episodeUrl = url.replace("/anime", "/ver") + "-";
|
||||
|
||||
detail.name = info.selectFirst("h1").text;
|
||||
detail.status = this.statusFromString(info.selectFirst("a").text);
|
||||
detail.imageUrl = this.source.baseUrl + info.selectFirst("img").getSrc;
|
||||
detail.description = info.selectFirst("p.sinopsis").text.trim();
|
||||
detail.genre = info.select("p.genres a").map(e => e.text.trim());
|
||||
detail.episodes = [];
|
||||
for (let i = 0; i < episodeCount; i++) {
|
||||
const name = `Episodio ${i + 1}`;
|
||||
const url = episodeUrl + (i + 1);
|
||||
detail.episodes.push({ name, url });
|
||||
}
|
||||
detail.episodes.reverse();
|
||||
return detail;
|
||||
}
|
||||
// For anime episode video list
|
||||
async getVideoList(url) {
|
||||
const res = await this.client.get(this.source.baseUrl + url);
|
||||
const doc = new Document(res.body);
|
||||
let promises = [];
|
||||
const videos = [];
|
||||
|
||||
// get type
|
||||
const type = /\blatino\b/i.test(url) ? 'Dub' : 'Sub';
|
||||
|
||||
// get links
|
||||
const raws = [...doc.select("script").pop().innerHtml.matchAll(/\[".*?\]/g)];
|
||||
|
||||
// extract videos
|
||||
for (const raw of raws) {
|
||||
const data = JSON.parse(raw[0]);
|
||||
const host = data[0];
|
||||
const link = data[1];
|
||||
promises.push(extractAny(link, host.toLowerCase(), 'Español', type, host));
|
||||
}
|
||||
for (const p of (await Promise.allSettled(promises))) {
|
||||
if (p.status == 'fulfilled') {
|
||||
videos.push.apply(videos, p.value);
|
||||
}
|
||||
}
|
||||
return sortVideos(videos);
|
||||
}
|
||||
getFilterList() {
|
||||
throw new Error("getFilterList not implemented");
|
||||
}
|
||||
getSourcePreferences() {
|
||||
return [
|
||||
{
|
||||
key: 'lang',
|
||||
listPreference: {
|
||||
title: 'Preferred Language',
|
||||
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
'Español'
|
||||
],
|
||||
entryValues: [
|
||||
'Español'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
listPreference: {
|
||||
title: 'Preferred Type',
|
||||
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
'Sub'
|
||||
],
|
||||
entryValues: [
|
||||
'Sub'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'res',
|
||||
listPreference: {
|
||||
title: 'Preferred Resolution',
|
||||
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
'1080p',
|
||||
'720p',
|
||||
'480p'
|
||||
],
|
||||
entryValues: [
|
||||
'1080p',
|
||||
'720p',
|
||||
'480p'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'host',
|
||||
listPreference: {
|
||||
title: 'Preferred Hoster',
|
||||
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
'Okru',
|
||||
'VidGuard',
|
||||
'Voe',
|
||||
'YourUpload'
|
||||
],
|
||||
entryValues: [
|
||||
'Okru',
|
||||
'VidGuard',
|
||||
'Voe',
|
||||
'YourUpload'
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************************************
|
||||
*
|
||||
* mangayomi-js-helpers v1.0
|
||||
*
|
||||
* # Video Extractors
|
||||
* - vidGuardExtractor
|
||||
* - doodExtractor
|
||||
* - vidozaExtractor
|
||||
* - okruExtractor
|
||||
* - amazonExtractor
|
||||
* - vidHideExtractor
|
||||
* - filemoonExtractor
|
||||
* - mixdropExtractor
|
||||
* - burstcloudExtractor (not working, see description)
|
||||
*
|
||||
* # Video Extractor Format Wrappers
|
||||
* - streamWishExtractor
|
||||
* - voeExtractor
|
||||
* - mp4UploadExtractor
|
||||
* - yourUploadExtractor
|
||||
* - streamTapeExtractor
|
||||
* - sendVidExtractor
|
||||
*
|
||||
* # Video Extractor helpers
|
||||
* - extractAny
|
||||
*
|
||||
* # Playlist Extractors
|
||||
* - m3u8Extractor
|
||||
* - jwplayerExtractor
|
||||
*
|
||||
* # Extension
|
||||
* - sortVideos()
|
||||
*
|
||||
* # Encoding/Decoding
|
||||
* - Uint8Array.fromBase64()
|
||||
* - Uint8Array.prototype.toBase64()
|
||||
* - Uint8Array.prototype.decode()
|
||||
* - String.prototype.encode()
|
||||
* - String.prototype.decode()
|
||||
*
|
||||
* # Random string
|
||||
* - getRandomString()
|
||||
*
|
||||
* # URL
|
||||
* - absUrl()
|
||||
*
|
||||
***************************************************************************************************/
|
||||
|
||||
async function vidGuardExtractor(url) {
|
||||
// get html
|
||||
const res = await new Client().get(url);
|
||||
const doc = new Document(res.body);
|
||||
const script = doc.selectFirst('script:contains(eval)');
|
||||
|
||||
// eval code
|
||||
const code = script.text;
|
||||
eval?.('var window = {};');
|
||||
eval?.(code);
|
||||
const playlistUrl = globalThis.window.svg.stream;
|
||||
|
||||
// decode sig
|
||||
const encoded = playlistUrl.match(/sig=(.*?)&/)[1];
|
||||
const charCodes = [];
|
||||
|
||||
for (let i = 0; i < encoded.length; i += 2) {
|
||||
charCodes.push(parseInt(encoded.slice(i, i + 2), 16) ^ 2);
|
||||
}
|
||||
|
||||
let decoded = Uint8Array.fromBase64(
|
||||
String.fromCharCode(...charCodes))
|
||||
.slice(5, -5)
|
||||
.reverse();
|
||||
|
||||
for (let i = 0; i < decoded.length; i += 2) {
|
||||
let tmp = decoded[i];
|
||||
decoded[i] = decoded[i + 1];
|
||||
decoded[i + 1] = tmp;
|
||||
}
|
||||
|
||||
decoded = decoded.decode();
|
||||
return await m3u8Extractor(playlistUrl.replace(encoded, decoded), null);
|
||||
}
|
||||
|
||||
async function doodExtractor(url) {
|
||||
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||
let response = await dartClient.get(url);
|
||||
while ("location" in response.headers) {
|
||||
response = await dartClient.get(response.headers.location);
|
||||
}
|
||||
const newUrl = response.request.url;
|
||||
const doodhost = newUrl.match(/https:\/\/(.*?)\//, newUrl)[0].slice(8, -1);
|
||||
const md5 = response.body.match(/'\/pass_md5\/(.*?)',/, newUrl)[0].slice(11, -2);
|
||||
const token = md5.substring(md5.lastIndexOf("/") + 1);
|
||||
const expiry = new Date().valueOf();
|
||||
const randomString = getRandomString(10);
|
||||
|
||||
response = await new Client().get(`https://${doodhost}/pass_md5/${md5}`, { "Referer": newUrl });
|
||||
const videoUrl = `${response.body}${randomString}?token=${token}&expiry=${expiry}`;
|
||||
const headers = { "User-Agent": "Mangayomi", "Referer": doodhost };
|
||||
return [{ url: videoUrl, originalUrl: videoUrl, headers: headers, quality: '' }];
|
||||
}
|
||||
|
||||
async function vidozaExtractor(url) {
|
||||
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": true }).get(url);
|
||||
const videoUrl = response.body.match(/https:\/\/\S*\.mp4/)[0];
|
||||
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
||||
}
|
||||
|
||||
async function okruExtractor(url) {
|
||||
const res = await new Client().get(url);
|
||||
const doc = new Document(res.body);
|
||||
const tag = doc.selectFirst('div[data-options]');
|
||||
const playlistUrl = tag.attr('data-options').match(/hlsManifestUrl.*?(h.*?id=\d+)/)[1].replaceAll('\\\\u0026', '&');
|
||||
return await m3u8Extractor(playlistUrl, null);
|
||||
}
|
||||
|
||||
async function amazonExtractor(url) {
|
||||
const res = await new Client().get(url);
|
||||
const doc = new Document(res.body);
|
||||
const videoUrl = doc.selectFirst('video').getSrc;
|
||||
return videoUrl ? [{ url: videoUrl, originalUrl: videoUrl, headers: null, quality: '' }] : [];
|
||||
}
|
||||
|
||||
async function vidHideExtractor(url) {
|
||||
const res = await new Client().get(url);
|
||||
return await jwplayerExtractor(res.body);
|
||||
}
|
||||
|
||||
async function filemoonExtractor(url) {
|
||||
let res = await new Client().get(url);
|
||||
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
||||
if (src) {
|
||||
res = await new Client().get(src, {
|
||||
'Referer': url,
|
||||
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3'
|
||||
});
|
||||
}
|
||||
return await jwplayerExtractor(res.body);
|
||||
}
|
||||
|
||||
async function mixdropExtractor(url) {
|
||||
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'};
|
||||
let res = await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(url, headers);
|
||||
while ("location" in res.headers) {
|
||||
res = await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(res.headers.location, headers);
|
||||
}
|
||||
const newUrl = res.request.url;
|
||||
let doc = new Document(res.body);
|
||||
|
||||
const code = doc.selectFirst('script:contains(MDCore):contains(eval)').text;
|
||||
const unpacked = unpackJs(code);
|
||||
let videoUrl = unpacked.match(/wurl="(.*?)"/)?.[1];
|
||||
|
||||
if (!videoUrl) return [];
|
||||
|
||||
videoUrl = 'https:' + videoUrl;
|
||||
headers.referer = newUrl;
|
||||
|
||||
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
||||
}
|
||||
|
||||
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
||||
async function burstcloudExtractor(url) {
|
||||
let client = new Client();
|
||||
let res = await client.get(url);
|
||||
|
||||
const id = res.body.match(/data-file-id="(.*?)"/)[1];
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
'Referer': url,
|
||||
};
|
||||
const data = {
|
||||
'fileId': id
|
||||
};
|
||||
|
||||
res = await client.post(`https://www.burstcloud.co/file/play-request/`, headers, data);
|
||||
const videoUrl = res.body.match(/cdnUrl":"(.*?)"/)[1];
|
||||
return [{
|
||||
url: videoUrl,
|
||||
originalUrl: videoUrl,
|
||||
headers: { 'Referer': url.match(/.*?:\/\/.*?\//) },
|
||||
quality: ''
|
||||
}];
|
||||
}
|
||||
|
||||
_streamWishExtractor = streamWishExtractor;
|
||||
streamWishExtractor = async (url) => {
|
||||
return (await _streamWishExtractor(url, '')).map(v => {
|
||||
v.quality = v.quality.slice(3, -1);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
_voeExtractor = voeExtractor;
|
||||
voeExtractor = async (url) => {
|
||||
return (await _voeExtractor(url, '')).map(v => {
|
||||
v.quality = v.quality.replace(/Voe: (\d+p?)/i, '$1');
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
_mp4UploadExtractor = mp4UploadExtractor;
|
||||
mp4UploadExtractor = async (url) => {
|
||||
return (await _mp4UploadExtractor(url)).map(v => {
|
||||
v.quality = v.quality.match(/\d+p/)?.[0] ?? '';
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
_yourUploadExtractor = yourUploadExtractor;
|
||||
yourUploadExtractor = async (url) => {
|
||||
return (await _yourUploadExtractor(url))
|
||||
.filter(v => !v.url.includes('/novideo'))
|
||||
.map(v => {
|
||||
v.quality = '';
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
_streamTapeExtractor = streamTapeExtractor;
|
||||
streamTapeExtractor = async (url) => {
|
||||
return await _streamTapeExtractor(url, '');
|
||||
}
|
||||
|
||||
_sendVidExtractor = sendVidExtractor;
|
||||
sendVidExtractor = async (url) => {
|
||||
let res = await new Client().get(url);
|
||||
var videoUrl, quality;
|
||||
try {
|
||||
videoUrl = res.body.match(/og:video" content="(.*?\.mp4.*?)"/)[1];
|
||||
quality = res.body.match(/og:video:height" content="(.*?)"/)?.[1];
|
||||
quality = quality ? quality + 'p' : '';
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
if (!videoUrl) {
|
||||
return _sendVidExtractor(url, null, '');
|
||||
}
|
||||
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
||||
}
|
||||
|
||||
async function extractAny(url, method, lang, type, host) {
|
||||
const m = extractAny.methods[method];
|
||||
return (!m) ? [] : (await m(url)).map(v => {
|
||||
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||
return v;
|
||||
});
|
||||
};
|
||||
|
||||
extractAny.methods = {
|
||||
'amazon': amazonExtractor,
|
||||
'burstcloud': burstcloudExtractor,
|
||||
'doodstream': doodExtractor,
|
||||
'filemoon': filemoonExtractor,
|
||||
'mixdrop': mixdropExtractor,
|
||||
'mp4upload': mp4UploadExtractor,
|
||||
'okru': okruExtractor,
|
||||
'sendvid': sendVidExtractor,
|
||||
'streamtape': streamTapeExtractor,
|
||||
'streamwish': vidHideExtractor,
|
||||
'vidguard': vidGuardExtractor,
|
||||
'vidhide': vidHideExtractor,
|
||||
'vidoza': vidozaExtractor,
|
||||
'voe': voeExtractor,
|
||||
'yourupload': yourUploadExtractor
|
||||
};
|
||||
|
||||
async function m3u8Extractor(url, headers = null) {
|
||||
// https://developer.apple.com/documentation/http-live-streaming/creating-a-multivariant-playlist
|
||||
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
||||
// define attribute lists
|
||||
const streamAttributes = [
|
||||
['avg_bandwidth', /AVERAGE-BANDWIDTH=(\d+)/],
|
||||
['bandwidth', /\bBANDWIDTH=(\d+)/],
|
||||
['resolution', /\bRESOLUTION=([\dx]+)/],
|
||||
['framerate', /\bFRAME-RATE=([\d\.]+)/],
|
||||
['codecs', /\bCODECS="(.*?)"/],
|
||||
['video', /\bVIDEO="(.*?)"/],
|
||||
['audio', /\bAUDIO="(.*?)"/],
|
||||
['subtitles', /\bSUBTITLES="(.*?)"/],
|
||||
['captions', /\bCLOSED-CAPTIONS="(.*?)"/]
|
||||
];
|
||||
const mediaAttributes = [
|
||||
['type', /\bTYPE=([\w-]*)/],
|
||||
['group', /\bGROUP-ID="(.*?)"/],
|
||||
['lang', /\bLANGUAGE="(.*?)"/],
|
||||
['name', /\bNAME="(.*?)"/],
|
||||
['autoselect', /\bAUTOSELECT=(\w*)/],
|
||||
['default', /\bDEFAULT=(\w*)/],
|
||||
['instream-id', /\bINSTREAM-ID="(.*?)"/],
|
||||
['assoc-lang', /\bASSOC-LANGUAGE="(.*?)"/],
|
||||
['channels', /\bCHANNELS="(.*?)"/],
|
||||
['uri', /\bURI="(.*?)"/]
|
||||
];
|
||||
const streams = [], videos = {}, audios = {}, subtitles = {}, captions = {};
|
||||
const dict = { 'VIDEO': videos, 'AUDIO': audios, 'SUBTITLES': subtitles, 'CLOSED-CAPTIONS': captions };
|
||||
|
||||
const res = await new Client().get(url, headers);
|
||||
const text = res.body;
|
||||
|
||||
// collect media
|
||||
for (const match of text.matchAll(/#EXT-X-MEDIA:(.*)/g)) {
|
||||
const info = match[1], medium = {};
|
||||
for (const attr of mediaAttributes) {
|
||||
const m = info.match(attr[1]);
|
||||
medium[attr[0]] = m ? m[1] : null;
|
||||
}
|
||||
|
||||
const type = medium.type;
|
||||
delete medium.type;
|
||||
const group = medium.group;
|
||||
delete medium.group;
|
||||
|
||||
const typedict = dict[type];
|
||||
if (typedict[group] == undefined)
|
||||
typedict[group] = [];
|
||||
typedict[group].push(medium);
|
||||
}
|
||||
|
||||
// collect streams
|
||||
for (const match of text.matchAll(/#EXT-X-STREAM-INF:(.*)\s*(.*)/g)) {
|
||||
const info = match[1], stream = { 'url': absUrl(match[2], url) };
|
||||
for (const attr of streamAttributes) {
|
||||
const m = info.match(attr[1]);
|
||||
stream[attr[0]] = m ? m[1] : null;
|
||||
}
|
||||
|
||||
stream['video'] = videos[stream.video] ?? null;
|
||||
stream['audio'] = audios[stream.audio] ?? null;
|
||||
stream['subtitles'] = subtitles[stream.subtitles] ?? null;
|
||||
stream['captions'] = captions[stream.captions] ?? null;
|
||||
|
||||
// format resolution or bandwidth
|
||||
let quality;
|
||||
if (stream.resolution) {
|
||||
quality = stream.resolution.match(/x(\d+)/)[1] + 'p';
|
||||
} else {
|
||||
quality = (parseInt(stream.avg_bandwidth ?? stream.bandwidth) / 1000000) + 'Mb/s'
|
||||
}
|
||||
|
||||
// add stream to list
|
||||
const subs = stream.subtitles?.map((s) => {
|
||||
return { file: s.uri, label: s.name };
|
||||
});
|
||||
const auds = stream.audio?.map((a) => {
|
||||
return { file: a.uri, label: a.name };
|
||||
});
|
||||
streams.push({
|
||||
url: stream.url,
|
||||
quality: quality,
|
||||
originalUrl: stream.url,
|
||||
headers: headers,
|
||||
subtitles: subs ?? null,
|
||||
audios: auds ?? null
|
||||
});
|
||||
}
|
||||
return streams.length ? streams : [{
|
||||
url: url,
|
||||
quality: '',
|
||||
originalUrl: url,
|
||||
headers: headers,
|
||||
subtitles: null,
|
||||
audios: null
|
||||
}];
|
||||
}
|
||||
|
||||
async function jwplayerExtractor(text, headers) {
|
||||
// https://docs.jwplayer.com/players/reference/playlists
|
||||
const getsetup = /setup\(({[\s\S]*?})\)/;
|
||||
const getsources = /sources:\s*(\[[\s\S]*?\])/;
|
||||
const gettracks = /tracks:\s*(\[[\s\S]*?\])/;
|
||||
const unpacked = unpackJs(text);
|
||||
|
||||
const videos = [], subtitles = [];
|
||||
|
||||
const data = eval('(' + (getsetup.exec(text) || getsetup.exec(unpacked))?.[1] + ')');
|
||||
|
||||
if (data){
|
||||
var sources = data.sources;
|
||||
var tracks = data.tracks;
|
||||
} else {
|
||||
var sources = eval('(' + (getsources.exec(text) || getsources.exec(unpacked))?.[1] + ')');
|
||||
var tracks = eval('(' + (gettracks.exec(text) || gettracks.exec(unpacked))?.[1] + ')');
|
||||
}
|
||||
for (t of tracks) {
|
||||
if (t.type == "captions") {
|
||||
subtitles.push({file: t.file, label: t.label});
|
||||
}
|
||||
}
|
||||
for (s of sources) {
|
||||
if (s.file.includes('master.m3u8')) {
|
||||
videos.push(...(await m3u8Extractor(s.file, headers)));
|
||||
} else if (s.file.includes('.mpd')) {
|
||||
|
||||
} else {
|
||||
videos.push({url: s.file, originalUrl: s.file, quality: '', headers: headers});
|
||||
}
|
||||
}
|
||||
return videos.map(v => {
|
||||
v.subtitles = subtitles;
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
function sortVideos(videos) {
|
||||
const pref = new SharedPreferences();
|
||||
const getres = RegExp('(\\d+)p?', 'i');
|
||||
const lang = RegExp(pref.get('lang'), 'i');
|
||||
const type = RegExp(pref.get('type'), 'i');
|
||||
const res = RegExp(getres.exec(pref.get('res'))[1], 'i');
|
||||
const host = RegExp(pref.get('host'), 'i');
|
||||
|
||||
let getScore = (q, hasRes) => {
|
||||
const bLang = lang.test(q), bType = type.test(q), bRes = res.test(q), bHost = host.test(q);
|
||||
if (hasRes) {
|
||||
return bLang * (8 + bType * (4 + bRes * (2 + bHost * 1)));
|
||||
} else {
|
||||
return bLang * (8 + bType * (4 + (bHost * 3)));
|
||||
}
|
||||
}
|
||||
|
||||
return videos.sort((a, b) => {
|
||||
const resA = getres.exec(a.quality)?.[1];
|
||||
const resB = getres.exec(b.quality)?.[1];
|
||||
const score = getScore(b.quality, resB) - getScore(a.quality, resA);
|
||||
|
||||
if (score) return score;
|
||||
|
||||
const qA = resA ? a.quality.replace(resA, (9999 - parseInt(resA)).toString()) : a.quality;
|
||||
const qB = resA ? b.quality.replace(resB, (9999 - parseInt(resB)).toString()) : b.quality;
|
||||
|
||||
return qA.localeCompare(qB);
|
||||
});
|
||||
}
|
||||
|
||||
Uint8Array.fromBase64 = function (b64) {
|
||||
// [00,01,02,03,04,05,06,07,08,\t,\n,0b,0c,\r,0e,0f,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f,' ', !, ", #, $, %, &, ', (, ), *, +,',', -, ., /, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <,'=', >, ?, @,A,B,C,D,E,F,G,H,I,J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, [, \, ], ^, _, `, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, {, |, }, ~,7f]
|
||||
const m = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]
|
||||
let data = [], val = 0, bits = -8
|
||||
for (const c of b64) {
|
||||
let n = m[c.charCodeAt(0)];
|
||||
if (n == -1) break;
|
||||
val = (val << 6) + n;
|
||||
bits += 6;
|
||||
for (; bits >= 0; bits -= 8)
|
||||
data.push((val >> bits) & 0xFF);
|
||||
}
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
Uint8Array.prototype.toBase64 = function () {
|
||||
const m = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
let b64 = '', val = 0, bits = -6;
|
||||
for (const b of this) {
|
||||
val = (val << 8) + b;
|
||||
bits += 8;
|
||||
while (bits >= 0) {
|
||||
b64 += m[(val >> bits) & 0x3F];
|
||||
bits -= 6;
|
||||
}
|
||||
}
|
||||
if (bits > -6)
|
||||
b64 += m[(val << -bits) & 0x3F];
|
||||
return b64 + ['', '', '==', '='][b64.length % 4];
|
||||
}
|
||||
|
||||
Uint8Array.prototype.decode = function (encoding = 'utf-8') {
|
||||
encoding = encoding.toLowerCase();
|
||||
if (encoding == 'utf-8') {
|
||||
return decodeUTF8(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String.prototype.encode = function (encoding = 'utf-8') {
|
||||
encoding = encoding.toLowerCase();
|
||||
if (encoding == 'utf-8') {
|
||||
return encodeUTF8(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String.decode = function (data, encoding = 'utf-8') {
|
||||
encoding = encoding.toLowerCase();
|
||||
if (encoding == 'utf-8') {
|
||||
return decodeUTF8(data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function decodeUTF8(data) {
|
||||
const codes = [];
|
||||
for (let i = 0; i < data.length;) {
|
||||
const c = data[i++];
|
||||
const len = (c > 0xBF) + (c > 0xDF) + (c > 0xEF);
|
||||
let val = c & (0xFF >> (len + 1));
|
||||
for (const end = i + len; i < end; i++) {
|
||||
val = (val << 6) + (data[i] & 0x3F);
|
||||
}
|
||||
codes.push(val);
|
||||
}
|
||||
return String.fromCharCode(...codes);
|
||||
}
|
||||
|
||||
function encodeUTF8(string) {
|
||||
const data = [];
|
||||
for (const c of string) {
|
||||
const code = c.charCodeAt(0);
|
||||
const len = (code > 0x7F) + (code > 0x7FF) + (code > 0xFFFF);
|
||||
let bits = len * 6;
|
||||
|
||||
data.push((len ? ~(0xFF >> len + 1) : (0)) + (code >> bits));
|
||||
while (bits > 0) {
|
||||
data.push(0x80 + ((code >> (bits -= 6)) & 0x3F))
|
||||
}
|
||||
}
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
function getRandomString(length) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const random = Math.floor(Math.random() * 61);
|
||||
result += chars[random];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function absUrl(url, base) {
|
||||
if (url.search(/^\w+:\/\//) == 0) {
|
||||
return url;
|
||||
} else if (url.startsWith('/')) {
|
||||
return base.slice(0, base.lastIndexOf('/')) + url;
|
||||
} else {
|
||||
return base.slice(0, base.lastIndexOf('/') + 1) + url;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ const mangayomiSources = [{
|
||||
"iconUrl": "https://mangafire.to/assets/sites/mangafire/favicon.png?v3",
|
||||
"typeSource": "single",
|
||||
"isManga": true,
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"dateFormat": "",
|
||||
"dateFormatLocale": "",
|
||||
"pkgPath": "manga/src/all/mangafire.js"
|
||||
@@ -30,16 +30,13 @@ class DefaultExtension extends MProvider {
|
||||
}
|
||||
|
||||
statusFromString(status){
|
||||
if (status == "Releasing")
|
||||
return 0;
|
||||
else if (status == "Completed")
|
||||
return 1;
|
||||
else if (status == "On_Hiatus")
|
||||
return 2;
|
||||
else if (status == "Discontinued")
|
||||
return 3;
|
||||
else
|
||||
return 5;
|
||||
return {
|
||||
"Releasing": 0,
|
||||
"Completed": 1,
|
||||
"On_Hiatus": 2,
|
||||
"Discontinued": 3,
|
||||
"Unrealeased": 4,
|
||||
}[status] ?? 5;
|
||||
}
|
||||
|
||||
parseDate(date) {
|
||||
@@ -73,6 +70,12 @@ class DefaultExtension extends MProvider {
|
||||
query = query.trim().replaceAll(/\ +/g, "+");
|
||||
let url = `${this.source.baseUrl}/filter?keyword=${query}`;
|
||||
|
||||
// Search sometimes failed because filters were empty. I experienced this mostly on android...
|
||||
if (!filters || filters.length == 0) {
|
||||
const res = await new Client().get(`${url}&language=${this.source.lang}&page=${page}`);
|
||||
return this.mangaListFromPage(res);
|
||||
}
|
||||
|
||||
for (const filter of filters[0].state) {
|
||||
if (filter.state == true)
|
||||
url += `&type%5B%5D=${filter.value}`;
|
||||
@@ -504,8 +507,8 @@ class DefaultExtension extends MProvider {
|
||||
},
|
||||
{
|
||||
type_name: "SelectOption",
|
||||
name: "Most Relevant",
|
||||
value: "most_relevant"
|
||||
name: "Most Relevance",
|
||||
value: "most_relevance"
|
||||
},
|
||||
{
|
||||
type_name: "SelectOption",
|
||||
|
||||
Reference in New Issue
Block a user