NHentai | Fixed some images not showing for some titles (#6070)

* NHentai | Fixed some images not showing for some titles

* little

* Apply AwkwardPeak's suggestions

* comma
This commit is contained in:
KenjieDec 2024-11-17 20:07:49 +07:00 committed by GitHub
parent 54578a5282
commit bc7e462516
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 70 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'NHentai'
extClass = '.NHFactory'
extVersionCode = 46
extVersionCode = 47
isNsfw = true
}

View File

@ -0,0 +1,37 @@
package eu.kanade.tachiyomi.extension.all.nhentai
import kotlinx.serialization.Serializable
@Serializable
class Hentai(
var id: Int,
val images: Images,
val media_id: String,
val tags: List<Tag>,
val title: Title,
val upload_date: Long,
val num_favorites: Long,
)
@Serializable
class Title(
var english: String? = null,
val japanese: String? = null,
val pretty: String? = null,
)
@Serializable
class Images(
val pages: List<Image>,
)
@Serializable
class Image(
val t: String,
)
@Serializable
class Tag(
val name: String,
val type: String,
)

View File

@ -1,63 +1,36 @@
package eu.kanade.tachiyomi.extension.all.nhentai
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
object NHUtils {
fun getArtists(document: Document): String {
val artists = document.select("#tags > div:nth-child(4) > span > a .name")
return artists.joinToString(", ") { it.cleanTag() }
fun getArtists(data: Hentai): String {
val artists = data.tags.filter { it.type == "artist" }
return artists.joinToString(", ") { it.name }
}
fun getGroups(document: Document): String? {
val groups = document.select("#tags > div:nth-child(5) > span > a .name")
return if (groups.isNotEmpty()) {
groups.joinToString(", ") { it.cleanTag() }
} else {
null
fun getGroups(data: Hentai): String? {
val groups = data.tags.filter { it.type == "group" }
return groups.joinToString(", ") { it.name }.takeIf { it.isBlank() }
}
fun getTagDescription(data: Hentai): String {
val tags = data.tags.groupBy { it.type }
return buildString {
tags["category"]?.joinToString { it.name }?.let {
append("Categories: ", it, "\n")
}
tags["parody"]?.joinToString { it.name }?.let {
append("Parodies: ", it, "\n")
}
tags["character"]?.joinToString { it.name }?.let {
append("Characters: ", it, "\n\n")
}
}
}
fun getTagDescription(document: Document): String {
val stringBuilder = StringBuilder()
val categories = document.select("#tags > div:nth-child(7) > span > a .name")
if (categories.isNotEmpty()) {
stringBuilder.append("Categories: ")
stringBuilder.append(categories.joinToString(", ") { it.cleanTag() })
stringBuilder.append("\n\n")
}
val parodies = document.select("#tags > div:nth-child(1) > span > a .name")
if (parodies.isNotEmpty()) {
stringBuilder.append("Parodies: ")
stringBuilder.append(parodies.joinToString(", ") { it.cleanTag() })
stringBuilder.append("\n\n")
}
val characters = document.select("#tags > div:nth-child(2) > span > a .name")
if (characters.isNotEmpty()) {
stringBuilder.append("Characters: ")
stringBuilder.append(characters.joinToString(", ") { it.cleanTag() })
}
return stringBuilder.toString()
}
fun getTags(document: Document): String {
val tags = document.select("#tags > div:nth-child(3) > span > a .name")
return tags.map { it.cleanTag() }.sorted().joinToString(", ")
}
fun getNumPages(document: Document): String {
return document.select("#tags > div:nth-child(8) > span > a .name").first()!!.cleanTag()
}
fun getTime(document: Document): Long {
val timeString = document.toString().substringAfter("datetime=\"").substringBefore("\">").replace("T", " ")
return SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSZ").parse(timeString)?.time ?: 0L
fun getTags(data: Hentai): String {
val artists = data.tags.filter { it.type == "tag" }
return artists.joinToString(", ") { it.name }
}
private fun Element.cleanTag(): String = text().replace(Regex("\\(.*\\)"), "").trim()

View File

@ -6,10 +6,8 @@ import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getArtists
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getGroups
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getNumPages
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTagDescription
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTags
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTime
import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
@ -27,6 +25,8 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
@ -36,6 +36,7 @@ import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
open class NHentai(
override val lang: String,
@ -50,6 +51,8 @@ open class NHentai(
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -71,6 +74,7 @@ open class NHentai(
}
private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
private val dataRegex = Regex("""JSON.parse\("([^*]*)"\)""")
private fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -103,7 +107,7 @@ open class NHentai(
title = element.select("a > div").text().replace("\"", "").let {
if (displayFullTitle) it.trim() else it.shortenTitle()
}
thumbnail_url = element.select(".cover img").first()!!.let { img ->
thumbnail_url = element.selectFirst(".cover img")!!.let { img ->
if (img.hasAttr("data-src")) img.attr("abs:data-src") else img.attr("abs:src")
}
}
@ -207,22 +211,25 @@ open class NHentai(
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga {
val fullTitle = document.select("#info > h1").text().replace("\"", "").trim()
val script = document.selectFirst("script:containsData(JSON.parse)")!!.data()
val json = dataRegex.find(script)?.groupValues!![1]
val data = json.parseAs<Hentai>()
return SManga.create().apply {
title = if (displayFullTitle) fullTitle else fullTitle.shortenTitle()
title = if (displayFullTitle) data.title.english ?: data.title.japanese ?: data.title.pretty!! else data.title.pretty ?: (data.title.english ?: data.title.japanese)!!.shortenTitle()
thumbnail_url = document.select("#cover > a > img").attr("data-src")
status = SManga.COMPLETED
artist = getArtists(document)
author = getGroups(document)
artist = getArtists(data)
author = getGroups(data)
// Some people want these additional details in description
description = "Full English and Japanese titles:\n"
.plus("$fullTitle\n")
.plus("${document.select("div#info h2").text()}\n\n")
.plus("Pages: ${getNumPages(document)}\n")
.plus("Favorited by: ${document.select("div#info i.fa-heart ~ span span").text().removeSurrounding("(", ")")}\n")
.plus(getTagDescription(document))
genre = getTags(document)
.plus("${data.title.english}\n")
.plus("${data.title.japanese}\n\n")
.plus("Pages: ${data.images.pages.size}\n")
.plus("Favorited by: ${data.num_favorites}\n")
.plus(getTagDescription(data))
genre = getTags(data)
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
}
}
@ -231,11 +238,16 @@ open class NHentai(
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val script = document.selectFirst("script:containsData(JSON.parse)")!!.data()
val json = dataRegex.find(script)?.groupValues!![1]
val data = json.parseAs<Hentai>()
return listOf(
SChapter.create().apply {
name = "Chapter"
scanlator = getGroups(document)
date_upload = getTime(document)
scanlator = getGroups(data)
date_upload = data.upload_date * 1000
setUrlWithoutDomain(response.request.url.encodedPath)
},
)
@ -246,11 +258,23 @@ open class NHentai(
override fun chapterListSelector() = throw UnsupportedOperationException()
override fun pageListParse(document: Document): List<Page> {
val script = document.select("script:containsData(media_server)").first()!!.data()
val mediaServer = Regex("""media_server\s*:\s*(\d+)""").find(script)?.groupValues!![1]
val script = document.selectFirst("script:containsData(media_server)")!!.data()
val script2 = document.selectFirst("script:containsData(JSON.parse)")!!.data()
return document.select("div.thumbs a > img").mapIndexed { i, img ->
Page(i, "", img.attr("abs:data-src").replace("t.nh", "i.nh").replace("t\\d+.nh".toRegex(), "i$mediaServer.nh").replace("t.", "."))
val mediaServer = Regex("""media_server\s*:\s*(\d+)""").find(script)?.groupValues!![1]
val json = dataRegex.find(script2)?.groupValues!![1]
val data = json.parseAs<Hentai>()
return data.images.pages.mapIndexed { i, image ->
Page(
i,
imageUrl = "${baseUrl.replace("https://", "https://i$mediaServer.")}/galleries/${data.media_id}/${i + 1}" +
when (image.t) {
"w" -> ".webp"
"p" -> ".png"
else -> ".jpg"
},
)
}
}
@ -303,6 +327,13 @@ open class NHentai(
),
)
private inline fun <reified T> String.parseAs(): T {
return json.decodeFromString(
Regex("""\\u([0-9A-Fa-f]{4})""").replace(this) {
it.groupValues[1].toInt(16).toChar().toString()
},
)
}
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second