Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ar/galaxynovels/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

keiyoushi {
name = "Galaxy Novels"
versionCode = 9
versionCode = 12
contentWarning = ContentWarning.SAFE

source {
Expand Down
2 changes: 1 addition & 1 deletion src/ar/hizomanga/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

keiyoushi {
name = "HizoManga"
versionCode = 5
versionCode = 6
contentWarning = ContentWarning.SAFE
theme = "madaranovel"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,32 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.SMangaUpdate
import keiyoushi.annotation.Source
import keiyoushi.network.get
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element

@Source
abstract class HizoManga : MadaraNovel() {
override val useNewChapterEndpointDefault = true
// hizomanga.net blocks the Madara chapter endpoints at the edge (both
// POST /content/<id>/ajax/chapters/ and the legacy
// admin-ajax.php?action=manga_get_chapters answer 403/400), but the novel page itself
// returns 200 and already renders the complete chapter list into
// `.listing-chapters_wrap .wp-manga-chapter` — verified on a 239-chapter series, where
// the `<li>` elements are contiguous 1..239 in the initial HTML with no pagination.
// So the list is read straight off the novel page instead of the ajax endpoint.
override val useNewChapterEndpointDefault = false

/** "Chapter 12", "Ch. 12 - Title", "Episode 3: Title" — stripped from the displayed name. */
private val chapterPrefixRegex =
Regex("""^(?:chapter|chap|ch|episode|ep)\.?\s*(\d+(?:\.\d+)?)\s*[-–—:.]?\s*""", RegexOption.IGNORE_CASE)

/** "12 - Title" — a bare number only counts when an explicit separator follows. */
private val chapterSeparatorRegex = Regex("""^(\d+(?:\.\d+)?)\s*[-–—:]\s*""")

override fun buildPopularMangaRequest(page: Int): Request = GET("$baseUrl/page/$page/?per_page=100&s=&post_type=wp-manga", headers)

Expand All @@ -33,6 +51,104 @@ abstract class HizoManga : MadaraNovel() {
return MangasPage(mangas, hasNextPage)
}

/**
* Reads the chapter list from the novel page's own HTML.
*
* Madara normally asks the ajax endpoint for chapters, but every chapter endpoint on this
* host is rejected at the CDN (403 for `ajax/chapters/`, 400 for `manga_get_chapters`), so
* the shared implementation would return nothing. The novel page itself always carries the
* full list, so parse it from there and keep the shared one only as a fallback.
*/
override suspend fun fetchMangaUpdate(
manga: SManga,
chapters: List<SChapter>,
fetchDetails: Boolean,
fetchChapters: Boolean,
): SMangaUpdate {
if (!fetchChapters) {
return SMangaUpdate(
if (fetchDetails) {
val doc = client.newCall(buildMangaDetailsRequest(manga)).execute().asJsoup()
mangaDetailsFromPage(doc, manga)
} else {
manga
},
chapters,
)
}

val doc = client.newCall(buildChapterListRequest(manga)).execute().asJsoup()
val updatedManga = if (fetchDetails) mangaDetailsFromPage(doc, manga) else manga
val parsed = chaptersFromPage(doc)
return SMangaUpdate(
updatedManga,
if (reverseChapterList) parsed else parsed.reversed(),
)
}

/** Mirrors the theme's detail parsing, which is private and so not callable from here. */
private fun mangaDetailsFromPage(doc: Document, manga: SManga): SManga {
val details = SManga.create().apply {
title = doc.selectFirst(".post-title h1, #manga-title h1")?.text() ?: manga.title
thumbnail_url = doc.selectFirst(".summary_image img")?.let { img ->
img.attr("data-lazy-src").ifEmpty { null }
?: img.attr("data-src").ifEmpty { null }
?: img.attr("src").ifEmpty { null }
} ?: manga.thumbnail_url
author = doc.selectFirst(".manga-authors")?.text() ?: manga.author
description = doc.selectFirst("div.summary__content")?.text()
?: doc.selectFirst("#tab-manga-about")?.text()
?: doc.selectFirst(".manga-excerpt")?.text()
?: manga.description
genre = manga.genre
status = if (
doc.select(".post-content_item, .post-content")
.find { it.selectFirst("h5")?.text() == "Status" }
?.selectFirst(".summary-content")?.text()
?.contains("Ongoing", ignoreCase = true) == true
) {
SManga.ONGOING
} else {
SManga.COMPLETED
}
}
return details.apply { url = manga.url }
}

/** Parses `.wp-manga-chapter` entries exactly as the theme does, but from page HTML. */
private fun chaptersFromPage(doc: Document): List<SChapter> {
val total = doc.select(".wp-manga-chapter").size
val result = mutableListOf<SChapter>()

doc.select(".wp-manga-chapter").forEachIndexed { index, element ->
try {
val anchor = element.selectFirst("a") ?: return@forEachIndexed
val rawName = anchor.text()
if (rawName.isBlank()) return@forEachIndexed

val chapterUrl = anchor.attr("href")
if (chapterUrl.isBlank() || chapterUrl == "#") return@forEachIndexed

val number = chapterPrefixRegex.find(rawName)
?: chapterSeparatorRegex.find(rawName)
val parsedNumber = number?.groupValues?.get(1)?.toFloatOrNull()
val name = number?.let { rawName.removeRange(it.range).trim() }?.ifEmpty { null } ?: rawName

result.add(
SChapter.create().apply {
url = chapterUrl.removePrefix(baseUrl)
this.name = if (element.className().contains("premium-block")) "🔒 $name" else name
date_upload = parseDate(element.selectFirst(".chapter-release-date")?.text().orEmpty())
chapter_number = parsedNumber ?: (total - index).toFloat()
},
)
} catch (_: Exception) {
// Skip malformed entries rather than failing the whole list.
}
}
return result
}

override suspend fun fetchPageText(page: Page): String {
// page.url is already an absolute URL (see MadaraNovel.getPageList) - baseUrl must not be
// prepended again here, or the request resolves against a bogus "site+https" host.
Expand Down
Loading