refactor: change ManhwaZ icon path

This commit is contained in:
xMohnad
2025-06-26 08:37:50 +00:00
parent a3b7a8f2ef
commit 227de96db1

View File

@@ -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,389 @@ 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.source.baseUrl}/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.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) {
if (query && query.trim()) {
// Search with query
const url = `${this.source.baseUrl}/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.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 { } 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")
} else if (statusLower.includes("hiatus")) { ? url
return 2; : `${this.source.baseUrl}${url}`;
} else if (statusLower.includes("cancelled") || statusLower.includes("dropped")) { const res = await new Client().get(fullUrl, this.getHeaders());
return 3; const doc = new Document(res.body);
} else {
return 5; // unknown // Extract manga details based on Kotlin implementation
} const title = doc.selectFirst("div.post-title h1")?.text || "";
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.source.baseUrl}${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.source.baseUrl + 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"); getSourcePreferences() {
const description = descElement?.text?.trim() || ""; return [];
}
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);
}
}
// 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
};
}
async getPageList(url) {
// 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;
}
getFilterList() {
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
}
];
}
getSourcePreferences() {
return [];
}
} }