fix(delegate): migrate NH to the v2 api (#1581)
* fix(delegate): migrate NH to the v2 api * remove extra comment * remove redundant data * linting * Code cleanup --------- Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
|
||||||
class NHentai(delegate: HttpSource, val context: Context) :
|
class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
DelegatedHttpSource(delegate),
|
DelegatedHttpSource(delegate),
|
||||||
@@ -70,12 +71,8 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
||||||
val body = input.body.string()
|
if (nhConfig == null) getNhConfig()
|
||||||
val server = MEDIA_SERVER_REGEX.find(body)?.groupValues?.get(1)?.toInt() ?: 1
|
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(input.body.string())
|
||||||
val json = GALLERY_JSON_REGEX.find(body)!!.groupValues[1].replace(
|
|
||||||
UNICODE_ESCAPE_REGEX,
|
|
||||||
) { it.groupValues[1].toInt(radix = 16).toChar().toString() }
|
|
||||||
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(json)
|
|
||||||
|
|
||||||
with(metadata) {
|
with(metadata) {
|
||||||
nhId = jsonResponse.id
|
nhId = jsonResponse.id
|
||||||
@@ -86,8 +83,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
|
|
||||||
mediaId = jsonResponse.mediaId
|
mediaId = jsonResponse.mediaId
|
||||||
|
|
||||||
mediaServer = server
|
|
||||||
|
|
||||||
jsonResponse.title?.let { title ->
|
jsonResponse.title?.let { title ->
|
||||||
japaneseTitle = title.japanese
|
japaneseTitle = title.japanese
|
||||||
shortTitle = title.pretty
|
shortTitle = title.pretty
|
||||||
@@ -96,15 +91,11 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
|
|
||||||
preferredTitle = this@NHentai.preferredTitle
|
preferredTitle = this@NHentai.preferredTitle
|
||||||
|
|
||||||
jsonResponse.images?.let { images ->
|
coverImageUrl =
|
||||||
coverImageType = images.cover?.type
|
jsonResponse.cover?.path?.let { "$thumbServer/$it" }
|
||||||
images.pages.mapNotNull {
|
?: jsonResponse.thumbnail?.path?.let { "$thumbServer/$it" }
|
||||||
it.type
|
|
||||||
}.let {
|
pageImagePreviewUrls = jsonResponse.pages.mapNotNull { it.thumbnail }
|
||||||
pageImageTypes = it
|
|
||||||
}
|
|
||||||
thumbnailImageType = images.thumbnail?.type
|
|
||||||
}
|
|
||||||
|
|
||||||
scanlator = jsonResponse.scanlator?.trimOrNull()
|
scanlator = jsonResponse.scanlator?.trimOrNull()
|
||||||
|
|
||||||
@@ -125,13 +116,22 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class JsonConfig(
|
||||||
|
@SerialName("image_servers")
|
||||||
|
val imageServers: List<String> = emptyList(),
|
||||||
|
@SerialName("thumb_servers")
|
||||||
|
val thumbServers: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class JsonResponse(
|
data class JsonResponse(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@SerialName("media_id")
|
@SerialName("media_id")
|
||||||
val mediaId: String? = null,
|
val mediaId: String? = null,
|
||||||
val title: JsonTitle? = null,
|
val title: JsonTitle? = null,
|
||||||
val images: JsonImages? = null,
|
val cover: JsonPage? = null,
|
||||||
|
val thumbnail: JsonPage? = null,
|
||||||
val scanlator: String? = null,
|
val scanlator: String? = null,
|
||||||
@SerialName("upload_date")
|
@SerialName("upload_date")
|
||||||
val uploadDate: Long? = null,
|
val uploadDate: Long? = null,
|
||||||
@@ -140,6 +140,7 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
val numPages: Int? = null,
|
val numPages: Int? = null,
|
||||||
@SerialName("num_favorites")
|
@SerialName("num_favorites")
|
||||||
val numFavorites: Long? = null,
|
val numFavorites: Long? = null,
|
||||||
|
val pages: List<JsonPage> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -149,21 +150,12 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
val pretty: String? = null,
|
val pretty: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class JsonImages(
|
|
||||||
val pages: List<JsonPage> = emptyList(),
|
|
||||||
val cover: JsonPage? = null,
|
|
||||||
val thumbnail: JsonPage? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class JsonPage(
|
data class JsonPage(
|
||||||
@SerialName("t")
|
val path: String? = null,
|
||||||
val type: String? = null,
|
|
||||||
@SerialName("w")
|
|
||||||
val width: Long? = null,
|
val width: Long? = null,
|
||||||
@SerialName("h")
|
|
||||||
val height: Long? = null,
|
val height: Long? = null,
|
||||||
|
val thumbnail: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -188,15 +180,16 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getPagePreviewList(manga: SManga, chapters: List<SChapter>, page: Int): PagePreviewPage {
|
override suspend fun getPagePreviewList(manga: SManga, chapters: List<SChapter>, page: Int): PagePreviewPage {
|
||||||
|
if (nhConfig == null) getNhConfig()
|
||||||
val metadata = fetchOrLoadMetadata(manga.id()) {
|
val metadata = fetchOrLoadMetadata(manga.id()) {
|
||||||
client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
|
client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
|
||||||
}
|
}
|
||||||
return PagePreviewPage(
|
return PagePreviewPage(
|
||||||
page,
|
page,
|
||||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
metadata.pageImagePreviewUrls.mapIndexed { index, path ->
|
||||||
PagePreviewInfo(
|
PagePreviewInfo(
|
||||||
index + 1,
|
index + 1,
|
||||||
imageUrl = thumbnailUrlFromType(metadata.mediaId!!, metadata.mediaServer ?: 1, index + 1, s)!!,
|
imageUrl = "$thumbServer/$path",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -204,15 +197,24 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun thumbnailUrlFromType(
|
var nhConfig: JsonConfig? = null
|
||||||
mediaId: String,
|
suspend fun getNhConfig() {
|
||||||
mediaServer: Int,
|
try {
|
||||||
page: Int,
|
val response =
|
||||||
t: String,
|
withIOContext { client.newCall(GET("https://nhentai.net/api/v2/config", headers)).awaitSuccess() }
|
||||||
) = NHentaiSearchMetadata.typeToExtension(t)?.let {
|
val body = response.body.string()
|
||||||
"https://t$mediaServer.nhentai.net/galleries/$mediaId/${page}t.$it"
|
nhConfig = jsonParser.decodeFromString<JsonConfig>(body)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
nhConfig = JsonConfig(
|
||||||
|
(1..4).map { n -> "https://i$n.nhentai.net" },
|
||||||
|
(1..4).map { n -> "https://t$n.nhentai.net" },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val thumbServer
|
||||||
|
get() = nhConfig?.thumbServers?.random()
|
||||||
|
|
||||||
override suspend fun fetchPreviewImage(page: PagePreviewInfo, cacheControl: CacheControl?): Response {
|
override suspend fun fetchPreviewImage(page: PagePreviewInfo, cacheControl: CacheControl?): Response {
|
||||||
return client.newCachelessCallWithProgress(
|
return client.newCachelessCallWithProgress(
|
||||||
if (cacheControl != null) {
|
if (cacheControl != null) {
|
||||||
@@ -230,10 +232,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
private val jsonParser = Json {
|
private val jsonParser = Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);")
|
|
||||||
private val MEDIA_SERVER_REGEX = Regex("media_server\\s*:\\s*(\\d+)")
|
|
||||||
private val UNICODE_ESCAPE_REGEX = Regex("\\\\u([0-9a-fA-F]{4})")
|
|
||||||
private const val TITLE_PREF = "Display manga title as:"
|
private const val TITLE_PREF = "Display manga title as:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
|||||||
|
|
||||||
binding.pages.text = context.pluralStringResource(
|
binding.pages.text = context.pluralStringResource(
|
||||||
SYMR.plurals.num_pages,
|
SYMR.plurals.num_pages,
|
||||||
meta.pageImageTypes.size,
|
meta.pageImagePreviewUrls.size,
|
||||||
meta.pageImageTypes.size,
|
meta.pageImagePreviewUrls.size,
|
||||||
)
|
)
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
||||||
|
|
||||||
|
|||||||
@@ -28,15 +28,13 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
var favoritesCount: Long? = null
|
var favoritesCount: Long? = null
|
||||||
|
|
||||||
var mediaId: String? = null
|
var mediaId: String? = null
|
||||||
var mediaServer: Int? = null
|
|
||||||
|
|
||||||
var japaneseTitle by titleDelegate(TITLE_TYPE_JAPANESE)
|
var japaneseTitle by titleDelegate(TITLE_TYPE_JAPANESE)
|
||||||
var englishTitle by titleDelegate(TITLE_TYPE_ENGLISH)
|
var englishTitle by titleDelegate(TITLE_TYPE_ENGLISH)
|
||||||
var shortTitle by titleDelegate(TITLE_TYPE_SHORT)
|
var shortTitle by titleDelegate(TITLE_TYPE_SHORT)
|
||||||
|
|
||||||
var coverImageType: String? = null
|
var coverImageUrl: String? = null
|
||||||
var pageImageTypes: List<String> = emptyList()
|
var pageImagePreviewUrls: List<String> = emptyList()
|
||||||
var thumbnailImageType: String? = null
|
|
||||||
|
|
||||||
var scanlator: String? = null
|
var scanlator: String? = null
|
||||||
|
|
||||||
@@ -45,14 +43,6 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
override fun createMangaInfo(manga: SManga): SManga {
|
override fun createMangaInfo(manga: SManga): SManga {
|
||||||
val key = nhId?.let { nhIdToPath(it) }
|
val key = nhId?.let { nhIdToPath(it) }
|
||||||
|
|
||||||
val cover = if (mediaId != null) {
|
|
||||||
typeToExtension(coverImageType)?.let {
|
|
||||||
"https://t${mediaServer ?: 1}.nhentai.net/galleries/$mediaId/cover.$it"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val title = when (preferredTitle) {
|
val title = when (preferredTitle) {
|
||||||
TITLE_TYPE_SHORT -> shortTitle ?: englishTitle ?: japaneseTitle ?: manga.title
|
TITLE_TYPE_SHORT -> shortTitle ?: englishTitle ?: japaneseTitle ?: manga.title
|
||||||
0, TITLE_TYPE_ENGLISH -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
0, TITLE_TYPE_ENGLISH -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
||||||
@@ -85,7 +75,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
|
|
||||||
return manga.copy(
|
return manga.copy(
|
||||||
url = key ?: manga.url,
|
url = key ?: manga.url,
|
||||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
thumbnail_url = coverImageUrl ?: manga.thumbnail_url,
|
||||||
title = title,
|
title = title,
|
||||||
artist = group ?: manga.artist,
|
artist = group ?: manga.artist,
|
||||||
author = artist ?: manga.artist,
|
author = artist ?: manga.artist,
|
||||||
@@ -113,9 +103,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
getItem(japaneseTitle) { stringResource(SYMR.strings.japanese_title) },
|
getItem(japaneseTitle) { stringResource(SYMR.strings.japanese_title) },
|
||||||
getItem(englishTitle) { stringResource(SYMR.strings.english_title) },
|
getItem(englishTitle) { stringResource(SYMR.strings.english_title) },
|
||||||
getItem(shortTitle) { stringResource(SYMR.strings.short_title) },
|
getItem(shortTitle) { stringResource(SYMR.strings.short_title) },
|
||||||
getItem(coverImageType) { stringResource(SYMR.strings.cover_image_file_type) },
|
getItem(coverImageUrl) { stringResource(SYMR.strings.thumbnail_url) },
|
||||||
getItem(pageImageTypes.size) { stringResource(SYMR.strings.page_count) },
|
getItem(pageImagePreviewUrls.size) { stringResource(SYMR.strings.page_count) },
|
||||||
getItem(thumbnailImageType) { stringResource(SYMR.strings.thumbnail_image_file_type) },
|
|
||||||
getItem(scanlator) { stringResource(MR.strings.scanlator) },
|
getItem(scanlator) { stringResource(MR.strings.scanlator) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -134,15 +123,6 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
private const val NHENTAI_GROUP_NAMESPACE = "group"
|
private const val NHENTAI_GROUP_NAMESPACE = "group"
|
||||||
const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
||||||
|
|
||||||
fun typeToExtension(t: String?) =
|
|
||||||
when (t) {
|
|
||||||
"w" -> "webp"
|
|
||||||
"p" -> "png"
|
|
||||||
"j" -> "jpg"
|
|
||||||
"g" -> "gif"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun nhUrlToId(url: String) =
|
fun nhUrlToId(url: String) =
|
||||||
url.split("/").last { it.isNotBlank() }.toLong()
|
url.split("/").last { it.isNotBlank() }.toLong()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user