diff --git a/javascript/anime/src/all/autoembed.js b/javascript/anime/src/all/autoembed.js index 1f5e35c6..ba2d2d7b 100644 --- a/javascript/anime/src/all/autoembed.js +++ b/javascript/anime/src/all/autoembed.js @@ -1,646 +1,714 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "Autoembed", "lang": "all", "baseUrl": "https://watch.autoembed.cc", "apiUrl": "https://tom.autoembed.cc", - "iconUrl": "https://www.google.com/s2/favicons?sz=64&domain=https://autoembed.cc/", + "iconUrl": + "https://www.google.com/s2/favicons?sz=64&domain=https://autoembed.cc/", "typeSource": "multi", "isManga": false, "itemType": 1, - "version": "1.2.6", + "version": "1.2.7", "dateFormat": "", "dateFormatLocale": "", "pkgPath": "anime/src/all/autoembed.js" -}]; + } +]; class DefaultExtension extends MProvider { - decodeBase64 = function (f) { - var g = {}, - b = 65, - d = 0, - a, c = 0, - h, e = "", - k = String.fromCharCode, - l = f.length; - for (a = ""; 91 > b;) a += k(b++); - a += a.toLowerCase() + "0123456789+/"; - for (b = 0; 64 > b; b++) g[a.charAt(b)] = b; - for (a = 0; a < l; a++) - for (b = g[f.charAt(a)], d = (d << 6) + b, c += 6; 8 <= c;)((h = d >>> (c -= 8) & 255) || a < l - 2) && (e += k(h)); - return e + decodeBase64 = function (f) { + var g = {}, + b = 65, + d = 0, + a, + c = 0, + h, + e = "", + k = String.fromCharCode, + l = f.length; + for (a = ""; 91 > b; ) a += k(b++); + a += a.toLowerCase() + "0123456789+/"; + for (b = 0; 64 > b; b++) g[a.charAt(b)] = b; + for (a = 0; a < l; a++) + for (b = g[f.charAt(a)], d = (d << 6) + b, c += 6; 8 <= c; ) + ((h = (d >>> (c -= 8)) & 255) || a < l - 2) && (e += k(h)); + return e; + }; + getHeaders(url) { + return { + Referer: url, + Origin: url, }; - getHeaders(url) { - return { - Referer: url, - Origin: url + } + + getPreference(key) { + const preferences = new SharedPreferences(); + return preferences.get(key); + } + + async tmdbRequest(slug) { + var api = `https://94c8cb9f702d-tmdb-addon.baby-beamup.club/${slug}`; + var response = await new Client().get(api); + var body = JSON.parse(response.body); + return body; + } + + async getSearchItems(body) { + var items = []; + var results = body.metas; + for (let i in results) { + var result = results[i]; + var id = result.id; + var media_type = result.type; + items.push({ + name: result.name, + imageUrl: result.poster, + link: `${media_type}||${id}`, + description: result.description, + genre: result.genre, + }); + } + return items; + } + async getSearchInfo(slug) { + var body = await this.tmdbRequest(`catalog/movie/${slug}`); + var popMovie = await this.getSearchItems(body); + + body = await this.tmdbRequest(`catalog/series/${slug}`); + var popSeries = await this.getSearchItems(body); + + var fullList = []; + + var priority = this.getPreference("pref_content_priority"); + if (priority === "series") { + fullList = [...popSeries, ...popMovie]; + } else { + fullList = [...popMovie, ...popSeries]; + } + var hasNextPage = slug.indexOf("search=") > -1 ? false : true; + return { + list: fullList, + hasNextPage, + }; + } + + async getPopular(page) { + var skip = (page - 1) * 20; + return await this.getSearchInfo(`tmdb.popular/skip=${skip}.json`); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + var trend_window = this.getPreference("pref_latest_time_window"); + var skip = (page - 1) * 20; + return await this.getSearchInfo( + `tmdb.trending/genre=${trend_window}&skip=${skip}.json` + ); + } + async search(query, page, filters) { + return await this.getSearchInfo(`tmdb.popular/search=${query}.json`); + } + async getDetail(url) { + var baseUrl = this.source.baseUrl; + var linkSlug = `${baseUrl}/title/`; + + if (url.includes(linkSlug)) { + url = url.replace(linkSlug, ""); + var id = url.replace("t", ""); + if (url.includes("t")) { + url = `series||tmdb:${id}`; + } else { + url = `movie||tmdb:${id}`; + } + } + + var parts = url.split("||"); + var media_type = parts[0]; + var id = parts[1]; + var body = await this.tmdbRequest(`meta/${media_type}/${id}.json`); + var result = body.meta; + + var tmdb_id = id.substring(5); + media_type = media_type == "series" ? "tv" : media_type; + + var dateNow = Date.now().valueOf(); + var release = result.released + ? new Date(result.released).valueOf() + : dateNow; + var chaps = []; + + var item = { + name: result.name, + imageUrl: result.poster, + link: `${linkSlug}${linkCode}`, + description: result.description, + genre: result.genre, + }; + + var link = `${media_type}||${tmdb_id}`; + + if (media_type == "tv") { + var videos = result.videos; + for (var i in videos) { + var video = videos[i]; + var seasonNum = video.season; + + if (!seasonNum) continue; + + release = video.released ? new Date(video.released).valueOf() : dateNow; + + if (release < dateNow) { + var episodeNum = video.episode; + var name = `S${seasonNum}:E${episodeNum} - ${video.name}`; + var eplink = `${link}||${seasonNum}||${episodeNum}`; + + chaps.push({ + name: name, + url: eplink, + dateUpload: release.toString(), + }); } + } + } else { + if (release < dateNow) { + chaps.push({ + name: "Movie", + url: link, + dateUpload: release.toString(), + }); + } } - getPreference(key) { - const preferences = new SharedPreferences(); - return preferences.get(key); + item.chapters = chaps; + chaps.reverse(); + return item; + } + + // Extracts the streams url for different resolutions from a hls stream. + async extractStreams(url, lang = "", hdr = {}, host = "") { + var streams = [ + { + url: url, + originalUrl: url, + quality: `${lang} Auto`, + headers: hdr, + }, + ]; + + var pref = this.getPreference("autoembed_split_stream_quality"); + if (!pref) return streams; + + const response = await new Client().get(url, hdr); + const body = response.body; + const lines = body.split("\n"); + + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith("#EXT-X-STREAM-INF:")) { + var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1]; + resolution = `${lang} ${resolution}`; + var m3u8Url = lines[i + 1].trim(); + m3u8Url = m3u8Url.replace("./", `${url}/`); + if (host.length > 0) { + m3u8Url = `${host}${m3u8Url}`; + } + streams.push({ + url: m3u8Url, + originalUrl: m3u8Url, + quality: resolution, + headers: hdr, + }); + } + } + return streams; + } + + // For some streams, we can form stream url using a default template. + async splitStreams(url, lang = "", hdr = {}) { + var streams = [ + { + url: url, + originalUrl: url, + quality: `${lang} - Auto`, + headers: hdr, + }, + ]; + + var pref = this.getPreference("autoembed_split_stream_quality"); + if (!pref) return streams; + + var quality = ["360", "480", "720", "1080"]; + for (var q of quality) { + var link = url; + if (q != "auto") { + link = link.replace("index.m3u8", `${q}/index.m3u8`); + q = `${q}p`; + } + streams.push({ + url: link, + originalUrl: link, + quality: `${lang} - ${q}`, + headers: hdr, + }); + } + return streams; + } + + // Sorts streams based on user preference. + async sortStreams(streams) { + var sortedStreams = []; + + var copyStreams = streams.slice(); + var pref = this.getPreference("pref_video_resolution"); + for (var i in streams) { + var stream = streams[i]; + if (stream.quality.indexOf(pref) > -1) { + sortedStreams.push(stream); + var index = copyStreams.indexOf(stream); + if (index > -1) { + copyStreams.splice(index, 1); + } + break; + } + } + return [...sortedStreams, ...copyStreams]; + } + + // Gets subtitles based on TMDB id. + async getSubtitleList(id, s, e) { + var subPref = parseInt( + this.getPreference("autoembed_pref_subtitle_source") + ); + + var api = `https://sub.wyzie.ru/search?id=${id}`; + var hdr = {}; + + if (subPref === 2) { + api = `https://sources.hexa.watch/subs/${id}`; + hdr = { "Origin": "https://api.hexa.watch" }; + if (s != "0") api = `${api}/${s}/${e}`; + } else { + if (s != "0") api = `${api}&season=${s}&episode=${e}`; + } + var response = await new Client().get(api, hdr); + var body = JSON.parse(response.body); + + var subs = []; + for (var sub of body) { + subs.push({ + file: sub.url, + label: sub.display, + }); } - async tmdbRequest(slug) { - var api = `https://94c8cb9f702d-tmdb-addon.baby-beamup.club/${slug}` + return subs; + } + + // For anime episode video list + async getVideoList(url) { + var streamAPI = parseInt(this.getPreference("autoembed_stream_source_3")); + var nativeSubs = this.getPreference("autoembed_pref_navtive_subtitle"); + + var parts = url.split("||"); + var media_type = parts[0]; + var id = parts[1]; + + var s = "0"; + var e = "0"; + if (media_type == "tv") { + s = parts[2]; + e = parts[3]; + } + + var tmdb = id; + var streams = []; + var subtitles = []; + switch (streamAPI) { + case 2: { + if (media_type == "tv") { + id = `${id}/${s}/${e}`; + } + var api = `https://play2.123embed.net/server/3?path=/${media_type}/${id}`; var response = await new Client().get(api); + + if (response.statusCode != 200) { + throw new Error( + "play2.123embed.net unavailable\nPlease choose a different server" + ); + } + var body = JSON.parse(response.body); - return body; - } - - async getSearchItems(body) { - var items = []; - var results = body.metas; - for (let i in results) { - var result = results[i]; - var id = result.id - var media_type = result.type; - items.push({ - name: result.name, - imageUrl: result.poster, - link: `${media_type}||${id}`, - description: result.description, - genre: result.genre - }); - } - return items; - - } - async getSearchInfo(slug) { - - var body = await this.tmdbRequest(`catalog/movie/${slug}`); - var popMovie = await this.getSearchItems(body); - - - body = await this.tmdbRequest(`catalog/series/${slug}`); - var popSeries = await this.getSearchItems(body); - - var fullList = []; - - var priority = this.getPreference("pref_content_priority"); - if (priority === "series") { - fullList = [...popSeries, ...popMovie]; - } else { - fullList = [...popMovie, ...popSeries] - } - var hasNextPage = slug.indexOf("search=") > -1 ? false : true; - return { - list: fullList, - hasNextPage - }; - - } - - - async getPopular(page) { - var skip = (page - 1) * 20; - return await this.getSearchInfo(`tmdb.popular/skip=${skip}.json`); - } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } - async getLatestUpdates(page) { - var trend_window = this.getPreference("pref_latest_time_window"); - var skip = (page - 1) * 20; - return await this.getSearchInfo(`tmdb.trending/genre=${trend_window}&skip=${skip}.json`); - } - async search(query, page, filters) { - return await this.getSearchInfo(`tmdb.popular/search=${query}.json`); - } - async getDetail(url) { - var parts = url.split("||"); - var media_type = parts[0]; - var id = parts[1]; - var body = await this.tmdbRequest(`meta/${media_type}/${id}.json`) - var result = body.meta; - - var tmdb_id = id.substring(5,) - media_type = media_type == "series" ? "tv" : media_type; - - var dateNow = Date.now().valueOf(); - var release = result.released ? new Date(result.released).valueOf() : dateNow - var chaps = []; - - var item = { - name: result.name, - imageUrl: result.poster, - link: `${this.source.baseUrl}/${media_type}/${tmdb_id}`, - description: result.description, - genre: result.genre, - }; - - var link = `${media_type}||${tmdb_id}` - + var link = body.playlist[0].file; + streams.push({ + url: link, + originalUrl: link, + quality: "auto", + headers: { "Origin": "https://play2.123embed.net" }, + }); + break; + } + case 3: { if (media_type == "tv") { + id = `${id}&s=${s}&e=${e}`; + } + var api = `https://autoembed.cc/embed/player.php?id=${id}`; - var videos = result.videos - for (var i in videos) { - var video = videos[i]; - var seasonNum = video.season; + var response = await new Client().get(api); - if (!seasonNum) continue; + if (response.statusCode != 200) { + throw new Error( + "autoembed.cc unavailable\nPlease choose a different server" + ); + } + var body = response.body; + var sKey = '"file": '; + var eKey = "]});"; + var start = body.indexOf(sKey); + if (start < 0) { + throw new Error( + "autoembed.cc videos unavailable\nPlease choose a different server" + ); + } + start += sKey.length; - release = video.released ? new Date(video.released).valueOf() : dateNow - - if (release < dateNow) { - var episodeNum = video.episode - var name = `S${seasonNum}:E${episodeNum} - ${video.name}` - var eplink = `${link}||${seasonNum}||${episodeNum}` - - chaps.push({ - name: name, - url: eplink, - dateUpload: release.toString(), - }) - } - } - } else { - if (release < dateNow) { - chaps.push({ - name: "Movie", - url: link, - dateUpload: release.toString(), - }) - } + var end = body.substring(start).indexOf(eKey) + start - 1; + var strms = JSON.parse(body.substring(start, end) + "]"); + for (var strm of strms) { + var link = strm.file; + var lang = strm.title; + var streamSplit = await this.splitStreams(link, lang); + streams = [...streams, ...streamSplit]; } - item.chapters = chaps; - chaps.reverse(); - return item; - } - - // Extracts the streams url for different resolutions from a hls stream. - async extractStreams(url, lang = "", hdr = {}, host = "") { - var streams = [{ - url: url, - originalUrl: url, - quality: `${lang} Auto`, - headers: hdr - }]; - - var pref = this.getPreference("autoembed_split_stream_quality"); - if (!pref) return streams - - - const response = await new Client().get(url, hdr); - const body = response.body; - const lines = body.split('\n'); - - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith('#EXT-X-STREAM-INF:')) { - var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1]; - resolution = `${lang} ${resolution}` - var m3u8Url = lines[i + 1].trim(); - m3u8Url = m3u8Url.replace("./", `${url}/`) - if (host.length > 0) { - m3u8Url = `${host}${m3u8Url}` - } - streams.push({ - url: m3u8Url, - originalUrl: m3u8Url, - quality: resolution, - headers: hdr - }); - } + break; + } + case 4: { + if (media_type == "tv") { + id = `${id}&season=${s}&episode=${e}`; } - return streams + var api = `https://flicky.host/player/desi.php?id=${id}`; + var response = await new Client().get(api, { + "Referer": "https://flicky.host/", + "sec-fetch-dest": "iframe", + }); - } - - // For some streams, we can form stream url using a default template. - async splitStreams(url, lang = "", hdr = {}) { - var streams = [{ - url: url, - originalUrl: url, - quality: `${lang} - Auto`, - headers: hdr - }]; - - var pref = this.getPreference("autoembed_split_stream_quality"); - if (!pref) return streams - - - var quality = ["360", "480", "720", "1080"] - for (var q of quality) { - var link = url - if (q != "auto") { - link = link.replace("index.m3u8", `${q}/index.m3u8`) - q = `${q}p` - } - streams.push({ - url: link, - originalUrl: link, - quality: `${lang} - ${q}`, - headers: hdr - }); + if (response.statusCode != 200) { + throw new Error( + "flicky.host unavailable\nPlease choose a different server" + ); } - return streams; - } - - // Sorts streams based on user preference. - async sortStreams(streams) { - var sortedStreams = []; - - var copyStreams = streams.slice() - var pref = this.getPreference("pref_video_resolution"); - for (var i in streams) { - var stream = streams[i]; - if (stream.quality.indexOf(pref) > -1) { - sortedStreams.push(stream); - var index = copyStreams.indexOf(stream); - if (index > -1) { - copyStreams.splice(index, 1); - } - break; - } + var body = response.body; + var sKey = "streams = "; + var eKey = "];"; + var start = body.indexOf(sKey); + if (start < 0) { + throw new Error( + "flicky.host videos unavailable\nPlease choose a different server" + ); } - return [...sortedStreams, ...copyStreams] - } + start += sKey.length; - // Gets subtitles based on TMDB id. - async getSubtitleList(id, s, e) { - var subPref = parseInt(this.getPreference("autoembed_pref_subtitle_source")); + var end = body.substring(start).indexOf(eKey) + start + 1; + var strms = JSON.parse(body.substring(start, end)); - var api = `https://sub.wyzie.ru/search?id=${id}` - var hdr = {} - - if (subPref === 2) { - api = `https://sources.hexa.watch/subs/${id}` - hdr = { "Origin": "https://api.hexa.watch" } - if (s != "0") api = `${api}/${s}/${e}` - } else { - if (s != "0") api = `${api}&season=${s}&episode=${e}` + for (var strm of strms) { + var link = strm.url; + var lang = strm.language; + var streamSplit = await this.splitStreams(link, lang); + streams = [...streams, ...streamSplit]; } + + break; + } + case 5: { + if (media_type == "tv") { + id = `${id}/${s}/${e}`; + } + var api = `https://vidapi.click/api/video/${media_type}/${id}`; + var response = await new Client().get(api); + + if (response.statusCode != 200) { + throw new Error( + "vidapi.click unavailable\nPlease choose a different server" + ); + } + + var body = JSON.parse(response.body); + var link = body.sources[0].file; + if (nativeSubs) subtitles = body.tracks; + streams = await this.extractStreams(link); + break; + } + case 6: { + if (media_type == "tv") { + id = `${id}/${s}/${e}`; + } + var api = `https://sources.hexa.watch/plsdontscrapemeuwu/${id}`; + var hdr = { "Origin": "https://api.hexa.watch" }; var response = await new Client().get(api, hdr); + + if (response.statusCode != 200) { + throw new Error( + "hexa.watch unavailable\nPlease choose a different server" + ); + } + var body = JSON.parse(response.body); - - var subs = [] - for (var sub of body) { - subs.push({ - file: sub.url, - label: sub.display - }) + var strms = body.streams; + for (var strm of strms) { + var streamLink = strm.url; + if (streamLink.length > 0) { + streams.push({ + url: strm.url, + originalUrl: strm.url, + quality: `${strm.label} - Auto`, + headers: strm.headers, + }); + } } - - return subs - } - - // For anime episode video list - async getVideoList(url) { - var streamAPI = parseInt(this.getPreference("autoembed_stream_source_3")) - var nativeSubs = this.getPreference("autoembed_pref_navtive_subtitle") - - var parts = url.split("||"); - var media_type = parts[0]; - var id = parts[1]; - - var s = "0" - var e = "0" + break; + } + case 7: { if (media_type == "tv") { - s = parts[2] - e = parts[3] + id = `${id}/${s}/${e}`; + } + var api = `https://vidsrc.su/embed/${media_type}/${id}`; + var response = await new Client().get(api); + + if (response.statusCode != 200) { + throw new Error( + "vidsrc.su unavailable\nPlease choose a different server" + ); + } + var body = response.body; + var sKey = "fixedServers = "; + var eKey = "];"; + var start = body.indexOf(sKey); + if (start < 0) { + throw new Error( + "vidsrc.su videos unavailable\nPlease choose a different server" + ); + } + start += sKey.length; + + var end = body.substring(start).indexOf(eKey) + start + 1; + var strms = body.substring(start, end); + + // Split the data into lines + var lines = strms.split("\n"); + + // Regex to match URLs in quotes that start with https:// + var regex = /url:\s*'(https:\/\/[^']+)'/; + var availableStreams = []; + + // Process each line + lines.forEach((line) => { + var match = line.match(regex); + if (match && match[1]) { + // Extract the label from the line + var labelMatch = line.match(/label:\s*'([^']+)'/); + var label = labelMatch ? labelMatch[1] : "Unknown"; + // Add to our results + availableStreams.push({ + url: match[1], + label: label, + }); + } + }); + + for (var stream of availableStreams) { + var streamSplit = await this.extractStreams(stream.url, stream.label); + streams = [...streams, ...streamSplit]; } - var tmdb = id - var streams = [] - var subtitles = [] - switch (streamAPI) { - case 2: { - if (media_type == "tv") { - id = `${id}/${s}/${e}` - } - var api = `https://play2.123embed.net/server/3?path=/${media_type}/${id}` - var response = await new Client().get(api); - - if (response.statusCode != 200) { - throw new Error("play2.123embed.net unavailable\nPlease choose a different server"); - } - - var body = JSON.parse(response.body); - var link = body.playlist[0].file - streams.push({ - url: link, - originalUrl: link, - quality: "auto", - headers: { "Origin": "https://play2.123embed.net" }, - }); - break; - } - case 3: { - if (media_type == "tv") { - id = `${id}&s=${s}&e=${e}` - } - var api = `https://autoembed.cc/embed/player.php?id=${id}` - - var response = await new Client().get(api); - - if (response.statusCode != 200) { - throw new Error("autoembed.cc unavailable\nPlease choose a different server"); - } - var body = response.body - var sKey = '"file": ' - var eKey = "]});" - var start = body.indexOf(sKey) - if (start < 0) { - throw new Error("autoembed.cc videos unavailable\nPlease choose a different server"); - } - start += sKey.length - - var end = body.substring(start,).indexOf(eKey) + start - 1 - var strms = JSON.parse(body.substring(start, end) + "]") - for (var strm of strms) { - var link = strm.file - var lang = strm.title - var streamSplit = await this.splitStreams(link, lang); - streams = [...streams, ...streamSplit] - } - - break; - } - case 4: { - if (media_type == "tv") { - id = `${id}&season=${s}&episode=${e}` - } - var api = `https://flicky.host/player/desi.php?id=${id}` - var response = await new Client().get(api, { "Referer": "https://flicky.host/", "sec-fetch-dest": "iframe" }); - - if (response.statusCode != 200) { - throw new Error("flicky.host unavailable\nPlease choose a different server"); - } - var body = response.body - var sKey = 'streams = ' - var eKey = "];" - var start = body.indexOf(sKey) - if (start < 0) { - throw new Error("flicky.host videos unavailable\nPlease choose a different server"); - } - start += sKey.length - - var end = body.substring(start,).indexOf(eKey) + start + 1 - var strms = JSON.parse(body.substring(start, end)) - - for (var strm of strms) { - var link = strm.url - var lang = strm.language - var streamSplit = await this.splitStreams(link, lang); - streams = [...streams, ...streamSplit] - } - - break; - } - case 5: { - if (media_type == "tv") { - id = `${id}/${s}/${e}` - } - var api = `https://vidapi.click/api/video/${media_type}/${id}` - var response = await new Client().get(api); - - if (response.statusCode != 200) { - throw new Error("vidapi.click unavailable\nPlease choose a different server"); - } - - var body = JSON.parse(response.body); - var link = body.sources[0].file - if(nativeSubs) subtitles = body.tracks - streams = await this.extractStreams(link); - break; - } - case 6: { - if (media_type == "tv") { - id = `${id}/${s}/${e}` - } - var api = `https://sources.hexa.watch/plsdontscrapemeuwu/${id}` - var hdr = { "Origin": "https://api.hexa.watch" } - var response = await new Client().get(api, hdr); - - if (response.statusCode != 200) { - throw new Error("hexa.watch unavailable\nPlease choose a different server"); - } - - var body = JSON.parse(response.body); - var strms = body.streams - for (var strm of strms) { - var streamLink = strm.url; - if (streamLink.length > 0) { - streams.push({ - url: strm.url, - originalUrl: strm.url, - quality: `${strm.label} - Auto`, - headers: strm.headers - }); - } - } - break; - } - case 7: { - if (media_type == "tv") { - id = `${id}/${s}/${e}` - } - var api = `https://vidsrc.su/embed/${media_type}/${id}` - var response = await new Client().get(api); - - if (response.statusCode != 200) { - throw new Error("vidsrc.su unavailable\nPlease choose a different server"); - } - var body = response.body - var sKey = 'fixedServers = ' - var eKey = "];" - var start = body.indexOf(sKey) - if (start < 0) { - throw new Error("vidsrc.su videos unavailable\nPlease choose a different server"); - } - start += sKey.length - - var end = body.substring(start,).indexOf(eKey) + start + 1 - var strms = body.substring(start, end) - - // Split the data into lines - var lines = strms.split('\n'); - - // Regex to match URLs in quotes that start with https:// - var regex = /url:\s*'(https:\/\/[^']+)'/; - var availableStreams = []; - - // Process each line - lines.forEach(line => { - var match = line.match(regex); - if (match && match[1]) { - // Extract the label from the line - var labelMatch = line.match(/label:\s*'([^']+)'/); - var label = labelMatch ? labelMatch[1] : "Unknown"; - // Add to our results - availableStreams.push({ - url: match[1], - label: label, - }); - } - }); - - for (var stream of availableStreams) { - var streamSplit = await this.extractStreams(stream.url, stream.label); - streams = [...streams, ...streamSplit] - } - - if (nativeSubs) { - // subtitles - sKey = 'const subtitles = ' - eKey = "];" - start = body.indexOf(sKey) - if (start < 0) { - break; // no need for native subtitle if not found. - } - start += sKey.length - - end = body.substring(start,).indexOf(eKey) + start + 1 - var natSubs = JSON.parse(body.substring(start, end)) - natSubs.forEach(sub=>{ - subtitles.push({ - file: sub.url, - label: sub.display - }) - }) - - } - break; - } - case 8: { - function reverse(str) { - return str.split("").reverse().join("") - } - - - if (media_type == "tv") { - id = `${id}/${s}/${e}` - } - var baseUrl = "https://embed.su" - var embedUrl = `${baseUrl}/embed/${media_type}/${id}` - var response = await new Client().get(embedUrl, this.getHeaders(baseUrl)); - - var body = response.body - var sKey = "JSON.parse(atob(`"; - var start = body.indexOf(sKey) + sKey.length; - var end = body.substring(start,).indexOf("`") + start - var configHash = body.substring(start, end) - - - var config = JSON.parse(this.decodeBase64(configHash)); - var encodedHash = this.decodeBase64(config.hash); - var decodeHash = reverse(encodedHash.split(".").map((item) => reverse(item)).join("")) - encodedHash = JSON.parse(this.decodeBase64(decodeHash)) - var serverHash = encodedHash[0].hash - - var api = `${baseUrl}/api/e/${serverHash}` - response = await new Client().get(api, this.getHeaders(baseUrl)); - var jsonRes = JSON.parse(response.body); - - streams = await this.extractStreams(jsonRes.source, "", this.getHeaders(baseUrl), baseUrl); - if (nativeSubs) subtitles = jsonRes.subtitles - break; - - } - default: { - if (media_type == "tv") { - id = `${id}/${s}/${e}` - } - var api = `${this.source.apiUrl}/api/getVideoSource?type=${media_type}&id=${id}` - var response = await new Client().get(api, this.getHeaders(this.source.apiUrl)); - - if (response.statusCode != 200) { - throw new Error("tom.autoembed.cc unavailable\nPlease choose a different server"); - } - - var body = JSON.parse(response.body); - var link = body.videoSource - if (nativeSubs) subtitles = body.subtitles - streams = await this.extractStreams(link); - break; - } + if (nativeSubs) { + // subtitles + sKey = "const subtitles = "; + eKey = "];"; + start = body.indexOf(sKey); + if (start < 0) { + break; // no need for native subtitle if not found. + } + start += sKey.length; + end = body.substring(start).indexOf(eKey) + start + 1; + var natSubs = JSON.parse(body.substring(start, end)); + natSubs.forEach((sub) => { + subtitles.push({ + file: sub.url, + label: sub.display, + }); + }); } - if (streams.length < 1) { - throw new Error("No streams unavailable\nPlease choose a different server"); + break; + } + case 8: { + function reverse(str) { + return str.split("").reverse().join(""); } - var apiSubs = await this.getSubtitleList(tmdb, s, e) - streams[0].subtitles = [...subtitles, ...apiSubs] + if (media_type == "tv") { + id = `${id}/${s}/${e}`; + } + var baseUrl = "https://embed.su"; + var embedUrl = `${baseUrl}/embed/${media_type}/${id}`; + var response = await new Client().get( + embedUrl, + this.getHeaders(baseUrl) + ); - return await this.sortStreams(streams) + var body = response.body; + var sKey = "JSON.parse(atob(`"; + var start = body.indexOf(sKey) + sKey.length; + var end = body.substring(start).indexOf("`") + start; + var configHash = body.substring(start, end); + + var config = JSON.parse(this.decodeBase64(configHash)); + var encodedHash = this.decodeBase64(config.hash); + var decodeHash = reverse( + encodedHash + .split(".") + .map((item) => reverse(item)) + .join("") + ); + encodedHash = JSON.parse(this.decodeBase64(decodeHash)); + var serverHash = encodedHash[0].hash; + + var api = `${baseUrl}/api/e/${serverHash}`; + response = await new Client().get(api, this.getHeaders(baseUrl)); + var jsonRes = JSON.parse(response.body); + + streams = await this.extractStreams( + jsonRes.source, + "", + this.getHeaders(baseUrl), + baseUrl + ); + if (nativeSubs) subtitles = jsonRes.subtitles; + break; + } + default: { + if (media_type == "tv") { + id = `${id}/${s}/${e}`; + } + var api = `${this.source.apiUrl}/api/getVideoSource?type=${media_type}&id=${id}`; + var response = await new Client().get( + api, + this.getHeaders(this.source.apiUrl) + ); + + if (response.statusCode != 200) { + throw new Error( + "tom.autoembed.cc unavailable\nPlease choose a different server" + ); + } + + var body = JSON.parse(response.body); + var link = body.videoSource; + if (nativeSubs) subtitles = body.subtitles; + streams = await this.extractStreams(link); + break; + } + } + if (streams.length < 1) { + throw new Error( + "No streams unavailable\nPlease choose a different server" + ); } - // For manga chapter pages - async getPageList() { - throw new Error("getPageList not implemented"); - } - getFilterList() { - throw new Error("getFilterList not implemented"); - } + var apiSubs = await this.getSubtitleList(tmdb, s, e); + streams[0].subtitles = [...subtitles, ...apiSubs]; - getSourcePreferences() { - return [{ - key: 'pref_latest_time_window', - listPreference: { - title: 'Preferred latest trend time window', - summary: '', - valueIndex: 0, - entries: ["Day", "Week"], - entryValues: ["day", "week"] - } - }, { - key: 'pref_video_resolution', - listPreference: { - title: 'Preferred video resolution', - summary: '', - valueIndex: 0, - entries: ["Auto", "1080p", "720p", "360p"], - entryValues: ["auto", "1080", "720", "360"] - } - }, { - key: 'pref_content_priority', - listPreference: { - title: 'Preferred content priority', - summary: 'Choose which type of content to show first', - valueIndex: 0, - entries: ["Movies", "Series"], - entryValues: ["movies", "series"] - } - }, - { - key: 'autoembed_split_stream_quality', - "switchPreferenceCompat": { - 'title': 'Split stream into different quality streams', - "summary": "Split stream Auto into 360p/720p/1080p", - "value": true - } - }, - { - key: 'autoembed_stream_source_3', - listPreference: { - title: 'Preferred stream source', - summary: '', - valueIndex: 0, - entries: ["tom.autoembed.cc", "123embed.net", "autoembed.cc - Indian languages", "flicky.host - Indian languages", "vidapi.click", "hexa.watch", "vidsrc.su", "embed.su"], - entryValues: ["1", "2", "3", "4", "5", "6", "7", "8"] - } - }, - { - key: 'autoembed_pref_navtive_subtitle', - "switchPreferenceCompat": { - 'title': 'Use native subtitles as well', - "summary": "Use subtitles provided by the source along with subtitle API", - "value": true - } - }, - { - key: 'autoembed_pref_subtitle_source', - listPreference: { - title: 'Preferred subtitle source', - summary: '', - valueIndex: 0, - entries: ["sub.wyzie.ru", "hexa.watch"], - entryValues: ["1", "2"] - } - }, - ]; + return await this.sortStreams(streams); + } - } -} \ No newline at end of file + // For manga chapter pages + async getPageList() { + throw new Error("getPageList not implemented"); + } + getFilterList() { + throw new Error("getFilterList not implemented"); + } + + getSourcePreferences() { + return [ + { + key: "pref_latest_time_window", + listPreference: { + title: "Preferred latest trend time window", + summary: "", + valueIndex: 0, + entries: ["Day", "Week"], + entryValues: ["day", "week"], + }, + }, + { + key: "pref_video_resolution", + listPreference: { + title: "Preferred video resolution", + summary: "", + valueIndex: 0, + entries: ["Auto", "1080p", "720p", "360p"], + entryValues: ["auto", "1080", "720", "360"], + }, + }, + { + key: "pref_content_priority", + listPreference: { + title: "Preferred content priority", + summary: "Choose which type of content to show first", + valueIndex: 0, + entries: ["Movies", "Series"], + entryValues: ["movies", "series"], + }, + }, + { + key: "autoembed_split_stream_quality", + "switchPreferenceCompat": { + "title": "Split stream into different quality streams", + "summary": "Split stream Auto into 360p/720p/1080p", + "value": true, + }, + }, + { + key: "autoembed_stream_source_3", + listPreference: { + title: "Preferred stream source", + summary: "", + valueIndex: 0, + entries: [ + "tom.autoembed.cc", + "123embed.net", + "autoembed.cc - Indian languages", + "flicky.host - Indian languages", + "vidapi.click", + "hexa.watch", + "vidsrc.su", + "embed.su", + ], + entryValues: ["1", "2", "3", "4", "5", "6", "7", "8"], + }, + }, + { + key: "autoembed_pref_navtive_subtitle", + "switchPreferenceCompat": { + "title": "Use native subtitles as well", + "summary": + "Use subtitles provided by the source along with subtitle API", + "value": true, + }, + }, + { + key: "autoembed_pref_subtitle_source", + listPreference: { + title: "Preferred subtitle source", + summary: "", + valueIndex: 0, + entries: ["sub.wyzie.ru", "hexa.watch"], + entryValues: ["1", "2"], + }, + }, + ]; + } +} diff --git a/javascript/anime/src/all/soaper.js b/javascript/anime/src/all/soaper.js index 49e2f917..7ccdbf4f 100644 --- a/javascript/anime/src/all/soaper.js +++ b/javascript/anime/src/all/soaper.js @@ -1,266 +1,279 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "Soaper", + "id": 764093578, "lang": "all", "baseUrl": "https://soaper.cc", "apiUrl": "", - "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://soaper.cc/", + "iconUrl": + "https://www.google.com/s2/favicons?sz=128&domain=https://soaper.cc/", "typeSource": "multi", - "version": "1.0.4", + "version": "1.0.5", "itemType": 1, "dateFormat": "", "dateFormatLocale": "", "pkgPath": "anime/src/all/soaper.js" -}]; + } +]; + +// Authors: - Swakshan, kodjodevf class DefaultExtension extends MProvider { - getHeaders(url) { - return { - "Referer": url, - "Origin": url + getHeaders(url) { + return { + Referer: url, + Origin: url, + }; + } + + getPreference(key) { + return new SharedPreferences().get(key); + } + + getBasueUrl() { + return this.getPreference("soaper_override_base_url"); + } + + async request(slug) { + const baseUrl = this.getBasueUrl(); + var url = `${baseUrl}/${slug}`; + var res = await new Client().get(url, this.getHeaders(baseUrl)); + var doc = new Document(res.body); + return doc; + } + + async requestJSON(slug, data) { + const baseUrl = this.getBasueUrl(); + var url = `${baseUrl}/${slug}`; + var res = await new Client().post(url, this.getHeaders(baseUrl), data); + return JSON.parse(res.body); + } + + async formatList(slug, page) { + const baseUrl = this.getPreference("soaper_override_base_url"); + slug = parseInt(page) > 1 ? `${slug}?page=${page}` : slug; + var doc = await this.request(slug); + var list = []; + var movies = doc.select(".thumbnail.text-center"); + + for (var movie of movies) { + var linkSection = movie.selectFirst("div.img-group > a"); + var link = linkSection.getHref.substring(1); + var poster = linkSection.selectFirst("img").getSrc; + var imageUrl = `${baseUrl}${poster}`; + var name = movie.selectFirst("h5").selectFirst("a").text; + + list.push({ name, imageUrl, link }); + } + + var hasNextPage = false; + if (slug.indexOf("search.html?") == -1) { + var pagination = doc.select("ul.pagination > li"); + var last_page_num = parseInt(pagination[pagination.length - 2].text); + hasNextPage = page < last_page_num ? true : false; + } + return { list, hasNextPage }; + } + + async filterList(year = "all", genre = "all", sort = "new", page = 1) { + year = year == "all" ? "" : `/year/${year}`; + genre = genre == "all" ? "" : `/cat/${genre}`; + sort = sort == "new" ? "" : `/sort/${sort}`; + + var slug = `${sort}${year}${genre}`; + var movieList = await this.formatList(`movielist${slug}`, page); + var seriesList = await this.formatList(`tvlist${slug}`, page); + + var list = []; + var priority = this.getPreference("soaper_content_priority"); + if (priority === "series") { + list = [...seriesList.list, ...movieList.list]; + } else { + list = [...movieList.list, ...seriesList.list]; + } + + var hasNextPage = seriesList.hasNextPage || movieList.hasNextPage; + + return { list, hasNextPage }; + } + + async getPopular(page) { + return await this.filterList("all", "all", "hot", page); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + return await this.filterList("all", "all", "new", page); + } + + async search(query, page, filters) { + var seriesList = []; + var movieList = []; + var list = []; + + var res = await this.formatList(`search.html?keyword=${query}`, 1); + var movies = res["list"]; + + for (var movie of movies) { + var link = movie.link; + if (link.indexOf("tv_") != -1) { + seriesList.push(movie); + } else { + movieList.push(movie); + } + } + + var priority = this.getPreference("soaper_content_priority"); + if (priority === "series") { + list = [...seriesList, ...movieList]; + } else { + list = [...movieList, ...seriesList]; + } + + return { list, hasNextPage: false }; + } + + async getDetail(url) { + const baseUrl = this.getPreference("soaper_override_base_url"); + var slug = url.replace(`${baseUrl}/`,'') + var doc = await this.request(slug); + var name = doc + .selectFirst(".col-sm-12.col-lg-12.text-center") + .selectFirst("h4") + .text.trim(); + var poster = doc + .selectFirst(".thumbnail.text-center") + .selectFirst("img").getSrc; + var imageUrl = `${baseUrl}${poster}`; + + var description = doc.selectFirst("p#wrap").text.trim(); + var link = `${baseUrl}/${slug}`; + + var chapters = []; + if (slug.indexOf("tv_") != -1) { + var seasonList = doc.select(".alert.alert-info-ex.col-sm-12"); + var seasonCount = seasonList.length; + for (var season of seasonList) { + var eps = season.select(".col-sm-12.col-md-6.col-lg-4.myp1"); + for (var ep of eps) { + var epLinkSection = ep.selectFirst("a"); + var epLink = epLinkSection.getHref.substring(1); + var epName = epLinkSection.text; + + chapters.push({ + name: `S${seasonCount}E${epName}`, + url: epLink, + }); } + seasonCount--; + } + } else { + chapters.push({ + name: "Movie", + url: slug, + }); } - getPreference(key) { - return new SharedPreferences().get(key); - } + return { name, imageUrl, description, link, chapters }; + } + // For anime episode video list + async getVideoList(url) { + var body = await this.request(url); + var baseUrl = this.getBasueUrl(); + var streams = []; - getBasueUrl() { - return this.getPreference("soaper_override_base_url") - } + // Traditional servers + var eId = body.selectFirst("#hId").attr("value"); + var hIsW = body.selectFirst("#hIsW").attr("value"); + var apiType = url[0].toUpperCase(); - async request(slug) { - const baseUrl = this.getBasueUrl() - var url = `${baseUrl}/${slug}` - var res = await new Client().get(url, this.getHeaders(baseUrl)); - var doc = new Document(res.body); - return doc - } + var servers = [0, 1]; + for (var serverNum of servers) { + var serverName = body.selectFirst(`#server_button_${serverNum}`).text; + if (serverName.length < 1) continue; + var data = { + pass: eId, + param: "", + extra: "1", + e2: hIsW, + server: "" + serverNum, + }; + var res = await this.requestJSON( + `home/index/Get${apiType}InfoAjax`, + data + ); - async requestJSON(slug, data) { - const baseUrl = this.getBasueUrl() - var url = `${baseUrl}/${slug}` - var res = await new Client().post(url, this.getHeaders(baseUrl), data); - return JSON.parse(res.body); - } - - async formatList(slug, page) { - const baseUrl = this.getPreference("soaper_override_base_url") - slug = parseInt(page) > 1 ? `${slug}?page=${page}` : slug - var doc = await this.request(slug); - var list = []; - var movies = doc.select(".thumbnail.text-center") - - for (var movie of movies) { - var linkSection = movie.selectFirst("div.img-group > a") - var link = linkSection.getHref.substring(1,); - var poster = linkSection.selectFirst("img").getSrc - var imageUrl = `${baseUrl}${poster}` - var name = movie.selectFirst("h5").selectFirst("a").text; - - list.push({ name, imageUrl, link }); + var streamUrl = baseUrl + res.val; + var subs = []; + var vidSubs = res.subs; + if (vidSubs != null && vidSubs.length > 0) { + for (var sub of vidSubs) { + subs.push({ + file: baseUrl + sub.path, + label: sub.name, + }); } - - var hasNextPage = false - if (slug.indexOf("search.html?") == -1) { - var pagination = doc.select("ul.pagination > li") - var last_page_num = parseInt(pagination[pagination.length - 2].text); - hasNextPage = page < last_page_num ? true : false; - } - return { list, hasNextPage } + } + streams.push({ + url: streamUrl, + originalUrl: streamUrl, + quality: serverName, + subtitles: subs, + }); } - async filterList(year = "all", genre = "all", sort = "new", page = 1) { - year = year == "all" ? "" : `/year/${year}` - genre = genre == "all" ? "" : `/cat/${genre}` - sort = sort == "new" ? "" : `/sort/${sort}` + // Download servers + var modal_footer = body.select(".modal-footer > a"); + if (modal_footer.length > 0) { + modal_footer.reverse(); + for (var item of modal_footer) { + var dSlug = item.getHref; + var dBody = await this.request(dSlug); - var slug = `${sort}${year}${genre}` - var movieList = await this.formatList(`movielist${slug}`, page); - var seriesList = await this.formatList(`tvlist${slug}`, page); + var res = dBody.selectFirst("#res").attr("value"); + var mb = dBody.selectFirst("#mb").attr("value"); + var streamLink = dBody.selectFirst("#link").attr("value"); - var list = []; - var priority = this.getPreference("soaper_content_priority"); - if (priority === "series") { - list = [...seriesList.list, ...movieList.list]; - } else { - list = [...movieList.list, ...seriesList.list]; - } - - var hasNextPage = seriesList.hasNextPage || movieList.hasNextPage; - - return { list, hasNextPage } + streams.push({ + url: streamLink, + originalUrl: streamLink, + quality: `Download Server: ${res} [${mb}]`, + }); + } } + return streams; + } + // For manga chapter pages + async getPageList() { + throw new Error("getPageList not implemented"); + } + getFilterList() { + throw new Error("getFilterList not implemented"); + } - async getPopular(page) { - return await this.filterList("all", "all", "hot", page); - } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } - async getLatestUpdates(page) { - return await this.filterList("all", "all", "new", page); - } - - async search(query, page, filters) { - var seriesList = [] - var movieList = [] - var list = []; - - var res = await this.formatList(`search.html?keyword=${query}`, 1) - var movies = res["list"] - - for (var movie of movies) { - var link = movie.link - if (link.indexOf("tv_") != -1) { - seriesList.push(movie); - } else { - movieList.push(movie); - } - } - - var priority = this.getPreference("soaper_content_priority"); - if (priority === "series") { - list = [...seriesList, ...movieList]; - } else { - list = [...movieList, ...seriesList]; - } - - return { list, hasNextPage: false } - } - - async getDetail(url) { - var doc = await this.request(url); - - const baseUrl = this.getPreference("soaper_override_base_url") - var name = doc.selectFirst(".col-sm-12.col-lg-12.text-center").selectFirst("h4").text.trim() - var poster = doc.selectFirst(".thumbnail.text-center").selectFirst("img").getSrc - var imageUrl = `${baseUrl}${poster}` - - var description = doc.selectFirst("p#wrap").text.trim() - var link = `${baseUrl}/${url}` - - var chapters = [] - if (url.indexOf("tv_") != -1) { - var seasonList = doc.select(".alert.alert-info-ex.col-sm-12") - var seasonCount = seasonList.length - for (var season of seasonList) { - var eps = season.select(".col-sm-12.col-md-6.col-lg-4.myp1") - for (var ep of eps) { - var epLinkSection = ep.selectFirst("a") - var epLink = epLinkSection.getHref.substring(1) - var epName = epLinkSection.text - - chapters.push({ - name: `S${seasonCount}E${epName}`, - url: epLink, - }) - } - seasonCount--; - } - - } else { - chapters.push({ - name: "Movie", - url: url, - }) - } - - return { name, imageUrl, description, link, chapters } - } - // For anime episode video list - async getVideoList(url) { - var body = await this.request(url) - var baseUrl = this.getBasueUrl() - var streams = [] - - // Traditional servers - var eId = body.selectFirst("#hId").attr('value') - var hIsW = body.selectFirst("#hIsW").attr('value') - var apiType = url[0].toUpperCase() - - var servers = [0, 1] - for (var serverNum of servers) { - var serverName = body.selectFirst(`#server_button_${serverNum}`).text - if (serverName.length < 1) continue; - var data = { - pass: eId, - param: "", - extra: "1", - e2: hIsW, - server: "" + serverNum, - } - var res = await this.requestJSON(`home/index/Get${apiType}InfoAjax`, data) - - var streamUrl = baseUrl + res.val - var subs = [] - var vidSubs = res.subs - if (vidSubs != null && vidSubs.length > 0) { - for (var sub of vidSubs) { - subs.push({ - file: baseUrl + sub.path, - label: sub.name - }) - } - } - streams.push({ - url: streamUrl, - originalUrl: streamUrl, - quality: serverName, - subtitles: subs - }); - } - - // Download servers - var modal_footer = body.select(".modal-footer > a") - if (modal_footer.length > 0) { - - modal_footer.reverse() - for (var item of modal_footer) { - var dSlug = item.getHref - var dBody = await this.request(dSlug) - - var res = dBody.selectFirst("#res").attr('value') - var mb = dBody.selectFirst("#mb").attr('value') - var streamLink = dBody.selectFirst("#link").attr('value') - - streams.push({ - url: streamLink, - originalUrl: streamLink, - quality: `Download Server: ${res} [${mb}]`, - }); - } - } - return streams - - } - // For manga chapter pages - async getPageList() { - throw new Error("getPageList not implemented"); - } - getFilterList() { - throw new Error("getFilterList not implemented"); - } - - getSourcePreferences() { - return [{ - "key": "soaper_override_base_url", - editTextPreference: { - title: "Override base url", - summary: "Default: https://soaper.cc", - value: "https://soaper.cc", - dialogTitle: "Override base url", - dialogMessage: "", - } - }, { - key: 'soaper_content_priority', - listPreference: { - title: 'Preferred content priority', - summary: 'Choose which type of content to show first', - valueIndex: 0, - entries: ["Movies", "Series"], - entryValues: ["movies", "series"] - } + getSourcePreferences() { + return [ + { + key: "soaper_override_base_url", + editTextPreference: { + title: "Override base url", + summary: "Default: https://soaper.cc", + value: "https://soaper.cc", + dialogTitle: "Override base url", + dialogMessage: "", }, - ]; - } + }, + { + key: "soaper_content_priority", + listPreference: { + title: "Preferred content priority", + summary: "Choose which type of content to show first", + valueIndex: 0, + entries: ["Movies", "Series"], + entryValues: ["movies", "series"], + }, + }, + ]; + } } diff --git a/javascript/anime/src/en/animegg.js b/javascript/anime/src/en/animegg.js index a62a6101..d14b06d4 100644 --- a/javascript/anime/src/en/animegg.js +++ b/javascript/anime/src/en/animegg.js @@ -1,258 +1,254 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "AnimeGG", "lang": "en", + "id": 209614032, "baseUrl": "https://www.animegg.org", "apiUrl": "", - "iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://www.animegg.org/", + "iconUrl": + "https://www.google.com/s2/favicons?sz=256&domain=https://www.animegg.org/", "typeSource": "single", "itemType": 1, - "version": "1.0.2", + "version": "1.0.3", "pkgPath": "anime/src/en/animegg.js" -}]; + } +]; class DefaultExtension extends MProvider { + constructor() { + super(); + this.client = new Client(); + } - constructor() { - super(); - this.client = new Client(); - } + getHeaders(url) { + return { + Referer: this.source.baseUrl, + Origin: this.source.baseUrl, + }; + } - getHeaders(url) { - return { - "Referer": this.source.baseUrl, - "Origin": this.source.baseUrl - } - } + getPreference(key) { + return parseInt(new SharedPreferences().get(key)); + } - getPreference(key) { - return parseInt(new SharedPreferences().get(key)); - } + async requestText(slug) { + var url = `${this.source.baseUrl}${slug}`; + var res = await this.client.get(url, this.getHeaders()); + return res.body; + } + async request(slug) { + return new Document(await this.requestText(slug)); + } - async requestText(slug) { - var url = `${this.source.baseUrl}${slug}` - var res = await this.client.get(url, this.getHeaders()); - return res.body; - } - async request(slug) { - return new Document(await this.requestText(slug)); - } - - async fetchPopularnLatest(slug) { - var body = await this.request(slug) - var items = body.select("li.fea") - var list = [] - var hasNextPage = true - if (items.length > 0) { - for (var item of items) { - var imageUrl = item.selectFirst('img').getSrc - var linkSection = item.selectFirst('.rightpop').selectFirst('a') - var link = linkSection.getHref - var name = linkSection.text - list.push({ - name, - imageUrl, - link - }); - - } - } - else { - hasNextPage = false - } - return { list, hasNextPage } - } - - async getPopular(page) { - var start = (page - 1) * 25; - var limit = start + 25; - - var category = "" - var pop = this.getPreference("animegg_popular_category") - switch (pop) { - case 1: { - category = "sortBy=createdAt&sortDirection=DESC&"; - break; - } - case 2: { - category = "ongoing=true&"; - break; - } - case 3: { - category = "ongoing=false&"; - break; - } - case 4: { - category = "sortBy=sortLetter&sortDirection=ASC&"; - break; - } - - } - var slug = `/popular-series?${category}start=${start}&limit=${limit}` - return await this.fetchPopularnLatest(slug) - - - } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } - async getLatestUpdates(page) { - var start = (page - 1) * 25; - var limit = start + 25; - - var slug = `/releases?start=${start}&limit=${limit}` - return await this.fetchPopularnLatest(slug) - - } - async search(query, page, filters) { - var slug = `/search?q=${query}` - var body = await this.request(slug) - var items = body.select(".moose.page > a") - var list = [] - for (var item of items) { - var imageUrl = item.selectFirst('img').getSrc - var link = item.getHref - var name = item.selectFirst("h2").text - list.push({ - name, - imageUrl, - link - }); - - } - - return { list, hasNextPage: false } - } - - statusCode(status) { - return { - "Ongoing": 0, - "Completed": 1, - }[status] ?? 5; - } - - async getDetail(url) { - var link = this.source.baseUrl + url; - - var body = await this.request(url) - - var media = body.selectFirst(".media") - var title = media.selectFirst("h1").text - var spans = media.selectFirst("p.infoami").select("span") - var statusText = spans[spans.length - 1].text.replace("Status: ", '') - var status = this.statusCode(statusText) - - - var tagscat = media.select(".tagscat > li") - var genre = [] - tagscat.forEach(tag => genre.push(tag.text)) - var description = body.selectFirst("p.ptext").text - var chapters = [] - - var episodesList = body.select(".newmanga > li") - episodesList.forEach(ep => { - var epTitle = ep.selectFirst('i.anititle').text - var epNumber = ep.selectFirst('strong').text.replace(title, "Episode") - var epName = epNumber == epTitle ? epNumber : `${epNumber} - ${epTitle}` - var epUrl = ep.selectFirst("a").getHref - - var scanlator = ""; - var type = ep.select("span.btn-xs") - type.forEach(t => { - scanlator += t.text + ", "; - - }) - scanlator = scanlator.slice(0, -2); - - chapters.push({ name: epName, url: epUrl, scanlator }) - }) - - - return { description, status, genre, chapters, link } - - - - } - - async exxtractStreams(div,audio){ - - var slug = div.selectFirst("iframe").getSrc - var streams = [] - if(slug.length < 1){ - return streams; - } - var body = await this.requestText(slug) - var sKey = "var videoSources = " - var eKey = "var httpProtocol" - var start = body.indexOf(sKey) + sKey.length - var end = body.indexOf(eKey) - 8 - var videoSourcesStr = body.substring(start, end) - let videoSources = eval("(" + videoSourcesStr + ")"); - var headers = this.getHeaders(); - videoSources.forEach(videoSource => { - var url = this.source.baseUrl +videoSource.file - var quality = `${videoSource.label} - ${audio}` - - streams.push({ - url, - originalUrl: url, - quality, - headers - }); + async fetchPopularnLatest(slug) { + var body = await this.request(slug); + var items = body.select("li.fea"); + var list = []; + var hasNextPage = true; + if (items.length > 0) { + for (var item of items) { + var imageUrl = item.selectFirst("img").getSrc; + var linkSection = item.selectFirst(".rightpop").selectFirst("a"); + var link = linkSection.getHref; + var name = linkSection.text; + list.push({ + name, + imageUrl, + link, }); - return streams.reverse(); + } + } else { + hasNextPage = false; + } + return { list, hasNextPage }; + } + + async getPopular(page) { + var start = (page - 1) * 25; + var limit = start + 25; + + var category = ""; + var pop = this.getPreference("animegg_popular_category"); + switch (pop) { + case 1: { + category = "sortBy=createdAt&sortDirection=DESC&"; + break; + } + case 2: { + category = "ongoing=true&"; + break; + } + case 3: { + category = "ongoing=false&"; + break; + } + case 4: { + category = "sortBy=sortLetter&sortDirection=ASC&"; + break; + } + } + var slug = `/popular-series?${category}start=${start}&limit=${limit}`; + return await this.fetchPopularnLatest(slug); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + var start = (page - 1) * 25; + var limit = start + 25; + + var slug = `/releases?start=${start}&limit=${limit}`; + return await this.fetchPopularnLatest(slug); + } + async search(query, page, filters) { + var slug = `/search?q=${query}`; + var body = await this.request(slug); + var items = body.select(".moose.page > a"); + var list = []; + for (var item of items) { + var imageUrl = item.selectFirst("img").getSrc; + var link = item.getHref; + var name = item.selectFirst("h2").text; + list.push({ + name, + imageUrl, + link, + }); } - // For anime episode video list - async getVideoList(url) { - var body = await this.request(url) - - var sub = body.selectFirst("#subbed-Animegg") - var subStreams = await this.exxtractStreams(sub,"Sub") + return { list, hasNextPage: false }; + } - var dub = body.selectFirst("#dubbed-Animegg") - var dubStreams = await this.exxtractStreams(dub,"Dub") + statusCode(status) { + return ( + { + Ongoing: 0, + Completed: 1, + }[status] ?? 5 + ); + } - var raw = body.selectFirst("#raw-Animegg") - var rawStreams = await this.exxtractStreams(raw,"Raw") + async getDetail(url) { + var baseUrl = this.source.baseUrl; + var slug = url.replace(baseUrl, ""); + var link = baseUrl + slug; + var body = await this.request(slug); + var media = body.selectFirst(".media"); + var title = media.selectFirst("h1").text; + var spans = media.selectFirst("p.infoami").select("span"); + var statusText = spans[spans.length - 1].text.replace("Status: ", ""); + var status = this.statusCode(statusText); - var pref = this.getPreference("animegg_stream_type_1") - var streams = []; - if(pref == 0){ - streams = [...subStreams,...dubStreams, ...rawStreams] - }else if(pref == 1){ - streams = [...dubStreams,...subStreams, ...rawStreams] - }else{ - streams = [...rawStreams,...subStreams, ...dubStreams] - } - - return streams + var tagscat = media.select(".tagscat > li"); + var genre = []; + tagscat.forEach((tag) => genre.push(tag.text)); + var description = body.selectFirst("p.ptext").text; + var chapters = []; + var episodesList = body.select(".newmanga > li"); + episodesList.forEach((ep) => { + var epTitle = ep.selectFirst("i.anititle").text; + var epNumber = ep.selectFirst("strong").text.replace(title, "Episode"); + var epName = epNumber == epTitle ? epNumber : `${epNumber} - ${epTitle}`; + var epUrl = ep.selectFirst("a").getHref; + + var scanlator = ""; + var type = ep.select("span.btn-xs"); + type.forEach((t) => { + scanlator += t.text + ", "; + }); + scanlator = scanlator.slice(0, -2); + + chapters.push({ name: epName, url: epUrl, scanlator }); + }); + + return { description, status, genre, chapters, link }; + } + + async exxtractStreams(div, audio) { + var slug = div.selectFirst("iframe").getSrc; + var streams = []; + if (slug.length < 1) { + return streams; + } + var body = await this.requestText(slug); + var sKey = "var videoSources = "; + var eKey = "var httpProtocol"; + var start = body.indexOf(sKey) + sKey.length; + var end = body.indexOf(eKey) - 8; + var videoSourcesStr = body.substring(start, end); + let videoSources = eval("(" + videoSourcesStr + ")"); + var headers = this.getHeaders(); + videoSources.forEach((videoSource) => { + var url = this.source.baseUrl + videoSource.file; + var quality = `${videoSource.label} - ${audio}`; + + streams.push({ + url, + originalUrl: url, + quality, + headers, + }); + }); + return streams.reverse(); + } + + // For anime episode video list + async getVideoList(url) { + var body = await this.request(url); + + var sub = body.selectFirst("#subbed-Animegg"); + var subStreams = await this.exxtractStreams(sub, "Sub"); + + var dub = body.selectFirst("#dubbed-Animegg"); + var dubStreams = await this.exxtractStreams(dub, "Dub"); + + var raw = body.selectFirst("#raw-Animegg"); + var rawStreams = await this.exxtractStreams(raw, "Raw"); + + var pref = this.getPreference("animegg_stream_type_1"); + var streams = []; + if (pref == 0) { + streams = [...subStreams, ...dubStreams, ...rawStreams]; + } else if (pref == 1) { + streams = [...dubStreams, ...subStreams, ...rawStreams]; + } else { + streams = [...rawStreams, ...subStreams, ...dubStreams]; } - getSourcePreferences() { - return [ - { - key: "animegg_popular_category", - listPreference: { - title: 'Preferred popular category', - summary: '', - valueIndex: 0, - entries: ["Popular", "Newest", "Ongoing", "Completed", "Alphabetical"], - entryValues: ["0", "1", "2", "3", "4"] - } - }, - { - key: "animegg_stream_type_1", - listPreference: { - title: 'Preferred stream type', - summary: '', - valueIndex: 0, - entries: ["Sub","Dub","Raw"], - entryValues: ["0", "1", "2"] - } - } - ] - } + return streams; + } + + getSourcePreferences() { + return [ + { + key: "animegg_popular_category", + listPreference: { + title: "Preferred popular category", + summary: "", + valueIndex: 0, + entries: [ + "Popular", + "Newest", + "Ongoing", + "Completed", + "Alphabetical", + ], + entryValues: ["0", "1", "2", "3", "4"], + }, + }, + { + key: "animegg_stream_type_1", + listPreference: { + title: "Preferred stream type", + summary: "", + valueIndex: 0, + entries: ["Sub", "Dub", "Raw"], + entryValues: ["0", "1", "2"], + }, + }, + ]; + } } diff --git a/javascript/anime/src/en/animeonsen.js b/javascript/anime/src/en/animeonsen.js index 7decd10b..f67af378 100644 --- a/javascript/anime/src/en/animeonsen.js +++ b/javascript/anime/src/en/animeonsen.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://www.animeonsen.xyz", "typeSource": "single", "itemType": 1, - "version": "1.0.0", + "version": "1.0.1", "pkgPath": "anime/src/all/animeonsen.js" }]; @@ -143,7 +143,9 @@ class DefaultExtension extends MProvider { } async getDetail(url) { - var link = `${this.source.baseUrl}/details/${url}` + var linkSlug = `${this.source.baseUrl}/details/` + url = url.replace(linkSlug, "") + var link = `${linkSlug}${url}` var detailsApiSlug = `/${url}/extensive` var animeDetails = await this.request(detailsApiSlug); diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index b5c60969..366e4891 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -1,241 +1,270 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "AnimeParadise", "lang": "en", "baseUrl": "https://animeparadise.moe", "apiUrl": "https://api.animeparadise.moe", - "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", + "iconUrl": + "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.1", + "version": "0.0.2", "pkgPath": "anime/src/en/animeparadise.js" -}]; + } +]; class DefaultExtension extends MProvider { + getPreference(key) { + const preferences = new SharedPreferences(); + return preferences.get(key); + } - getPreference(key) { - const preferences = new SharedPreferences(); - return preferences.get(key); + async extractFromUrl(url) { + var res = await new Client().get(this.source.baseUrl + url); + var doc = new Document(res.body); + var jsonData = doc.selectFirst("#__NEXT_DATA__").text; + return JSON.parse(jsonData).props.pageProps; + } + + async requestAPI(slug) { + var api = `${this.source.apiUrl}/${slug}`; + var response = await new Client().get(api); + var body = JSON.parse(response.body); + return body; + } + + async formList(slug) { + var jsonData = await this.requestAPI(slug); + var list = []; + if ("episodes" in jsonData) { + jsonData.episodes.forEach((item) => { + list.push({ + "name": item.origin.title, + "link": item.origin.link, + "imageUrl": item.image, + }); + }); + } else { + jsonData.data.forEach((item) => { + list.push({ + "name": item.title, + "link": item.link, + "imageUrl": item.posterImage.original, + }); + }); } - async extractFromUrl(url) { - var res = await new Client().get(this.source.baseUrl + url); - var doc = new Document(res.body); - var jsonData = doc.selectFirst("#__NEXT_DATA__").text - return JSON.parse(jsonData).props.pageProps - } + return { + "list": list, + "hasNextPage": false, + }; + } - async requestAPI(slug) { - var api = `${this.source.apiUrl}/${slug}` - var response = await new Client().get(api); - var body = JSON.parse(response.body); - return body; - } + async getPopular(page) { + return await this.formList('?sort={"rate": -1 }'); + } - async formList(slug) { - var jsonData = await this.requestAPI(slug); - var list = []; - if ("episodes" in jsonData) { - jsonData.episodes.forEach(item => { - list.push({ - "name": item.origin.title, - "link": item.origin.link, - "imageUrl": item.image - }); - }) - } else { - jsonData.data.forEach(item => { - list.push({ - "name": item.title, - "link": item.link, - "imageUrl": item.posterImage.original - }); - }) + async getLatestUpdates(page) { + var slug = '?sort={"postDate": -1 }'; + + var choice = this.getPreference("animeparadise_pref_latest_tab"); + if (choice === "recent_ep") slug = "ep/recently-added"; + + return await this.formList(slug); + } + async search(query, page, filters) { + var season = filters[0].values[filters[0].state].value; + var year = filters[1].values[filters[1].state].value; + + var genre = "genre[]="; + for (var filter of filters[2].state) { + if (filter.state == true) genre += `${filter.value}&genre[]=`; + } + var slug = `search?q=${query}&year=${year}&season=${season}&${genre}`; + return await this.formList(slug); + } + statusCode(status) { + return ( + { + "current": 0, + "finished": 1, + }[status] ?? 5 + ); + } + + async getDetail(url) { + var linkSlug = this.source.baseUrl + `/anime/`; + if (url.includes(linkSlug)) url = url.replace(linkSlug, ""); + + var jsonData = await this.extractFromUrl(`/anime/${url}`); + jsonData = jsonData.data; + var details = {}; + var chapters = []; + details.imageUrl = jsonData.posterImage.original; + details.description = jsonData.synopsys; + details.genre = jsonData.genres; + details.status = this.statusCode(jsonData.status); + var id = jsonData._id; + var epAPI = await this.requestAPI(`anime/${id}/episode`); + epAPI.data.forEach((ep) => { + var epName = `E${ep.number}: ${ep.title}`; + var epUrl = `${ep.uid}?origin=${ep.origin}`; + chapters.push({ name: epName, url: epUrl }); + }); + details.link = `${linkSlug}${url}`; + details.chapters = chapters.reverse(); + return details; + } + // Sorts streams based on user preference. + async sortStreams(streams) { + var sortedStreams = []; + var copyStreams = streams.slice(); + + var pref = await this.getPreference("animeparadise_pref_video_resolution"); + for (var stream of streams) { + if (stream.quality.indexOf(pref) > -1) { + sortedStreams.push(stream); + var index = copyStreams.indexOf(stream); + if (index > -1) { + copyStreams.splice(index, 1); } - - return { - "list": list, - "hasNextPage": false - } - + break; + } } + return [...sortedStreams, ...copyStreams]; + } - async getPopular(page) { - return await this.formList('?sort={"rate": -1 }') + // Extracts the streams url for different resolutions from a hls stream. + async extractStreams(url) { + const response = await new Client().get(url); + const body = response.body; + const lines = body.split("\n"); + var streams = [ + { + url: url, + originalUrl: url, + quality: `Auto`, + }, + ]; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith("#EXT-X-STREAM-INF:")) { + var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1]; + var m3u8Url = lines[i + 1].trim(); + m3u8Url = url.replace("master.m3u8", m3u8Url); + + streams.push({ + url: m3u8Url, + originalUrl: m3u8Url, + quality: resolution, + }); + } } + return streams; + } - async getLatestUpdates(page) { - var slug = '?sort={"postDate": -1 }'; + // For anime episode video list + async getVideoList(url) { + var streams = []; + var jsonData = await this.extractFromUrl(`/watch/${url}`); + var epData = jsonData.episode; + streams = await this.extractStreams(epData.streamLink); - var choice = this.getPreference("animeparadise_pref_latest_tab"); - if (choice === "recent_ep") slug = 'ep/recently-added'; + var subtitles = []; + epData.subData.forEach((sub) => { + subtitles.push({ + "label": sub.label, + "file": `${this.source.apiUrl}/stream/file/${sub.src}`, + }); + }); - return await this.formList(slug) - } - async search(query, page, filters) { - var season = filters[0].values[filters[0].state].value - var year = filters[1].values[filters[1].state].value + streams[0].subtitles = subtitles; - var genre = "genre[]=" - for (var filter of filters[2].state) { - if (filter.state == true) - genre += `${filter.value}&genre[]=` - } - var slug = `search?q=${query}&year=${year}&season=${season}&${genre}` - return await this.formList(slug); - } - statusCode(status) { - return { - "current": 0, - "finished": 1, - }[status] ?? 5; - } + return streams; + } - async getDetail(url) { - var link = this.source.baseUrl + `/anime/${url}` - var jsonData = await this.extractFromUrl(`/anime/${url}`) - jsonData = jsonData.data - var details = {} - var chapters = [] - details.imageUrl = jsonData.posterImage.original - details.description = jsonData.synopsys - details.genre = jsonData.genres - details.status = this.statusCode(jsonData.status) - var id = jsonData._id - var epAPI = await this.requestAPI(`anime/${id}/episode`) - epAPI.data.forEach(ep => { - var epName = `E${ep.number}: ${ep.title}`; - var epUrl = `${ep.uid}?origin=${ep.origin}` - chapters.push({ name: epName, url: epUrl }) - }) - details.chapters = chapters.reverse(); - return details; - } - // Sorts streams based on user preference. - async sortStreams(streams) { - var sortedStreams = []; - var copyStreams = streams.slice() + addCatogory(arr, typ) { + arr = arr.map((x) => ({ type_name: typ, name: x, value: x })); + arr.unshift({ + type_name: typ, + name: "All", + value: "", + }); + return arr; + } - var pref = await this.getPreference("animeparadise_pref_video_resolution"); - for (var stream of streams) { + getFilterList() { + var seasons = ["Winter", "Spring", "Summer", "Fall"]; - if (stream.quality.indexOf(pref) > -1) { - sortedStreams.push(stream); - var index = copyStreams.indexOf(stream); - if (index > -1) { - copyStreams.splice(index, 1); - } - break; - } - } - return [...sortedStreams, ...copyStreams] - } + const currentYear = new Date().getFullYear(); + var years = Array.from({ length: currentYear - 1939 }, (_, i) => + (i + 1940).toString() + ).reverse(); - // Extracts the streams url for different resolutions from a hls stream. - async extractStreams(url) { - const response = await new Client().get(url); - const body = response.body; - const lines = body.split('\n'); - var streams = [{ - url: url, - originalUrl: url, - quality: `Auto`, - }]; + var genres = [ + "Action", + "Adventure", + "Comedy", + "Drama", + "Ecchi", + "Fantasy", + "Horror", + "Mahou Shojo", + "Mecha", + "Music", + "Mystery", + "Psychological", + "Romance", + "Sci-Fi", + "Slice of Life", + "Sports", + "Supernatural", + "Thriller", + ].map((x) => ({ type_name: "CheckBox", name: x, value: x })); - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith('#EXT-X-STREAM-INF:')) { - var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1]; - var m3u8Url = lines[i + 1].trim(); - m3u8Url = url.replace("master.m3u8", m3u8Url) + return [ + { + type_name: "SelectFilter", + name: "Season", + state: 0, + values: this.addCatogory(seasons, "SelectOption"), + }, + { + type_name: "SelectFilter", + name: "Year", + state: 0, + values: this.addCatogory(years, "SelectOption"), + }, + { + type_name: "GroupFilter", + name: "Genres", + state: genres, + }, + ]; + } - streams.push({ - url: m3u8Url, - originalUrl: m3u8Url, - quality: resolution - }); - } - } - return streams - - } - - // For anime episode video list - async getVideoList(url) { - var streams = [] - var jsonData = await this.extractFromUrl(`/watch/${url}`); - var epData = jsonData.episode - streams = await this.extractStreams(epData.streamLink) - - var subtitles = [] - epData.subData.forEach(sub => { - subtitles.push({ - "label": sub.label, - "file": `${this.source.apiUrl}/stream/file/${sub.src}`, - }); - }) - - streams[0].subtitles = subtitles - - return streams - - } - - addCatogory(arr, typ) { - arr = arr.map(x => ({ type_name: typ, name: x, value: x })) - arr.unshift({ - type_name: typ, - name: 'All', - value: '' - }) - return arr - } - - getFilterList() { - var seasons = ["Winter", "Spring", "Summer", "Fall"] - - const currentYear = new Date().getFullYear(); - var years = Array.from({ length: currentYear - 1939 }, (_, i) => (i + 1940).toString()).reverse() - - var genres = ["Action", "Adventure", "Comedy", "Drama", "Ecchi", "Fantasy", "Horror", "Mahou Shojo", "Mecha", "Music", "Mystery", "Psychological", "Romance", "Sci-Fi", "Slice of Life", "Sports", "Supernatural", "Thriller"].map(x => ({ type_name: "CheckBox", name: x, value: x })) - - return [ - { - type_name: "SelectFilter", - name: "Season", - state: 0, - values: this.addCatogory(seasons, "SelectOption") - }, { - type_name: "SelectFilter", - name: "Year", - state: 0, - values: this.addCatogory(years, "SelectOption") - }, { - type_name: "GroupFilter", - name: "Genres", - state: genres - } - ] - } - - getSourcePreferences() { - return [{ - key: 'animeparadise_pref_latest_tab', - listPreference: { - title: 'Latest tab category', - summary: 'Anime list to be shown in latest tab', - valueIndex: 0, - entries: ["Recently added anime", "Recently added episode"], - entryValues: ["recent_ani", "recent_ep"] - } - }, { - key: 'animeparadise_pref_video_resolution', - listPreference: { - title: 'Preferred video resolution', - summary: '', - valueIndex: 0, - entries: ["Auto", "1080p", "720p", "360p"], - entryValues: ["auto", "1080", "720", "360"] - } + getSourcePreferences() { + return [ + { + key: "animeparadise_pref_latest_tab", + listPreference: { + title: "Latest tab category", + summary: "Anime list to be shown in latest tab", + valueIndex: 0, + entries: ["Recently added anime", "Recently added episode"], + entryValues: ["recent_ani", "recent_ep"], }, - ] - } + }, + { + key: "animeparadise_pref_video_resolution", + listPreference: { + title: "Preferred video resolution", + summary: "", + valueIndex: 0, + entries: ["Auto", "1080p", "720p", "360p"], + entryValues: ["auto", "1080", "720", "360"], + }, + }, + ]; + } } diff --git a/javascript/anime/src/en/animez.js b/javascript/anime/src/en/animez.js index 430597ef..b14db558 100644 --- a/javascript/anime/src/en/animez.js +++ b/javascript/anime/src/en/animez.js @@ -1,267 +1,300 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "AnimeZ", "lang": "en", "baseUrl": "https://animez.org", "apiUrl": "", - "iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://animez.org/", + "iconUrl": + "https://www.google.com/s2/favicons?sz=256&domain=https://animez.org/", "typeSource": "multi", "itemType": 1, - "version": "1.0.1", + "version": "1.0.2", "pkgPath": "anime/src/en/animez.js" -}]; + } +]; class DefaultExtension extends MProvider { - constructor() { - super(); - this.client = new Client(); - } + constructor() { + super(); + this.client = new Client(); + } - getHeaders(url) { - return { - "Referer": this.source.baseUrl, + getHeaders(url) { + return { + "Referer": this.source.baseUrl, + }; + } + + getPreference(key) { + return new SharedPreferences().get(key); + } + + async request(slug) { + var url = this.source.baseUrl + slug; + var res = await this.client.get(url, this.getHeaders()); + return new Document(res.body); + } + async page(slug) { + var body = await this.request(slug); + var list = []; + var hasNextPage = false; + + var animes = body.select("li.TPostMv"); + animes.forEach((anime) => { + var link = anime.selectFirst("a").getHref; + var name = anime.selectFirst("h2.Title").text; + var imageUrl = + this.source.baseUrl + "/" + anime.selectFirst("img").getSrc; + + list.push({ name, link, imageUrl }); + }); + + var paginations = body.select(".pagination > li"); + hasNextPage = + paginations[paginations.length - 1].text == "Last" ? true : false; + + return { list, hasNextPage }; + } + + sortByPref(key) { + var sort = parseInt(this.getPreference(key)); + var sortBy = "hot"; + switch (sort) { + case 1: { + sortBy = "lastest-chap"; + break; + } + case 2: { + sortBy = "hot"; + break; + } + case 3: { + sortBy = "lastest-manga"; + break; + } + case 4: { + sortBy = "top-manga"; + break; + } + case 5: { + sortBy = "top-month"; + break; + } + case 6: { + sortBy = "top-week"; + break; + } + case 7: { + sortBy = "top-day"; + break; + } + case 8: { + sortBy = "follow"; + break; + } + case 9: { + sortBy = "comment"; + break; + } + case 10: { + sortBy = "num-chap"; + break; + } + } + return sortBy; + } + + async getPopular(page) { + var sortBy = this.sortByPref("animez_pref_popular_section"); + var slug = `/?act=search&f[status]=all&f[sortby]=${sortBy}&&pageNum=${page}`; + return await this.page(slug); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + var sortBy = this.sortByPref("animez_pref_latest_section"); + var slug = `/?act=search&f[status]=all&f[sortby]=${sortBy}&&pageNum=${page}`; + return await this.page(slug); + } + async search(query, page, filters) { + var slug = `/?act=search&f[status]=all&f[keyword]=${query}&&pageNum=${page}`; + return await this.page(slug); + } + async getDetail(url) { + var baseUrl = this.source.baseUrl; + if (url.includes(baseUrl)) url = url.replace(baseUrl, ""); + var link = +url; + var body = await this.request(url); + var name = body.selectFirst("#title-detail-manga").text; + var animeId = body.selectFirst("#title-detail-manga").attr("data-manga"); + var genre = []; + body + .select("li.AAIco-adjust")[3] + .select("a") + .forEach((g) => genre.push(g.text)); + var description = body.selectFirst("#summary_shortened").text; + + var chapters = []; + var chapLen = 0; + var pageNum = 1; + var hasNextPage = true; + while (hasNextPage) { + var pageSlug = `?act=ajax&code=load_list_chapter&manga_id=${animeId}&page_num=${pageNum}&chap_id=0&keyword=`; + var pageBody = await this.request(pageSlug); + var parsedBody = JSON.parse(pageBody.html); + var nav = parsedBody.nav; + if (nav == null) { + // if "nav" doesnt exists there is no next page + hasNextPage = false; + } else { + var navLi = new Document(nav).select(".page-link.next").length; + if (navLi > 0) { + // if "nav" exists and has li.next then there is next page + pageNum++; + } else { + // if "nav" exists and doesn't have li.next then there is no next page + hasNextPage = false; } - } + } - getPreference(key) { - return new SharedPreferences().get(key); - } + var list_chap = new Document(parsedBody.list_chap).select( + "li.wp-manga-chapter" + ); - async request(slug) { - var url = this.source.baseUrl + slug - var res = await this.client.get(url, this.getHeaders()); - return new Document(res.body); - } - async page(slug) { - var body = await this.request(slug) - var list = [] - var hasNextPage = false; - - var animes = body.select("li.TPostMv") - animes.forEach(anime => { - var link = anime.selectFirst("a").getHref - var name = anime.selectFirst('h2.Title').text; - var imageUrl = this.source.baseUrl +"/"+ anime.selectFirst('img').getSrc; - - list.push({ name, link, imageUrl }); - }); - - var paginations = body.select(".pagination > li") - hasNextPage = paginations[paginations.length - 1].text == "Last" ? true : false - - return { list, hasNextPage } - } - - sortByPref(key) { - var sort = parseInt(this.getPreference(key)) - var sortBy = "hot" - switch (sort) { - case 1: { - sortBy = "lastest-chap" - break; - } case 2: { - sortBy = "hot" - break; - } - case 3: { - sortBy = "lastest-manga" - break; - } - case 4: { - sortBy = "top-manga" - break; - } - case 5: { - sortBy = "top-month" - break; - } - case 6: { - sortBy = "top-week" - break; - } - case 7: { - sortBy = "top-day" - break; - } - case 8: { - sortBy = "follow" - break; - } - case 9: { - sortBy = "comment" - break; - } - case 10: { - sortBy = "num-chap" - break; - } + list_chap.forEach((chapter) => { + var a = chapter.selectFirst("a"); + var title = a.text; + var epLink = a.getHref; + var scanlator = "Sub"; + if (title.indexOf("Dub") > 0) { + title = title.replace("-Dub", ""); + scanlator = "Dub"; } - return sortBy; - - } - - async getPopular(page) { - var sortBy = this.sortByPref("animez_pref_popular_section") - var slug = `/?act=search&f[status]=all&f[sortby]=${sortBy}&&pageNum=${page}` - return await this.page(slug) - - } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } - async getLatestUpdates(page) { - var sortBy = this.sortByPref("animez_pref_latest_section") - var slug = `/?act=search&f[status]=all&f[sortby]=${sortBy}&&pageNum=${page}` - return await this.page(slug) - } - async search(query, page, filters) { - var slug = `/?act=search&f[status]=all&f[keyword]=${query}&&pageNum=${page}` - return await this.page(slug) - } - async getDetail(url) { - var link = this.source.baseUrl + url; - var body = await this.request(url); - var name = body.selectFirst("#title-detail-manga").text - var animeId = body.selectFirst("#title-detail-manga").attr("data-manga") - var genre = [] - body.select("li.AAIco-adjust")[3].select("a").forEach(g => genre.push(g.text)) - var description = body.selectFirst("#summary_shortened").text - - - var chapters = [] - var chapLen = 0 - var pageNum = 1 - var hasNextPage = true; - while(hasNextPage) { - var pageSlug = `?act=ajax&code=load_list_chapter&manga_id=${animeId}&page_num=${pageNum}&chap_id=0&keyword=` - var pageBody = await this.request(pageSlug); - var parsedBody = JSON.parse(pageBody.html); - var nav = parsedBody.nav - if(nav==null){ // if "nav" doesnt exists there is no next page - hasNextPage = false; - - }else{ - var navLi = new Document(nav).select(".page-link.next").length - if(navLi>0){ // if "nav" exists and has li.next then there is next page - pageNum++; - }else{// if "nav" exists and doesn't have li.next then there is no next page - hasNextPage = false; - } - } - - - var list_chap = new Document(parsedBody.list_chap).select('li.wp-manga-chapter') - - list_chap.forEach(chapter => { - var a = chapter.selectFirst("a") - var title = a.text - var epLink = a.getHref - var scanlator = "Sub" - if(title.indexOf("Dub")>0){ - title = title.replace("-Dub","") - scanlator = "Dub" - - } - title = title.indexOf("Movie") > -1? title : `Episode ${title}` - var epData = { - name:title, - url:epLink, - scanlator - } - if(chapLen>0){ - var pos = chapLen -1 - var lastEntry = chapters[pos] - if(lastEntry.name == epData.name){ // if last entries name is same then append url and scanlator to last entry - chapters.pop() // remove the last entry - epData.url = `${epData.url}||${lastEntry.url}` - epData.scanlator = `${lastEntry.scanlator}, ${epData.scanlator}` - chapLen = pos; // since the last entry is removed the chapLen will decrease - } - } - - chapters.push(epData) - chapLen++; - }) - - } - - return { - link, - description, - chapters, - genre, + title = title.indexOf("Movie") > -1 ? title : `Episode ${title}`; + var epData = { + name: title, + url: epLink, + scanlator, }; - } - - // Sorts streams based on user preference. - sortStreams(streams) { - var sortedStreams = []; - - var copyStreams = streams.slice() - var pref = this.getPreference("animez_pref_stream_audio"); - for (var stream of streams) { - if (stream.quality.indexOf(pref) > -1) { - sortedStreams.push(stream); - var index = copyStreams.indexOf(stream); - if (index > -1) { - copyStreams.splice(index, 1); - } - break; - } - } - return [...sortedStreams, ...copyStreams] - } - - // For anime episode video list - async getVideoList(url) { - var linkSlugs = url.split("||") - var streams = []; - for(var slug of linkSlugs){ - var body = await this.request(slug) - var iframeSrc = body.selectFirst("iframe").getSrc - var streamLink = iframeSrc.replace("/embed/","/anime/") - var audio = slug.indexOf("dub-") > -1 ? "Dub" : "Sub" - - streams.push({ - url: streamLink, - originalUrl: streamLink, - quality: audio, - }) + if (chapLen > 0) { + var pos = chapLen - 1; + var lastEntry = chapters[pos]; + if (lastEntry.name == epData.name) { + // if last entries name is same then append url and scanlator to last entry + chapters.pop(); // remove the last entry + epData.url = `${epData.url}||${lastEntry.url}`; + epData.scanlator = `${lastEntry.scanlator}, ${epData.scanlator}`; + chapLen = pos; // since the last entry is removed the chapLen will decrease + } } - - return sortStreams(streams); - - + chapters.push(epData); + chapLen++; + }); } - - getSourcePreferences() { - return [{ - key: 'animez_pref_popular_section', - listPreference: { - title: 'Preferred popular content', - summary: '', - valueIndex: 1, - entries: ["Latest update", "Hot", "New releases", "Top all", "Top month", "Top week", "Top day", "Top follow", "Top comments", "Number of episodes"], - entryValues: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] - } - }, { - key: 'animez_pref_latest_section', - listPreference: { - title: 'Preferred latest content', - summary: '', - valueIndex: 0, - entries: ["Latest update", "Hot", "New releases", "Top all", "Top month", "Top week", "Top day", "Top follow", "Top comments", "Number of episodes"], - entryValues: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] - } - }, { - key: 'animez_pref_stream_audio', - listPreference: { - title: 'Preferred stream audio', - summary: '', - valueIndex: 0, - entries: ["Sub","Dub"], - entryValues: ["Sub","Dub"], - } - },] + + return { + link, + description, + chapters, + genre, + }; + } + + // Sorts streams based on user preference. + sortStreams(streams) { + var sortedStreams = []; + + var copyStreams = streams.slice(); + var pref = this.getPreference("animez_pref_stream_audio"); + for (var stream of streams) { + if (stream.quality.indexOf(pref) > -1) { + sortedStreams.push(stream); + var index = copyStreams.indexOf(stream); + if (index > -1) { + copyStreams.splice(index, 1); + } + break; + } } + return [...sortedStreams, ...copyStreams]; + } + + // For anime episode video list + async getVideoList(url) { + var linkSlugs = url.split("||"); + var streams = []; + for (var slug of linkSlugs) { + var body = await this.request(slug); + var iframeSrc = body.selectFirst("iframe").getSrc; + var streamLink = iframeSrc.replace("/embed/", "/anime/"); + var audio = slug.indexOf("dub-") > -1 ? "Dub" : "Sub"; + + streams.push({ + url: streamLink, + originalUrl: streamLink, + quality: audio, + }); + } + + return sortStreams(streams); + } + + getSourcePreferences() { + return [ + { + key: "animez_pref_popular_section", + listPreference: { + title: "Preferred popular content", + summary: "", + valueIndex: 1, + entries: [ + "Latest update", + "Hot", + "New releases", + "Top all", + "Top month", + "Top week", + "Top day", + "Top follow", + "Top comments", + "Number of episodes", + ], + entryValues: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], + }, + }, + { + key: "animez_pref_latest_section", + listPreference: { + title: "Preferred latest content", + summary: "", + valueIndex: 0, + entries: [ + "Latest update", + "Hot", + "New releases", + "Top all", + "Top month", + "Top week", + "Top day", + "Top follow", + "Top comments", + "Number of episodes", + ], + entryValues: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], + }, + }, + { + key: "animez_pref_stream_audio", + listPreference: { + title: "Preferred stream audio", + summary: "", + valueIndex: 0, + entries: ["Sub", "Dub"], + entryValues: ["Sub", "Dub"], + }, + }, + ]; + } } diff --git a/javascript/anime/src/en/gojo.js b/javascript/anime/src/en/gojo.js index d49ca089..fefba063 100644 --- a/javascript/anime/src/en/gojo.js +++ b/javascript/anime/src/en/gojo.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://gojo.wtf/", "typeSource": "multi", "itemType": 1, - "version": "0.0.5", + "version": "0.0.6", "pkgPath": "anime/src/en/gojo.js" }]; @@ -103,6 +103,9 @@ class DefaultExtension extends MProvider { async getDetail(url) { + var linkSlug = `${this.source.baseUrl}/watch/` + if (url.includes(linkSlug)) url = url.replace(linkSlug, ""); + var anilistId = url var res = await this.gojoAPI(`/info/${anilistId}`) if (res == null) { @@ -111,7 +114,7 @@ class DefaultExtension extends MProvider { var name = this.getTitle(res.title) var imageUrl = res.coverImage.large var description = res.description; - var link = `${this.source.baseUrl}/watch/${anilistId}` + var link = `${linkSlug}${anilistId}` var genres = res.genres var status = (() => { switch (res.status) { diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js index 85e3b37b..cf2dfb2c 100644 --- a/javascript/anime/src/en/sudatchi.js +++ b/javascript/anime/src/en/sudatchi.js @@ -5,7 +5,7 @@ const mangayomiSources = [{ "apiUrl": "", "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com", "typeSource": "single", - "version": "1.1.0", + "version": "1.1.1", "dateFormat": "", "dateFormatLocale": "", "itemType": 1, @@ -148,8 +148,11 @@ class DefaultExtension extends MProvider { async getDetail(url) { + var linkSlug = "https://sudatchi.com/anime/" + if (url.includes(linkSlug)) url = url.replace(linkSlug, ""); + var lang = this.getPreference("sudatchi_pref_lang") - var link = `https://sudatchi.com/anime/${url}` + var link = `${linkSlug}${url}` var details = await this.requestApi(`/anime/${url}`); var titles = details.title var name = titles.romaji diff --git a/javascript/manga/src/en/mangapill.js b/javascript/manga/src/en/mangapill.js index 1a509607..a6152cdc 100644 --- a/javascript/manga/src/en/mangapill.js +++ b/javascript/manga/src/en/mangapill.js @@ -1,280 +1,306 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "Mangapill", "lang": "en", "baseUrl": "https://mangapill.com", "apiUrl": "", - "iconUrl": "https://www.google.com/s2/favicons?sz=64&domain=https://mangapill.com/", + "iconUrl": + "https://www.google.com/s2/favicons?sz=64&domain=https://mangapill.com/", "typeSource": "single", "isManga": true, - "version": "1.0.2", + "version": "1.0.3", "dateFormat": "", "dateFormatLocale": "", "pkgPath": "manga/src/en/mangapill.js" -}]; + } +]; class DefaultExtension extends MProvider { - getHeaders(url) { - return { - "Referer": this.source.baseUrl - } + getHeaders(url) { + return { + "Referer": this.source.baseUrl, + }; + } + + statusCode(status) { + return ( + { + "publishing": 0, + "finished": 1, + "on hiatus": 2, + "discontinued": 3, + "not yet published": 4, + }[status] ?? 5 + ); + } + + async getPreference(key) { + const preferences = new SharedPreferences(); + return parseInt(preferences.get(key)); + } + + async getMangaList(slug) { + var lang = await this.getPreference("pref_title_lang"); + + var url = `${this.source.baseUrl}/${slug}`; + var res = await new Client().get(url, this.getHeaders()); + var doc = new Document(res.body); + var list = []; + var mangaElements = doc.select("div.grid.gap-3.lg > div"); + for (var manga of mangaElements) { + var details = manga.selectFirst("div").select("a"); + var detLen = details.length; + details = details[detLen - 1]; + + var imageUrl = manga.selectFirst("img").getSrc; + var link = details.getHref; + var nameSection = details.select("div"); + + var name = + nameSection[1] && lang == 2 ? nameSection[1].text : nameSection[0].text; + + list.push({ name, imageUrl, link }); + } + var hasNextPage = false; + if (slug.includes("search?q")) { + hasNextPage = doc.selectFirst(".container.py-3 a.btn.btn-sm").className + ? true + : false; + } + return { list, hasNextPage }; + } + + async getNavPage(prefKey) { + var val = await this.getPreference(prefKey); + var slug = ""; + switch (val) { + case 1: { + slug = "mangas/new"; + break; + } + case 2: { + slug = "chapters"; + break; + } + } + return await this.getMangaList(slug); + } + + async getPopular(page) { + return await this.getNavPage("pref_popular_content"); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + + async getLatestUpdates(page) { + return await this.getNavPage("pref_latest_content"); + } + + async searchManga(query, status, type, genre, page) { + var slug = `search?q=${query}&status=${status}&type=${type}${genre}&page=${page}`; + return await this.getMangaList(slug); + } + + async search(query, page, filters) { + var type = filters[0].values[filters[0].state].value; + var status = filters[1].values[filters[1].state].value; + + var genre = ""; + for (var filter of filters[2].state) { + if (filter.state == true) genre += `&genre=${filter.value}`; + } + return await this.searchManga(query, status, type, genre, page); + } + + async getMangaDetail(slug) { + var lang = await this.getPreference("pref_title_lang"); + var baseUrl = this.source.baseUrl; + if (slug.includes(baseUrl)) slug = slug.replace(baseUrl, ""); + + var link = `${baseUrl}${slug}`; + var res = await new Client().get(link, this.getHeaders()); + var doc = new Document(res.body); + + var mangaName = doc.selectFirst(".mb-3 .font-bold.text-lg").text; + if (doc.selectFirst(".mb-3 .text-sm.text-secondary") && lang == 2) + mangaName = doc.selectFirst(".mb-3 .text-sm.text-secondary").text; + var description = doc + .selectFirst("meta[name='description']") + .attr("content"); + var imageUrl = doc.selectFirst(".w-full.h-full").getSrc; + var statusText = doc + .select(".grid.grid-cols-1 > div")[1] + .selectFirst("div").text; + var status = this.statusCode(statusText); + + var genre = []; + var genreList = doc.select("a.mr-1"); + for (var gen of genreList) { + genre.push(gen.text); } - statusCode(status) { - return { - "publishing": 0, - "finished": 1, - "on hiatus": 2, - "discontinued": 3, - "not yet published": 4, - }[status] ?? 5; + var chapters = []; + var chapList = doc.select("div.my-3.grid > a"); + for (var chap of chapList) { + var name = chap.text; + var url = chap.getHref; + chapters.push({ name, url }); + } + return { + name: mangaName, + description, + link, + imageUrl, + status, + genre, + chapters, + }; + } + + async getDetail(url) { + return await this.getMangaDetail(url); + } + // For anime episode video list + async getVideoList(url) { + throw new Error("getVideoList not implemented"); + } + + // For manga chapter pages + async getPageList(url) { + var link = `${this.source.baseUrl}${url}`; + + var res = await new Client().get(link, this.getHeaders()); + var doc = new Document(res.body); + + var urls = []; + + var pages = doc.select("chapter-page"); + for (var page of pages) { + var img = page.selectFirst("img").getSrc; + if (img != null) urls.push(img); } - async getPreference(key) { - const preferences = new SharedPreferences(); - return parseInt(preferences.get(key)) - } + return urls; + } - async getMangaList(slug) { - var lang = await this.getPreference("pref_title_lang"); + getFilterList() { + return [ + { + type_name: "SelectFilter", + name: "Type", + state: 0, + values: [ + ["All", ""], + ["Manga", "manga"], + ["Novel", "novel"], + ["One-Shot", "one-shot"], + ["Doujinshi", "doujinshi"], + ["Manhwa", "manhwa"], + ["Manhua", "manhua"], + ["Oel", "oel"], + ].map((x) => ({ type_name: "SelectOption", name: x[0], value: x[1] })), + }, + { + type_name: "SelectFilter", + name: "Status", + state: 0, + values: [ + ["All", ""], + ["Publishing", "publishing"], + ["Finished", "finished"], + ["On hiatus", "on hiatus"], + ["Discontinued", "discontinued"], + ["Not yet published", "not yet published"], + ].map((x) => ({ type_name: "SelectOption", name: x[0], value: x[1] })), + }, + { + type_name: "GroupFilter", + name: "Genre", + state: [ + ["Action", "Action"], + ["Adventure", "Adventure"], + ["Cars", "Cars"], + ["Comedy", "Comedy"], + ["Dementia", "Dementia"], + ["Demons", "Demons"], + ["Doujinshi", "Doujinshi"], + ["Drama", "Drama"], + ["Ecchi", "Ecchi"], + ["Fantasy", "Fantasy"], + ["Game", "Game"], + ["Gender Bender", "Gender Bender"], + ["Harem", "Harem"], + ["Historical", "Historical"], + ["Horror", "Horror"], + ["Isekai", "Isekai"], + ["Josei", "Josei"], + ["Kids", "Kids"], + ["Magic", "Magic"], + ["Martial Arts", "Martial Arts"], + ["Mecha", "Mecha"], + ["Military", "Military"], + ["Music", "Music"], + ["Mystery", "Mystery"], + ["Parody", "Parody"], + ["Police", "Police"], + ["Psychological", "Psychological"], + ["Romance", "Romance"], + ["Samurai", "Samurai"], + ["School", "School"], + ["Sci-Fi", "Sci-Fi"], + ["Seinen", "Seinen"], + ["Shoujo", "Shoujo"], + ["Shoujo Ai", "Shoujo Ai"], + ["Shounen", "Shounen"], + ["Shounen Ai", "Shounen Ai"], + ["Slice of Life", "Slice of Life"], + ["Space", "Space"], + ["Sports", "Sports"], + ["Super Power", "Super Power"], + ["Supernatural", "Supernatural"], + ["Thriller", "Thriller"], + ["Tragedy", "Tragedy"], + ["Vampire", "Vampire"], + ["Yaoi", "Yaoi"], + ["Yuri", "Yuri"], + ].map((x) => ({ type_name: "CheckBox", name: x[0], value: x[1] })), + }, + ]; + } - var url = `${this.source.baseUrl}/${slug}` - var res = await new Client().get(url, this.getHeaders()); - var doc = new Document(res.body); - var list = []; - var mangaElements = doc.select("div.grid.gap-3.lg > div") - for (var manga of mangaElements) { - var details = manga.selectFirst('div').select('a'); - var detLen = details.length - details = details[detLen - 1] - - var imageUrl = manga.selectFirst("img").getSrc; - var link = details.getHref; - var nameSection = details.select('div'); - - var name = (nameSection[1] && lang == 2) ? nameSection[1].text : nameSection[0].text - - list.push({ name, imageUrl, link }); - } - var hasNextPage = false; - if (slug.includes("search?q")) { - hasNextPage = doc.selectFirst(".container.py-3 a.btn.btn-sm").className ? true : false - } - return { list, hasNextPage } - } - - async getNavPage(prefKey) { - var val = await this.getPreference(prefKey); - var slug = '' - switch (val) { - case 1: { - slug = 'mangas/new' - break; - } - case 2: { - slug = 'chapters' - break; - } - } - return await this.getMangaList(slug) - } - - async getPopular(page) { - return await this.getNavPage("pref_popular_content"); - } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } - - async getLatestUpdates(page) { - return await this.getNavPage("pref_latest_content"); - } - - async searchManga(query, status, type, genre, page) { - var slug = `search?q=${query}&status=${status}&type=${type}${genre}&page=${page}` - return await this.getMangaList(slug) - } - - async search(query, page, filters) { - var type = filters[0].values[filters[0].state].value - var status = filters[1].values[filters[1].state].value - - var genre = "" - for (var filter of filters[2].state) { - if (filter.state == true) - genre += `&genre=${filter.value}` - } - return await this.searchManga(query, status, type, genre, page); - } - - async getMangaDetail(slug) { - var lang = await this.getPreference("pref_title_lang"); - - var link = `${this.source.baseUrl}${slug}` - var res = await new Client().get(link, this.getHeaders()); - var doc = new Document(res.body); - - var mangaName = doc.selectFirst(".mb-3 .font-bold.text-lg").text - if (doc.selectFirst(".mb-3 .text-sm.text-secondary") && lang == 2) mangaName = doc.selectFirst(".mb-3 .text-sm.text-secondary").text - var description = doc.selectFirst("meta[name='description']").attr("content") - var imageUrl = doc.selectFirst(".w-full.h-full").getSrc - var statusText = doc.select(".grid.grid-cols-1 > div")[1].selectFirst("div").text - var status = this.statusCode(statusText) - - var genre = [] - var genreList = doc.select("a.mr-1") - for (var gen of genreList) { genre.push(gen.text) } - - var chapters = [] - var chapList = doc.select("div.my-3.grid > a") - for (var chap of chapList) { - var name = chap.text - var url = chap.getHref - chapters.push({ name, url }) - } - return { name: mangaName, description, link, imageUrl, status, genre, chapters } - } - - async getDetail(url) { - return await this.getMangaDetail(url); - - } - // For anime episode video list - async getVideoList(url) { - throw new Error("getVideoList not implemented"); - } - - // For manga chapter pages - async getPageList(url) { - var link = `${this.source.baseUrl}${url}` - - var res = await new Client().get(link, this.getHeaders()); - var doc = new Document(res.body); - - var urls = []; - - var pages = doc.select("chapter-page") - for (var page of pages) { - var img = page.selectFirst("img").getSrc - if (img != null) urls.push(img); - } - - return urls - } - - getFilterList() { - return [ - { - type_name: "SelectFilter", - name: "Type", - state: 0, - values: [ - ["All", ""], - ["Manga", "manga"], - ["Novel", "novel"], - ["One-Shot", "one-shot"], - ["Doujinshi", "doujinshi"], - ["Manhwa", "manhwa"], - ["Manhua", "manhua"], - ["Oel", "oel"] - ].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] })) - }, - { - type_name: "SelectFilter", - name: "Status", - state: 0, - values: [ - ["All", ""], - ["Publishing", "publishing"], - ["Finished", "finished"], - ["On hiatus", "on hiatus"], - ["Discontinued", "discontinued"], - ["Not yet published", "not yet published"] - ].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] })) - }, { - type_name: "GroupFilter", - name: "Genre", - state: [ - ["Action", "Action"], - ["Adventure", "Adventure"], - ["Cars", "Cars"], - ["Comedy", "Comedy"], - ["Dementia", "Dementia"], - ["Demons", "Demons"], - ["Doujinshi", "Doujinshi"], - ["Drama", "Drama"], - ["Ecchi", "Ecchi"], - ["Fantasy", "Fantasy"], - ["Game", "Game"], - ["Gender Bender", "Gender Bender"], - ["Harem", "Harem"], - ["Historical", "Historical"], - ["Horror", "Horror"], - ["Isekai", "Isekai"], - ["Josei", "Josei"], - ["Kids", "Kids"], - ["Magic", "Magic"], - ["Martial Arts", "Martial Arts"], - ["Mecha", "Mecha"], - ["Military", "Military"], - ["Music", "Music"], - ["Mystery", "Mystery"], - ["Parody", "Parody"], - ["Police", "Police"], - ["Psychological", "Psychological"], - ["Romance", "Romance"], - ["Samurai", "Samurai"], - ["School", "School"], - ["Sci-Fi", "Sci-Fi"], - ["Seinen", "Seinen"], - ["Shoujo", "Shoujo"], - ["Shoujo Ai", "Shoujo Ai"], - ["Shounen", "Shounen"], - ["Shounen Ai", "Shounen Ai"], - ["Slice of Life", "Slice of Life"], - ["Space", "Space"], - ["Sports", "Sports"], - ["Super Power", "Super Power"], - ["Supernatural", "Supernatural"], - ["Thriller", "Thriller"], - ["Tragedy", "Tragedy"], - ["Vampire", "Vampire"], - ["Yaoi", "Yaoi"], - ["Yuri", "Yuri"] - ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) - } - - ]; - } - - getSourcePreferences() { - return [{ - key: 'pref_popular_content', - listPreference: { - title: 'Preferred popular content', - summary: '', - valueIndex: 0, - entries: ["New Mangas", "Recent Chapters"], - entryValues: ["1", "2"] - } - }, { - key: 'pref_latest_content', - listPreference: { - title: 'Preferred latest content', - summary: '', - valueIndex: 1, - entries: ["New Mangas", "Recent Chapters"], - entryValues: ["1", "2"] - } - }, { - key: 'pref_title_lang', - listPreference: { - title: 'Preferred title language', - summary: '', - valueIndex: 0, - entries: ["Romaji", "English"], - entryValues: ["1", "2"] - } - } - ]; - } + getSourcePreferences() { + return [ + { + key: "pref_popular_content", + listPreference: { + title: "Preferred popular content", + summary: "", + valueIndex: 0, + entries: ["New Mangas", "Recent Chapters"], + entryValues: ["1", "2"], + }, + }, + { + key: "pref_latest_content", + listPreference: { + title: "Preferred latest content", + summary: "", + valueIndex: 1, + entries: ["New Mangas", "Recent Chapters"], + entryValues: ["1", "2"], + }, + }, + { + key: "pref_title_lang", + listPreference: { + title: "Preferred title language", + summary: "", + valueIndex: 0, + entries: ["Romaji", "English"], + entryValues: ["1", "2"], + }, + }, + ]; + } } diff --git a/javascript/manga/src/en/readcomiconline.js b/javascript/manga/src/en/readcomiconline.js index 5fafc70f..d245d099 100644 --- a/javascript/manga/src/en/readcomiconline.js +++ b/javascript/manga/src/en/readcomiconline.js @@ -1,340 +1,459 @@ -const mangayomiSources = [{ +const mangayomiSources = [ + { "name": "ReadComicOnline", "lang": "en", "baseUrl": "https://readcomiconline.li", "apiUrl": "", - "iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://readcomiconline.li/", + "iconUrl": + "https://www.google.com/s2/favicons?sz=256&domain=https://readcomiconline.li/", "typeSource": "single", "itemType": 0, - "version": "0.1.2", + "version": "0.1.3", "pkgPath": "manga/src/en/readcomiconline.js" -}]; + } +]; class DefaultExtension extends MProvider { - constructor() { - super(); - this.client = new Client(); - } + constructor() { + super(); + this.client = new Client(); + } - getPreference(key) { - return new SharedPreferences().get(key); - } + getPreference(key) { + return new SharedPreferences().get(key); + } - getHeaders() { - return { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6832.64 Safari/537.36", - "Referer": this.source.baseUrl, - "Origin": this.source.baseUrl, + getHeaders() { + return { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6832.64 Safari/537.36", + "Referer": this.source.baseUrl, + "Origin": this.source.baseUrl, + }; + } + + async request(slug) { + var url = slug; + var baseUrl = this.source.baseUrl; + if (!slug.includes(baseUrl)) url = baseUrl + slug; + var res = await this.client.get(url, this.getHeaders()); + return new Document(res.body); + } + + async getListPage(slug, page) { + var url = `${slug}page=${page}`; + var doc = await this.request(url); + var baseUrl = this.source.baseUrl; + var list = []; + + var comicList = doc.select(".list-comic > .item"); + comicList.forEach((item) => { + var name = item.selectFirst(".title").text; + var link = item.selectFirst("a").getHref; + var imageSlug = item.selectFirst("img").getSrc; + var imageUrl = imageSlug.includes("http") + ? imageSlug + : `${baseUrl}${imageSlug}`; + list.push({ name, link, imageUrl }); + }); + + var pager = doc.select("ul.pager > li"); + + var hasNextPage = false; + if (pager.length > 0) + hasNextPage = pager[pager.length - 1].text.includes("Last") + ? true + : false; + + return { list, hasNextPage }; + } + + async getPopular(page) { + return await this.getListPage("/ComicList/MostPopular?", page); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + return await this.getListPage("/ComicList/LatestUpdate?", page); + } + async search(query, page, filters) { + function getFilter(state) { + var rd = ""; + state.forEach((item) => { + if (item.state) { + rd += `${item.value},`; } + }); + return rd.slice(0, -1); } - async request(slug) { - var url = slug - var baseUrl = this.source.baseUrl - if (!slug.includes(baseUrl)) url = baseUrl + slug; - var res = await this.client.get(url, this.getHeaders()); - return new Document(res.body); + var isFiltersAvailable = !filters || filters.length != 0; + var genre = isFiltersAvailable ? getFilter(filters[0].state) : []; + var status = isFiltersAvailable + ? filters[1].values[filters[1].state].value + : ""; + var year = isFiltersAvailable + ? filters[2].values[filters[2].state].value + : ""; + + var slug = `/AdvanceSearch?comicName=${query}&ig=${encodeURIComponent( + genre + )}&status=${status}&pubDate=${year}&`; + + return await this.getListPage(slug, page); + } + + async getDetail(url) { + function statusCode(status) { + return ( + { + "Ongoing": 0, + "Completed": 1, + }[status] ?? 5 + ); } - async getListPage(slug, page) { - var url = `${slug}page=${page}` - var doc = await this.request(url); - var baseUrl = this.source.baseUrl - var list = [] + var baseUrl = this.source.baseUrl; + if (url.includes(baseUrl)) url = url.replace(baseUrl, ""); - var comicList = doc.select(".list-comic > .item") - comicList.forEach(item => { - var name = item.selectFirst(".title").text; - var link = item.selectFirst("a").getHref - var imageSlug = item.selectFirst("img").getSrc - var imageUrl = imageSlug.includes("http") ? imageSlug : `${baseUrl}${imageSlug}`; - list.push({ name, link, imageUrl }); - }); + var doc = await this.request(url); - var pager = doc.select("ul.pager > li") + var detailsSection = doc.selectFirst(".barContent"); + var name = detailsSection.selectFirst("a").text; + var imageSlug = doc.selectFirst(".rightBox").selectFirst("img").getSrc; + var imageUrl = imageSlug.includes("http") + ? imageSlug + : `${this.source.baseUrl}${imageSlug}`; + var pTag = detailsSection.select("p"); - var hasNextPage = false - if (pager.length > 0) hasNextPage = pager[pager.length - 1].text.includes("Last") ? true : false; + var description = pTag[pTag.length - 2].text; - return { list, hasNextPage } + var status = 5; + var genre = []; + var author = ""; + var artist = ""; + pTag.forEach((p) => { + var itemText = p.text.trim(); + + if (itemText.includes("Genres")) { + genre = itemText.replace("Genres:", "").trim().split(", "); + } else if (itemText.includes("Status")) { + var sts = itemText.replace("Status: ", "").trim().split("\n")[0]; + status = statusCode(sts); + } else if (itemText.includes("Writer")) { + author = itemText.replace("Writer: ", ""); + } else if (itemText.includes("Artist")) { + artist = itemText.replace("Artist: ", ""); + } + }); + var chapters = []; + var tr = doc.selectFirst("table").select("tr"); + tr.splice(0, 2); // 1st item in the table is headers & 2nd item is a line break + tr.forEach((item) => { + var tds = item.select("td"); + var aTag = tds[0].selectFirst("a"); + var chapLink = aTag.getHref; + + var chapTitle = aTag.text.trim().replace(`${name} `, ""); + chapTitle = chapTitle[0] == "_" ? chapTitle.substring(1) : chapTitle; + + var uploadDate = tds[1].text.trim(); + var date = new Date(uploadDate); + var dateUpload = date.getTime().toString(); + + chapters.push({ url: chapLink, name: chapTitle, dateUpload }); + }); + var link = baseUrl + url; + + return { + name, + link, + imageUrl, + description, + genre, + status, + author, + artist, + chapters, + }; + } + + // For manga chapter pages + async getPageList(url) { + var pages = []; + var hdr = this.getHeaders(); + let match; + var imageQuality = this.getPreference("readcomiconline_page_quality"); + + var doc = await this.request(url); + var html = doc.html; + + // Find host url for images + var baseUrlOverride = ""; + const hostRegex = /return\s+baeu\s*\(\s*l\s*,\s*'([^']+?)'\s*\);?/g; + match = hostRegex.exec(html); + if (match.length > 0) { + baseUrlOverride = match[1]; + if (baseUrlOverride.slice(-1) != "/") baseUrlOverride += "/"; } - async getPopular(page) { - return await this.getListPage("/ComicList/MostPopular?", page) + const pageRegex = /pht\s*=\s*'([^']+?)';?/g; + while ((match = pageRegex.exec(html)) !== null) { + var encodedImageUrl = match[1]; + var decodedImageUrl = this.decodeImageUrl( + encodedImageUrl, + imageQuality, + baseUrlOverride + ); + pages.push({ + url: decodedImageUrl, + headers: hdr, + }); } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } - async getLatestUpdates(page) { - return await this.getListPage("/ComicList/LatestUpdate?", page) - } - async search(query, page, filters) { - function getFilter(state) { - var rd = "" - state.forEach(item => { - if (item.state) { - rd += `${item.value},` - } - }) - return rd.slice(0, -1) - } - - var isFiltersAvailable = !filters || filters.length != 0 - var genre = isFiltersAvailable ? getFilter(filters[0].state): [] - var status = isFiltersAvailable ? filters[1].values[filters[1].state].value: "" - var year = isFiltersAvailable ? filters[2].values[filters[2].state].value: "" - - - var slug = `/AdvanceSearch?comicName=${query}&ig=${encodeURIComponent(genre)}&status=${status}&pubDate=${year}&` - - return await this.getListPage(slug, page) + return pages; + } + getFilterList() { + function formateState(type_name, items, values) { + var state = []; + for (var i = 0; i < items.length; i++) { + state.push({ type_name: type_name, name: items[i], value: values[i] }); + } + return state; } - async getDetail(url) { - function statusCode(status) { - return { - "Ongoing": 0, - "Completed": 1, - }[status] ?? 5; - } - var link = this.source.baseUrl + url - var doc = await this.request(url) + var filters = []; - var detailsSection = doc.selectFirst(".barContent") - var name = detailsSection.selectFirst("a").text - var imageSlug = doc.selectFirst(".rightBox").selectFirst("img").getSrc - var imageUrl = imageSlug.includes("http") ? imageSlug : `${this.source.baseUrl}${imageSlug}`; - var pTag = detailsSection.select("p") + // Genre + var items = [ + "Action", + "Adventure", + "Anthology", + "Anthropomorphic", + "Biography", + "Children", + "Comedy", + "Crime", + "Drama", + "Family", + "Fantasy", + "Fighting", + "Graphic Novels", + "Historical", + "Horror", + "Leading Ladies", + "LGBTQ", + "Literature", + "Manga", + "Martial Arts", + "Mature", + "Military", + "Mini-Series", + "Movies & TV", + "Music", + "Mystery", + "Mythology", + "Personal", + "Political", + "Post-Apocalyptic", + "Psychological", + "Pulp", + "Religious", + "Robots", + "Romance", + "School Life", + "Sci-Fi", + "Slice of Life", + "Sport", + "Spy", + "Superhero", + "Supernatural", + "Suspense", + "Teen", + "Thriller", + "Vampires", + "Video Games", + "War", + "Western", + "Zombies", + ]; - var description = pTag[pTag.length - 2].text + var values = [ + "1", + "2", + "38", + "46", + "41", + "49", + "3", + "17", + "19", + "25", + "20", + "31", + "5", + "28", + "15", + "35", + "51", + "44", + "40", + "4", + "8", + "33", + "56", + "47", + "55", + "23", + "21", + "48", + "42", + "43", + "27", + "39", + "53", + "9", + "32", + "52", + "16", + "50", + "54", + "30", + "22", + "24", + "29", + "57", + "18", + "34", + "37", + "26", + "45", + "36", + ]; + filters.push({ + type_name: "GroupFilter", + name: "Genres", + state: formateState("CheckBox", items, values), + }); - var status = 5 - var genre = [] - var author = "" - var artist = "" + // Status + items = ["Any", "Ongoing", "Completed"]; + values = ["", "Ongoing", "Completed"]; + filters.push({ + type_name: "SelectFilter", + name: "Status", + state: 0, + values: formateState("SelectOption", items, values), + }); - pTag.forEach(p => { - var itemText = p.text.trim() + // Years + const currentYear = new Date().getFullYear(); + items = Array.from({ length: currentYear - 1919 }, (_, i) => + (1920 + i).toString() + ).reverse(); + items = ["All", ...items]; + values = ["", ...items]; + filters.push({ + type_name: "SelectFilter", + name: "Year", + state: 0, + values: formateState("SelectOption", items, values), + }); - if (itemText.includes("Genres")) { - genre = itemText.replace("Genres:", "").trim().split(", ") - } else if (itemText.includes("Status")) { - var sts = itemText.replace("Status: ", "").trim().split("\n")[0] - status = statusCode(sts) - } else if (itemText.includes("Writer")) { - author = itemText.replace("Writer: ", "") - } else if (itemText.includes("Artist")) { - artist = itemText.replace("Artist: ", "") - } + return filters; + } + getSourcePreferences() { + return [ + { + key: "readcomiconline_page_quality", + listPreference: { + title: "Preferred image quality", + summary: "", + valueIndex: 2, + entries: ["Low", "Medium", "High", "Highest"], + entryValues: ["l", "m", "h", "vh"], + }, + }, + ]; + } - }) - var chapters = [] - var tr = doc.selectFirst("table").select("tr") - tr.splice(0, 2) // 1st item in the table is headers & 2nd item is a line break - tr.forEach(item => { - var tds = item.select("td") - var aTag = tds[0].selectFirst("a") - var chapLink = aTag.getHref + // -------- ReadComicOnline Image Decoder -------- + // Source:- https://readcomiconline.li/Scripts/rguard.min.js - var chapTitle = aTag.text.trim().replace(`${name} `, "") - chapTitle = chapTitle[0] == "_" ? chapTitle.substring(1,) : chapTitle + base64UrlDecode(input) { + let base64 = input.replace(/-/g, "+").replace(/_/g, "/"); - var uploadDate = tds[1].text.trim() - var date = new Date(uploadDate); - var dateUpload = date.getTime().toString(); - - - chapters.push({ url: chapLink, name: chapTitle, dateUpload }) - - }) - - return { name, link, imageUrl, description, genre, status, author, artist, chapters } + while (base64.length % 4 !== 0) { + base64 += "="; } - // For manga chapter pages - async getPageList(url) { - var pages = []; - var hdr = this.getHeaders() - let match; - var imageQuality = this.getPreference("readcomiconline_page_quality"); + const base64abc = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const outputBytes = []; - var doc = await this.request(url) - var html = doc.html + for (let i = 0; i < base64.length; i += 4) { + const c1 = base64abc.indexOf(base64[i]); + const c2 = base64abc.indexOf(base64[i + 1]); + const c3 = base64abc.indexOf(base64[i + 2]); + const c4 = base64abc.indexOf(base64[i + 3]); - // Find host url for images - var baseUrlOverride = "" - const hostRegex = /return\s+baeu\s*\(\s*l\s*,\s*'([^']+?)'\s*\);?/g; - match = hostRegex.exec(html) - if (match.length > 0) { - baseUrlOverride = match[1] - if (baseUrlOverride.slice(-1) != "/") baseUrlOverride += "/" - } + const triplet = (c1 << 18) | (c2 << 12) | ((c3 & 63) << 6) | (c4 & 63); - - const pageRegex = /pht\s*=\s*'([^']+?)';?/g; - while ((match = pageRegex.exec(html)) !== null) { - var encodedImageUrl = match[1] - var decodedImageUrl = this.decodeImageUrl(encodedImageUrl, imageQuality, baseUrlOverride) - pages.push({ - url: decodedImageUrl, - headers: hdr, - }); - - } - return pages; - } - getFilterList() { - function formateState(type_name, items, values) { - var state = []; - for (var i = 0; i < items.length; i++) { - state.push({ type_name: type_name, name: items[i], value: values[i] }) - } - return state; - } - - var filters = []; - - // Genre - var items = [ - "Action", "Adventure", "Anthology", "Anthropomorphic", "Biography", "Children", "Comedy", - "Crime", "Drama", "Family", "Fantasy", "Fighting", "Graphic Novels", "Historical", "Horror", - "Leading Ladies", "LGBTQ", "Literature", "Manga", "Martial Arts", "Mature", "Military", - "Mini-Series", "Movies & TV", "Music", "Mystery", "Mythology", "Personal", "Political", - "Post-Apocalyptic", "Psychological", "Pulp", "Religious", "Robots", "Romance", "School Life", - "Sci-Fi", "Slice of Life", "Sport", "Spy", "Superhero", "Supernatural", "Suspense", "Teen", - "Thriller", "Vampires", "Video Games", "War", "Western", "Zombies" - ]; - - var values = [ - "1", "2", "38", "46", "41", "49", "3", - "17", "19", "25", "20", "31", "5", "28", "15", - "35", "51", "44", "40", "4", "8", "33", - "56", "47", "55", "23", "21", "48", "42", - "43", "27", "39", "53", "9", "32", "52", - "16", "50", "54", "30", "22", "24", "29", "57", - "18", "34", "37", "26", "45", "36" - ]; - filters.push({ - type_name: "GroupFilter", - name: "Genres", - state: formateState("CheckBox", items, values) - }) - - // Status - items = ["Any", "Ongoing", "Completed"]; - values = ["", "Ongoing", "Completed"]; - filters.push({ - type_name: "SelectFilter", - name: "Status", - state: 0, - values: formateState("SelectOption", items, values) - }) - - // Years - const currentYear = new Date().getFullYear(); - items = Array.from({ length: currentYear - 1919 }, (_, i) => (1920 + i).toString()).reverse() - items = ["All", ...items] - values = ["", ...items] - filters.push({ - type_name: "SelectFilter", - name: "Year", - state: 0, - values: formateState("SelectOption", items, values) - }) - - return filters; - } - getSourcePreferences() { - return [ - { - key: "readcomiconline_page_quality", - listPreference: { - title: 'Preferred image quality', - summary: '', - valueIndex: 2, - entries: ["Low", "Medium", "High", "Highest"], - entryValues: ["l", "m", "h", "vh"] - } - }, - ] + outputBytes.push((triplet >> 16) & 0xff); + if (base64[i + 2] !== "=") outputBytes.push((triplet >> 8) & 0xff); + if (base64[i + 3] !== "=") outputBytes.push(triplet & 0xff); } - // -------- ReadComicOnline Image Decoder -------- - // Source:- https://readcomiconline.li/Scripts/rguard.min.js + // Convert bytes to ISO-8859-1 string + return String.fromCharCode(...outputBytes); + } - base64UrlDecode(input) { - let base64 = input - .replace(/-/g, "+") - .replace(/_/g, "/"); + extractBeforeDecode(url) { + return url.substring(15, 33) + url.substring(50); + } - while (base64.length % 4 !== 0) { - base64 += "="; - } + finalizeDecodedString(decoded) { + return ( + decoded.substring(0, decoded.length - 11) + + decoded[decoded.length - 2] + + decoded[decoded.length - 1] + ); + } - const base64abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - const outputBytes = []; + decoderFunction(encodedUrl) { + var decodedUrl = this.extractBeforeDecode(encodedUrl); + decodedUrl = this.finalizeDecodedString(decodedUrl); + decodedUrl = decodeURIComponent(this.base64UrlDecode(decodedUrl)); + decodedUrl = decodedUrl.substring(0, 13) + decodedUrl.substring(17); + return decodedUrl.slice(0, -2) + "=s1600"; + } - for (let i = 0; i < base64.length; i += 4) { - const c1 = base64abc.indexOf(base64[i]); - const c2 = base64abc.indexOf(base64[i + 1]); - const c3 = base64abc.indexOf(base64[i + 2]); - const c4 = base64abc.indexOf(base64[i + 3]); + decodeImageUrl(encodedImageUrl, imageQuality, baseUrlOverride) { + // Default image qualities + var IMAGEQUALITY = [ + { "l": "900", "m": "0", "h": "1600", "vh": "2041" }, + { "l": "900", "m": "1600", "h": "2041", "vh": "0" }, + ]; - const triplet = (c1 << 18) | (c2 << 12) | ((c3 & 63) << 6) | (c4 & 63); + let finalUrl; + var qType = 0; + // Check if the url starts with https, if not then decode the url + if (!encodedImageUrl.startsWith("https")) { + encodedImageUrl = encodedImageUrl + .replace(/6UUQS__ACd__/g, "b") + .replace(/pw_.g28x/g, "b"); - outputBytes.push((triplet >> 16) & 0xFF); - if (base64[i + 2] !== "=") outputBytes.push((triplet >> 8) & 0xFF); - if (base64[i + 3] !== "=") outputBytes.push(triplet & 0xFF); - } + var encodedUrl = encodedImageUrl.split("=s")[0]; + var decodedUrl = this.decoderFunction(encodedUrl); - // Convert bytes to ISO-8859-1 string - return String.fromCharCode(...outputBytes); - } - - - extractBeforeDecode(url) { - return url.substring(15, 33) + url.substring(50); - } - - - finalizeDecodedString(decoded) { - return decoded.substring(0, decoded.length - 11) + decoded[decoded.length - 2] + decoded[decoded.length - 1]; - } - - decoderFunction(encodedUrl) { - var decodedUrl = this.extractBeforeDecode(encodedUrl); - decodedUrl = this.finalizeDecodedString(decodedUrl); - decodedUrl = decodeURIComponent(this.base64UrlDecode(decodedUrl)); - decodedUrl = decodedUrl.substring(0, 13) + decodedUrl.substring(17); - return decodedUrl.slice(0, -2) + "=s1600" - } - - decodeImageUrl(encodedImageUrl, imageQuality, baseUrlOverride) { - // Default image qualities - var IMAGEQUALITY = [ - { "l": "900", "m": "0", "h": "1600", "vh": "2041" }, - { "l": "900", "m": "1600", "h": "2041", "vh": "0" } - ] - - let finalUrl; - var qType = 0 - // Check if the url starts with https, if not then decode the url - if (!encodedImageUrl.startsWith("https")) { - encodedImageUrl = encodedImageUrl - .replace(/6UUQS__ACd__/g, 'b') - .replace(/pw_.g28x/g, "b") - - var encodedUrl = encodedImageUrl.split("=s")[0] - var decodedUrl = this.decoderFunction(encodedUrl); - - var queryParams = encodedImageUrl.substring(encodedImageUrl.indexOf("?")); - finalUrl = baseUrlOverride + decodedUrl + queryParams - } else { - // If the url starts with https, then just override the base url - qType = 1 - finalUrl = baseUrlOverride + encodedImageUrl.split(".com/")[1] - } - return finalUrl.replace("s1600", `s${IMAGEQUALITY[qType][imageQuality]}`) + var queryParams = encodedImageUrl.substring(encodedImageUrl.indexOf("?")); + finalUrl = baseUrlOverride + decodedUrl + queryParams; + } else { + // If the url starts with https, then just override the base url + qType = 1; + finalUrl = baseUrlOverride + encodedImageUrl.split(".com/")[1]; } + return finalUrl.replace("s1600", `s${IMAGEQUALITY[qType][imageQuality]}`); + } }