diff --git a/src/ar/arnovel/build.gradle b/src/ar/arnovel/build.gradle index f291735c8..f55e6d95b 100644 --- a/src/ar/arnovel/build.gradle +++ b/src/ar/arnovel/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.ArNovel' themePkg = 'madaranovel' baseUrl = 'https://ar-no.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/arnovel/res/mipmap-hdpi/ic_launcher.png b/src/ar/arnovel/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..70d9094da Binary files /dev/null and b/src/ar/arnovel/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/arnovel/res/mipmap-mdpi/ic_launcher.png b/src/ar/arnovel/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..377633497 Binary files /dev/null and b/src/ar/arnovel/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/arnovel/res/mipmap-xhdpi/ic_launcher.png b/src/ar/arnovel/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..017f83cfe Binary files /dev/null and b/src/ar/arnovel/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/arnovel/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/arnovel/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..6ad21e411 Binary files /dev/null and b/src/ar/arnovel/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/arnovel/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/arnovel/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..84501bf26 Binary files /dev/null and b/src/ar/arnovel/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/azora/build.gradle b/src/ar/azora/build.gradle index 283032579..545878e13 100644 --- a/src/ar/azora/build.gradle +++ b/src/ar/azora/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Azora' themePkg = 'madaranovel' baseUrl = 'https://azoramoon.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/azora/res/mipmap-hdpi/ic_launcher.png b/src/ar/azora/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..70b23f60d Binary files /dev/null and b/src/ar/azora/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/azora/res/mipmap-mdpi/ic_launcher.png b/src/ar/azora/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..79e0bb030 Binary files /dev/null and b/src/ar/azora/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/azora/res/mipmap-xhdpi/ic_launcher.png b/src/ar/azora/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..545740837 Binary files /dev/null and b/src/ar/azora/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/azora/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/azora/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..a9759d64f Binary files /dev/null and b/src/ar/azora/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/azora/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/azora/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..a856907cd Binary files /dev/null and b/src/ar/azora/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/freekolnovel/build.gradle b/src/ar/freekolnovel/build.gradle index 82013c04f..c5142ddc3 100644 --- a/src/ar/freekolnovel/build.gradle +++ b/src/ar/freekolnovel/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.FreeKolNovel' themePkg = 'lightnovelwpnovel' baseUrl = 'https://free.kolnovel.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/freekolnovel/res/mipmap-hdpi/ic_launcher.png b/src/ar/freekolnovel/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..b2023e209 Binary files /dev/null and b/src/ar/freekolnovel/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/freekolnovel/res/mipmap-mdpi/ic_launcher.png b/src/ar/freekolnovel/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5bd788ae2 Binary files /dev/null and b/src/ar/freekolnovel/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/freekolnovel/res/mipmap-xhdpi/ic_launcher.png b/src/ar/freekolnovel/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..f2a3f1daa Binary files /dev/null and b/src/ar/freekolnovel/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/freekolnovel/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/freekolnovel/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..35e9a10e0 Binary files /dev/null and b/src/ar/freekolnovel/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/freekolnovel/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/freekolnovel/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..e61da2b16 Binary files /dev/null and b/src/ar/freekolnovel/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/galaxynovels/build.gradle b/src/ar/galaxynovels/build.gradle index 54b92f409..7e1a4d0ed 100644 --- a/src/ar/galaxynovels/build.gradle +++ b/src/ar/galaxynovels/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Galaxy Novels' extClass = '.GalaxyNovels' - extVersionCode = 2 + extVersionCode = 3 isNsfw = false isNovel = true } diff --git a/src/ar/galaxynovels/res/mipmap-hdpi/ic_launcher.png b/src/ar/galaxynovels/res/mipmap-hdpi/ic_launcher.png index b40ecd43e..833e57595 100644 Binary files a/src/ar/galaxynovels/res/mipmap-hdpi/ic_launcher.png and b/src/ar/galaxynovels/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/galaxynovels/res/mipmap-mdpi/ic_launcher.png b/src/ar/galaxynovels/res/mipmap-mdpi/ic_launcher.png index d9396ad80..6408db135 100644 Binary files a/src/ar/galaxynovels/res/mipmap-mdpi/ic_launcher.png and b/src/ar/galaxynovels/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/galaxynovels/res/mipmap-xhdpi/ic_launcher.png b/src/ar/galaxynovels/res/mipmap-xhdpi/ic_launcher.png index 255dd98af..c6009b3de 100644 Binary files a/src/ar/galaxynovels/res/mipmap-xhdpi/ic_launcher.png and b/src/ar/galaxynovels/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/galaxynovels/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/galaxynovels/res/mipmap-xxhdpi/ic_launcher.png index ad6c6bac4..fb6dfe0de 100644 Binary files a/src/ar/galaxynovels/res/mipmap-xxhdpi/ic_launcher.png and b/src/ar/galaxynovels/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/galaxynovels/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/galaxynovels/res/mipmap-xxxhdpi/ic_launcher.png index 08c88deaa..a5f413d17 100644 Binary files a/src/ar/galaxynovels/res/mipmap-xxxhdpi/ic_launcher.png and b/src/ar/galaxynovels/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/galaxynovels/src/eu/kanade/tachiyomi/extension/ar/galaxynovels/GalaxyNovels.kt b/src/ar/galaxynovels/src/eu/kanade/tachiyomi/extension/ar/galaxynovels/GalaxyNovels.kt index a8769bd51..c3c141b78 100644 --- a/src/ar/galaxynovels/src/eu/kanade/tachiyomi/extension/ar/galaxynovels/GalaxyNovels.kt +++ b/src/ar/galaxynovels/src/eu/kanade/tachiyomi/extension/ar/galaxynovels/GalaxyNovels.kt @@ -101,7 +101,9 @@ class GalaxyNovels : SManga.create().apply { this.title = title url = link.attr("href").removePrefix(baseUrl) - thumbnail_url = card.selectFirst("img.wor-cover-img")?.attr("src").toAbsoluteUrl() + val img = card.selectFirst("img.wor-cover-img") + thumbnail_url = img?.attr("data-src")?.toAbsoluteUrl() + ?: img?.attr("src")?.toAbsoluteUrl() } } @@ -165,7 +167,8 @@ class GalaxyNovels : SManga.create().apply { this.title = title url = relativeUrl - thumbnail_url = imgElement?.attr("src").toAbsoluteUrl() + thumbnail_url = imgElement?.attr("data-src")?.toAbsoluteUrl() + ?: imgElement?.attr("src")?.toAbsoluteUrl() } } else { null @@ -185,7 +188,9 @@ class GalaxyNovels : return SManga.create().apply { title = doc.selectFirst("h1")?.text()?.trim() ?: "No Title" - thumbnail_url = doc.selectFirst("img.wor-cover-img")?.attr("src").toAbsoluteUrl() + val img = doc.selectFirst("img.wor-cover-img") + thumbnail_url = img?.attr("data-src")?.toAbsoluteUrl() + ?: img?.attr("src")?.toAbsoluteUrl() author = doc.selectFirst(".wor-single-hero__meta-text span")?.text()?.trim() diff --git a/src/ar/goldenrest/build.gradle b/src/ar/goldenrest/build.gradle index 09901d299..11d60a10a 100644 --- a/src/ar/goldenrest/build.gradle +++ b/src/ar/goldenrest/build.gradle @@ -2,7 +2,7 @@ ext { extName = 'Golden Rest' extClass = '.GoldenRest' baseUrl = 'https://golden.rest' - extVersionCode = 2 + extVersionCode = 3 isNsfw = false } diff --git a/src/ar/goldenrest/res/mipmap-hdpi/ic_launcher.png b/src/ar/goldenrest/res/mipmap-hdpi/ic_launcher.png index b40ecd43e..2908b7aa1 100644 Binary files a/src/ar/goldenrest/res/mipmap-hdpi/ic_launcher.png and b/src/ar/goldenrest/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/goldenrest/res/mipmap-mdpi/ic_launcher.png b/src/ar/goldenrest/res/mipmap-mdpi/ic_launcher.png index d9396ad80..fd633f1b1 100644 Binary files a/src/ar/goldenrest/res/mipmap-mdpi/ic_launcher.png and b/src/ar/goldenrest/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/goldenrest/res/mipmap-xhdpi/ic_launcher.png b/src/ar/goldenrest/res/mipmap-xhdpi/ic_launcher.png index 255dd98af..d20223b8b 100644 Binary files a/src/ar/goldenrest/res/mipmap-xhdpi/ic_launcher.png and b/src/ar/goldenrest/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/goldenrest/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/goldenrest/res/mipmap-xxhdpi/ic_launcher.png index ad6c6bac4..2e1ddbb03 100644 Binary files a/src/ar/goldenrest/res/mipmap-xxhdpi/ic_launcher.png and b/src/ar/goldenrest/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/goldenrest/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/goldenrest/res/mipmap-xxxhdpi/ic_launcher.png index 08c88deaa..19b08572a 100644 Binary files a/src/ar/goldenrest/res/mipmap-xxxhdpi/ic_launcher.png and b/src/ar/goldenrest/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/hizomanga/build.gradle b/src/ar/hizomanga/build.gradle index 91d4d0f90..f57b1ee4d 100644 --- a/src/ar/hizomanga/build.gradle +++ b/src/ar/hizomanga/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.HizoManga' themePkg = 'madaranovel' baseUrl = 'https://hizomanga.net' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/hizomanga/res/mipmap-hdpi/ic_launcher.png b/src/ar/hizomanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..62e1a7a3e Binary files /dev/null and b/src/ar/hizomanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/hizomanga/res/mipmap-mdpi/ic_launcher.png b/src/ar/hizomanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..78f821863 Binary files /dev/null and b/src/ar/hizomanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/hizomanga/res/mipmap-xhdpi/ic_launcher.png b/src/ar/hizomanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..b982b86a5 Binary files /dev/null and b/src/ar/hizomanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/hizomanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/hizomanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..fb00fc0c8 Binary files /dev/null and b/src/ar/hizomanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/hizomanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/hizomanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b936ddb49 Binary files /dev/null and b/src/ar/hizomanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/kolnovel/build.gradle b/src/ar/kolnovel/build.gradle index 6c4c9f996..96ab7d830 100644 --- a/src/ar/kolnovel/build.gradle +++ b/src/ar/kolnovel/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.KolNovel' themePkg = 'lightnovelwpnovel' baseUrl = 'https://kolnovel.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/kolnovel/res/mipmap-hdpi/ic_launcher.png b/src/ar/kolnovel/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..b2023e209 Binary files /dev/null and b/src/ar/kolnovel/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/kolnovel/res/mipmap-mdpi/ic_launcher.png b/src/ar/kolnovel/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5bd788ae2 Binary files /dev/null and b/src/ar/kolnovel/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/kolnovel/res/mipmap-xhdpi/ic_launcher.png b/src/ar/kolnovel/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..f2a3f1daa Binary files /dev/null and b/src/ar/kolnovel/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/kolnovel/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/kolnovel/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..35e9a10e0 Binary files /dev/null and b/src/ar/kolnovel/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/kolnovel/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/kolnovel/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..e61da2b16 Binary files /dev/null and b/src/ar/kolnovel/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/kolnovel/src/eu/kanade/tachiyomi/extension/ar/kolnovel/KolNovel.kt b/src/ar/kolnovel/src/eu/kanade/tachiyomi/extension/ar/kolnovel/KolNovel.kt index 7c7991c29..dd61d29de 100644 --- a/src/ar/kolnovel/src/eu/kanade/tachiyomi/extension/ar/kolnovel/KolNovel.kt +++ b/src/ar/kolnovel/src/eu/kanade/tachiyomi/extension/ar/kolnovel/KolNovel.kt @@ -31,15 +31,84 @@ class KolNovel : doc.select(selector).remove() } + // Remove navigation, ads, and non-content elements doc.select( ".unlock-buttons, .ads, script, style, .sharedaddy, .su-spoiler-title, " + - "noscript, ins, .adsbygoogle, iframe, [id*=google], [class*=google]", + "noscript, ins, .adsbygoogle, iframe, [id*=google], [class*=google], " + + ".chapter-navigation, .prev-next, .navigation, .post-nav, " + + ".related-novels, .recommendations, .sidebar, .widget, " + + ".author-info, .author-box, .post-author, " + + ".comments, .comment-section, #comments, " + + ".footer, .post-footer, .entry-footer, " + + ".breadcrumb, .breadcrumbs, .post-categories, .post-tags, " + + ".share-buttons, .social-share, .sharethis, " + + ".rating, .post-rating, .star-rating, " + + ".chapter-actions, .download-chapter", ).remove() val content = doc.select(".epcontent.entry-content").maxByOrNull { it.select("p").sumOf { p -> p.text().length } } ?: return "" + // Remove paragraphs that are too short (likely spam/ads) + content.select("p").forEach { p -> + val text = p.text().trim() + + // Skip empty paragraphs + if (text.isEmpty()) { + p.remove() + return@forEach + } + + // Remove very short paragraphs (likely spam labels) + if (text.length < 10) { + p.remove() + return@forEach + } + + // Remove paragraphs that are mostly English (spam) + val arabicCount = text.count { it in '\u0600'..'\u06FF' || it in '\u0750'..'\u077F' || it in '\uFB50'..'\uFDFF' || it in '\uFE70'..'\uFEFF' } + val totalCount = text.replace("\\s".toRegex(), "").length + if (totalCount > 0 && arabicCount.toFloat() / totalCount < 0.2f) { + p.remove() + return@forEach + } + + // Remove common spam patterns + val spamPatterns = listOf( + "subscribe", "follow us", "discord", "patreon", + "donate", "support us", "bookmark", "rate this", + "chapter list", "table of contents", "next chapter", + "previous chapter", "read more", "click here", + "register", "sign up", "log in", "login", + "join us", "telegram", "facebook", "twitter", + "instagram", "youtube", "tiktok", + ) + val lowerText = text.lowercase() + if (spamPatterns.any { pattern -> lowerText.contains(pattern) }) { + p.remove() + return@forEach + } + + // Remove paragraphs that are just numbers or chapter references + if (text.matches(Regex("^\\d+$")) || text.matches(Regex("^(chapter|فصل)\\s*\\d+$", RegexOption.IGNORE_CASE))) { + p.remove() + return@forEach + } + } + + // Remove any remaining non-content divs + content.select("div").forEach { div -> + val text = div.text().trim() + if (text.isEmpty() || text.length < 20) { + // Check if this div has no meaningful content + val hasImages = div.select("img").isNotEmpty() + if (!hasImages) { + div.remove() + } + } + } + return content.html() } } diff --git a/src/ar/markazriwayat/build.gradle b/src/ar/markazriwayat/build.gradle index bd4bcad95..14ffff87d 100644 --- a/src/ar/markazriwayat/build.gradle +++ b/src/ar/markazriwayat/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Markazriwayat' extClass = '.Markazriwayat' - extVersionCode = 5 + extVersionCode = 6 isNsfw = false isNovel = true } diff --git a/src/ar/markazriwayat/res/mipmap-hdpi/ic_launcher.png b/src/ar/markazriwayat/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..a3d13aaab Binary files /dev/null and b/src/ar/markazriwayat/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/markazriwayat/res/mipmap-mdpi/ic_launcher.png b/src/ar/markazriwayat/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..8ebcad42d Binary files /dev/null and b/src/ar/markazriwayat/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/markazriwayat/res/mipmap-xhdpi/ic_launcher.png b/src/ar/markazriwayat/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..f0dc6ea20 Binary files /dev/null and b/src/ar/markazriwayat/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/markazriwayat/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/markazriwayat/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..9ea1d9876 Binary files /dev/null and b/src/ar/markazriwayat/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/markazriwayat/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/markazriwayat/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..10526fd02 Binary files /dev/null and b/src/ar/markazriwayat/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/novelarab/build.gradle b/src/ar/novelarab/build.gradle index 8107b85c8..2252c3745 100644 --- a/src/ar/novelarab/build.gradle +++ b/src/ar/novelarab/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.NovelArab' themePkg = 'madaranovel' baseUrl = 'https://novelarab.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/novelarab/res/mipmap-hdpi/ic_launcher.png b/src/ar/novelarab/res/mipmap-hdpi/ic_launcher.png index b40ecd43e..6c95aae64 100644 Binary files a/src/ar/novelarab/res/mipmap-hdpi/ic_launcher.png and b/src/ar/novelarab/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/novelarab/res/mipmap-mdpi/ic_launcher.png b/src/ar/novelarab/res/mipmap-mdpi/ic_launcher.png index d9396ad80..d5264eaf8 100644 Binary files a/src/ar/novelarab/res/mipmap-mdpi/ic_launcher.png and b/src/ar/novelarab/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/novelarab/res/mipmap-xhdpi/ic_launcher.png b/src/ar/novelarab/res/mipmap-xhdpi/ic_launcher.png index 255dd98af..a8e6d723c 100644 Binary files a/src/ar/novelarab/res/mipmap-xhdpi/ic_launcher.png and b/src/ar/novelarab/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/novelarab/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/novelarab/res/mipmap-xxhdpi/ic_launcher.png index ad6c6bac4..34bfe2ac7 100644 Binary files a/src/ar/novelarab/res/mipmap-xxhdpi/ic_launcher.png and b/src/ar/novelarab/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/novelarab/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/novelarab/res/mipmap-xxxhdpi/ic_launcher.png index 08c88deaa..b7c40611f 100644 Binary files a/src/ar/novelarab/res/mipmap-xxxhdpi/ic_launcher.png and b/src/ar/novelarab/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/novelsparadise/build.gradle b/src/ar/novelsparadise/build.gradle index 7faaa9ce2..ea72a4d4b 100644 --- a/src/ar/novelsparadise/build.gradle +++ b/src/ar/novelsparadise/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.NovelsParadise' themePkg = 'lightnovelwpnovel' baseUrl = 'https://novelsparadise.site' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/novelsparadise/res/mipmap-hdpi/ic_launcher.png b/src/ar/novelsparadise/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..b728b682a Binary files /dev/null and b/src/ar/novelsparadise/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/novelsparadise/res/mipmap-mdpi/ic_launcher.png b/src/ar/novelsparadise/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..82c4a69e8 Binary files /dev/null and b/src/ar/novelsparadise/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/novelsparadise/res/mipmap-xhdpi/ic_launcher.png b/src/ar/novelsparadise/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..34e4192dd Binary files /dev/null and b/src/ar/novelsparadise/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/novelsparadise/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/novelsparadise/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..b6dbe5b7a Binary files /dev/null and b/src/ar/novelsparadise/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/novelsparadise/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/novelsparadise/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..40f7f4f13 Binary files /dev/null and b/src/ar/novelsparadise/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/novelsparadise/src/eu/kanade/tachiyomi/extension/ar/novelsparadise/NovelsParadise.kt b/src/ar/novelsparadise/src/eu/kanade/tachiyomi/extension/ar/novelsparadise/NovelsParadise.kt index c5fbbea94..c2f9aab2f 100644 --- a/src/ar/novelsparadise/src/eu/kanade/tachiyomi/extension/ar/novelsparadise/NovelsParadise.kt +++ b/src/ar/novelsparadise/src/eu/kanade/tachiyomi/extension/ar/novelsparadise/NovelsParadise.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.novelextension.ar.novelsparadise +package eu.kanade.tachiyomi.novelextension.ar.novelsparadise import eu.kanade.tachiyomi.multisrc.lightnovelwpnovel.LightNovelWPNovel diff --git a/src/ar/rewayahfans/build.gradle b/src/ar/rewayahfans/build.gradle index 16c26aa76..92febcce7 100644 --- a/src/ar/rewayahfans/build.gradle +++ b/src/ar/rewayahfans/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Rewayah Fans' extClass = '.RewayahFans' - extVersionCode = 1 + extVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/rewayahfans/res/mipmap-hdpi/ic_launcher.png b/src/ar/rewayahfans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..f5bb64048 Binary files /dev/null and b/src/ar/rewayahfans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/rewayahfans/res/mipmap-mdpi/ic_launcher.png b/src/ar/rewayahfans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..8e7da7180 Binary files /dev/null and b/src/ar/rewayahfans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/rewayahfans/res/mipmap-xhdpi/ic_launcher.png b/src/ar/rewayahfans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..d37a152bf Binary files /dev/null and b/src/ar/rewayahfans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/rewayahfans/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/rewayahfans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..bb1e342e8 Binary files /dev/null and b/src/ar/rewayahfans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/rewayahfans/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/rewayahfans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..748d56a65 Binary files /dev/null and b/src/ar/rewayahfans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/rewayatclub/build.gradle b/src/ar/rewayatclub/build.gradle index 4399fc2e4..acc5eb54e 100644 --- a/src/ar/rewayatclub/build.gradle +++ b/src/ar/rewayatclub/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Rewayat Club' extClass = '.RewayatClub' - extVersionCode = 2 + extVersionCode = 3 isNsfw = false isNovel = true } diff --git a/src/ar/rewayatclub/res/mipmap-hdpi/ic_launcher.png b/src/ar/rewayatclub/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..b85cb9b6b Binary files /dev/null and b/src/ar/rewayatclub/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/rewayatclub/res/mipmap-mdpi/ic_launcher.png b/src/ar/rewayatclub/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..83689f98e Binary files /dev/null and b/src/ar/rewayatclub/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/rewayatclub/res/mipmap-xhdpi/ic_launcher.png b/src/ar/rewayatclub/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..e0116730f Binary files /dev/null and b/src/ar/rewayatclub/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/rewayatclub/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/rewayatclub/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..f85629d7e Binary files /dev/null and b/src/ar/rewayatclub/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/rewayatclub/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/rewayatclub/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..858722403 Binary files /dev/null and b/src/ar/rewayatclub/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/rewayatfans/build.gradle b/src/ar/rewayatfans/build.gradle index 8d42b1fb0..3dc3c50ba 100644 --- a/src/ar/rewayatfans/build.gradle +++ b/src/ar/rewayatfans/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Rewayat Fans' extClass = '.RewayatFans' - extVersionCode = 1 + extVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/rewayatfans/res/mipmap-hdpi/ic_launcher.png b/src/ar/rewayatfans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..bd85ef104 Binary files /dev/null and b/src/ar/rewayatfans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/rewayatfans/res/mipmap-mdpi/ic_launcher.png b/src/ar/rewayatfans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..de4afa95b Binary files /dev/null and b/src/ar/rewayatfans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/rewayatfans/res/mipmap-xhdpi/ic_launcher.png b/src/ar/rewayatfans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..be708b747 Binary files /dev/null and b/src/ar/rewayatfans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/rewayatfans/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/rewayatfans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..c68669629 Binary files /dev/null and b/src/ar/rewayatfans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/rewayatfans/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/rewayatfans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..07603de8d Binary files /dev/null and b/src/ar/rewayatfans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/riwyat/build.gradle b/src/ar/riwyat/build.gradle index bb34d4338..d344329c0 100644 --- a/src/ar/riwyat/build.gradle +++ b/src/ar/riwyat/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Riwyat' themePkg = 'madaranovel' baseUrl = 'https://cenele.com' - overrideVersionCode = 2 + overrideVersionCode = 3 isNsfw = false isNovel = true } diff --git a/src/ar/riwyat/res/mipmap-hdpi/ic_launcher.png b/src/ar/riwyat/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..84c0e137e Binary files /dev/null and b/src/ar/riwyat/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/riwyat/res/mipmap-mdpi/ic_launcher.png b/src/ar/riwyat/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5ffff2bd4 Binary files /dev/null and b/src/ar/riwyat/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/riwyat/res/mipmap-xhdpi/ic_launcher.png b/src/ar/riwyat/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..e1e42fdf7 Binary files /dev/null and b/src/ar/riwyat/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/riwyat/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/riwyat/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..58ff7efd7 Binary files /dev/null and b/src/ar/riwyat/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/riwyat/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/riwyat/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..9a2148a83 Binary files /dev/null and b/src/ar/riwyat/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/seanovel/build.gradle b/src/ar/seanovel/build.gradle index 3769af65f..c0a03c5f3 100644 --- a/src/ar/seanovel/build.gradle +++ b/src/ar/seanovel/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'SeaNovel' extClass = '.SeaNovel' - extVersionCode = 2 + extVersionCode = 3 isNsfw = false isNovel = true } diff --git a/src/ar/seanovel/res/mipmap-hdpi/ic_launcher.png b/src/ar/seanovel/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cdb8fee70 Binary files /dev/null and b/src/ar/seanovel/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/seanovel/res/mipmap-mdpi/ic_launcher.png b/src/ar/seanovel/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..80f2dd8cc Binary files /dev/null and b/src/ar/seanovel/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/seanovel/res/mipmap-xhdpi/ic_launcher.png b/src/ar/seanovel/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..388413201 Binary files /dev/null and b/src/ar/seanovel/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/seanovel/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/seanovel/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..077a7cfe9 Binary files /dev/null and b/src/ar/seanovel/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/seanovel/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/seanovel/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..82fe39801 Binary files /dev/null and b/src/ar/seanovel/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/sunovels/build.gradle b/src/ar/sunovels/build.gradle index 4cab10657..fc8014f15 100644 --- a/src/ar/sunovels/build.gradle +++ b/src/ar/sunovels/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Sunovels' extClass = '.Sunovels' - extVersionCode = 1 + extVersionCode = 2 isNsfw = false isNovel = true } diff --git a/src/ar/sunovels/res/mipmap-hdpi/ic_launcher.png b/src/ar/sunovels/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..2baf79442 Binary files /dev/null and b/src/ar/sunovels/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ar/sunovels/res/mipmap-mdpi/ic_launcher.png b/src/ar/sunovels/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5c9ef4ef2 Binary files /dev/null and b/src/ar/sunovels/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ar/sunovels/res/mipmap-xhdpi/ic_launcher.png b/src/ar/sunovels/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..40ede37cb Binary files /dev/null and b/src/ar/sunovels/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ar/sunovels/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/sunovels/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..71d5f84d6 Binary files /dev/null and b/src/ar/sunovels/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ar/sunovels/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/sunovels/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..f990f6bd2 Binary files /dev/null and b/src/ar/sunovels/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ar/sunovels/src/eu/kanade/tachiyomi/extension/ar/sunovels/Sunovels.kt b/src/ar/sunovels/src/eu/kanade/tachiyomi/extension/ar/sunovels/Sunovels.kt index cf878fd7c..20ab97be5 100644 --- a/src/ar/sunovels/src/eu/kanade/tachiyomi/extension/ar/sunovels/Sunovels.kt +++ b/src/ar/sunovels/src/eu/kanade/tachiyomi/extension/ar/sunovels/Sunovels.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Request import okhttp3.Response +import org.jsoup.Jsoup class Sunovels : HttpSource(), @@ -26,24 +27,51 @@ class Sunovels : override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/library?page=$page", headers) override fun popularMangaParse(response: Response): MangasPage { - val doc = response.asJsoup() - val novels = doc.select("li.list-item").mapNotNull { item -> - val link = item.selectFirst("a[href*=/novel/]") ?: return@mapNotNull null - val title = item.selectFirst("h4")?.text()?.trim() ?: return@mapNotNull null - val img = item.selectFirst("img") - val thumbnail = img?.attr("data-src") - ?: img?.attr("data-lazy-src") - ?: img?.attr("src")?.takeIf { - it.isNotEmpty() && !it.contains("placeholder") - } - SManga.create().apply { - url = link.attr("href") - this.title = title - thumbnail_url = thumbnail + val body = response.body?.string() ?: return MangasPage(emptyList(), false) + val doc = Jsoup.parse(body) + val novels = mutableListOf() + + // Extract per-novel data from RSC: each list-item has href + src + title together + val listItemPattern = Regex( + """"list-item","children".*?"href":"/novel/([^"]+)".*?"src":"/uploads/([^"]+)".*?"children":"([^"]*[\u0600-\u06FF][^"]*)"""", + RegexOption.DOT_MATCHES_ALL, + ) + val rscBody = extractRscBody(body) + listItemPattern.findAll(rscBody).forEach { match -> + val slug = match.groupValues[1] + val src = "/uploads/${match.groupValues[2]}" + val title = match.groupValues[3].trim() + if (novels.any { it.url == "/novel/$slug" }) return@forEach + if (title.isBlank()) return@forEach + novels.add( + SManga.create().apply { + url = "/novel/$slug" + this.title = title + thumbnail_url = src + }, + ) + } + + // Fallback: Parse regular HTML if RSC parsing found nothing + if (novels.isEmpty()) { + doc.select("li.list-item").forEach { item -> + val link = item.selectFirst("a[href*=/novel/]") ?: return@forEach + val title = item.selectFirst("h4")?.text()?.trim() ?: return@forEach + if (novels.any { it.url == link.attr("href") }) return@forEach + val slug = link.attr("href").removePrefix("/novel/") + val realImg = findImageForSlug(body, slug) + novels.add( + SManga.create().apply { + url = link.attr("href") + this.title = title + thumbnail_url = realImg + }, + ) } - }.distinctBy { it.url } - val hasNextPage = doc.selectFirst("li.next a[aria-disabled=false]") != null - return MangasPage(novels, hasNextPage) + } + + val hasNextPage = doc.selectFirst("li.next:not(.disabled)") != null + return MangasPage(novels.distinctBy { it.url }, hasNextPage) } override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/library?page=$page&sort=latest", headers) @@ -52,34 +80,66 @@ class Sunovels : override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val q = java.net.URLEncoder.encode(query, "UTF-8") - return GET("$baseUrl/search/?title=$q", headers) + return GET("$baseUrl/search/?title=$q&page=$page", headers) } override fun searchMangaParse(response: Response): MangasPage { - val doc = response.asJsoup() - val novels = doc.select("li.list-item").mapNotNull { item -> - val link = item.selectFirst("a[href*=/novel/]") ?: return@mapNotNull null - val title = item.selectFirst("h4")?.text()?.trim() ?: return@mapNotNull null - val img = item.selectFirst("img") - val thumbnail = img?.attr("data-src") - ?: img?.attr("data-lazy-src") - ?: img?.attr("src")?.takeIf { - it.isNotEmpty() && !it.contains("placeholder") - } - SManga.create().apply { - url = link.attr("href") - this.title = title - thumbnail_url = thumbnail + val body = response.body?.string() ?: return MangasPage(emptyList(), false) + val doc = Jsoup.parse(body) + val novels = mutableListOf() + + // Parse from RSC data (search results are in RSC, not regular HTML) + val rscBody = extractRscBody(body) + val listItemPattern = Regex( + """"list-item","children".*?"href":"/novel/([^"]+)".*?"src":"/uploads/([^"]+)".*?"children":"([^"]*[\u0600-\u06FF][^"]*)"""", + RegexOption.DOT_MATCHES_ALL, + ) + listItemPattern.findAll(rscBody).forEach { match -> + val slug = match.groupValues[1] + val src = "/uploads/${match.groupValues[2]}" + val title = match.groupValues[3].trim() + if (novels.any { it.url == "/novel/$slug" }) return@forEach + if (title.isBlank()) return@forEach + novels.add( + SManga.create().apply { + url = "/novel/$slug" + this.title = title + thumbnail_url = src + }, + ) + } + + // Fallback: Parse regular HTML + if (novels.isEmpty()) { + doc.select("li.list-item").forEach { item -> + val link = item.selectFirst("a[href*=/novel/]") ?: return@forEach + val title = item.selectFirst("h4")?.text()?.trim() ?: return@forEach + if (novels.any { it.url == link.attr("href") }) return@forEach + val slug = link.attr("href").removePrefix("/novel/") + val realImg = findImageForSlug(body, slug) + novels.add( + SManga.create().apply { + url = link.attr("href") + this.title = title + thumbnail_url = realImg + }, + ) } - }.distinctBy { it.url } - return MangasPage(novels, false) + } + + // Check for next page + val hasNextPage = Regex(""""page":(\d+)"""").findAll(rscBody).any { + it.groupValues[1].toIntOrNull()?.let { p -> p > 1 } == true + } || doc.selectFirst("li.next:not(.disabled)") != null + + return MangasPage(novels, hasNextPage) } override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl${manga.url}", headers) override fun mangaDetailsParse(response: Response): SManga { - val doc = response.asJsoup() - val html = doc.html() + val body = response.body?.string() ?: return SManga.create() + val doc = Jsoup.parse(body) return SManga.create().apply { val novelH1 = doc.selectFirst(".info h1, .novel-header h1, .main-head h1") val novelH3 = doc.selectFirst(".info h3, .novel-header h3, .main-head h3") @@ -87,13 +147,13 @@ class Sunovels : ?: novelH1?.text()?.trim()?.ifEmpty { null } ?: doc.selectFirst("meta[property=og:title]") ?.attr("content") - ?.removePrefix("رواية ") - ?.substringBefore(" | شمس الروايات") + ?.removePrefix("\u0631\u0648\u0627\u064a\u0629 ") + ?.substringBefore(" | \u0634\u0645\u0633 \u0627\u0644\u0631\u0648\u0627\u064a\u0627\u062a") ?.substringBefore(" | Sunovels") ?.trim() ?: doc.title() - .removePrefix("رواية ") - .substringBefore(" | شمس الروايات") + .removePrefix("\u0631\u0648\u0627\u064a\u0629 ") + .substringBefore(" | \u0634\u0645\u0633 \u0627\u0644\u0631\u0648\u0627\u064a\u0627\u062a") .substringBefore(" | Sunovels") .trim() status = when { @@ -101,72 +161,134 @@ class Sunovels : doc.selectFirst(".top.Completed, .Completed") != null -> SManga.COMPLETED else -> SManga.UNKNOWN } - val imgMatch = Regex("\"image\":\"([^\"]*)\"").find(html) + val imgMatch = Regex("\"image\":\"([^\"]*)\"").find(body) thumbnail_url = imgMatch?.groupValues?.get(1)?.let { if (it.startsWith("/")) "$baseUrl$it" else it - } ?: doc.selectFirst(".cover img, figure.cover img, .img-container img")?.attr("src")?.let { + } ?: doc.selectFirst("figure.cover img, .img-container img")?.attr("src")?.let { if (it.startsWith("/")) "$baseUrl$it" else it } - genre = doc.select(".badge, .category, .tag") + genre = doc.select(".tag, .tags a.tag") .mapNotNull { it.text().trim().takeIf { t -> t.isNotEmpty() } } .distinct() .joinToString(", ") - description = doc.selectFirst(".description, .info-section .description")?.text()?.trim() + description = doc.selectFirst(".description p, .description")?.text()?.trim() ?: doc.selectFirst("meta[property=og:description]") ?.attr("content")?.trim() ?: "" } } - override fun chapterListRequest(manga: SManga): Request = GET("$baseUrl${manga.url}", headers) + override fun chapterListRequest(manga: SManga): Request = GET("$baseUrl${manga.url}?activeTab=chapters", headers) override fun chapterListParse(response: Response): List { - val doc = response.asJsoup() - val html = doc.html() + val body = response.body?.string() ?: return emptyList() + val slug = response.request.url.encodedPath.substringAfter("/novel/").substringBefore("?") + val novelUrl = "${response.request.url.scheme}://${response.request.url.host}/novel/$slug" val chapters = mutableListOf() - val slug = response.request.url.toString().substringAfterLast("/novel/").substringBefore("?") - val chapterPattern = Regex("\"chapters\":\\[([^\\]]*)\\]") - val chapterMatch = chapterPattern.find(html) - if (chapterMatch != null) { - val chapterBlock = chapterMatch.groupValues[1] - val chapRegex = Regex( - "\"id\":(\\d+),\"number\":(\\d+),\"title\":\"([^\"]*)\",\"slug\":\"([^\"]*)\"", - ) - chapRegex.findAll(chapterBlock).forEach { match -> - val number = match.groupValues[2].toFloatOrNull() ?: 0f - val title = match.groupValues[3].ifEmpty { match.groupValues[2] } - val chapSlug = match.groupValues[4] - chapters.add( - SChapter.create().apply { - url = "/novel/$slug/chapter/$chapSlug" - name = "الفصل $number: $title" - chapter_number = number - }, - ) - } - } - if (chapters.isEmpty()) { - doc.select("a[href*=chapter]").forEach { link -> - val href = link.attr("href") - if (href.isEmpty()) return@forEach - val text = link.text().trim() - if (text.isEmpty()) return@forEach - val exists = chapters.any { it.url == href } - if (!exists) { - chapters.add( - SChapter.create().apply { - url = href - name = text - chapter_number = Regex( - "chapter[-\\s]?(\\d+)", - RegexOption.IGNORE_CASE, - ).find(href)?.groupValues?.get(1)?.toFloatOrNull() ?: 0f - }, - ) + + // Parse first page chapters (default = page 0 = chapters 1-50) + parseChaptersFromHtml(body, slug, chapters) + + // Extract total pages + val totalPages = extractTotalPages(body) + if (totalPages <= 1) return chapters.sortedBy { it.chapter_number } + + // Fetch remaining pages in parallel with retry + val pagesToFetch = (1 until totalPages).toMutableList() + val concurrency = 5 + val maxRetries = 2 + + for (attempt in 0..maxRetries) { + if (pagesToFetch.isEmpty()) break + val failedPages = mutableListOf() + for (batch in pagesToFetch.chunked(concurrency)) { + val futures = batch.map { page -> + Thread { + try { + val pageUrl = "$novelUrl?activeTab=chapters&page=$page" + val pageResponse = client.newCall(GET(pageUrl, headers)).execute() + val pageBody = pageResponse.body?.string() ?: return@Thread + synchronized(chapters) { + val before = chapters.size + parseChaptersFromHtml(pageBody, slug, chapters) + if (chapters.size == before) { + synchronized(failedPages) { failedPages.add(page) } + } + } + } catch (_: Exception) { + synchronized(failedPages) { failedPages.add(page) } + } + } } + futures.forEach { it.start() } + futures.forEach { it.join() } } + pagesToFetch.clear() + pagesToFetch.addAll(failedPages) + } + + return chapters.sortedBy { it.chapter_number } + } + + private fun parseChaptersFromHtml(body: String, slug: String, chapters: MutableList) { + // Method 1: Plain HTML links + val doc = Jsoup.parse(body) + doc.select("a[href*=/novel/$slug/]").forEach { link -> + val href = link.attr("href") + if (href.isEmpty()) return@forEach + val chapterNum = Regex("/novel/$slug/(\\d+)").find(href) + ?.groupValues?.get(1)?.toFloatOrNull() ?: return@forEach + if (chapters.any { it.chapter_number == chapterNum }) return@forEach + val title = link.selectFirst("span, strong")?.text()?.trim() + ?: link.text().trim() + chapters.add( + SChapter.create().apply { + url = "/novel/$slug/${chapterNum.toInt()}" + name = title.ifEmpty { "\u0627\u0644\u0641\u0635\u0644 ${chapterNum.toInt()}" } + chapter_number = chapterNum + }, + ) + } + // Method 2: Unescaped RSC data - find href patterns + val rscBody = extractRscBody(body) + val hrefPattern = Regex(""""href":"/novel/$slug/(\d+)"""") + val titlePattern = Regex(""""title":"(\d+ [^"]+)"""") + val hrefes = hrefPattern.findAll(rscBody).map { it.groupValues[1].toFloatOrNull() }.filterNotNull().toList() + val titles = titlePattern.findAll(rscBody).map { it.groupValues[1] }.toList() + + for ((i, num) in hrefes.withIndex()) { + if (chapters.any { it.chapter_number == num }) continue + val title = titles.getOrElse(i) { "" } + chapters.add( + SChapter.create().apply { + url = "/novel/$slug/${num.toInt()}" + name = title.ifEmpty { "\u0627\u0644\u0641\u0635\u0644 ${num.toInt()}" } + chapter_number = num + }, + ) } - return chapters.reversed() + } + + private fun extractTotalPages(body: String): Int { + // Try RSC data first + val rscMatch = Regex(""""totalPages\\?":(\d+)""").find(body) + if (rscMatch != null) return rscMatch.groupValues[1].toIntOrNull() ?: 1 + + // Try parsing unescaped RSC + val rscBody = extractRscBody(body) + val rscMatch2 = Regex(""""totalPages":(\d+)""").find(rscBody) + if (rscMatch2 != null) return rscMatch2.groupValues[1].toIntOrNull() ?: 1 + + // Fallback: parse pagination from HTML + val doc = Jsoup.parse(body) + val pageLinks = doc.select("ul.pagination li a") + var maxPage = 1 + pageLinks.forEach { link -> + val num = Regex("Page (\\d+)").find(link.attr("aria-label")) + ?.groupValues?.get(1)?.toIntOrNull() + if (num != null && num > maxPage) maxPage = num + } + return maxPage } override fun pageListParse(response: Response): List { @@ -176,23 +298,12 @@ class Sunovels : override suspend fun fetchPageText(page: Page): String { val doc = client.newCall(GET("$baseUrl${page.url}", headers)).execute().asJsoup() - val html = doc.html() - val contentMatch = Regex( - "\"content\":\"((?:[^\"\\\\]|\\\\.)*)\"", - ).find(html) - if (contentMatch != null) { - return contentMatch.groupValues[1] - .replace("\\n", "\n") - .replace("\\t", "\t") - .replace("\\\"", "\"") - .replace("\\/", "/") - .replace("\\u003C", "<") - .replace("\\u003E", ">") - .trim() - } val content = doc.selectFirst( ".chapter-content, .content, .entry-content, .post-content, article, .text", ) ?: return "" + // Remove hidden watermark elements (d-none class contains anti-scraping hashes) + content.select("p.d-none, .d-none").remove() + // Remove ads, navigation, and other non-content elements content.select( "script, style, .ads, .navigation, .chapter-nav, " + ".social-share, .comments, nav, footer", @@ -201,4 +312,60 @@ class Sunovels : } override fun imageUrlParse(response: Response): String = "" + + /** + * Extract and concatenate all RSC flight data into a single string for easy searching. + */ + private fun extractRscBody(html: String): String { + val sb = StringBuilder() + val pattern = Regex("""self\.__next_f\.push\(\[1,"((?:[^"\\]|\\.)*)"\]\)""") + pattern.findAll(html).forEach { match -> + val raw = match.groupValues[1] + sb.append( + raw.replace("\\\"", "\"") + .replace("\\\\", "\\") + .replace("\\n", "\n") + .replace("\\t", "\t"), + ) + sb.append("\n") + } + return sb.toString() + } + + /** + * Extract RSC (React Server Components) flight data chunks from the page body. + * These are embedded in script tags like: self.__next_f.push([1,"..."]) + */ + private fun extractRscChunks(html: String): List { + val chunks = mutableListOf() + val pattern = Regex("""self\.__next_f\.push\(\[1,"((?:[^"\\]|\\.)*)"\]\)""") + pattern.findAll(html).forEach { match -> + val raw = match.groupValues[1] + // Unescape JSON string escapes + val unescaped = raw + .replace("\\\"", "\"") + .replace("\\\\", "\\") + .replace("\\n", "\n") + .replace("\\t", "\t") + chunks.add(unescaped) + } + return chunks + } + + /** + * Find the image URL for a given novel slug from the raw HTML body. + * Searches RSC flight data for a matching src/href pair. + */ + private fun findImageForSlug(html: String, slug: String): String? { + val rscBody = extractRscBody(html) + val idx = rscBody.indexOf("/novel/$slug") + if (idx < 0) return null + // Search nearby for the image src + val searchRange = rscBody.substring( + maxOf(0, idx - 500), + minOf(rscBody.length, idx + 500), + ) + val srcMatch = Regex(""""src":"/uploads/([^"]+)"""").find(searchRange) + return srcMatch?.groupValues?.get(1)?.let { "/uploads/$it" } + } }