From 24147b6556d357f9433d7cb1c902b53fb2018136 Mon Sep 17 00:00:00 2001 From: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Date: Sun, 14 May 2023 10:39:03 +0800 Subject: [PATCH] DMZJ: tweak page list parsing and remove special lists (#16427) * remove special lists * use kotlin class for tags * adjust page list * bump version * tweak preference summary --- src/zh/dmzj/API.md | 2 + src/zh/dmzj/build.gradle | 2 +- .../tachiyomi/extension/zh/dmzj/ApiV3.kt | 4 +- .../tachiyomi/extension/zh/dmzj/ApiV4.kt | 28 ++++---- .../extension/zh/dmzj/CommentsInterceptor.kt | 2 +- .../tachiyomi/extension/zh/dmzj/Common.kt | 24 +------ .../tachiyomi/extension/zh/dmzj/Dmzj.kt | 43 ++++-------- .../extension/zh/dmzj/ImageUrlInterceptor.kt | 2 +- .../extension/zh/dmzj/Preferences.kt | 68 ++----------------- 9 files changed, 40 insertions(+), 135 deletions(-) diff --git a/src/zh/dmzj/API.md b/src/zh/dmzj/API.md index 84da5bc51..9e844a338 100644 --- a/src/zh/dmzj/API.md +++ b/src/zh/dmzj/API.md @@ -116,7 +116,9 @@ class ChapterImagesDto( @ProtoNumber(3) val name: String, @ProtoNumber(4) val order: Int, @ProtoNumber(5) val direction: Int, + // initial letter is sometimes different from that in original URLs, see manga ID 56649 @ProtoNumber(6) val lowResImages: List, + // page count of low-res images @ProtoNumber(7) val pageCount: Int?, @ProtoNumber(8) val images: List, @ProtoNumber(9) val commentCount: Int, diff --git a/src/zh/dmzj/build.gradle b/src/zh/dmzj/build.gradle index 9d8bbdbe6..080436573 100644 --- a/src/zh/dmzj/build.gradle +++ b/src/zh/dmzj/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'DMZJ' pkgNameSuffix = 'zh.dmzj' extClass = '.Dmzj' - extVersionCode = 38 + extVersionCode = 39 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt index 397373879..46603e8d9 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt @@ -97,11 +97,9 @@ object ApiV3 { @Serializable class ChapterImagesDto( - private val id: Int, - private val comic_id: Int, private val page_url: List, ) { - fun toPageList() = parsePageList(comic_id, id, page_url, emptyList()) + fun toPageList() = parsePageList(page_url) } @Serializable diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt index 20cbf73be..15eb39165 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt @@ -20,20 +20,17 @@ object ApiV4 { fun mangaInfoUrl(id: String) = "$v4apiUrl/comic/detail/$id?uid=2665531" - fun parseMangaInfo(response: Response): ParseResult { + fun parseMangaInfo(response: Response): MangaDto? { val result: ResponseDto = response.decrypt() - return when (val manga = result.data) { - null -> ParseResult.Error(result.message) - else -> ParseResult.Ok(manga) - } + return result.data } // path = "mangaId/chapterId" fun chapterImagesUrl(path: String) = "$v4apiUrl/comic/chapter/$path" - fun parseChapterImages(response: Response): ArrayList { + fun parseChapterImages(response: Response, isLowRes: Boolean): ArrayList { val result: ResponseDto = response.decrypt() - return result.data!!.toPageList() + return result.data!!.toPageList(isLowRes) } fun rankingUrl(page: Int, filters: RankingGroup) = @@ -128,12 +125,18 @@ object ApiV4 { @Serializable class ChapterImagesDto( - @ProtoNumber(1) private val id: Int, - @ProtoNumber(2) private val mangaId: Int, @ProtoNumber(6) private val lowResImages: List, @ProtoNumber(8) private val images: List, ) { - fun toPageList() = parsePageList(mangaId, id, images, lowResImages) + fun toPageList(isLowRes: Boolean) = + // page count can be messy, see manga ID 55847 chapters 107-109 + if (images.size == lowResImages.size) { + parsePageList(images, lowResImages) + } else if (isLowRes) { + parsePageList(lowResImages, lowResImages) + } else { + parsePageList(images) + } } // same as ApiV3.MangaDto @@ -167,10 +170,5 @@ object ApiV4 { @ProtoNumber(3) val data: T?, ) - sealed interface ParseResult { - class Ok(val manga: MangaDto) : ParseResult - class Error(val message: String?) : ParseResult - } - private val cipher by lazy { RSA.getPrivateKey("MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAK8nNR1lTnIfIes6oRWJNj3mB6OssDGx0uGMpgpbVCpf6+VwnuI2stmhZNoQcM417Iz7WqlPzbUmu9R4dEKmLGEEqOhOdVaeh9Xk2IPPjqIu5TbkLZRxkY3dJM1htbz57d/roesJLkZXqssfG5EJauNc+RcABTfLb4IiFjSMlTsnAgMBAAECgYEAiz/pi2hKOJKlvcTL4jpHJGjn8+lL3wZX+LeAHkXDoTjHa47g0knYYQteCbv+YwMeAGupBWiLy5RyyhXFoGNKbbnvftMYK56hH+iqxjtDLnjSDKWnhcB7089sNKaEM9Ilil6uxWMrMMBH9v2PLdYsqMBHqPutKu/SigeGPeiB7VECQQDizVlNv67go99QAIv2n/ga4e0wLizVuaNBXE88AdOnaZ0LOTeniVEqvPtgUk63zbjl0P/pzQzyjitwe6HoCAIpAkEAxbOtnCm1uKEp5HsNaXEJTwE7WQf7PrLD4+BpGtNKkgja6f6F4ld4QZ2TQ6qvsCizSGJrjOpNdjVGJ7bgYMcczwJBALvJWPLmDi7ToFfGTB0EsNHZVKE66kZ/8Stx+ezueke4S556XplqOflQBjbnj2PigwBN/0afT+QZUOBOjWzoDJkCQClzo+oDQMvGVs9GEajS/32mJ3hiWQZrWvEzgzYRqSf3XVcEe7PaXSd8z3y3lACeeACsShqQoc8wGlaHXIJOHTcCQQCZw5127ZGs8ZDTSrogrH73Kw/HvX55wGAeirKYcv28eauveCG7iyFR0PFB/P/EDZnyb+ifvyEFlucPUI0+Y87F") } } diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt index b70f242d2..245690289 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt @@ -24,7 +24,7 @@ object CommentsInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val response = chain.proceed(request) - if (request.tag(Tag::class.java) == null) return response + if (request.tag(Tag::class) == null) return response val comments = ApiV3.parseChapterComments(response) .take(MAX_HEIGHT / (UNIT * 2)) diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt index 553fac686..e067604ad 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt @@ -4,11 +4,9 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Response import uy.kohesive.injekt.injectLazy import java.net.URLDecoder -import kotlin.math.max const val PREFIX_ID_SEARCH = "id:" @@ -42,34 +40,18 @@ fun String.formatChapterName(): String { return "第$number$type" } -private const val imageSmallUrl = "https://imgsmall.idmzj.com" - fun parsePageList( - mangaId: Int, - chapterId: Int, images: List, - lowResImages: List, + lowResImages: List = List(images.size) { "" }, ): ArrayList { - // page count can be messy, see manga ID 55847 chapters 107-109 - val pageCount = max(images.size, lowResImages.size) + val pageCount = images.size val list = ArrayList(pageCount + 1) // for comments page for (i in 0 until pageCount) { - val imageUrl = images.getOrNull(i)?.fixFilename()?.toHttps() - val lowResUrl = lowResImages.getOrElse(i) { - // this is sometimes different in low-res URLs and might fail, see manga ID 56649 - val initial = imageUrl!!.decodePath().toHttpUrl().pathSegments[0] - "$imageSmallUrl/$initial/$mangaId/$chapterId/$i.jpg" - }.toHttps() - list.add(Page(i, url = lowResUrl, imageUrl = imageUrl ?: lowResUrl)) + list.add(Page(i, lowResImages[i], images[i])) } return list } -fun String.toHttps() = "https:" + substringAfter(':') - -// see https://github.com/tachiyomiorg/tachiyomi-extensions/issues/3457 -fun String.fixFilename() = if (endsWith(".jp")) this + 'g' else this - fun String.decodePath(): String = URLDecoder.decode(this, "UTF-8") const val COMMENTS_FLAG = "COMMENTS" diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt index 7069a7d93..09f857459 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.zh.dmzj import android.app.Application import android.content.SharedPreferences -import android.util.Log import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimit @@ -32,7 +31,6 @@ class Dmzj : ConfigurableSource, HttpSource() { private val preferences: SharedPreferences = Injekt.get().getSharedPreferences("source_$id", 0x0000) - .migrate() override val client: OkHttpClient = network.client.newBuilder() .addInterceptor(ImageUrlInterceptor) @@ -57,18 +55,7 @@ class Dmzj : ConfigurableSource, HttpSource() { private fun fetchMangaInfoV4(id: String): ApiV4.MangaDto? { val response = retryClient.newCall(GET(ApiV4.mangaInfoUrl(id), headers)).execute() - return when (val result = ApiV4.parseMangaInfo(response)) { - is ApiV4.ParseResult.Ok -> { - val manga = result.manga - if (manga.isLicensed) preferences.addLicensed(id) - manga - } - is ApiV4.ParseResult.Error -> { - Log.e("DMZJ", "no data for manga $id: ${result.message}") - preferences.addHidden(id) - null - } - } + return ApiV4.parseMangaInfo(response) } override fun popularMangaRequest(page: Int) = GET(ApiV3.popularMangaUrl(page), headers) @@ -139,9 +126,7 @@ class Dmzj : ConfigurableSource, HttpSource() { } private fun fetchMangaDetails(id: String): SManga { - if (id !in preferences.hiddenList) { - fetchMangaInfoV4(id)?.run { return toSManga() } - } + fetchMangaInfoV4(id)?.run { return toSManga() } val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute() return ApiV3.parseMangaDetailsV1(response) } @@ -159,16 +144,14 @@ class Dmzj : ConfigurableSource, HttpSource() { throw UnsupportedOperationException() } - override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException("Not used.") + override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException() override fun fetchChapterList(manga: SManga): Observable> { return Observable.fromCallable { val id = manga.url.extractMangaId() - if (id !in preferences.licensedList && id !in preferences.hiddenList) { - val result = fetchMangaInfoV4(id) - if (result != null && !result.isLicensed) { - return@fromCallable result.parseChapterList() - } + val result = fetchMangaInfoV4(id) + if (result != null && !result.isLicensed) { + return@fromCallable result.parseChapterList() } val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute() ApiV3.parseChapterListV1(response) @@ -186,7 +169,7 @@ class Dmzj : ConfigurableSource, HttpSource() { return Observable.fromCallable { val response = retryClient.newCall(GET(ApiV4.chapterImagesUrl(path), headers)).execute() val result = try { - ApiV4.parseChapterImages(response) + ApiV4.parseChapterImages(response, preferences.imageQuality == LOW_RES) } catch (_: Throwable) { client.newCall(GET(ApiV3.chapterImagesUrlV1(path), headers)).execute() .let(ApiV3::parseChapterImagesV1) @@ -204,31 +187,31 @@ class Dmzj : ConfigurableSource, HttpSource() { // see https://github.com/tachiyomiorg/tachiyomi-extensions/issues/10475 override fun imageRequest(page: Page): Request { - val url = page.url + val url = page.url.takeIf { it.isNotEmpty() } val imageUrl = page.imageUrl!! if (url == COMMENTS_FLAG) { return GET(imageUrl, headers).newBuilder() - .tag(CommentsInterceptor.Tag::class.java, CommentsInterceptor.Tag()) + .tag(CommentsInterceptor.Tag::class, CommentsInterceptor.Tag()) .build() } val fallbackUrl = when (preferences.imageQuality) { AUTO_RES -> url ORIGINAL_RES -> null - LOW_RES -> return GET(url, headers) + LOW_RES -> if (url == null) null else return GET(url, headers) else -> url } return GET(imageUrl, headers).newBuilder() - .tag(ImageUrlInterceptor.Tag::class.java, ImageUrlInterceptor.Tag(fallbackUrl)) + .tag(ImageUrlInterceptor.Tag::class, ImageUrlInterceptor.Tag(fallbackUrl)) .build() } // Unused, we can get image urls directly from the chapter page override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("This method should not be called!") + throw UnsupportedOperationException() override fun getFilterList() = getFilterListInternal(preferences.isMultiGenreFilter) override fun setupPreferenceScreen(screen: PreferenceScreen) { - getPreferencesInternal(screen.context, preferences).forEach(screen::addPreference) + getPreferencesInternal(screen.context).forEach(screen::addPreference) } } diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt index 1cc8c5c06..caaf0035b 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt @@ -11,7 +11,7 @@ object ImageUrlInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() - val tag = request.tag(Tag::class.java) ?: return chain.proceed(request) + val tag = request.tag(Tag::class) ?: return chain.proceed(request) try { val response = chain.proceed(request) diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt index bcb21f844..a04d614b6 100644 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt +++ b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt @@ -3,20 +3,21 @@ package eu.kanade.tachiyomi.extension.zh.dmzj import android.content.Context import android.content.SharedPreferences import androidx.preference.ListPreference -import androidx.preference.MultiSelectListPreference import androidx.preference.SwitchPreferenceCompat // Legacy preferences: // "apiRatelimitPreference" -> 1..10 default "5" // "imgCDNRatelimitPreference" -> 1..10 default "5" +// "licensedList" -> StringSet of manga ID +// "hiddenList" -> StringSet of manga ID -fun getPreferencesInternal(context: Context, preferences: SharedPreferences) = arrayOf( +fun getPreferencesInternal(context: Context) = arrayOf( ListPreference(context).apply { key = IMAGE_QUALITY_PREF title = "图片质量" - summary = "%s\n如果选择“只用原图”可能会有部分图片无法加载。" - entries = arrayOf("优先原图", "只用原图", "只用低清") + summary = "%s\n修改后,已加载的章节需要清除章节缓存才能生效。" + entries = arrayOf("优先原图", "只用原图 (加载出错概率更高)", "优先低清") entryValues = arrayOf(AUTO_RES, ORIGINAL_RES, LOW_RES) setDefaultValue(AUTO_RES) }, @@ -34,18 +35,6 @@ fun getPreferencesInternal(context: Context, preferences: SharedPreferences) = a summary = "可以更精细地筛选出同时符合多个题材的作品。" setDefaultValue(false) }, - - MultiSelectListPreference(context).setupIdList( - LICENSED_LIST_PREF, - "特殊漫画 ID 列表 (1)", - preferences.licensedList.toTypedArray(), - ), - - MultiSelectListPreference(context).setupIdList( - HIDDEN_LIST_PREF, - "特殊漫画 ID 列表 (2)", - preferences.hiddenList.toTypedArray(), - ), ) val SharedPreferences.imageQuality get() = getString(IMAGE_QUALITY_PREF, AUTO_RES)!! @@ -54,50 +43,6 @@ val SharedPreferences.showChapterComments get() = getBoolean(CHAPTER_COMMENTS_PR val SharedPreferences.isMultiGenreFilter get() = getBoolean(MULTI_GENRE_FILTER_PREF, false) -val SharedPreferences.licensedList: Set get() = getStringSet(LICENSED_LIST_PREF, emptySet())!! -val SharedPreferences.hiddenList: Set get() = getStringSet(HIDDEN_LIST_PREF, emptySet())!! - -fun SharedPreferences.addLicensed(id: String) = addToSet(LICENSED_LIST_PREF, id, licensedList) -fun SharedPreferences.addHidden(id: String) = addToSet(HIDDEN_LIST_PREF, id, hiddenList) - -private fun MultiSelectListPreference.setupIdList( - key: String, - title: String, - values: Array, -): MultiSelectListPreference { - this.key = key - this.title = title - summary = "如果漫画网页版可以正常访问,但是应用内章节目录加载异常,可以点开列表删除记录。" + - "删除方法是【取消勾选】要删除的 ID 再点击确定,勾选的项目会保留。" + - "如果点开为空,就表示没有记录。刷新漫画页并展开简介即可查看 ID。" - entries = values - entryValues = values - setDefaultValue(emptySet()) - return this -} - -@Synchronized -private fun SharedPreferences.addToSet(key: String, id: String, oldSet: Set) { - if (id in oldSet) return - val newSet = HashSet((oldSet.size + 1) * 2) - newSet.addAll(oldSet) - newSet.add(id) - edit().putStringSet(key, newSet).apply() -} - -fun SharedPreferences.migrate(): SharedPreferences { - val currentVersion = 1 - val versionPref = "version" - val oldVersion = getInt(versionPref, 0) - if (oldVersion >= currentVersion) return this - val editor = edit() - if (oldVersion < 1) { - editor.remove(LICENSED_LIST_PREF).remove(HIDDEN_LIST_PREF) - } - editor.putInt(versionPref, currentVersion).apply() - return this -} - private const val IMAGE_QUALITY_PREF = "imageSourcePreference" const val AUTO_RES = "PREFER_ORIG_RES" const val ORIGINAL_RES = "ORIG_RES_ONLY" @@ -105,6 +50,3 @@ const val LOW_RES = "LOW_RES_ONLY" private const val CHAPTER_COMMENTS_PREF = "chapterComments" private const val MULTI_GENRE_FILTER_PREF = "multiGenreFilter" - -private const val LICENSED_LIST_PREF = "licensedList" -private const val HIDDEN_LIST_PREF = "hiddenList"