mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 19:01:15 +00:00
Merge pull request #104 from RndDev123/main
AniWorld, AnimeFenix, JKAnime and TioAnime: New host, fixes and helper lib update
This commit is contained in:
@@ -7,7 +7,7 @@ const mangayomiSources = [{
|
|||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"isManga": false,
|
"isManga": false,
|
||||||
"isNsfw": false,
|
"isNsfw": false,
|
||||||
"version": "0.0.28",
|
"version": "0.3.0",
|
||||||
"dateFormat": "",
|
"dateFormat": "",
|
||||||
"dateFormatLocale": "",
|
"dateFormatLocale": "",
|
||||||
"pkgPath": "anime/src/de/aniworld.js"
|
"pkgPath": "anime/src/de/aniworld.js"
|
||||||
@@ -137,20 +137,20 @@ class DefaultExtension extends MProvider {
|
|||||||
const videos = [];
|
const videos = [];
|
||||||
|
|
||||||
const redirectsElements = document.select("ul.row li");
|
const redirectsElements = document.select("ul.row li");
|
||||||
const hosterSelection = new SharedPreferences().get("hoster_selection_new");
|
const hostFilter = new SharedPreferences().get("host_filter");
|
||||||
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
|
|
||||||
for (const element of redirectsElements) {
|
for (const element of redirectsElements) {
|
||||||
const host = element.selectFirst("a h4").text;
|
const host = element.selectFirst("a h4").text;
|
||||||
|
|
||||||
if (hosterSelection.includes(host)) {
|
if (hostFilter.includes(host)) {
|
||||||
const langkey = element.attr("data-lang-key");
|
const langkey = element.attr("data-lang-key");
|
||||||
const lang = (langkey == 1 || langkey == 3) ? 'Deutscher' : 'Englischer';
|
const lang = (langkey == 1 || langkey == 3) ? 'Deutscher' : 'Englischer';
|
||||||
const type = (langkey == 1) ? 'Dub' : 'Sub';
|
const type = (langkey == 1) ? 'Dub' : 'Sub';
|
||||||
const redirect = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
|
const redirect = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
|
||||||
promises.push((async (redirect, lang, type, host) => {
|
promises.push((async (redirect, lang, type, host) => {
|
||||||
const location = (await dartClient.get(redirect)).headers.location;
|
const location = (await dartClient.get(redirect)).headers.location;
|
||||||
return await extractAny(location, host.toLowerCase(), lang, type, host);
|
return await extractAny(location, host.toLowerCase(), lang, type, host, {'Referer': this.source.baseUrl});
|
||||||
})(redirect, lang, type, host));
|
})(redirect, lang, type, host));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,57 +159,164 @@ class DefaultExtension extends MProvider {
|
|||||||
videos.push.apply(videos, p.value);
|
videos.push.apply(videos, p.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.sortVideos(videos);
|
return sortVideos(videos);
|
||||||
}
|
|
||||||
sortVideos(videos) {
|
|
||||||
const preference = new SharedPreferences();
|
|
||||||
const hoster = RegExp(preference.get("preferred_hoster_new"));
|
|
||||||
const lang = RegExp(preference.get("preferred_lang"));
|
|
||||||
videos.sort((a, b) => {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
getSourcePreferences() {
|
getSourcePreferences() {
|
||||||
const hosterOptions = ["Streamtape", "VOE", "Vidoza", "Doodstream"];
|
const languages = ['Deutsch', 'Englisch'];
|
||||||
const languageOptions = ["Deutscher Sub", "Deutscher Dub", "Englischer Sub"];
|
const languageValues = ['Deutscher', 'Englischer'];
|
||||||
|
const types = ['Dub', 'Sub'];
|
||||||
|
const resolutions = ['1080p', '720p', '480p'];
|
||||||
|
const hosts = ['Doodstream', 'Filemoon', 'SpeedFiles', 'Streamtape', 'Vidoza', 'VOE'];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "preferred_lang",
|
key: 'lang',
|
||||||
"listPreference": {
|
listPreference: {
|
||||||
"title": "Bevorzugte Sprache",
|
title: 'Bevorzugte Sprache',
|
||||||
"summary": "",
|
summary: 'Wenn verfügbar, wird diese Sprache ausgewählt. Priority = 0 (lower is better)',
|
||||||
"valueIndex": 0,
|
valueIndex: 0,
|
||||||
"entries": languageOptions,
|
entries: languages,
|
||||||
"entryValues": languageOptions
|
entryValues: languageValues
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "preferred_hoster_new",
|
key: 'type',
|
||||||
"listPreference": {
|
listPreference: {
|
||||||
"title": "Standard-Hoster",
|
title: 'Bevorzugter Typ',
|
||||||
"summary": "",
|
summary: 'Wenn verfügbar, wird dieser Typ ausgewählt. Priority = 1 (lower is better)',
|
||||||
"valueIndex": 0,
|
valueIndex: 0,
|
||||||
"entries": hosterOptions,
|
entries: types,
|
||||||
"entryValues": hosterOptions
|
entryValues: types
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "hoster_selection_new",
|
key: 'res',
|
||||||
"multiSelectListPreference": {
|
listPreference: {
|
||||||
"title": "Hoster auswählen",
|
title: 'Bevorzugte Auflösung',
|
||||||
"summary": "",
|
summary: 'Wenn verfügbar, wird diese Auflösung ausgewählt. Priority = 2 (lower is better)',
|
||||||
"entries": hosterOptions,
|
valueIndex: 0,
|
||||||
"entryValues": hosterOptions,
|
entries: resolutions,
|
||||||
"values": hosterOptions
|
entryValues: resolutions
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'host',
|
||||||
|
listPreference: {
|
||||||
|
title: 'Bevorzugter Hoster',
|
||||||
|
summary: 'Wenn verfügbar, wird dieser Hoster ausgewählt. Priority = 3 (lower is better)',
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: hosts,
|
||||||
|
entryValues: hosts
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "host_filter",
|
||||||
|
multiSelectListPreference: {
|
||||||
|
title: "Hoster auswählen",
|
||||||
|
summary: "Wähle aus welche Hoster dir angezeigt werden sollen. Weniger hoster zu laden beschleunigt den Start der Videos.",
|
||||||
|
entries: hosts,
|
||||||
|
entryValues: hosts,
|
||||||
|
values: hosts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
*
|
||||||
|
* mangayomi-js-helpers v1.1
|
||||||
|
*
|
||||||
|
* # Video Extractors
|
||||||
|
* - vidGuardExtractor
|
||||||
|
* - doodExtractor
|
||||||
|
* - vidozaExtractor
|
||||||
|
* - okruExtractor
|
||||||
|
* - amazonExtractor
|
||||||
|
* - vidHideExtractor
|
||||||
|
* - filemoonExtractor
|
||||||
|
* - mixdropExtractor
|
||||||
|
* - speedfilesExtractor
|
||||||
|
* - burstcloudExtractor (not working, see description)
|
||||||
|
*
|
||||||
|
* # Video Extractor Wrappers
|
||||||
|
* - streamWishExtractor
|
||||||
|
* - voeExtractor
|
||||||
|
* - mp4UploadExtractor
|
||||||
|
* - yourUploadExtractor
|
||||||
|
* - streamTapeExtractor
|
||||||
|
* - sendVidExtractor
|
||||||
|
*
|
||||||
|
* # Video Extractor helpers
|
||||||
|
* - extractAny
|
||||||
|
*
|
||||||
|
* # Playlist Extractors
|
||||||
|
* - m3u8Extractor
|
||||||
|
* - jwplayerExtractor
|
||||||
|
*
|
||||||
|
* # Extension Helpers
|
||||||
|
* - sortVideos()
|
||||||
|
*
|
||||||
|
* # Uint8Array
|
||||||
|
* - Uint8Array.fromBase64()
|
||||||
|
* - Uint8Array.prototype.toBase64()
|
||||||
|
* - Uint8Array.prototype.decode()
|
||||||
|
*
|
||||||
|
* # String
|
||||||
|
* - String.prototype.encode()
|
||||||
|
* - String.decode()
|
||||||
|
* - String.prototype.reverse()
|
||||||
|
* - String.prototype.swapcase()
|
||||||
|
* - getRandomString()
|
||||||
|
*
|
||||||
|
* # Encode/Decode Functions
|
||||||
|
* - decodeUTF8
|
||||||
|
* - encodeUTF8
|
||||||
|
*
|
||||||
|
* # Url
|
||||||
|
* - absUrl()
|
||||||
|
*
|
||||||
|
***************************************************************************************************/
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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) {
|
async function doodExtractor(url) {
|
||||||
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
let response = await dartClient.get(url);
|
let response = await dartClient.get(url);
|
||||||
@@ -235,9 +342,124 @@ async function vidozaExtractor(url) {
|
|||||||
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
_streamTapeExtractor = streamTapeExtractor;
|
async function okruExtractor(url) {
|
||||||
streamTapeExtractor = async (url) => {
|
const res = await new Client().get(url);
|
||||||
return await _streamTapeExtractor(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, headers) {
|
||||||
|
let res = await new Client().get(url, headers);
|
||||||
|
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}];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function speedfilesExtractor(url) {
|
||||||
|
let res = await new Client().get(url);
|
||||||
|
let doc = new Document(res.body);
|
||||||
|
|
||||||
|
const code = doc.selectFirst('script:contains(var)').text;
|
||||||
|
let b64;
|
||||||
|
|
||||||
|
// Get b64
|
||||||
|
for (const match of code.matchAll(/(?:var|let|const)\s*\w+\s*=\s*["']([^"']+)/g)) {
|
||||||
|
if (match[1].match(/[g-zG-Z]/)) {
|
||||||
|
b64 = match[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode b64 => b64
|
||||||
|
const step1 = Uint8Array.fromBase64(b64).reverse().decode().swapcase();
|
||||||
|
// decode b64 => hex
|
||||||
|
const step2 = Uint8Array.fromBase64(step1).reverse().decode();
|
||||||
|
// decode hex => b64
|
||||||
|
let step3 = [];
|
||||||
|
for (let i = 0; i < step2.length; i += 2) {
|
||||||
|
step3.push(parseInt(step2.slice(i, i + 2), 16) - 3);
|
||||||
|
}
|
||||||
|
step3 = String.fromCharCode(...step3.reverse()).swapcase();
|
||||||
|
// decode b64 => url
|
||||||
|
const videoUrl = Uint8Array.fromBase64(step3).decode();
|
||||||
|
|
||||||
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: null}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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: ''
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Wrappers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_streamWishExtractor = streamWishExtractor;
|
||||||
|
streamWishExtractor = async (url) => {
|
||||||
|
return (await _streamWishExtractor(url, '')).map(v => {
|
||||||
|
v.quality = v.quality.slice(3, -1);
|
||||||
|
return v;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_voeExtractor = voeExtractor;
|
_voeExtractor = voeExtractor;
|
||||||
@@ -248,26 +470,381 @@ voeExtractor = async (url) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractAny(link, method, lang, type, host) {
|
_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}];
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function extractAny(url, method, lang, type, host, headers = null) {
|
||||||
const m = extractAny.methods[method];
|
const m = extractAny.methods[method];
|
||||||
return (!m) ? [] : (await m(link)).map(v => {
|
return (!m) ? [] : (await m(url, headers)).map(v => {
|
||||||
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
extractAny.methods = {
|
extractAny.methods = {
|
||||||
|
'amazon': amazonExtractor,
|
||||||
|
'burstcloud': burstcloudExtractor,
|
||||||
'doodstream': doodExtractor,
|
'doodstream': doodExtractor,
|
||||||
|
'filemoon': filemoonExtractor,
|
||||||
|
'mixdrop': mixdropExtractor,
|
||||||
|
'mp4upload': mp4UploadExtractor,
|
||||||
|
'okru': okruExtractor,
|
||||||
|
'sendvid': sendVidExtractor,
|
||||||
|
'speedfiles': speedfilesExtractor,
|
||||||
'streamtape': streamTapeExtractor,
|
'streamtape': streamTapeExtractor,
|
||||||
|
'streamwish': vidHideExtractor,
|
||||||
|
'vidguard': vidGuardExtractor,
|
||||||
|
'vidhide': vidHideExtractor,
|
||||||
'vidoza': vidozaExtractor,
|
'vidoza': vidozaExtractor,
|
||||||
'voe': voeExtractor
|
'voe': voeExtractor,
|
||||||
|
'yourupload': yourUploadExtractor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Playlist Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Extension Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.reverse = function () {
|
||||||
|
return this.split('').reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.swapcase = function () {
|
||||||
|
const isAsciiLetter = /[A-z]/;
|
||||||
|
const result = [];
|
||||||
|
for (const l of this)
|
||||||
|
result.push(isAsciiLetter.test(l) ? String.fromCharCode(l.charCodeAt() ^ 32) : l);
|
||||||
|
return result.join('');
|
||||||
|
}
|
||||||
|
|
||||||
function getRandomString(length) {
|
function getRandomString(length) {
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
const charArray = new Array(length);
|
let result = "";
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
charArray[i] = chars[Math.floor(Math.random() * chars.length)];
|
const random = Math.floor(Math.random() * 61);
|
||||||
|
result += chars[random];
|
||||||
}
|
}
|
||||||
return charArray.join("");
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Encode/Decode Functions
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Url
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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://www3.animefenix.tv/themes/fenix-neo/images/AveFenix.png",
|
"iconUrl": "https://www3.animefenix.tv/themes/fenix-neo/images/AveFenix.png",
|
||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"isManga": false,
|
"isManga": false,
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"dateFormat": "",
|
"dateFormat": "",
|
||||||
"dateFormatLocale": "",
|
"dateFormatLocale": "",
|
||||||
"pkgPath": "anime/src/es/animefenix.js"
|
"pkgPath": "anime/src/es/animefenix.js"
|
||||||
@@ -37,10 +37,11 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
statusFromString(status) {
|
statusFromString(status) {
|
||||||
return {
|
return {
|
||||||
"En emision": 0, // releasing
|
"en emision": 0, // releasing
|
||||||
"Finalizado": 1, // finished
|
"emisión": 0, // releasing
|
||||||
"Proximamente": 4, // unreleased
|
"finalizado": 1, // finished
|
||||||
}[status] ?? 5;
|
"proximamente": 4, // unreleased
|
||||||
|
}[status.toLowerCase()] ?? 5;
|
||||||
}
|
}
|
||||||
async getPopular(page) {
|
async getPopular(page) {
|
||||||
return this.parseAnimeList(`${this.source.baseUrl}/animes?order=visits&page=${page}`);
|
return this.parseAnimeList(`${this.source.baseUrl}/animes?order=visits&page=${page}`);
|
||||||
@@ -80,7 +81,7 @@ class DefaultExtension extends MProvider {
|
|||||||
const info = doc.selectFirst('main div.flex');
|
const info = doc.selectFirst('main div.flex');
|
||||||
|
|
||||||
detail.name = info.selectFirst("h1").text;
|
detail.name = info.selectFirst("h1").text;
|
||||||
detail.status = this.statusFromString(info.selectFirst("a").text);
|
detail.status = this.statusFromString(info.selectFirst("a").text.trim());
|
||||||
detail.imageUrl = info.selectFirst("img").getSrc;
|
detail.imageUrl = info.selectFirst("img").getSrc;
|
||||||
detail.description = info.selectFirst("h2 + p").text.trim();
|
detail.description = info.selectFirst("h2 + p").text.trim();
|
||||||
detail.genre = info.select("h2 + div a").map(e => e.text.trim());
|
detail.genre = info.select("h2 + div a").map(e => e.text.trim());
|
||||||
@@ -114,13 +115,8 @@ class DefaultExtension extends MProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// extract remote video links
|
// extract remote video links
|
||||||
const renameLUT = {
|
const renameLUT = { 'amazones': 'amazon', 'burst': 'burstcloud', 'hide': 'vidhide',
|
||||||
'amazones': 'amazon',
|
'ru': 'okru', 'stream2': 'vidhide', };
|
||||||
'burst': 'burstcloud',
|
|
||||||
'hide': 'vidhide',
|
|
||||||
'ru': 'okru',
|
|
||||||
'stream2': 'vidhide',
|
|
||||||
};
|
|
||||||
for (let i = 0; i < hosts.length; i++) {
|
for (let i = 0; i < hosts.length; i++) {
|
||||||
const host = hosts[i].trim();
|
const host = hosts[i].trim();
|
||||||
const lhost = host.toLowerCase();
|
const lhost = host.toLowerCase();
|
||||||
@@ -509,6 +505,11 @@ class DefaultExtension extends MProvider {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
getSourcePreferences() {
|
getSourcePreferences() {
|
||||||
|
const languages = ['Español'];
|
||||||
|
const types = ['Sub'];
|
||||||
|
const resolutions = ['1080p', '720p', '480p'];
|
||||||
|
const hosts = ['Amazon', 'AmazonEs', 'Burst', 'Mp4Upload', 'RU', 'Sendvid', 'STREAM2', 'HIDE', 'YourUpload'];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'lang',
|
key: 'lang',
|
||||||
@@ -516,12 +517,8 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Language',
|
title: 'Preferred Language',
|
||||||
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: languages,
|
||||||
'Español'
|
entryValues: languages
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Español'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -530,12 +527,8 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Type',
|
title: 'Preferred Type',
|
||||||
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: types,
|
||||||
'Sub'
|
entryValues: types
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Sub'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -544,44 +537,18 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Resolution',
|
title: 'Preferred Resolution',
|
||||||
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: resolutions,
|
||||||
'1080p',
|
entryValues: resolutions
|
||||||
'720p',
|
|
||||||
'480p'
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'1080p',
|
|
||||||
'720p',
|
|
||||||
'480p'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'host',
|
key: 'host',
|
||||||
listPreference: {
|
listPreference: {
|
||||||
title: 'Preferred Hoster',
|
title: 'Preferred Host',
|
||||||
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: hosts,
|
||||||
'Amazon',
|
entryValues: hosts
|
||||||
'Burst',
|
|
||||||
'Mp4Upload',
|
|
||||||
'RU',
|
|
||||||
'Sendvid',
|
|
||||||
'STREAM2',
|
|
||||||
'HIDE',
|
|
||||||
'YourUpload',
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Amazon',
|
|
||||||
'Burst',
|
|
||||||
'Mp4Upload',
|
|
||||||
'RU',
|
|
||||||
'Sendvid',
|
|
||||||
'STREAM2',
|
|
||||||
'HIDE',
|
|
||||||
'YourUpload',
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -590,7 +557,7 @@ class DefaultExtension extends MProvider {
|
|||||||
|
|
||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
*
|
*
|
||||||
* mangayomi-js-helpers v1.0
|
* mangayomi-js-helpers v1.1
|
||||||
*
|
*
|
||||||
* # Video Extractors
|
* # Video Extractors
|
||||||
* - vidGuardExtractor
|
* - vidGuardExtractor
|
||||||
@@ -601,9 +568,10 @@ class DefaultExtension extends MProvider {
|
|||||||
* - vidHideExtractor
|
* - vidHideExtractor
|
||||||
* - filemoonExtractor
|
* - filemoonExtractor
|
||||||
* - mixdropExtractor
|
* - mixdropExtractor
|
||||||
|
* - speedfilesExtractor
|
||||||
* - burstcloudExtractor (not working, see description)
|
* - burstcloudExtractor (not working, see description)
|
||||||
*
|
*
|
||||||
* # Video Extractor Format Wrappers
|
* # Video Extractor Wrappers
|
||||||
* - streamWishExtractor
|
* - streamWishExtractor
|
||||||
* - voeExtractor
|
* - voeExtractor
|
||||||
* - mp4UploadExtractor
|
* - mp4UploadExtractor
|
||||||
@@ -618,24 +586,34 @@ class DefaultExtension extends MProvider {
|
|||||||
* - m3u8Extractor
|
* - m3u8Extractor
|
||||||
* - jwplayerExtractor
|
* - jwplayerExtractor
|
||||||
*
|
*
|
||||||
* # Extension
|
* # Extension Helpers
|
||||||
* - sortVideos()
|
* - sortVideos()
|
||||||
*
|
*
|
||||||
* # Encoding/Decoding
|
* # Uint8Array
|
||||||
* - Uint8Array.fromBase64()
|
* - Uint8Array.fromBase64()
|
||||||
* - Uint8Array.prototype.toBase64()
|
* - Uint8Array.prototype.toBase64()
|
||||||
* - Uint8Array.prototype.decode()
|
* - Uint8Array.prototype.decode()
|
||||||
|
*
|
||||||
|
* # String
|
||||||
* - String.prototype.encode()
|
* - String.prototype.encode()
|
||||||
* - String.prototype.decode()
|
* - String.decode()
|
||||||
*
|
* - String.prototype.reverse()
|
||||||
* # Random string
|
* - String.prototype.swapcase()
|
||||||
* - getRandomString()
|
* - getRandomString()
|
||||||
|
*
|
||||||
|
* # Encode/Decode Functions
|
||||||
|
* - decodeUTF8
|
||||||
|
* - encodeUTF8
|
||||||
*
|
*
|
||||||
* # URL
|
* # Url
|
||||||
* - absUrl()
|
* - absUrl()
|
||||||
*
|
*
|
||||||
***************************************************************************************************/
|
***************************************************************************************************/
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
async function vidGuardExtractor(url) {
|
async function vidGuardExtractor(url) {
|
||||||
// get html
|
// get html
|
||||||
const res = await new Client().get(url);
|
const res = await new Client().get(url);
|
||||||
@@ -716,8 +694,8 @@ async function vidHideExtractor(url) {
|
|||||||
return await jwplayerExtractor(res.body);
|
return await jwplayerExtractor(res.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filemoonExtractor(url) {
|
async function filemoonExtractor(url, headers) {
|
||||||
let res = await new Client().get(url);
|
let res = await new Client().get(url, headers);
|
||||||
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
||||||
if (src) {
|
if (src) {
|
||||||
res = await new Client().get(src, {
|
res = await new Client().get(src, {
|
||||||
@@ -749,6 +727,37 @@ async function mixdropExtractor(url) {
|
|||||||
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function speedfilesExtractor(url) {
|
||||||
|
let res = await new Client().get(url);
|
||||||
|
let doc = new Document(res.body);
|
||||||
|
|
||||||
|
const code = doc.selectFirst('script:contains(var)').text;
|
||||||
|
let b64;
|
||||||
|
|
||||||
|
// Get b64
|
||||||
|
for (const match of code.matchAll(/(?:var|let|const)\s*\w+\s*=\s*["']([^"']+)/g)) {
|
||||||
|
if (match[1].match(/[g-zG-Z]/)) {
|
||||||
|
b64 = match[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode b64 => b64
|
||||||
|
const step1 = Uint8Array.fromBase64(b64).reverse().decode().swapcase();
|
||||||
|
// decode b64 => hex
|
||||||
|
const step2 = Uint8Array.fromBase64(step1).reverse().decode();
|
||||||
|
// decode hex => b64
|
||||||
|
let step3 = [];
|
||||||
|
for (let i = 0; i < step2.length; i += 2) {
|
||||||
|
step3.push(parseInt(step2.slice(i, i + 2), 16) - 3);
|
||||||
|
}
|
||||||
|
step3 = String.fromCharCode(...step3.reverse()).swapcase();
|
||||||
|
// decode b64 => url
|
||||||
|
const videoUrl = Uint8Array.fromBase64(step3).decode();
|
||||||
|
|
||||||
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: null}];
|
||||||
|
}
|
||||||
|
|
||||||
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
||||||
async function burstcloudExtractor(url) {
|
async function burstcloudExtractor(url) {
|
||||||
let client = new Client();
|
let client = new Client();
|
||||||
@@ -773,6 +782,10 @@ async function burstcloudExtractor(url) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Wrappers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_streamWishExtractor = streamWishExtractor;
|
_streamWishExtractor = streamWishExtractor;
|
||||||
streamWishExtractor = async (url) => {
|
streamWishExtractor = async (url) => {
|
||||||
return (await _streamWishExtractor(url, '')).map(v => {
|
return (await _streamWishExtractor(url, '')).map(v => {
|
||||||
@@ -829,9 +842,13 @@ sendVidExtractor = async (url) => {
|
|||||||
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractAny(url, method, lang, type, host) {
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function extractAny(url, method, lang, type, host, headers = null) {
|
||||||
const m = extractAny.methods[method];
|
const m = extractAny.methods[method];
|
||||||
return (!m) ? [] : (await m(url)).map(v => {
|
return (!m) ? [] : (await m(url, headers)).map(v => {
|
||||||
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
@@ -846,6 +863,7 @@ extractAny.methods = {
|
|||||||
'mp4upload': mp4UploadExtractor,
|
'mp4upload': mp4UploadExtractor,
|
||||||
'okru': okruExtractor,
|
'okru': okruExtractor,
|
||||||
'sendvid': sendVidExtractor,
|
'sendvid': sendVidExtractor,
|
||||||
|
'speedfiles': speedfilesExtractor,
|
||||||
'streamtape': streamTapeExtractor,
|
'streamtape': streamTapeExtractor,
|
||||||
'streamwish': vidHideExtractor,
|
'streamwish': vidHideExtractor,
|
||||||
'vidguard': vidGuardExtractor,
|
'vidguard': vidGuardExtractor,
|
||||||
@@ -855,6 +873,10 @@ extractAny.methods = {
|
|||||||
'yourupload': yourUploadExtractor
|
'yourupload': yourUploadExtractor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Playlist Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
async function m3u8Extractor(url, headers = null) {
|
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/creating-a-multivariant-playlist
|
||||||
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
||||||
@@ -992,6 +1014,10 @@ async function jwplayerExtractor(text, headers) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Extension Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function sortVideos(videos) {
|
function sortVideos(videos) {
|
||||||
const pref = new SharedPreferences();
|
const pref = new SharedPreferences();
|
||||||
const getres = RegExp('(\\d+)p?', 'i');
|
const getres = RegExp('(\\d+)p?', 'i');
|
||||||
@@ -1023,6 +1049,10 @@ function sortVideos(videos) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Uint8Array
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Uint8Array.fromBase64 = function (b64) {
|
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]
|
// [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]
|
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]
|
||||||
@@ -1062,6 +1092,10 @@ Uint8Array.prototype.decode = function (encoding = 'utf-8') {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// String
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
String.prototype.encode = function (encoding = 'utf-8') {
|
String.prototype.encode = function (encoding = 'utf-8') {
|
||||||
encoding = encoding.toLowerCase();
|
encoding = encoding.toLowerCase();
|
||||||
if (encoding == 'utf-8') {
|
if (encoding == 'utf-8') {
|
||||||
@@ -1078,6 +1112,32 @@ String.decode = function (data, encoding = 'utf-8') {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String.prototype.reverse = function () {
|
||||||
|
return this.split('').reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.swapcase = function () {
|
||||||
|
const isAsciiLetter = /[A-z]/;
|
||||||
|
const result = [];
|
||||||
|
for (const l of this)
|
||||||
|
result.push(isAsciiLetter.test(l) ? String.fromCharCode(l.charCodeAt() ^ 32) : l);
|
||||||
|
return result.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Encode/Decode Functions
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function decodeUTF8(data) {
|
function decodeUTF8(data) {
|
||||||
const codes = [];
|
const codes = [];
|
||||||
for (let i = 0; i < data.length;) {
|
for (let i = 0; i < data.length;) {
|
||||||
@@ -1107,15 +1167,9 @@ function encodeUTF8(string) {
|
|||||||
return new Uint8Array(data);
|
return new Uint8Array(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomString(length) {
|
//--------------------------------------------------------------------------------------------------
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
// Url
|
||||||
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) {
|
function absUrl(url, base) {
|
||||||
if (url.search(/^\w+:\/\//) == 0) {
|
if (url.search(/^\w+:\/\//) == 0) {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ const mangayomiSources = [{
|
|||||||
"lang": "es",
|
"lang": "es",
|
||||||
"baseUrl": "https://jkanime.net",
|
"baseUrl": "https://jkanime.net",
|
||||||
"apiUrl": "",
|
"apiUrl": "",
|
||||||
"iconUrl": "https://cdn.jkdesu.com/assets2/css/img/favicon.ico",
|
"iconUrl": "https://cdn.jkanime.net/logo_jk.png",
|
||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"isManga": false,
|
"isManga": false,
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"dateFormat": "",
|
"dateFormat": "",
|
||||||
"dateFormatLocale": "",
|
"dateFormatLocale": "",
|
||||||
"pkgPath": "anime/src/es/jkanime.js"
|
"pkgPath": "anime/src/es/jkanime.js"
|
||||||
@@ -102,6 +102,7 @@ class DefaultExtension extends MProvider {
|
|||||||
detail.description = info.selectFirst("p.sinopsis").text.trim();
|
detail.description = info.selectFirst("p.sinopsis").text.trim();
|
||||||
detail.status = this.statusFromString(extInfo.selectFirst("span:contains(Estado) + span").text);
|
detail.status = this.statusFromString(extInfo.selectFirst("span:contains(Estado) + span").text);
|
||||||
detail.genre = extInfo.select("li:contains(Genero) a").map(e => e.text);
|
detail.genre = extInfo.select("li:contains(Genero) a").map(e => e.text);
|
||||||
|
detail.author = extInfo.select("li:contains(Studios) a").map(e => e.text).join(', ');
|
||||||
|
|
||||||
// get episodes
|
// get episodes
|
||||||
detail.episodes = [];
|
detail.episodes = [];
|
||||||
@@ -779,6 +780,11 @@ class DefaultExtension extends MProvider {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
getSourcePreferences() {
|
getSourcePreferences() {
|
||||||
|
const languages = ['Español'];
|
||||||
|
const types = ['Sub'];
|
||||||
|
const resolutions = ['1080p', '720p', '480p'];
|
||||||
|
const hosts = ['Desu', 'Filemoon', 'Mixdrop', 'Mp4upload', 'Streamtape', 'Streamwish', 'Vidhide', 'VOE'];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'lang',
|
key: 'lang',
|
||||||
@@ -786,12 +792,8 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Language',
|
title: 'Preferred Language',
|
||||||
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: languages,
|
||||||
'Español'
|
entryValues: languages
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Español'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -800,12 +802,8 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Type',
|
title: 'Preferred Type',
|
||||||
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: types,
|
||||||
'Sub'
|
entryValues: types
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Sub'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -814,44 +812,18 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Resolution',
|
title: 'Preferred Resolution',
|
||||||
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: resolutions,
|
||||||
'1080p',
|
entryValues: resolutions
|
||||||
'720p',
|
|
||||||
'480p'
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'1080p',
|
|
||||||
'720p',
|
|
||||||
'480p'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'host',
|
key: 'host',
|
||||||
listPreference: {
|
listPreference: {
|
||||||
title: 'Preferred Hoster',
|
title: 'Preferred Host',
|
||||||
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: hosts,
|
||||||
'Desu',
|
entryValues: hosts
|
||||||
'Filemoon',
|
|
||||||
'Mixdrop',
|
|
||||||
'Mp4Upload',
|
|
||||||
'Streamtape',
|
|
||||||
'Streamwish',
|
|
||||||
'Vidhide',
|
|
||||||
'Voe'
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Desu',
|
|
||||||
'Filemoon',
|
|
||||||
'Mixdrop',
|
|
||||||
'Mp4Upload',
|
|
||||||
'Streamtape',
|
|
||||||
'Streamwish',
|
|
||||||
'Vidhide',
|
|
||||||
'Voe'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -860,7 +832,7 @@ class DefaultExtension extends MProvider {
|
|||||||
|
|
||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
*
|
*
|
||||||
* mangayomi-js-helpers v1.0
|
* mangayomi-js-helpers v1.1
|
||||||
*
|
*
|
||||||
* # Video Extractors
|
* # Video Extractors
|
||||||
* - vidGuardExtractor
|
* - vidGuardExtractor
|
||||||
@@ -871,9 +843,10 @@ class DefaultExtension extends MProvider {
|
|||||||
* - vidHideExtractor
|
* - vidHideExtractor
|
||||||
* - filemoonExtractor
|
* - filemoonExtractor
|
||||||
* - mixdropExtractor
|
* - mixdropExtractor
|
||||||
|
* - speedfilesExtractor
|
||||||
* - burstcloudExtractor (not working, see description)
|
* - burstcloudExtractor (not working, see description)
|
||||||
*
|
*
|
||||||
* # Video Extractor Format Wrappers
|
* # Video Extractor Wrappers
|
||||||
* - streamWishExtractor
|
* - streamWishExtractor
|
||||||
* - voeExtractor
|
* - voeExtractor
|
||||||
* - mp4UploadExtractor
|
* - mp4UploadExtractor
|
||||||
@@ -888,24 +861,34 @@ class DefaultExtension extends MProvider {
|
|||||||
* - m3u8Extractor
|
* - m3u8Extractor
|
||||||
* - jwplayerExtractor
|
* - jwplayerExtractor
|
||||||
*
|
*
|
||||||
* # Extension
|
* # Extension Helpers
|
||||||
* - sortVideos()
|
* - sortVideos()
|
||||||
*
|
*
|
||||||
* # Encoding/Decoding
|
* # Uint8Array
|
||||||
* - Uint8Array.fromBase64()
|
* - Uint8Array.fromBase64()
|
||||||
* - Uint8Array.prototype.toBase64()
|
* - Uint8Array.prototype.toBase64()
|
||||||
* - Uint8Array.prototype.decode()
|
* - Uint8Array.prototype.decode()
|
||||||
|
*
|
||||||
|
* # String
|
||||||
* - String.prototype.encode()
|
* - String.prototype.encode()
|
||||||
* - String.prototype.decode()
|
* - String.decode()
|
||||||
*
|
* - String.prototype.reverse()
|
||||||
* # Random string
|
* - String.prototype.swapcase()
|
||||||
* - getRandomString()
|
* - getRandomString()
|
||||||
|
*
|
||||||
|
* # Encode/Decode Functions
|
||||||
|
* - decodeUTF8
|
||||||
|
* - encodeUTF8
|
||||||
*
|
*
|
||||||
* # URL
|
* # Url
|
||||||
* - absUrl()
|
* - absUrl()
|
||||||
*
|
*
|
||||||
***************************************************************************************************/
|
***************************************************************************************************/
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
async function vidGuardExtractor(url) {
|
async function vidGuardExtractor(url) {
|
||||||
// get html
|
// get html
|
||||||
const res = await new Client().get(url);
|
const res = await new Client().get(url);
|
||||||
@@ -986,8 +969,8 @@ async function vidHideExtractor(url) {
|
|||||||
return await jwplayerExtractor(res.body);
|
return await jwplayerExtractor(res.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filemoonExtractor(url) {
|
async function filemoonExtractor(url, headers) {
|
||||||
let res = await new Client().get(url);
|
let res = await new Client().get(url, headers);
|
||||||
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
||||||
if (src) {
|
if (src) {
|
||||||
res = await new Client().get(src, {
|
res = await new Client().get(src, {
|
||||||
@@ -1019,6 +1002,37 @@ async function mixdropExtractor(url) {
|
|||||||
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function speedfilesExtractor(url) {
|
||||||
|
let res = await new Client().get(url);
|
||||||
|
let doc = new Document(res.body);
|
||||||
|
|
||||||
|
const code = doc.selectFirst('script:contains(var)').text;
|
||||||
|
let b64;
|
||||||
|
|
||||||
|
// Get b64
|
||||||
|
for (const match of code.matchAll(/(?:var|let|const)\s*\w+\s*=\s*["']([^"']+)/g)) {
|
||||||
|
if (match[1].match(/[g-zG-Z]/)) {
|
||||||
|
b64 = match[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode b64 => b64
|
||||||
|
const step1 = Uint8Array.fromBase64(b64).reverse().decode().swapcase();
|
||||||
|
// decode b64 => hex
|
||||||
|
const step2 = Uint8Array.fromBase64(step1).reverse().decode();
|
||||||
|
// decode hex => b64
|
||||||
|
let step3 = [];
|
||||||
|
for (let i = 0; i < step2.length; i += 2) {
|
||||||
|
step3.push(parseInt(step2.slice(i, i + 2), 16) - 3);
|
||||||
|
}
|
||||||
|
step3 = String.fromCharCode(...step3.reverse()).swapcase();
|
||||||
|
// decode b64 => url
|
||||||
|
const videoUrl = Uint8Array.fromBase64(step3).decode();
|
||||||
|
|
||||||
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: null}];
|
||||||
|
}
|
||||||
|
|
||||||
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
||||||
async function burstcloudExtractor(url) {
|
async function burstcloudExtractor(url) {
|
||||||
let client = new Client();
|
let client = new Client();
|
||||||
@@ -1043,6 +1057,10 @@ async function burstcloudExtractor(url) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Wrappers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_streamWishExtractor = streamWishExtractor;
|
_streamWishExtractor = streamWishExtractor;
|
||||||
streamWishExtractor = async (url) => {
|
streamWishExtractor = async (url) => {
|
||||||
return (await _streamWishExtractor(url, '')).map(v => {
|
return (await _streamWishExtractor(url, '')).map(v => {
|
||||||
@@ -1099,9 +1117,13 @@ sendVidExtractor = async (url) => {
|
|||||||
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractAny(url, method, lang, type, host) {
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function extractAny(url, method, lang, type, host, headers = null) {
|
||||||
const m = extractAny.methods[method];
|
const m = extractAny.methods[method];
|
||||||
return (!m) ? [] : (await m(url)).map(v => {
|
return (!m) ? [] : (await m(url, headers)).map(v => {
|
||||||
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
@@ -1116,6 +1138,7 @@ extractAny.methods = {
|
|||||||
'mp4upload': mp4UploadExtractor,
|
'mp4upload': mp4UploadExtractor,
|
||||||
'okru': okruExtractor,
|
'okru': okruExtractor,
|
||||||
'sendvid': sendVidExtractor,
|
'sendvid': sendVidExtractor,
|
||||||
|
'speedfiles': speedfilesExtractor,
|
||||||
'streamtape': streamTapeExtractor,
|
'streamtape': streamTapeExtractor,
|
||||||
'streamwish': vidHideExtractor,
|
'streamwish': vidHideExtractor,
|
||||||
'vidguard': vidGuardExtractor,
|
'vidguard': vidGuardExtractor,
|
||||||
@@ -1125,6 +1148,10 @@ extractAny.methods = {
|
|||||||
'yourupload': yourUploadExtractor
|
'yourupload': yourUploadExtractor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Playlist Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
async function m3u8Extractor(url, headers = null) {
|
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/creating-a-multivariant-playlist
|
||||||
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
||||||
@@ -1262,6 +1289,10 @@ async function jwplayerExtractor(text, headers) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Extension Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function sortVideos(videos) {
|
function sortVideos(videos) {
|
||||||
const pref = new SharedPreferences();
|
const pref = new SharedPreferences();
|
||||||
const getres = RegExp('(\\d+)p?', 'i');
|
const getres = RegExp('(\\d+)p?', 'i');
|
||||||
@@ -1293,6 +1324,10 @@ function sortVideos(videos) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Uint8Array
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Uint8Array.fromBase64 = function (b64) {
|
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]
|
// [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]
|
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]
|
||||||
@@ -1332,6 +1367,10 @@ Uint8Array.prototype.decode = function (encoding = 'utf-8') {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// String
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
String.prototype.encode = function (encoding = 'utf-8') {
|
String.prototype.encode = function (encoding = 'utf-8') {
|
||||||
encoding = encoding.toLowerCase();
|
encoding = encoding.toLowerCase();
|
||||||
if (encoding == 'utf-8') {
|
if (encoding == 'utf-8') {
|
||||||
@@ -1348,6 +1387,32 @@ String.decode = function (data, encoding = 'utf-8') {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String.prototype.reverse = function () {
|
||||||
|
return this.split('').reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.swapcase = function () {
|
||||||
|
const isAsciiLetter = /[A-z]/;
|
||||||
|
const result = [];
|
||||||
|
for (const l of this)
|
||||||
|
result.push(isAsciiLetter.test(l) ? String.fromCharCode(l.charCodeAt() ^ 32) : l);
|
||||||
|
return result.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Encode/Decode Functions
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function decodeUTF8(data) {
|
function decodeUTF8(data) {
|
||||||
const codes = [];
|
const codes = [];
|
||||||
for (let i = 0; i < data.length;) {
|
for (let i = 0; i < data.length;) {
|
||||||
@@ -1377,15 +1442,9 @@ function encodeUTF8(string) {
|
|||||||
return new Uint8Array(data);
|
return new Uint8Array(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomString(length) {
|
//--------------------------------------------------------------------------------------------------
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
// Url
|
||||||
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) {
|
function absUrl(url, base) {
|
||||||
if (url.search(/^\w+:\/\//) == 0) {
|
if (url.search(/^\w+:\/\//) == 0) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const mangayomiSources = [{
|
|||||||
"iconUrl": "https://tioanime.com/assets/img/tio_fb.jpg",
|
"iconUrl": "https://tioanime.com/assets/img/tio_fb.jpg",
|
||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"isManga": false,
|
"isManga": false,
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"dateFormat": "",
|
"dateFormat": "",
|
||||||
"dateFormatLocale": "",
|
"dateFormatLocale": "",
|
||||||
"pkgPath": "anime/src/es/tioanime.js"
|
"pkgPath": "anime/src/es/tioanime.js"
|
||||||
@@ -32,8 +32,8 @@ class DefaultExtension extends MProvider {
|
|||||||
const link = element.selectFirst("a").getHref;
|
const link = element.selectFirst("a").getHref;
|
||||||
list.push({ name, imageUrl, link });
|
list.push({ name, imageUrl, link });
|
||||||
}
|
}
|
||||||
const hasNextPage = doc.selectFirst("li.page-item.active + li").text == "»";
|
const isLastPage = doc.selectFirst("li.page-item.active + li").text == "»";
|
||||||
return { "list": list, "hasNextPage": hasNextPage };
|
return { "list": list, "hasNextPage": !isLastPage };
|
||||||
}
|
}
|
||||||
statusFromString(status) {
|
statusFromString(status) {
|
||||||
return {
|
return {
|
||||||
@@ -106,6 +106,11 @@ class DefaultExtension extends MProvider {
|
|||||||
throw new Error("getFilterList not implemented");
|
throw new Error("getFilterList not implemented");
|
||||||
}
|
}
|
||||||
getSourcePreferences() {
|
getSourcePreferences() {
|
||||||
|
const languages = ['Español'];
|
||||||
|
const types = ['Sub'];
|
||||||
|
const resolutions = ['1080p', '720p', '480p'];
|
||||||
|
const hosts = ['Okru', 'VidGuard', 'Voe', 'YourUpload'];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'lang',
|
key: 'lang',
|
||||||
@@ -113,12 +118,8 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Language',
|
title: 'Preferred Language',
|
||||||
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: languages,
|
||||||
'Español'
|
entryValues: languages
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Español'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -127,12 +128,8 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Type',
|
title: 'Preferred Type',
|
||||||
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: types,
|
||||||
'Sub'
|
entryValues: types
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Sub'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -141,36 +138,18 @@ class DefaultExtension extends MProvider {
|
|||||||
title: 'Preferred Resolution',
|
title: 'Preferred Resolution',
|
||||||
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: resolutions,
|
||||||
'1080p',
|
entryValues: resolutions
|
||||||
'720p',
|
|
||||||
'480p'
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'1080p',
|
|
||||||
'720p',
|
|
||||||
'480p'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'host',
|
key: 'host',
|
||||||
listPreference: {
|
listPreference: {
|
||||||
title: 'Preferred Hoster',
|
title: 'Preferred Host',
|
||||||
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: [
|
entries: hosts,
|
||||||
'Okru',
|
entryValues: hosts
|
||||||
'VidGuard',
|
|
||||||
'Voe',
|
|
||||||
'YourUpload'
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
'Okru',
|
|
||||||
'VidGuard',
|
|
||||||
'Voe',
|
|
||||||
'YourUpload'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -179,7 +158,7 @@ class DefaultExtension extends MProvider {
|
|||||||
|
|
||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
*
|
*
|
||||||
* mangayomi-js-helpers v1.0
|
* mangayomi-js-helpers v1.1
|
||||||
*
|
*
|
||||||
* # Video Extractors
|
* # Video Extractors
|
||||||
* - vidGuardExtractor
|
* - vidGuardExtractor
|
||||||
@@ -190,9 +169,10 @@ class DefaultExtension extends MProvider {
|
|||||||
* - vidHideExtractor
|
* - vidHideExtractor
|
||||||
* - filemoonExtractor
|
* - filemoonExtractor
|
||||||
* - mixdropExtractor
|
* - mixdropExtractor
|
||||||
|
* - speedfilesExtractor
|
||||||
* - burstcloudExtractor (not working, see description)
|
* - burstcloudExtractor (not working, see description)
|
||||||
*
|
*
|
||||||
* # Video Extractor Format Wrappers
|
* # Video Extractor Wrappers
|
||||||
* - streamWishExtractor
|
* - streamWishExtractor
|
||||||
* - voeExtractor
|
* - voeExtractor
|
||||||
* - mp4UploadExtractor
|
* - mp4UploadExtractor
|
||||||
@@ -207,24 +187,34 @@ class DefaultExtension extends MProvider {
|
|||||||
* - m3u8Extractor
|
* - m3u8Extractor
|
||||||
* - jwplayerExtractor
|
* - jwplayerExtractor
|
||||||
*
|
*
|
||||||
* # Extension
|
* # Extension Helpers
|
||||||
* - sortVideos()
|
* - sortVideos()
|
||||||
*
|
*
|
||||||
* # Encoding/Decoding
|
* # Uint8Array
|
||||||
* - Uint8Array.fromBase64()
|
* - Uint8Array.fromBase64()
|
||||||
* - Uint8Array.prototype.toBase64()
|
* - Uint8Array.prototype.toBase64()
|
||||||
* - Uint8Array.prototype.decode()
|
* - Uint8Array.prototype.decode()
|
||||||
|
*
|
||||||
|
* # String
|
||||||
* - String.prototype.encode()
|
* - String.prototype.encode()
|
||||||
* - String.prototype.decode()
|
* - String.decode()
|
||||||
*
|
* - String.prototype.reverse()
|
||||||
* # Random string
|
* - String.prototype.swapcase()
|
||||||
* - getRandomString()
|
* - getRandomString()
|
||||||
|
*
|
||||||
|
* # Encode/Decode Functions
|
||||||
|
* - decodeUTF8
|
||||||
|
* - encodeUTF8
|
||||||
*
|
*
|
||||||
* # URL
|
* # Url
|
||||||
* - absUrl()
|
* - absUrl()
|
||||||
*
|
*
|
||||||
***************************************************************************************************/
|
***************************************************************************************************/
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
async function vidGuardExtractor(url) {
|
async function vidGuardExtractor(url) {
|
||||||
// get html
|
// get html
|
||||||
const res = await new Client().get(url);
|
const res = await new Client().get(url);
|
||||||
@@ -305,8 +295,8 @@ async function vidHideExtractor(url) {
|
|||||||
return await jwplayerExtractor(res.body);
|
return await jwplayerExtractor(res.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filemoonExtractor(url) {
|
async function filemoonExtractor(url, headers) {
|
||||||
let res = await new Client().get(url);
|
let res = await new Client().get(url, headers);
|
||||||
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
||||||
if (src) {
|
if (src) {
|
||||||
res = await new Client().get(src, {
|
res = await new Client().get(src, {
|
||||||
@@ -338,6 +328,37 @@ async function mixdropExtractor(url) {
|
|||||||
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function speedfilesExtractor(url) {
|
||||||
|
let res = await new Client().get(url);
|
||||||
|
let doc = new Document(res.body);
|
||||||
|
|
||||||
|
const code = doc.selectFirst('script:contains(var)').text;
|
||||||
|
let b64;
|
||||||
|
|
||||||
|
// Get b64
|
||||||
|
for (const match of code.matchAll(/(?:var|let|const)\s*\w+\s*=\s*["']([^"']+)/g)) {
|
||||||
|
if (match[1].match(/[g-zG-Z]/)) {
|
||||||
|
b64 = match[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode b64 => b64
|
||||||
|
const step1 = Uint8Array.fromBase64(b64).reverse().decode().swapcase();
|
||||||
|
// decode b64 => hex
|
||||||
|
const step2 = Uint8Array.fromBase64(step1).reverse().decode();
|
||||||
|
// decode hex => b64
|
||||||
|
let step3 = [];
|
||||||
|
for (let i = 0; i < step2.length; i += 2) {
|
||||||
|
step3.push(parseInt(step2.slice(i, i + 2), 16) - 3);
|
||||||
|
}
|
||||||
|
step3 = String.fromCharCode(...step3.reverse()).swapcase();
|
||||||
|
// decode b64 => url
|
||||||
|
const videoUrl = Uint8Array.fromBase64(step3).decode();
|
||||||
|
|
||||||
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: null}];
|
||||||
|
}
|
||||||
|
|
||||||
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
||||||
async function burstcloudExtractor(url) {
|
async function burstcloudExtractor(url) {
|
||||||
let client = new Client();
|
let client = new Client();
|
||||||
@@ -362,6 +383,10 @@ async function burstcloudExtractor(url) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Wrappers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_streamWishExtractor = streamWishExtractor;
|
_streamWishExtractor = streamWishExtractor;
|
||||||
streamWishExtractor = async (url) => {
|
streamWishExtractor = async (url) => {
|
||||||
return (await _streamWishExtractor(url, '')).map(v => {
|
return (await _streamWishExtractor(url, '')).map(v => {
|
||||||
@@ -418,9 +443,13 @@ sendVidExtractor = async (url) => {
|
|||||||
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractAny(url, method, lang, type, host) {
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Video Extractor Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function extractAny(url, method, lang, type, host, headers = null) {
|
||||||
const m = extractAny.methods[method];
|
const m = extractAny.methods[method];
|
||||||
return (!m) ? [] : (await m(url)).map(v => {
|
return (!m) ? [] : (await m(url, headers)).map(v => {
|
||||||
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
@@ -435,6 +464,7 @@ extractAny.methods = {
|
|||||||
'mp4upload': mp4UploadExtractor,
|
'mp4upload': mp4UploadExtractor,
|
||||||
'okru': okruExtractor,
|
'okru': okruExtractor,
|
||||||
'sendvid': sendVidExtractor,
|
'sendvid': sendVidExtractor,
|
||||||
|
'speedfiles': speedfilesExtractor,
|
||||||
'streamtape': streamTapeExtractor,
|
'streamtape': streamTapeExtractor,
|
||||||
'streamwish': vidHideExtractor,
|
'streamwish': vidHideExtractor,
|
||||||
'vidguard': vidGuardExtractor,
|
'vidguard': vidGuardExtractor,
|
||||||
@@ -444,6 +474,10 @@ extractAny.methods = {
|
|||||||
'yourupload': yourUploadExtractor
|
'yourupload': yourUploadExtractor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Playlist Extractors
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
async function m3u8Extractor(url, headers = null) {
|
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/creating-a-multivariant-playlist
|
||||||
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
||||||
@@ -581,6 +615,10 @@ async function jwplayerExtractor(text, headers) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Extension Helpers
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function sortVideos(videos) {
|
function sortVideos(videos) {
|
||||||
const pref = new SharedPreferences();
|
const pref = new SharedPreferences();
|
||||||
const getres = RegExp('(\\d+)p?', 'i');
|
const getres = RegExp('(\\d+)p?', 'i');
|
||||||
@@ -612,6 +650,10 @@ function sortVideos(videos) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Uint8Array
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Uint8Array.fromBase64 = function (b64) {
|
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]
|
// [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]
|
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]
|
||||||
@@ -651,6 +693,10 @@ Uint8Array.prototype.decode = function (encoding = 'utf-8') {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// String
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
String.prototype.encode = function (encoding = 'utf-8') {
|
String.prototype.encode = function (encoding = 'utf-8') {
|
||||||
encoding = encoding.toLowerCase();
|
encoding = encoding.toLowerCase();
|
||||||
if (encoding == 'utf-8') {
|
if (encoding == 'utf-8') {
|
||||||
@@ -667,6 +713,32 @@ String.decode = function (data, encoding = 'utf-8') {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String.prototype.reverse = function () {
|
||||||
|
return this.split('').reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.swapcase = function () {
|
||||||
|
const isAsciiLetter = /[A-z]/;
|
||||||
|
const result = [];
|
||||||
|
for (const l of this)
|
||||||
|
result.push(isAsciiLetter.test(l) ? String.fromCharCode(l.charCodeAt() ^ 32) : l);
|
||||||
|
return result.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
// Encode/Decode Functions
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function decodeUTF8(data) {
|
function decodeUTF8(data) {
|
||||||
const codes = [];
|
const codes = [];
|
||||||
for (let i = 0; i < data.length;) {
|
for (let i = 0; i < data.length;) {
|
||||||
@@ -696,15 +768,9 @@ function encodeUTF8(string) {
|
|||||||
return new Uint8Array(data);
|
return new Uint8Array(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomString(length) {
|
//--------------------------------------------------------------------------------------------------
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
// Url
|
||||||
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) {
|
function absUrl(url, base) {
|
||||||
if (url.search(/^\w+:\/\//) == 0) {
|
if (url.search(/^\w+:\/\//) == 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user