mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
Merge pull request #299 from xMohnad/add-update-sources
Add update sources
This commit is contained in:
@@ -47,6 +47,7 @@ import 'src/es/gremorymangas/gremorymangas.dart';
|
|||||||
import 'src/es/ryujinmanga/ryujinmanga.dart';
|
import 'src/es/ryujinmanga/ryujinmanga.dart';
|
||||||
import 'src/es/senpaiediciones/senpaiediciones.dart';
|
import 'src/es/senpaiediciones/senpaiediciones.dart';
|
||||||
import 'src/es/skymangas/skymangas.dart';
|
import 'src/es/skymangas/skymangas.dart';
|
||||||
|
import 'src/es/erosscans/erosscans.dart';
|
||||||
import 'src/fr/flamescansfr/flamescansfr.dart';
|
import 'src/fr/flamescansfr/flamescansfr.dart';
|
||||||
import 'src/fr/mangasscans/mangasscans.dart';
|
import 'src/fr/mangasscans/mangasscans.dart';
|
||||||
import 'src/fr/rimuscans/rimuscans.dart';
|
import 'src/fr/rimuscans/rimuscans.dart';
|
||||||
@@ -177,6 +178,8 @@ List<Source> _mangareaderSourcesList =
|
|||||||
rizzcomicSource,
|
rizzcomicSource,
|
||||||
//Berserker Scan (ES)
|
//Berserker Scan (ES)
|
||||||
berserkerscanSource,
|
berserkerscanSource,
|
||||||
|
// Eros Scan (ES)
|
||||||
|
erosscansSource,
|
||||||
//Cartel de Manhwas (ES)
|
//Cartel de Manhwas (ES)
|
||||||
carteldemanhwasSource,
|
carteldemanhwasSource,
|
||||||
//De Todo Un Poco Scan (ES)
|
//De Todo Un Poco Scan (ES)
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import '../../../../../../../model/source.dart';
|
||||||
|
|
||||||
|
Source get erosscansSource => _erosscansSource;
|
||||||
|
|
||||||
|
Source _erosscansSource = Source(
|
||||||
|
name: "Eros Scans",
|
||||||
|
baseUrl: "https://eros-void.xyz",
|
||||||
|
lang: "en",
|
||||||
|
typeSource: "mangareader",
|
||||||
|
iconUrl:
|
||||||
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/multisrc/mangareader/src/en/erosscans/icon.png",
|
||||||
|
dateFormat: "MMMM dd, yyyy",
|
||||||
|
dateFormatLocale: "en_us",
|
||||||
|
);
|
||||||
BIN
dart/manga/multisrc/mangareader/src/en/erosscans/icon.png
Normal file
BIN
dart/manga/multisrc/mangareader/src/en/erosscans/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -1,9 +1,10 @@
|
|||||||
|
// prettier-ignore
|
||||||
const mangayomiSources = [{
|
const mangayomiSources = [{
|
||||||
"name": "ManhwaZ",
|
"name": "ManhwaZ",
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"baseUrl": "https://manhwaz.com",
|
"baseUrl": "https://manhwaz.com",
|
||||||
"apiUrl": "",
|
"apiUrl": "",
|
||||||
"iconUrl": "https://manhwaz.com/favicon.ico",
|
"iconUrl": "https://manhwaz.com/apple-touch-icon.png",
|
||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"itemType": 0,
|
"itemType": 0,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@@ -12,368 +13,408 @@ const mangayomiSources = [{
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
class DefaultExtension extends MProvider {
|
||||||
getHeaders(url) {
|
getHeaders(url) {
|
||||||
return {
|
return {
|
||||||
"Referer": this.source.baseUrl,
|
Referer: this.source.baseUrl,
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
"User-Agent":
|
||||||
};
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Helper method to parse manga list from page
|
// Helper method to parse manga list from page
|
||||||
mangaListFromPage(res, selector = ".page-item-detail") {
|
mangaListFromPage(res, selector = ".page-item-detail") {
|
||||||
const doc = new Document(res.body);
|
const doc = new Document(res.body);
|
||||||
const list = [];
|
const list = [];
|
||||||
|
|
||||||
// Look for manga items using the specified selector
|
// Look for manga items using the specified selector
|
||||||
const mangaElements = doc.select(selector);
|
const mangaElements = doc.select(selector);
|
||||||
|
|
||||||
for (const element of mangaElements) {
|
for (const element of mangaElements) {
|
||||||
let linkElement, titleElement, imageElement;
|
let linkElement, titleElement, imageElement;
|
||||||
let name = "";
|
let name = "";
|
||||||
let imageUrl = "";
|
let imageUrl = "";
|
||||||
let link = "";
|
let link = "";
|
||||||
|
|
||||||
if (selector === "#slide-top > .item") {
|
if (selector === "#slide-top > .item") {
|
||||||
// Popular manga from homepage
|
// Popular manga from homepage
|
||||||
linkElement = element.selectFirst(".info-item a");
|
linkElement = element.selectFirst(".info-item a");
|
||||||
if (linkElement) {
|
if (linkElement) {
|
||||||
name = linkElement.text;
|
name = linkElement.text;
|
||||||
link = linkElement.attr("href");
|
link = linkElement.attr("href");
|
||||||
}
|
|
||||||
imageElement = element.selectFirst(".img-item img");
|
|
||||||
} else {
|
|
||||||
// Latest updates and search results
|
|
||||||
linkElement = element.selectFirst(".item-summary a");
|
|
||||||
if (linkElement) {
|
|
||||||
name = linkElement.text;
|
|
||||||
link = linkElement.attr("href");
|
|
||||||
}
|
|
||||||
imageElement = element.selectFirst(".item-thumb img");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageElement) {
|
|
||||||
imageUrl = this.getImageUrl(imageElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name && link) {
|
|
||||||
list.push({ name, imageUrl, link });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
imageElement = element.selectFirst(".img-item img");
|
||||||
|
} else {
|
||||||
|
// Latest updates and search results
|
||||||
|
linkElement = element.selectFirst(".item-summary a");
|
||||||
|
if (linkElement) {
|
||||||
|
name = linkElement.text;
|
||||||
|
link = linkElement.attr("href");
|
||||||
|
}
|
||||||
|
imageElement = element.selectFirst(".item-thumb img");
|
||||||
|
}
|
||||||
|
|
||||||
// Check for next page
|
if (imageElement) {
|
||||||
const hasNextPage = doc.selectFirst("ul.pager a[rel=next]") !== null;
|
imageUrl = this.getImageUrl(imageElement);
|
||||||
|
}
|
||||||
|
|
||||||
return { "list": list, hasNextPage };
|
if (name && link) {
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to get image URL with fallbacks
|
// Check for next page
|
||||||
getImageUrl(imageElement) {
|
const hasNextPage = doc.selectFirst("ul.pager a[rel=next]") !== null;
|
||||||
if (imageElement.attr("data-src")) {
|
|
||||||
return imageElement.attr("data-src");
|
return { list: list, hasNextPage };
|
||||||
} else if (imageElement.attr("data-lazy-src")) {
|
}
|
||||||
return imageElement.attr("data-lazy-src");
|
|
||||||
} else if (imageElement.attr("srcset")) {
|
// Helper method to get image URL with fallbacks
|
||||||
return imageElement.attr("srcset").split(" ")[0];
|
getImageUrl(imageElement) {
|
||||||
} else if (imageElement.attr("data-cfsrc")) {
|
if (imageElement.attr("data-src")) {
|
||||||
return imageElement.attr("data-cfsrc");
|
return imageElement.attr("data-src");
|
||||||
|
} else if (imageElement.attr("data-lazy-src")) {
|
||||||
|
return imageElement.attr("data-lazy-src");
|
||||||
|
} else if (imageElement.attr("srcset")) {
|
||||||
|
return imageElement.attr("srcset").split(" ")[0];
|
||||||
|
} else if (imageElement.attr("data-cfsrc")) {
|
||||||
|
return imageElement.attr("data-cfsrc");
|
||||||
|
} else {
|
||||||
|
return imageElement.attr("src") || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert status text to status code
|
||||||
|
toStatus(status) {
|
||||||
|
const statusLower = status?.toLowerCase() || "";
|
||||||
|
if (statusLower.includes("ongoing") || statusLower.includes("publishing")) {
|
||||||
|
return 0;
|
||||||
|
} else if (
|
||||||
|
statusLower.includes("completed") ||
|
||||||
|
statusLower.includes("complete")
|
||||||
|
) {
|
||||||
|
return 1;
|
||||||
|
} else if (statusLower.includes("hiatus")) {
|
||||||
|
return 2;
|
||||||
|
} else if (
|
||||||
|
statusLower.includes("cancelled") ||
|
||||||
|
statusLower.includes("dropped")
|
||||||
|
) {
|
||||||
|
return 3;
|
||||||
|
} else {
|
||||||
|
return 5; // unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse relative date string to milliseconds
|
||||||
|
parseRelativeDate(dateStr) {
|
||||||
|
if (!dateStr) return String(new Date().valueOf());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lowerDateStr = dateStr.toLowerCase().trim();
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// Extract number and unit
|
||||||
|
const match = lowerDateStr.match(
|
||||||
|
/(\d+)\s*(second|minute|hour|day|week|month|year)s?\s*ago/,
|
||||||
|
);
|
||||||
|
if (!match) {
|
||||||
|
// Try to parse as regular date
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return String(date.valueOf());
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = parseInt(match[1]);
|
||||||
|
const unit = match[2];
|
||||||
|
|
||||||
|
const calendar = new Date(now);
|
||||||
|
|
||||||
|
switch (unit) {
|
||||||
|
case "second":
|
||||||
|
calendar.setSeconds(calendar.getSeconds() - value);
|
||||||
|
break;
|
||||||
|
case "minute":
|
||||||
|
calendar.setMinutes(calendar.getMinutes() - value);
|
||||||
|
break;
|
||||||
|
case "hour":
|
||||||
|
calendar.setHours(calendar.getHours() - value);
|
||||||
|
break;
|
||||||
|
case "day":
|
||||||
|
calendar.setDate(calendar.getDate() - value);
|
||||||
|
break;
|
||||||
|
case "week":
|
||||||
|
calendar.setDate(calendar.getDate() - value * 7);
|
||||||
|
break;
|
||||||
|
case "month":
|
||||||
|
calendar.setMonth(calendar.getMonth() - value);
|
||||||
|
break;
|
||||||
|
case "year":
|
||||||
|
calendar.setFullYear(calendar.getFullYear() - value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return String(now.valueOf());
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(calendar.valueOf());
|
||||||
|
} catch (e) {
|
||||||
|
return String(new Date().valueOf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPopular(page) {
|
||||||
|
const url = `${this.getBaseUrl()}/genre/manhwa?page=${page}&m_orderby=views`;
|
||||||
|
const res = await new Client().get(url, this.getHeaders());
|
||||||
|
return this.mangaListFromPage(res, ".page-item-detail");
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsLatest() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const url = `${this.getBaseUrl()}/?page=${page}`;
|
||||||
|
const res = await new Client().get(url, this.getHeaders());
|
||||||
|
return this.mangaListFromPage(res, ".page-item-detail");
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query, page, filters) {
|
||||||
|
if (query && query.trim()) {
|
||||||
|
// Search with query
|
||||||
|
const url = `${this.getBaseUrl()}/search?s=${encodeURIComponent(query)}&page=${page}`;
|
||||||
|
const res = await new Client().get(url, this.getHeaders());
|
||||||
|
return this.mangaListFromPage(res, ".page-item-detail");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter-based search
|
||||||
|
let url = this.getBaseUrl();
|
||||||
|
let hasGenreFilter = false;
|
||||||
|
|
||||||
|
// Process filters
|
||||||
|
if (filters && filters.length > 0) {
|
||||||
|
const genreFilter = filters.find(
|
||||||
|
(f) => f.type === "select" && f.name === "genre",
|
||||||
|
);
|
||||||
|
const orderByFilter = filters.find(
|
||||||
|
(f) => f.type === "select" && f.name === "orderby",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (genreFilter && genreFilter.state > 0) {
|
||||||
|
const selectedGenre = genreFilter.values[genreFilter.state];
|
||||||
|
if (selectedGenre && selectedGenre.value) {
|
||||||
|
url += "/" + selectedGenre.value;
|
||||||
|
hasGenreFilter = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add order by parameter for genre pages
|
||||||
|
if (hasGenreFilter && orderByFilter && orderByFilter.state > 0) {
|
||||||
|
const selectedOrder = orderByFilter.values[orderByFilter.state];
|
||||||
|
if (selectedOrder && selectedOrder.value) {
|
||||||
|
url += `?m_orderby=${selectedOrder.value}`;
|
||||||
|
url += `&page=${page}`;
|
||||||
} else {
|
} else {
|
||||||
return imageElement.attr("src") || "";
|
url += `?page=${page}`;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
url += `?page=${page}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url += `?page=${page}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert status text to status code
|
const res = await new Client().get(url, this.getHeaders());
|
||||||
toStatus(status) {
|
return this.mangaListFromPage(res, ".page-item-detail");
|
||||||
const statusLower = status?.toLowerCase() || "";
|
}
|
||||||
if (statusLower.includes("ongoing") || statusLower.includes("publishing")) {
|
|
||||||
return 0;
|
async getDetail(url) {
|
||||||
} else if (statusLower.includes("completed") || statusLower.includes("complete")) {
|
// Ensure we have the full URL
|
||||||
return 1;
|
const fullUrl = url.startsWith("http") ? url : `${this.getBaseUrl()}${url}`;
|
||||||
} else if (statusLower.includes("hiatus")) {
|
const res = await new Client().get(fullUrl, this.getHeaders());
|
||||||
return 2;
|
const doc = new Document(res.body);
|
||||||
} else if (statusLower.includes("cancelled") || statusLower.includes("dropped")) {
|
|
||||||
return 3;
|
// Extract manga details based on Kotlin implementation
|
||||||
} else {
|
const title = doc.selectFirst("div.post-title h1")?.text || "";
|
||||||
return 5; // unknown
|
|
||||||
}
|
const descElement = doc.selectFirst("div.summary__content");
|
||||||
|
const description = descElement?.text?.trim() || "";
|
||||||
|
|
||||||
|
const imageElement = doc.selectFirst("div.summary_image img");
|
||||||
|
const imageUrl = imageElement ? this.getImageUrl(imageElement) : "";
|
||||||
|
|
||||||
|
// Extract author
|
||||||
|
const authorElement = doc.selectFirst(
|
||||||
|
"div.post-content_item .summary-heading:contains(Author) + .summary-content",
|
||||||
|
);
|
||||||
|
const author = authorElement?.text?.trim() || "";
|
||||||
|
|
||||||
|
// Extract status
|
||||||
|
const statusElement = doc.selectFirst(
|
||||||
|
"div.summary-heading:contains(status) + div.summary-content",
|
||||||
|
);
|
||||||
|
const statusText = statusElement?.text?.toLowerCase() || "";
|
||||||
|
const status = this.toStatus(statusText);
|
||||||
|
|
||||||
|
// Extract genres
|
||||||
|
const genre = [];
|
||||||
|
const genreElements = doc.select("div.genres-content a[rel=tag]");
|
||||||
|
for (const genreEl of genreElements) {
|
||||||
|
const genreText = genreEl.text?.trim();
|
||||||
|
if (genreText) {
|
||||||
|
genre.push(genreText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse relative date string to milliseconds
|
// Extract chapters
|
||||||
parseRelativeDate(dateStr) {
|
const chapters = [];
|
||||||
if (!dateStr) return String(new Date().valueOf());
|
const chapterElements = doc.select("li.wp-manga-chapter");
|
||||||
|
|
||||||
try {
|
for (const chapterEl of chapterElements) {
|
||||||
const lowerDateStr = dateStr.toLowerCase().trim();
|
const chapterLink = chapterEl.selectFirst("a");
|
||||||
const now = new Date();
|
if (!chapterLink) continue;
|
||||||
|
|
||||||
// Extract number and unit
|
const chapterUrl = chapterLink.attr("href");
|
||||||
const match = lowerDateStr.match(/(\d+)\s*(second|minute|hour|day|week|month|year)s?\s*ago/);
|
const chapterName = chapterLink.text?.trim() || "";
|
||||||
if (!match) {
|
|
||||||
// Try to parse as regular date
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
return String(date.valueOf());
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = parseInt(match[1]);
|
// Try to get upload date
|
||||||
const unit = match[2];
|
const dateElement = chapterEl.selectFirst("span.chapter-release-date");
|
||||||
|
const dateUpload = this.parseRelativeDate(dateElement?.text);
|
||||||
|
|
||||||
const calendar = new Date(now);
|
if (chapterName && chapterUrl) {
|
||||||
|
chapters.push({
|
||||||
switch (unit) {
|
name: chapterName,
|
||||||
case "second":
|
url: chapterUrl,
|
||||||
calendar.setSeconds(calendar.getSeconds() - value);
|
dateUpload,
|
||||||
break;
|
});
|
||||||
case "minute":
|
}
|
||||||
calendar.setMinutes(calendar.getMinutes() - value);
|
|
||||||
break;
|
|
||||||
case "hour":
|
|
||||||
calendar.setHours(calendar.getHours() - value);
|
|
||||||
break;
|
|
||||||
case "day":
|
|
||||||
calendar.setDate(calendar.getDate() - value);
|
|
||||||
break;
|
|
||||||
case "week":
|
|
||||||
calendar.setDate(calendar.getDate() - (value * 7));
|
|
||||||
break;
|
|
||||||
case "month":
|
|
||||||
calendar.setMonth(calendar.getMonth() - value);
|
|
||||||
break;
|
|
||||||
case "year":
|
|
||||||
calendar.setFullYear(calendar.getFullYear() - value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return String(now.valueOf());
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(calendar.valueOf());
|
|
||||||
} catch (e) {
|
|
||||||
return String(new Date().valueOf());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPopular(page) {
|
return {
|
||||||
const url = `${this.source.baseUrl}/genre/manhwa?page=${page}&m_orderby=views`;
|
title,
|
||||||
const res = await new Client().get(url, this.getHeaders());
|
description,
|
||||||
return this.mangaListFromPage(res, ".page-item-detail");
|
imageUrl,
|
||||||
}
|
status,
|
||||||
|
author,
|
||||||
|
genre,
|
||||||
|
chapters,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
get supportsLatest() {
|
async getPageList(url) {
|
||||||
return true;
|
// Ensure we have the full URL
|
||||||
}
|
const fullUrl = url.startsWith("http") ? url : `${this.getBaseUrl()}${url}`;
|
||||||
|
const res = await new Client().get(fullUrl, this.getHeaders());
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
|
||||||
async getLatestUpdates(page) {
|
const pages = [];
|
||||||
const url = `${this.source.baseUrl}/?page=${page}`;
|
|
||||||
const res = await new Client().get(url, this.getHeaders());
|
|
||||||
return this.mangaListFromPage(res, ".page-item-detail");
|
|
||||||
}
|
|
||||||
|
|
||||||
async search(query, page, filters) {
|
// Look for images in page break containers as per Kotlin implementation
|
||||||
if (query && query.trim()) {
|
const imageElements = doc.select("div.page-break img");
|
||||||
// Search with query
|
|
||||||
const url = `${this.source.baseUrl}/search?s=${encodeURIComponent(query)}&page=${page}`;
|
for (const img of imageElements) {
|
||||||
const res = await new Client().get(url, this.getHeaders());
|
const imageUrl = this.getImageUrl(img);
|
||||||
return this.mangaListFromPage(res, ".page-item-detail");
|
|
||||||
|
if (imageUrl) {
|
||||||
|
// Handle relative URLs
|
||||||
|
let finalUrl = imageUrl;
|
||||||
|
if (imageUrl.startsWith("//")) {
|
||||||
|
finalUrl = "https:" + imageUrl;
|
||||||
|
} else if (imageUrl.startsWith("/")) {
|
||||||
|
finalUrl = this.getBaseUrl() + imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter-based search
|
pages.push(finalUrl);
|
||||||
let url = this.source.baseUrl;
|
}
|
||||||
let hasGenreFilter = false;
|
|
||||||
|
|
||||||
// Process filters
|
|
||||||
if (filters && filters.length > 0) {
|
|
||||||
const genreFilter = filters.find(f => f.type === "select" && f.name === "genre");
|
|
||||||
const orderByFilter = filters.find(f => f.type === "select" && f.name === "orderby");
|
|
||||||
|
|
||||||
if (genreFilter && genreFilter.state > 0) {
|
|
||||||
const selectedGenre = genreFilter.values[genreFilter.state];
|
|
||||||
if (selectedGenre && selectedGenre.value) {
|
|
||||||
url += "/" + selectedGenre.value;
|
|
||||||
hasGenreFilter = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add order by parameter for genre pages
|
|
||||||
if (hasGenreFilter && orderByFilter && orderByFilter.state > 0) {
|
|
||||||
const selectedOrder = orderByFilter.values[orderByFilter.state];
|
|
||||||
if (selectedOrder && selectedOrder.value) {
|
|
||||||
url += `?m_orderby=${selectedOrder.value}`;
|
|
||||||
url += `&page=${page}`;
|
|
||||||
} else {
|
|
||||||
url += `?page=${page}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += `?page=${page}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += `?page=${page}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await new Client().get(url, this.getHeaders());
|
|
||||||
return this.mangaListFromPage(res, ".page-item-detail");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDetail(url) {
|
return pages;
|
||||||
// Ensure we have the full URL
|
}
|
||||||
const fullUrl = url.startsWith("http") ? url : `${this.source.baseUrl}${url}`;
|
|
||||||
const res = await new Client().get(fullUrl, this.getHeaders());
|
|
||||||
const doc = new Document(res.body);
|
|
||||||
|
|
||||||
// Extract manga details based on Kotlin implementation
|
getFilterList() {
|
||||||
const title = doc.selectFirst("div.post-title h1")?.text || "";
|
return [
|
||||||
|
{
|
||||||
|
type: "header",
|
||||||
|
name: "Note: Filters only work when search query is empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
name: "genre",
|
||||||
|
label: "Genre",
|
||||||
|
values: [
|
||||||
|
{ name: "All", value: "" },
|
||||||
|
{ name: "Completed", value: "completed" },
|
||||||
|
{ name: "Action", value: "genre/action" },
|
||||||
|
{ name: "Adult", value: "genre/adult" },
|
||||||
|
{ name: "Adventure", value: "genre/adventure" },
|
||||||
|
{ name: "Comedy", value: "genre/comedy" },
|
||||||
|
{ name: "Drama", value: "genre/drama" },
|
||||||
|
{ name: "Ecchi", value: "genre/ecchi" },
|
||||||
|
{ name: "Fantasy", value: "genre/fantasy" },
|
||||||
|
{ name: "Harem", value: "genre/harem" },
|
||||||
|
{ name: "Historical", value: "genre/historical" },
|
||||||
|
{ name: "Horror", value: "genre/horror" },
|
||||||
|
{ name: "Isekai", value: "genre/isekai" },
|
||||||
|
{ name: "Josei", value: "genre/josei" },
|
||||||
|
{ name: "Manhwa", value: "genre/manhwa" },
|
||||||
|
{ name: "Martial Arts", value: "genre/martial-arts" },
|
||||||
|
{ name: "Mature", value: "genre/mature" },
|
||||||
|
{ name: "Mecha", value: "genre/mecha" },
|
||||||
|
{ name: "Mystery", value: "genre/mystery" },
|
||||||
|
{ name: "Psychological", value: "genre/psychological" },
|
||||||
|
{ name: "Romance", value: "genre/romance" },
|
||||||
|
{ name: "School Life", value: "genre/school-life" },
|
||||||
|
{ name: "Sci-fi", value: "genre/sci-fi" },
|
||||||
|
{ name: "Seinen", value: "genre/seinen" },
|
||||||
|
{ name: "Shoujo", value: "genre/shoujo" },
|
||||||
|
{ name: "Shounen", value: "genre/shounen" },
|
||||||
|
{ name: "Slice of Life", value: "genre/slice-of-life" },
|
||||||
|
{ name: "Sports", value: "genre/sports" },
|
||||||
|
{ name: "Supernatural", value: "genre/supernatural" },
|
||||||
|
{ name: "Tragedy", value: "genre/tragedy" },
|
||||||
|
{ name: "Webtoons", value: "genre/webtoons" },
|
||||||
|
],
|
||||||
|
state: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
name: "orderby",
|
||||||
|
label: "Order By",
|
||||||
|
values: [
|
||||||
|
{ name: "Latest", value: "latest" },
|
||||||
|
{ name: "Rating", value: "rating" },
|
||||||
|
{ name: "Most Views", value: "views" },
|
||||||
|
{ name: "New", value: "new" },
|
||||||
|
],
|
||||||
|
state: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const descElement = doc.selectFirst("div.summary__content");
|
getBaseUrl() {
|
||||||
const description = descElement?.text?.trim() || "";
|
const preference = new SharedPreferences();
|
||||||
|
var base_url = preference.get("domain_url");
|
||||||
const imageElement = doc.selectFirst("div.summary_image img");
|
if (base_url.length == 0) {
|
||||||
const imageUrl = imageElement ? this.getImageUrl(imageElement) : "";
|
return this.source.baseUrl;
|
||||||
|
|
||||||
// Extract author
|
|
||||||
const authorElement = doc.selectFirst("div.post-content_item .summary-heading:contains(Author) + .summary-content");
|
|
||||||
const author = authorElement?.text?.trim() || "";
|
|
||||||
|
|
||||||
// Extract status
|
|
||||||
const statusElement = doc.selectFirst("div.summary-heading:contains(status) + div.summary-content");
|
|
||||||
const statusText = statusElement?.text?.toLowerCase() || "";
|
|
||||||
const status = this.toStatus(statusText);
|
|
||||||
|
|
||||||
// Extract genres
|
|
||||||
const genre = [];
|
|
||||||
const genreElements = doc.select("div.genres-content a[rel=tag]");
|
|
||||||
for (const genreEl of genreElements) {
|
|
||||||
const genreText = genreEl.text?.trim();
|
|
||||||
if (genreText) {
|
|
||||||
genre.push(genreText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract chapters
|
|
||||||
const chapters = [];
|
|
||||||
const chapterElements = doc.select("li.wp-manga-chapter");
|
|
||||||
|
|
||||||
for (const chapterEl of chapterElements) {
|
|
||||||
const chapterLink = chapterEl.selectFirst("a");
|
|
||||||
if (!chapterLink) continue;
|
|
||||||
|
|
||||||
const chapterUrl = chapterLink.attr("href");
|
|
||||||
const chapterName = chapterLink.text?.trim() || "";
|
|
||||||
|
|
||||||
// Try to get upload date
|
|
||||||
const dateElement = chapterEl.selectFirst("span.chapter-release-date");
|
|
||||||
const dateUpload = this.parseRelativeDate(dateElement?.text);
|
|
||||||
|
|
||||||
if (chapterName && chapterUrl) {
|
|
||||||
chapters.push({
|
|
||||||
name: chapterName,
|
|
||||||
url: chapterUrl,
|
|
||||||
dateUpload
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
imageUrl,
|
|
||||||
status,
|
|
||||||
author,
|
|
||||||
genre,
|
|
||||||
chapters
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
if (base_url.endsWith("/")) {
|
||||||
async getPageList(url) {
|
return base_url.slice(0, -1);
|
||||||
// Ensure we have the full URL
|
|
||||||
const fullUrl = url.startsWith("http") ? url : `${this.source.baseUrl}${url}`;
|
|
||||||
const res = await new Client().get(fullUrl, this.getHeaders());
|
|
||||||
const doc = new Document(res.body);
|
|
||||||
|
|
||||||
const pages = [];
|
|
||||||
|
|
||||||
// Look for images in page break containers as per Kotlin implementation
|
|
||||||
const imageElements = doc.select("div.page-break img");
|
|
||||||
|
|
||||||
for (const img of imageElements) {
|
|
||||||
const imageUrl = this.getImageUrl(img);
|
|
||||||
|
|
||||||
if (imageUrl) {
|
|
||||||
// Handle relative URLs
|
|
||||||
let finalUrl = imageUrl;
|
|
||||||
if (imageUrl.startsWith("//")) {
|
|
||||||
finalUrl = "https:" + imageUrl;
|
|
||||||
} else if (imageUrl.startsWith("/")) {
|
|
||||||
finalUrl = this.source.baseUrl + imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
pages.push(finalUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
}
|
}
|
||||||
|
return base_url;
|
||||||
|
}
|
||||||
|
|
||||||
getFilterList() {
|
getSourcePreferences() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: "header",
|
key: "domain_url",
|
||||||
name: "Note: Filters only work when search query is empty"
|
editTextPreference: {
|
||||||
},
|
title: "Edit URL",
|
||||||
{
|
summary: "",
|
||||||
type: "separator"
|
value: this.source.baseUrl,
|
||||||
},
|
dialogTitle: "URL",
|
||||||
{
|
dialogMessage: "",
|
||||||
type: "select",
|
},
|
||||||
name: "genre",
|
},
|
||||||
label: "Genre",
|
];
|
||||||
values: [
|
}
|
||||||
{ name: "All", value: "" },
|
|
||||||
{ name: "Completed", value: "completed" },
|
|
||||||
{ name: "Action", value: "genre/action" },
|
|
||||||
{ name: "Adult", value: "genre/adult" },
|
|
||||||
{ name: "Adventure", value: "genre/adventure" },
|
|
||||||
{ name: "Comedy", value: "genre/comedy" },
|
|
||||||
{ name: "Drama", value: "genre/drama" },
|
|
||||||
{ name: "Ecchi", value: "genre/ecchi" },
|
|
||||||
{ name: "Fantasy", value: "genre/fantasy" },
|
|
||||||
{ name: "Harem", value: "genre/harem" },
|
|
||||||
{ name: "Historical", value: "genre/historical" },
|
|
||||||
{ name: "Horror", value: "genre/horror" },
|
|
||||||
{ name: "Isekai", value: "genre/isekai" },
|
|
||||||
{ name: "Josei", value: "genre/josei" },
|
|
||||||
{ name: "Manhwa", value: "genre/manhwa" },
|
|
||||||
{ name: "Martial Arts", value: "genre/martial-arts" },
|
|
||||||
{ name: "Mature", value: "genre/mature" },
|
|
||||||
{ name: "Mecha", value: "genre/mecha" },
|
|
||||||
{ name: "Mystery", value: "genre/mystery" },
|
|
||||||
{ name: "Psychological", value: "genre/psychological" },
|
|
||||||
{ name: "Romance", value: "genre/romance" },
|
|
||||||
{ name: "School Life", value: "genre/school-life" },
|
|
||||||
{ name: "Sci-fi", value: "genre/sci-fi" },
|
|
||||||
{ name: "Seinen", value: "genre/seinen" },
|
|
||||||
{ name: "Shoujo", value: "genre/shoujo" },
|
|
||||||
{ name: "Shounen", value: "genre/shounen" },
|
|
||||||
{ name: "Slice of Life", value: "genre/slice-of-life" },
|
|
||||||
{ name: "Sports", value: "genre/sports" },
|
|
||||||
{ name: "Supernatural", value: "genre/supernatural" },
|
|
||||||
{ name: "Tragedy", value: "genre/tragedy" },
|
|
||||||
{ name: "Webtoons", value: "genre/webtoons" }
|
|
||||||
],
|
|
||||||
state: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "select",
|
|
||||||
name: "orderby",
|
|
||||||
label: "Order By",
|
|
||||||
values: [
|
|
||||||
{ name: "Latest", value: "latest" },
|
|
||||||
{ name: "Rating", value: "rating" },
|
|
||||||
{ name: "Most Views", value: "views" },
|
|
||||||
{ name: "New", value: "new" }
|
|
||||||
],
|
|
||||||
state: 0
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourcePreferences() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user