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