diff --git a/src/zh/zaimanhua/build.gradle b/src/zh/zaimanhua/build.gradle index fc35f92bd..4a388751a 100644 --- a/src/zh/zaimanhua/build.gradle +++ b/src/zh/zaimanhua/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Zaimanhua' extClass = '.Zaimanhua' - extVersionCode = 1 + extVersionCode = 2 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/Zaimanhua.kt b/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/Zaimanhua.kt index d95c46ff3..6c4c0de93 100644 --- a/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/Zaimanhua.kt +++ b/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/Zaimanhua.kt @@ -15,6 +15,10 @@ 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.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.Headers import okhttp3.HttpUrl @@ -24,9 +28,13 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import okhttp3.Response +import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.io.IOException import java.security.MessageDigest +import java.util.concurrent.TimeUnit class Zaimanhua : HttpSource(), ConfigurableSource { override val lang = "zh" @@ -37,11 +45,16 @@ class Zaimanhua : HttpSource(), ConfigurableSource { private val apiUrl = "https://v4api.zaimanhua.com/app/v1" private val accountApiUrl = "https://account-api.zaimanhua.com/v1" + private val json by injectLazy() + private val preferences: SharedPreferences = Injekt.get().getSharedPreferences("source_$id", 0x0000) - override val client: OkHttpClient = - network.client.newBuilder().rateLimit(5).addInterceptor(::authIntercept).build() + override val client: OkHttpClient = network.client.newBuilder() + .rateLimit(5) + .addInterceptor(::authIntercept) + .addInterceptor(::imageRetryInterceptor) + .build() private fun authIntercept(chain: Interceptor.Chain): Response { val request = chain.request() @@ -129,24 +142,48 @@ class Zaimanhua : HttpSource(), ConfigurableSource { } } - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() - // PageList // path: "/comic/chapter/mangaId/chapterId" - override fun pageListRequest(chapter: SChapter) = - GET("$apiUrl/comic/chapter/${chapter.url}", apiHeaders) + private fun pageListApiRequest(path: String): Request = + GET("$apiUrl/comic/chapter/$path", apiHeaders, USE_CACHE) - override fun pageListParse(response: Response): List { + override fun pageListParse(response: Response): List = throw UnsupportedOperationException() + + override fun fetchPageList(chapter: SChapter): Observable> { + val response = client.newCall(pageListApiRequest(chapter.url)).execute() val result = response.parseAs>>() if (result.errmsg.isNotBlank()) { throw Exception(result.errmsg) } else { - return result.data.data!!.images.mapIndexed { index, it -> - Page(index, imageUrl = it) - } + return Observable.just( + result.data.data!!.images.mapIndexed { index, it -> + val fragment = json.encodeToString(ImageRetryParamsDto(chapter.url, index)) + Page(index, imageUrl = "$it#$fragment") + }, + ) } } + private fun imageRetryInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + val fragment = request.url.fragment + if (response.isSuccessful || request.url.host != "images.zaimanhua.com" || fragment == null) return response + response.close() + + val params = json.decodeFromString(fragment) + val pageListResponse = client.newCall(pageListApiRequest(params.url)).execute() + val result = pageListResponse.parseAs>>() + if (result.errmsg.isNotBlank()) { + throw IOException(result.errmsg) + } else { + val imageUrl = result.data.data!!.images[params.index] + return chain.proceed(GET(imageUrl, headers)) + } + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() + // Popular private fun rankApiUrl(): HttpUrl.Builder = "$apiUrl/comic/rank/list".toHttpUrl().newBuilder().addQueryParameter("by_time", "3") @@ -187,6 +224,9 @@ class Zaimanhua : HttpSource(), ConfigurableSource { return MangasPage(mangas.map { it.toSManga() }, true) } + companion object { + val USE_CACHE = CacheControl.Builder().maxStale(170, TimeUnit.SECONDS).build() + } override fun setupPreferenceScreen(screen: PreferenceScreen) { ListPreference(screen.context).apply { EditTextPreference(screen.context).apply { diff --git a/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/ZaimanhuaDto.kt b/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/ZaimanhuaDto.kt index cf4e8c334..2fb15e862 100644 --- a/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/ZaimanhuaDto.kt +++ b/src/zh/zaimanhua/src/eu/kanade/tachiyomi/extension/zh/zaimanhua/ZaimanhuaDto.kt @@ -47,10 +47,14 @@ class ChapterGroupDto( fun toSChapterList(mangaId: String): List { val groupName = title val isDefaultGroup = groupName == "连载" + val current = System.currentTimeMillis() return data.map { it.toSChapterInternal().apply { url = "$mangaId/$url" if (!isDefaultGroup) scanlator = groupName + // For some chapters, api will always return current time as upload time + // Therefore upload times that differ too little from the current time will be ignored + if ((current - date_upload) < 10000) date_upload = 0 } } } @@ -142,3 +146,9 @@ class ResponseDto( val errmsg: String = "", val data: T, ) + +@Serializable +data class ImageRetryParamsDto( + val url: String, + val index: Int, +)