Dynasty: original covers & chapter metadata (#9604)

* original covers & chapter metadata

* use cached cover if new one is same
This commit is contained in:
AwkwardPeak7 2025-07-11 10:20:56 +05:00 committed by Draff
parent ea34656edf
commit aedd777371
Signed by: Draff
GPG Key ID: E8A89F3211677653
5 changed files with 90 additions and 35 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Dynasty' extName = 'Dynasty'
extClass = '.DynastyFactory' extClass = '.DynastyFactory'
extVersionCode = 26 extVersionCode = 27
isNsfw = true isNsfw = true
} }

View File

@ -74,6 +74,7 @@ class MangaEntry(
class MangaResponse( class MangaResponse(
val name: String, val name: String,
val type: String, val type: String,
val permalink: String,
val tags: List<BrowseTag>, val tags: List<BrowseTag>,
val cover: String?, val cover: String?,
val description: String?, val description: String?,
@ -81,7 +82,15 @@ class MangaResponse(
@Serializable(with = ChapterItemListSerializer::class) @Serializable(with = ChapterItemListSerializer::class)
val taggings: List<ChapterItem>, val taggings: List<ChapterItem>,
@SerialName("total_pages") val totalPages: Int = 0, @SerialName("total_pages") val totalPages: Int = 0,
) ) {
val directory get() = when (type) {
SERIES_TYPE -> SERIES_DIR
ANTHOLOGY_TYPE -> ANTHOLOGIES_DIR
DOUJIN_TYPE -> DOUJINS_DIR
ISSUE_TYPE -> ISSUES_DIR
else -> throw Exception("Unsupported Type for directory: $type")
}
}
@Serializable @Serializable
sealed class ChapterItem sealed class ChapterItem
@ -122,6 +131,7 @@ object ChapterItemListSerializer : JsonTransformingSerializer<List<ChapterItem>>
@Serializable @Serializable
class ChapterResponse( class ChapterResponse(
val title: String, val title: String,
val permalink: String,
val tags: List<BrowseTag>, val tags: List<BrowseTag>,
val pages: List<Page>, val pages: List<Page>,
@SerialName("released_on") val releasedOn: String, @SerialName("released_on") val releasedOn: String,

View File

@ -50,7 +50,7 @@ open class Dynasty : HttpSource(), ConfigurableSource {
override val client = network.cloudflareClient.newBuilder() override val client = network.cloudflareClient.newBuilder()
.addInterceptor(::fetchCoverUrlInterceptor) .addInterceptor(::fetchCoverUrlInterceptor)
.addInterceptor(::coverInterceptor) .addInterceptor(::coverInterceptor)
.rateLimit(1, 2) .rateLimit(1)
.build() .build()
private val coverClient = network.cloudflareClient private val coverClient = network.cloudflareClient
@ -74,7 +74,7 @@ open class Dynasty : HttpSource(), ConfigurableSource {
MangaEntry( MangaEntry(
url = "/${tag.directory}/${tag.permalink}", url = "/${tag.directory}/${tag.permalink}",
title = tag.name, title = tag.name,
cover = getCoverUrl(tag.directory, tag.permalink), cover = getCachedCoverUrl(tag.directory, tag.permalink),
).also(entries::add) ).also(entries::add)
// true if an associated series is found // true if an associated series is found
@ -115,7 +115,7 @@ open class Dynasty : HttpSource(), ConfigurableSource {
val entry = MangaEntry( val entry = MangaEntry(
url = "/$directory/$permalink", url = "/$directory/$permalink",
title = permalink.permalinkToTitle(), title = permalink.permalinkToTitle(),
cover = getCoverUrl(directory, permalink), cover = getCachedCoverUrl(directory, permalink),
).toSManga() ).toSManga()
return Observable.just( return Observable.just(
@ -280,7 +280,7 @@ open class Dynasty : HttpSource(), ConfigurableSource {
val entry = MangaEntry( val entry = MangaEntry(
url = "/$directory/$permalink", url = "/$directory/$permalink",
title = title, title = title,
cover = getCoverUrl(directory, permalink), cover = getCachedCoverUrl(directory, permalink),
) )
if (firstEntry == null) { if (firstEntry == null) {
@ -422,7 +422,29 @@ open class Dynasty : HttpSource(), ConfigurableSource {
.any { publishingStatus.contains(it) } -> SManga.CANCELLED .any { publishingStatus.contains(it) } -> SManga.CANCELLED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
thumbnail_url = data.cover?.let { buildCoverUrl(it) } // if new cover is same as cached cover, use cached cover
// to avoid making HEAD requests in `getHDCoverUrlIfAvailable`
thumbnail_url = run {
val newCover = data.cover?.let { buildCoverUrl(it) }
val cachedCover = getCachedCoverUrl(data.directory, data.permalink)
if (newCover == null || cachedCover == null) {
newCover?.let { getHDCoverUrlIfAvailable(it) } ?: cachedCover
} else {
val path = cachedCover.toHttpUrl().pathSegments
val tmpSDCover = cachedCover.toHttpUrl().newBuilder().apply {
val file = path.last().substringBeforeLast(".") + ".jpg"
setPathSegment(5, "medium")
setPathSegment(path.size - 1, file)
}.toString()
if (tmpSDCover == newCover) {
cachedCover
} else {
getHDCoverUrlIfAvailable(newCover)
}
}
}
} }
} }
@ -470,30 +492,15 @@ open class Dynasty : HttpSource(), ConfigurableSource {
} }
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.url.contains("/$CHAPTERS_DIR/")) {
Observable.just(
listOf(
SChapter.create().apply {
url = manga.url
name = "Chapter"
date_upload = dateFormat.tryParse(
manga.description
?.substringAfter("Released:", ""),
)
},
),
)
} else {
super.fetchChapterList(manga)
}
}
override fun chapterListRequest(manga: SManga): Request { override fun chapterListRequest(manga: SManga): Request {
return mangaDetailsRequest(manga) return mangaDetailsRequest(manga)
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
if (response.request.url.pathSegments[0] == CHAPTERS_DIR) {
return listOf(individualChapterParse(response))
}
val data = response.parseAs<MangaResponse>() val data = response.parseAs<MangaResponse>()
val chapters = data.taggings.toMutableList() val chapters = data.taggings.toMutableList()
@ -542,6 +549,17 @@ open class Dynasty : HttpSource(), ConfigurableSource {
} }
} }
private fun individualChapterParse(response: Response): SChapter {
val data = response.parseAs<ChapterResponse>()
return SChapter.create().apply {
url = "/$CHAPTERS_DIR/${data.permalink}"
name = "Chapter"
scanlator = data.tags.filter { it.type == "Scanlator" }.joinToString { it.name }
date_upload = dateFormat.tryParse(data.releasedOn)
}
}
override fun getChapterUrl(chapter: SChapter): String { override fun getChapterUrl(chapter: SChapter): String {
return baseUrl + chapter.url return baseUrl + chapter.url
} }
@ -606,7 +624,7 @@ open class Dynasty : HttpSource(), ConfigurableSource {
.parseAs() .parseAs()
} }
private fun getCoverUrl(directory: String?, permalink: String): String? { private fun getCachedCoverUrl(directory: String?, permalink: String): String? {
directory ?: return null directory ?: return null
if (directory == CHAPTERS_DIR) { if (directory == CHAPTERS_DIR) {
@ -619,17 +637,44 @@ open class Dynasty : HttpSource(), ConfigurableSource {
return buildCoverUrl(file) return buildCoverUrl(file)
} }
private fun getHDCoverUrlIfAvailable(coverUrl: String): String {
val path = coverUrl.toHttpUrl().pathSegments
if (path.size == 7 && path[5] == "medium") {
listOf("jpg", "png", "jpeg", "webp").forEach { format ->
val newUrl = coverUrl.toHttpUrl().newBuilder().apply {
val file = path.last().substringBeforeLast(".") + ".$format"
setPathSegment(5, "original")
setPathSegment(path.size - 1, file)
}.build()
val request = Request.Builder()
.url(newUrl)
.headers(headers)
.head()
.build()
if (coverClient.newCall(request).execute().isSuccessful) {
return newUrl.toString()
}
}
}
return coverUrl
}
private fun buildCoverUrl(file: String): String { private fun buildCoverUrl(file: String): String {
val path = "$baseUrl$file".toHttpUrl() val path = "$baseUrl$file".toHttpUrl()
.encodedPath .encodedPath
.removePrefix("/") .removePrefix("/")
return baseUrl.toHttpUrl() return baseUrl.toHttpUrl().newBuilder().apply {
.newBuilder() if (!path.startsWith("system/")) {
.addEncodedPathSegments(path) addEncodedPathSegments("system/tag_contents_covers/000")
.fragment(COVER_URL_FRAGMENT) }
.build() addEncodedPathSegments(path)
.toString() fragment(COVER_URL_FRAGMENT)
}.toString()
} }
private fun buildChapterCoverFetchUrl(permalink: String): String { private fun buildChapterCoverFetchUrl(permalink: String): String {