Merge pull request #259 from Swakshan/fix/repo

fix: Refresh contain issue
This commit is contained in:
Moustapha Kodjo Amadou
2025-05-20 12:11:21 +01:00
committed by GitHub
10 changed files with 2379 additions and 2087 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,266 +1,279 @@
const mangayomiSources = [{ const mangayomiSources = [
{
"name": "Soaper", "name": "Soaper",
"id": 764093578,
"lang": "all", "lang": "all",
"baseUrl": "https://soaper.cc", "baseUrl": "https://soaper.cc",
"apiUrl": "", "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", "typeSource": "multi",
"version": "1.0.4", "version": "1.0.5",
"itemType": 1, "itemType": 1,
"dateFormat": "", "dateFormat": "",
"dateFormatLocale": "", "dateFormatLocale": "",
"pkgPath": "anime/src/all/soaper.js" "pkgPath": "anime/src/all/soaper.js"
}]; }
];
// Authors: - Swakshan, kodjodevf
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
getHeaders(url) { getHeaders(url) {
return { return {
"Referer": url, Referer: url,
"Origin": 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 { name, imageUrl, description, link, chapters };
return new SharedPreferences().get(key); }
} // For anime episode video list
async getVideoList(url) {
var body = await this.request(url);
var baseUrl = this.getBasueUrl();
var streams = [];
getBasueUrl() { // Traditional servers
return this.getPreference("soaper_override_base_url") var eId = body.selectFirst("#hId").attr("value");
} var hIsW = body.selectFirst("#hIsW").attr("value");
var apiType = url[0].toUpperCase();
async request(slug) { var servers = [0, 1];
const baseUrl = this.getBasueUrl() for (var serverNum of servers) {
var url = `${baseUrl}/${slug}` var serverName = body.selectFirst(`#server_button_${serverNum}`).text;
var res = await new Client().get(url, this.getHeaders(baseUrl)); if (serverName.length < 1) continue;
var doc = new Document(res.body); var data = {
return doc pass: eId,
} param: "",
extra: "1",
e2: hIsW,
server: "" + serverNum,
};
var res = await this.requestJSON(
`home/index/Get${apiType}InfoAjax`,
data
);
async requestJSON(slug, data) { var streamUrl = baseUrl + res.val;
const baseUrl = this.getBasueUrl() var subs = [];
var url = `${baseUrl}/${slug}` var vidSubs = res.subs;
var res = await new Client().post(url, this.getHeaders(baseUrl), data); if (vidSubs != null && vidSubs.length > 0) {
return JSON.parse(res.body); for (var sub of vidSubs) {
} subs.push({
file: baseUrl + sub.path,
async formatList(slug, page) { label: sub.name,
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 streams.push({
if (slug.indexOf("search.html?") == -1) { url: streamUrl,
var pagination = doc.select("ul.pagination > li") originalUrl: streamUrl,
var last_page_num = parseInt(pagination[pagination.length - 2].text); quality: serverName,
hasNextPage = page < last_page_num ? true : false; subtitles: subs,
} });
return { list, hasNextPage }
} }
async filterList(year = "all", genre = "all", sort = "new", page = 1) { // Download servers
year = year == "all" ? "" : `/year/${year}` var modal_footer = body.select(".modal-footer > a");
genre = genre == "all" ? "" : `/cat/${genre}` if (modal_footer.length > 0) {
sort = sort == "new" ? "" : `/sort/${sort}` 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 res = dBody.selectFirst("#res").attr("value");
var movieList = await this.formatList(`movielist${slug}`, page); var mb = dBody.selectFirst("#mb").attr("value");
var seriesList = await this.formatList(`tvlist${slug}`, page); var streamLink = dBody.selectFirst("#link").attr("value");
var list = []; streams.push({
var priority = this.getPreference("soaper_content_priority"); url: streamLink,
if (priority === "series") { originalUrl: streamLink,
list = [...seriesList.list, ...movieList.list]; quality: `Download Server: ${res} [${mb}]`,
} else { });
list = [...movieList.list, ...seriesList.list]; }
}
var hasNextPage = seriesList.hasNextPage || movieList.hasNextPage;
return { list, hasNextPage }
} }
return streams;
}
// For manga chapter pages
async getPageList() {
throw new Error("getPageList not implemented");
}
getFilterList() {
throw new Error("getFilterList not implemented");
}
async getPopular(page) { getSourcePreferences() {
return await this.filterList("all", "all", "hot", page); return [
} {
get supportsLatest() { key: "soaper_override_base_url",
throw new Error("supportsLatest not implemented"); editTextPreference: {
} title: "Override base url",
async getLatestUpdates(page) { summary: "Default: https://soaper.cc",
return await this.filterList("all", "all", "new", page); value: "https://soaper.cc",
} dialogTitle: "Override base url",
dialogMessage: "",
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"]
}
}, },
]; },
} {
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"],
},
},
];
}
} }

View File

@@ -1,258 +1,254 @@
const mangayomiSources = [{ const mangayomiSources = [
{
"name": "AnimeGG", "name": "AnimeGG",
"lang": "en", "lang": "en",
"id": 209614032,
"baseUrl": "https://www.animegg.org", "baseUrl": "https://www.animegg.org",
"apiUrl": "", "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", "typeSource": "single",
"itemType": 1, "itemType": 1,
"version": "1.0.2", "version": "1.0.3",
"pkgPath": "anime/src/en/animegg.js" "pkgPath": "anime/src/en/animegg.js"
}]; }
];
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
constructor() {
super();
this.client = new Client();
}
constructor() { getHeaders(url) {
super(); return {
this.client = new Client(); Referer: this.source.baseUrl,
} Origin: this.source.baseUrl,
};
}
getHeaders(url) { getPreference(key) {
return { return parseInt(new SharedPreferences().get(key));
"Referer": this.source.baseUrl, }
"Origin": this.source.baseUrl
}
}
getPreference(key) { async requestText(slug) {
return parseInt(new SharedPreferences().get(key)); 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) { async fetchPopularnLatest(slug) {
var url = `${this.source.baseUrl}${slug}` var body = await this.request(slug);
var res = await this.client.get(url, this.getHeaders()); var items = body.select("li.fea");
return res.body; var list = [];
} var hasNextPage = true;
async request(slug) { if (items.length > 0) {
return new Document(await this.requestText(slug)); for (var item of items) {
} var imageUrl = item.selectFirst("img").getSrc;
var linkSection = item.selectFirst(".rightpop").selectFirst("a");
async fetchPopularnLatest(slug) { var link = linkSection.getHref;
var body = await this.request(slug) var name = linkSection.text;
var items = body.select("li.fea") list.push({
var list = [] name,
var hasNextPage = true imageUrl,
if (items.length > 0) { link,
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
});
}); });
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 return { list, hasNextPage: false };
async getVideoList(url) { }
var body = await this.request(url)
var sub = body.selectFirst("#subbed-Animegg") statusCode(status) {
var subStreams = await this.exxtractStreams(sub,"Sub") return (
{
Ongoing: 0,
Completed: 1,
}[status] ?? 5
);
}
var dub = body.selectFirst("#dubbed-Animegg") async getDetail(url) {
var dubStreams = await this.exxtractStreams(dub,"Dub") var baseUrl = this.source.baseUrl;
var slug = url.replace(baseUrl, "");
var link = baseUrl + slug;
var raw = body.selectFirst("#raw-Animegg") var body = await this.request(slug);
var rawStreams = await this.exxtractStreams(raw,"Raw")
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 pref = this.getPreference("animegg_stream_type_1") var episodesList = body.select(".newmanga > li");
var streams = []; episodesList.forEach((ep) => {
if(pref == 0){ var epTitle = ep.selectFirst("i.anititle").text;
streams = [...subStreams,...dubStreams, ...rawStreams] var epNumber = ep.selectFirst("strong").text.replace(title, "Episode");
}else if(pref == 1){ var epName = epNumber == epTitle ? epNumber : `${epNumber} - ${epTitle}`;
streams = [...dubStreams,...subStreams, ...rawStreams] var epUrl = ep.selectFirst("a").getHref;
}else{
streams = [...rawStreams,...subStreams, ...dubStreams]
}
return streams 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 streams;
return [ }
{
key: "animegg_popular_category", getSourcePreferences() {
listPreference: { return [
title: 'Preferred popular category', {
summary: '', key: "animegg_popular_category",
valueIndex: 0, listPreference: {
entries: ["Popular", "Newest", "Ongoing", "Completed", "Alphabetical"], title: "Preferred popular category",
entryValues: ["0", "1", "2", "3", "4"] summary: "",
} valueIndex: 0,
}, entries: [
{ "Popular",
key: "animegg_stream_type_1", "Newest",
listPreference: { "Ongoing",
title: 'Preferred stream type', "Completed",
summary: '', "Alphabetical",
valueIndex: 0, ],
entries: ["Sub","Dub","Raw"], entryValues: ["0", "1", "2", "3", "4"],
entryValues: ["0", "1", "2"] },
} },
} {
] key: "animegg_stream_type_1",
} listPreference: {
title: "Preferred stream type",
summary: "",
valueIndex: 0,
entries: ["Sub", "Dub", "Raw"],
entryValues: ["0", "1", "2"],
},
},
];
}
} }

View File

@@ -6,7 +6,7 @@ const mangayomiSources = [{
"iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://www.animeonsen.xyz", "iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://www.animeonsen.xyz",
"typeSource": "single", "typeSource": "single",
"itemType": 1, "itemType": 1,
"version": "1.0.0", "version": "1.0.1",
"pkgPath": "anime/src/all/animeonsen.js" "pkgPath": "anime/src/all/animeonsen.js"
}]; }];
@@ -143,7 +143,9 @@ class DefaultExtension extends MProvider {
} }
async getDetail(url) { 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 detailsApiSlug = `/${url}/extensive`
var animeDetails = await this.request(detailsApiSlug); var animeDetails = await this.request(detailsApiSlug);

View File

@@ -1,241 +1,270 @@
const mangayomiSources = [{ const mangayomiSources = [
{
"name": "AnimeParadise", "name": "AnimeParadise",
"lang": "en", "lang": "en",
"baseUrl": "https://animeparadise.moe", "baseUrl": "https://animeparadise.moe",
"apiUrl": "https://api.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", "typeSource": "single",
"itemType": 1, "itemType": 1,
"version": "0.0.1", "version": "0.0.2",
"pkgPath": "anime/src/en/animeparadise.js" "pkgPath": "anime/src/en/animeparadise.js"
}]; }
];
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
getPreference(key) {
const preferences = new SharedPreferences();
return preferences.get(key);
}
getPreference(key) { async extractFromUrl(url) {
const preferences = new SharedPreferences(); var res = await new Client().get(this.source.baseUrl + url);
return preferences.get(key); 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) { return {
var res = await new Client().get(this.source.baseUrl + url); "list": list,
var doc = new Document(res.body); "hasNextPage": false,
var jsonData = doc.selectFirst("#__NEXT_DATA__").text };
return JSON.parse(jsonData).props.pageProps }
}
async requestAPI(slug) { async getPopular(page) {
var api = `${this.source.apiUrl}/${slug}` return await this.formList('?sort={"rate": -1 }');
var response = await new Client().get(api); }
var body = JSON.parse(response.body);
return body;
}
async formList(slug) { async getLatestUpdates(page) {
var jsonData = await this.requestAPI(slug); var slug = '?sort={"postDate": -1 }';
var list = [];
if ("episodes" in jsonData) { var choice = this.getPreference("animeparadise_pref_latest_tab");
jsonData.episodes.forEach(item => { if (choice === "recent_ep") slug = "ep/recently-added";
list.push({
"name": item.origin.title, return await this.formList(slug);
"link": item.origin.link, }
"imageUrl": item.image async search(query, page, filters) {
}); var season = filters[0].values[filters[0].state].value;
}) var year = filters[1].values[filters[1].state].value;
} else {
jsonData.data.forEach(item => { var genre = "genre[]=";
list.push({ for (var filter of filters[2].state) {
"name": item.title, if (filter.state == true) genre += `${filter.value}&genre[]=`;
"link": item.link, }
"imageUrl": item.posterImage.original 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);
} }
break;
return { }
"list": list,
"hasNextPage": false
}
} }
return [...sortedStreams, ...copyStreams];
}
async getPopular(page) { // Extracts the streams url for different resolutions from a hls stream.
return await this.formList('?sort={"rate": -1 }') 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) { // For anime episode video list
var slug = '?sort={"postDate": -1 }'; 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"); var subtitles = [];
if (choice === "recent_ep") slug = 'ep/recently-added'; epData.subData.forEach((sub) => {
subtitles.push({
"label": sub.label,
"file": `${this.source.apiUrl}/stream/file/${sub.src}`,
});
});
return await this.formList(slug) streams[0].subtitles = subtitles;
}
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[]=" return streams;
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) { addCatogory(arr, typ) {
var link = this.source.baseUrl + `/anime/${url}` arr = arr.map((x) => ({ type_name: typ, name: x, value: x }));
var jsonData = await this.extractFromUrl(`/anime/${url}`) arr.unshift({
jsonData = jsonData.data type_name: typ,
var details = {} name: "All",
var chapters = [] value: "",
details.imageUrl = jsonData.posterImage.original });
details.description = jsonData.synopsys return arr;
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()
var pref = await this.getPreference("animeparadise_pref_video_resolution"); getFilterList() {
for (var stream of streams) { var seasons = ["Winter", "Spring", "Summer", "Fall"];
if (stream.quality.indexOf(pref) > -1) { const currentYear = new Date().getFullYear();
sortedStreams.push(stream); var years = Array.from({ length: currentYear - 1939 }, (_, i) =>
var index = copyStreams.indexOf(stream); (i + 1940).toString()
if (index > -1) { ).reverse();
copyStreams.splice(index, 1);
}
break;
}
}
return [...sortedStreams, ...copyStreams]
}
// Extracts the streams url for different resolutions from a hls stream. var genres = [
async extractStreams(url) { "Action",
const response = await new Client().get(url); "Adventure",
const body = response.body; "Comedy",
const lines = body.split('\n'); "Drama",
var streams = [{ "Ecchi",
url: url, "Fantasy",
originalUrl: url, "Horror",
quality: `Auto`, "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++) { return [
if (lines[i].startsWith('#EXT-X-STREAM-INF:')) { {
var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1]; type_name: "SelectFilter",
var m3u8Url = lines[i + 1].trim(); name: "Season",
m3u8Url = url.replace("master.m3u8", m3u8Url) 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({ getSourcePreferences() {
url: m3u8Url, return [
originalUrl: m3u8Url, {
quality: resolution key: "animeparadise_pref_latest_tab",
}); listPreference: {
} title: "Latest tab category",
} summary: "Anime list to be shown in latest tab",
return streams valueIndex: 0,
entries: ["Recently added anime", "Recently added episode"],
} entryValues: ["recent_ani", "recent_ep"],
// 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"]
}
}, },
] },
} {
key: "animeparadise_pref_video_resolution",
listPreference: {
title: "Preferred video resolution",
summary: "",
valueIndex: 0,
entries: ["Auto", "1080p", "720p", "360p"],
entryValues: ["auto", "1080", "720", "360"],
},
},
];
}
} }

View File

@@ -1,267 +1,300 @@
const mangayomiSources = [{ const mangayomiSources = [
{
"name": "AnimeZ", "name": "AnimeZ",
"lang": "en", "lang": "en",
"baseUrl": "https://animez.org", "baseUrl": "https://animez.org",
"apiUrl": "", "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", "typeSource": "multi",
"itemType": 1, "itemType": 1,
"version": "1.0.1", "version": "1.0.2",
"pkgPath": "anime/src/en/animez.js" "pkgPath": "anime/src/en/animez.js"
}]; }
];
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
constructor() { constructor() {
super(); super();
this.client = new Client(); this.client = new Client();
} }
getHeaders(url) { getHeaders(url) {
return { return {
"Referer": this.source.baseUrl, "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) { var list_chap = new Document(parsedBody.list_chap).select(
return new SharedPreferences().get(key); "li.wp-manga-chapter"
} );
async request(slug) { list_chap.forEach((chapter) => {
var url = this.source.baseUrl + slug var a = chapter.selectFirst("a");
var res = await this.client.get(url, this.getHeaders()); var title = a.text;
return new Document(res.body); var epLink = a.getHref;
} var scanlator = "Sub";
async page(slug) { if (title.indexOf("Dub") > 0) {
var body = await this.request(slug) title = title.replace("-Dub", "");
var list = [] scanlator = "Dub";
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; title = title.indexOf("Movie") > -1 ? title : `Episode ${title}`;
var epData = {
} name: title,
url: epLink,
async getPopular(page) { scanlator,
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,
}; };
} if (chapLen > 0) {
var pos = chapLen - 1;
// Sorts streams based on user preference. var lastEntry = chapters[pos];
sortStreams(streams) { if (lastEntry.name == epData.name) {
var sortedStreams = []; // if last entries name is same then append url and scanlator to last entry
chapters.pop(); // remove the last entry
var copyStreams = streams.slice() epData.url = `${epData.url}||${lastEntry.url}`;
var pref = this.getPreference("animez_pref_stream_audio"); epData.scanlator = `${lastEntry.scanlator}, ${epData.scanlator}`;
for (var stream of streams) { chapLen = pos; // since the last entry is removed the chapLen will decrease
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,
})
} }
chapters.push(epData);
return sortStreams(streams); chapLen++;
});
} }
getSourcePreferences() { return {
return [{ link,
key: 'animez_pref_popular_section', description,
listPreference: { chapters,
title: 'Preferred popular content', genre,
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"] // Sorts streams based on user preference.
} sortStreams(streams) {
}, { var sortedStreams = [];
key: 'animez_pref_latest_section',
listPreference: { var copyStreams = streams.slice();
title: 'Preferred latest content', var pref = this.getPreference("animez_pref_stream_audio");
summary: '', for (var stream of streams) {
valueIndex: 0, if (stream.quality.indexOf(pref) > -1) {
entries: ["Latest update", "Hot", "New releases", "Top all", "Top month", "Top week", "Top day", "Top follow", "Top comments", "Number of episodes"], sortedStreams.push(stream);
entryValues: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] var index = copyStreams.indexOf(stream);
} if (index > -1) {
}, { copyStreams.splice(index, 1);
key: 'animez_pref_stream_audio', }
listPreference: { break;
title: 'Preferred stream audio', }
summary: '',
valueIndex: 0,
entries: ["Sub","Dub"],
entryValues: ["Sub","Dub"],
}
},]
} }
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"],
},
},
];
}
} }

View File

@@ -6,7 +6,7 @@ const mangayomiSources = [{
"iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://gojo.wtf/", "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://gojo.wtf/",
"typeSource": "multi", "typeSource": "multi",
"itemType": 1, "itemType": 1,
"version": "0.0.5", "version": "0.0.6",
"pkgPath": "anime/src/en/gojo.js" "pkgPath": "anime/src/en/gojo.js"
}]; }];
@@ -103,6 +103,9 @@ class DefaultExtension extends MProvider {
async getDetail(url) { async getDetail(url) {
var linkSlug = `${this.source.baseUrl}/watch/`
if (url.includes(linkSlug)) url = url.replace(linkSlug, "");
var anilistId = url var anilistId = url
var res = await this.gojoAPI(`/info/${anilistId}`) var res = await this.gojoAPI(`/info/${anilistId}`)
if (res == null) { if (res == null) {
@@ -111,7 +114,7 @@ class DefaultExtension extends MProvider {
var name = this.getTitle(res.title) var name = this.getTitle(res.title)
var imageUrl = res.coverImage.large var imageUrl = res.coverImage.large
var description = res.description; var description = res.description;
var link = `${this.source.baseUrl}/watch/${anilistId}` var link = `${linkSlug}${anilistId}`
var genres = res.genres var genres = res.genres
var status = (() => { var status = (() => {
switch (res.status) { switch (res.status) {

View File

@@ -5,7 +5,7 @@ const mangayomiSources = [{
"apiUrl": "", "apiUrl": "",
"iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com", "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com",
"typeSource": "single", "typeSource": "single",
"version": "1.1.0", "version": "1.1.1",
"dateFormat": "", "dateFormat": "",
"dateFormatLocale": "", "dateFormatLocale": "",
"itemType": 1, "itemType": 1,
@@ -148,8 +148,11 @@ class DefaultExtension extends MProvider {
async getDetail(url) { 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 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 details = await this.requestApi(`/anime/${url}`);
var titles = details.title var titles = details.title
var name = titles.romaji var name = titles.romaji

View File

@@ -1,280 +1,306 @@
const mangayomiSources = [{ const mangayomiSources = [
{
"name": "Mangapill", "name": "Mangapill",
"lang": "en", "lang": "en",
"baseUrl": "https://mangapill.com", "baseUrl": "https://mangapill.com",
"apiUrl": "", "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", "typeSource": "single",
"isManga": true, "isManga": true,
"version": "1.0.2", "version": "1.0.3",
"dateFormat": "", "dateFormat": "",
"dateFormatLocale": "", "dateFormatLocale": "",
"pkgPath": "manga/src/en/mangapill.js" "pkgPath": "manga/src/en/mangapill.js"
}]; }
];
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
getHeaders(url) { getHeaders(url) {
return { return {
"Referer": this.source.baseUrl "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) { var chapters = [];
return { var chapList = doc.select("div.my-3.grid > a");
"publishing": 0, for (var chap of chapList) {
"finished": 1, var name = chap.text;
"on hiatus": 2, var url = chap.getHref;
"discontinued": 3, chapters.push({ name, url });
"not yet published": 4, }
}[status] ?? 5; 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) { return urls;
const preferences = new SharedPreferences(); }
return parseInt(preferences.get(key))
}
async getMangaList(slug) { getFilterList() {
var lang = await this.getPreference("pref_title_lang"); 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}` getSourcePreferences() {
var res = await new Client().get(url, this.getHeaders()); return [
var doc = new Document(res.body); {
var list = []; key: "pref_popular_content",
var mangaElements = doc.select("div.grid.gap-3.lg > div") listPreference: {
for (var manga of mangaElements) { title: "Preferred popular content",
var details = manga.selectFirst('div').select('a'); summary: "",
var detLen = details.length valueIndex: 0,
details = details[detLen - 1] entries: ["New Mangas", "Recent Chapters"],
entryValues: ["1", "2"],
var imageUrl = manga.selectFirst("img").getSrc; },
var link = details.getHref; },
var nameSection = details.select('div'); {
key: "pref_latest_content",
var name = (nameSection[1] && lang == 2) ? nameSection[1].text : nameSection[0].text listPreference: {
title: "Preferred latest content",
list.push({ name, imageUrl, link }); summary: "",
} valueIndex: 1,
var hasNextPage = false; entries: ["New Mangas", "Recent Chapters"],
if (slug.includes("search?q")) { entryValues: ["1", "2"],
hasNextPage = doc.selectFirst(".container.py-3 a.btn.btn-sm").className ? true : false },
} },
return { list, hasNextPage } {
} key: "pref_title_lang",
listPreference: {
async getNavPage(prefKey) { title: "Preferred title language",
var val = await this.getPreference(prefKey); summary: "",
var slug = '' valueIndex: 0,
switch (val) { entries: ["Romaji", "English"],
case 1: { entryValues: ["1", "2"],
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"]
}
}
];
}
} }

View File

@@ -1,340 +1,459 @@
const mangayomiSources = [{ const mangayomiSources = [
{
"name": "ReadComicOnline", "name": "ReadComicOnline",
"lang": "en", "lang": "en",
"baseUrl": "https://readcomiconline.li", "baseUrl": "https://readcomiconline.li",
"apiUrl": "", "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", "typeSource": "single",
"itemType": 0, "itemType": 0,
"version": "0.1.2", "version": "0.1.3",
"pkgPath": "manga/src/en/readcomiconline.js" "pkgPath": "manga/src/en/readcomiconline.js"
}]; }
];
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
constructor() { constructor() {
super(); super();
this.client = new Client(); this.client = new Client();
} }
getPreference(key) { getPreference(key) {
return new SharedPreferences().get(key); return new SharedPreferences().get(key);
} }
getHeaders() { getHeaders() {
return { 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", "User-Agent":
"Referer": this.source.baseUrl, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6832.64 Safari/537.36",
"Origin": this.source.baseUrl, "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 isFiltersAvailable = !filters || filters.length != 0;
var url = slug var genre = isFiltersAvailable ? getFilter(filters[0].state) : [];
var baseUrl = this.source.baseUrl var status = isFiltersAvailable
if (!slug.includes(baseUrl)) url = baseUrl + slug; ? filters[1].values[filters[1].state].value
var res = await this.client.get(url, this.getHeaders()); : "";
return new Document(res.body); 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 baseUrl = this.source.baseUrl;
var url = `${slug}page=${page}` if (url.includes(baseUrl)) url = url.replace(baseUrl, "");
var doc = await this.request(url);
var baseUrl = this.source.baseUrl
var list = []
var comicList = doc.select(".list-comic > .item") var doc = await this.request(url);
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 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 var description = pTag[pTag.length - 2].text;
if (pager.length > 0) hasNextPage = pager[pager.length - 1].text.includes("Last") ? true : false;
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) { const pageRegex = /pht\s*=\s*'([^']+?)';?/g;
return await this.getListPage("/ComicList/MostPopular?", page) 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() { return pages;
throw new Error("supportsLatest not implemented"); }
} getFilterList() {
async getLatestUpdates(page) { function formateState(type_name, items, values) {
return await this.getListPage("/ComicList/LatestUpdate?", page) var state = [];
} for (var i = 0; i < items.length; i++) {
async search(query, page, filters) { state.push({ type_name: type_name, name: items[i], value: values[i] });
function getFilter(state) { }
var rd = "" return state;
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)
} }
async getDetail(url) { var filters = [];
function statusCode(status) {
return {
"Ongoing": 0,
"Completed": 1,
}[status] ?? 5;
}
var link = this.source.baseUrl + url
var doc = await this.request(url)
var detailsSection = doc.selectFirst(".barContent") // Genre
var name = detailsSection.selectFirst("a").text var items = [
var imageSlug = doc.selectFirst(".rightBox").selectFirst("img").getSrc "Action",
var imageUrl = imageSlug.includes("http") ? imageSlug : `${this.source.baseUrl}${imageSlug}`; "Adventure",
var pTag = detailsSection.select("p") "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 // Status
var genre = [] items = ["Any", "Ongoing", "Completed"];
var author = "" values = ["", "Ongoing", "Completed"];
var artist = "" filters.push({
type_name: "SelectFilter",
name: "Status",
state: 0,
values: formateState("SelectOption", items, values),
});
pTag.forEach(p => { // Years
var itemText = p.text.trim() 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")) { return filters;
genre = itemText.replace("Genres:", "").trim().split(", ") }
} else if (itemText.includes("Status")) { getSourcePreferences() {
var sts = itemText.replace("Status: ", "").trim().split("\n")[0] return [
status = statusCode(sts) {
} else if (itemText.includes("Writer")) { key: "readcomiconline_page_quality",
author = itemText.replace("Writer: ", "") listPreference: {
} else if (itemText.includes("Artist")) { title: "Preferred image quality",
artist = itemText.replace("Artist: ", "") summary: "",
} valueIndex: 2,
entries: ["Low", "Medium", "High", "Highest"],
entryValues: ["l", "m", "h", "vh"],
},
},
];
}
}) // -------- ReadComicOnline Image Decoder --------
var chapters = [] // Source:- https://readcomiconline.li/Scripts/rguard.min.js
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} `, "") base64UrlDecode(input) {
chapTitle = chapTitle[0] == "_" ? chapTitle.substring(1,) : chapTitle let base64 = input.replace(/-/g, "+").replace(/_/g, "/");
var uploadDate = tds[1].text.trim() while (base64.length % 4 !== 0) {
var date = new Date(uploadDate); base64 += "=";
var dateUpload = date.getTime().toString();
chapters.push({ url: chapLink, name: chapTitle, dateUpload })
})
return { name, link, imageUrl, description, genre, status, author, artist, chapters }
} }
// For manga chapter pages const base64abc =
async getPageList(url) { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var pages = []; const outputBytes = [];
var hdr = this.getHeaders()
let match;
var imageQuality = this.getPreference("readcomiconline_page_quality");
var doc = await this.request(url) for (let i = 0; i < base64.length; i += 4) {
var html = doc.html 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 const triplet = (c1 << 18) | (c2 << 12) | ((c3 & 63) << 6) | (c4 & 63);
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 += "/"
}
outputBytes.push((triplet >> 16) & 0xff);
const pageRegex = /pht\s*=\s*'([^']+?)';?/g; if (base64[i + 2] !== "=") outputBytes.push((triplet >> 8) & 0xff);
while ((match = pageRegex.exec(html)) !== null) { if (base64[i + 3] !== "=") outputBytes.push(triplet & 0xff);
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"]
}
},
]
} }
// -------- ReadComicOnline Image Decoder -------- // Convert bytes to ISO-8859-1 string
// Source:- https://readcomiconline.li/Scripts/rguard.min.js return String.fromCharCode(...outputBytes);
}
base64UrlDecode(input) { extractBeforeDecode(url) {
let base64 = input return url.substring(15, 33) + url.substring(50);
.replace(/-/g, "+") }
.replace(/_/g, "/");
while (base64.length % 4 !== 0) { finalizeDecodedString(decoded) {
base64 += "="; return (
} decoded.substring(0, decoded.length - 11) +
decoded[decoded.length - 2] +
decoded[decoded.length - 1]
);
}
const base64abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; decoderFunction(encodedUrl) {
const outputBytes = []; 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) { decodeImageUrl(encodedImageUrl, imageQuality, baseUrlOverride) {
const c1 = base64abc.indexOf(base64[i]); // Default image qualities
const c2 = base64abc.indexOf(base64[i + 1]); var IMAGEQUALITY = [
const c3 = base64abc.indexOf(base64[i + 2]); { "l": "900", "m": "0", "h": "1600", "vh": "2041" },
const c4 = base64abc.indexOf(base64[i + 3]); { "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); var encodedUrl = encodedImageUrl.split("=s")[0];
if (base64[i + 2] !== "=") outputBytes.push((triplet >> 8) & 0xFF); var decodedUrl = this.decoderFunction(encodedUrl);
if (base64[i + 3] !== "=") outputBytes.push(triplet & 0xFF);
}
// Convert bytes to ISO-8859-1 string var queryParams = encodedImageUrl.substring(encodedImageUrl.indexOf("?"));
return String.fromCharCode(...outputBytes); finalUrl = baseUrlOverride + decodedUrl + queryParams;
} } else {
// If the url starts with https, then just override the base url
qType = 1;
extractBeforeDecode(url) { finalUrl = baseUrlOverride + encodedImageUrl.split(".com/")[1];
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]}`)
} }
return finalUrl.replace("s1600", `s${IMAGEQUALITY[qType][imageQuality]}`);
}
} }