Happymh: Fix temporary chapter url issue. (#6731)

* Happymh: Fix temporary chapter url issue.

* Apply suggestions from code review

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

* Happymh: Apply review suggestions.

* Use Httpurl to parse.

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
This commit is contained in:
AlphaBoom 2024-12-25 07:53:40 +08:00 committed by Draff
parent a90166842c
commit f2208ff245
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 55 additions and 18 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Happymh' extName = 'Happymh'
extClass = '.Happymh' extClass = '.Happymh'
extVersionCode = 15 extVersionCode = 16
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.zh.happymh package eu.kanade.tachiyomi.extension.zh.happymh
import android.app.Application import android.app.Application
import android.util.LruCache
import android.widget.Toast import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
@ -43,6 +44,7 @@ class Happymh : HttpSource(), ConfigurableSource {
override val baseUrl: String = "https://m.happymh.com" override val baseUrl: String = "https://m.happymh.com"
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val chapterUrlToCode = LruCache<String, String>(10000)
private val preferences = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) private val preferences = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
@ -151,10 +153,9 @@ class Happymh : HttpSource(), ConfigurableSource {
// Chapters // Chapters
private fun fetchChapterByPage(manga: SManga, page: Int): ChapterByPageResponseData { private fun fetchChapterByPage(comicId: String, page: Int): ChapterByPageResponseData {
val code = manga.url.substringAfterLast("/")
val url = "$baseUrl/v2.0/apis/manga/chapterByPage".toHttpUrl().newBuilder() val url = "$baseUrl/v2.0/apis/manga/chapterByPage".toHttpUrl().newBuilder()
.addQueryParameter("code", code) .addQueryParameter("code", comicId)
.addQueryParameter("lang", "cn") .addQueryParameter("lang", "cn")
.addQueryParameter("order", "asc") .addQueryParameter("order", "asc")
.addQueryParameter("page", "$page") .addQueryParameter("page", "$page")
@ -162,30 +163,37 @@ class Happymh : HttpSource(), ConfigurableSource {
return client.newCall(GET(url, headers)).execute().parseAs<ChapterByPageResponse>().data return client.newCall(GET(url, headers)).execute().parseAs<ChapterByPageResponse>().data
} }
private fun fetchChapterByPageObservable( private fun fetchChapterByPage(manga: SManga, page: Int): ChapterByPageResponseData {
val comicId = "$baseUrl${manga.url}".toHttpUrl().pathSegments.last()
return fetchChapterByPage(comicId, page)
}
private fun fetchChapterByPageAsObservable(
manga: SManga, manga: SManga,
page: Int, page: Int,
): Observable<ChapterByPageResponseData> { ): Observable<Pair<Int, ChapterByPageResponseData>> {
return Observable.just(fetchChapterByPage(manga, page)).concatMap { return Observable.just(fetchChapterByPage(manga, page)).concatMap {
if (it.isEnd == 1 || it.items.isEmpty()) { if (it.isPageEnd()) {
Observable.just(it) Observable.just(page to it)
} else { } else {
Observable.just(it).concatWith(fetchChapterByPageObservable(manga, page + 1)) Observable.just(page to it).concatWith(fetchChapterByPageAsObservable(manga, page + 1))
} }
} }
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val comicId = "$baseUrl${manga.url}".toHttpUrl().pathSegments.last()
return Observable return Observable
.defer { .defer {
fetchChapterByPageObservable(manga, 1) fetchChapterByPageAsObservable(manga, 1)
} }
.map { .map {
it.items.map { it.second.items.map { data ->
SChapter.create().apply { SChapter.create().apply {
name = it.chapterName name = data.chapterName
url = "/mangaread/${it.codes}" // create a dummy chapter url : /comic_id/dummy_mark/chapter_id#expect_page
chapter_number = it.order.toFloat() url = "/$comicId/$DUMMY_CHAPTER_MARK/${data.id}#${it.first}"
chapter_number = data.order.toFloat()
} }
} }
} }
@ -194,7 +202,7 @@ class Happymh : HttpSource(), ConfigurableSource {
.map { .map {
// remove order mark // remove order mark
it.onEach { chapter -> it.onEach { chapter ->
chapter.chapter_number = 0f chapter.chapter_number = -1f
} }
} }
} }
@ -202,17 +210,37 @@ class Happymh : HttpSource(), ConfigurableSource {
override fun chapterListParse(response: Response) = throw UnsupportedOperationException() override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun getChapterUrl(chapter: SChapter): String { override fun getChapterUrl(chapter: SChapter): String {
return baseUrl + chapter.url return baseUrl + (chapterUrlToCode.get(chapter.url)?.let { "/mangaread/$it" } ?: chapter.url)
} }
// Pages // Pages
private fun fetchChapterCode(chapter: SChapter): String? {
val url = "$baseUrl${chapter.url}".toHttpUrl()
val expectPage = url.fragment?.toIntOrNull() ?: 1
val comicId = url.pathSegments[0]
val chapterId = url.pathSegments[2].toLong()
var code = fetchChapterByPage(comicId, expectPage).items.find { it.id == chapterId }?.codes
if (code == null) {
// Do full search for find target code
var page = 1
var end = false
while (!end && code == null) {
val resp = fetchChapterByPage(comicId, page)
code = resp.items.find { it.id == chapterId }?.codes
end = resp.isPageEnd()
page += 1
}
}
return code?.also { chapterUrlToCode.put(chapter.url, it) }
}
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
if (!chapter.url.contains("mangaread")) { if (!chapter.url.contains(DUMMY_CHAPTER_MARK)) {
// Old format is detected // Old format is detected
throw Exception("请刷新章节列表") throw Exception("请刷新章节列表")
} }
val code = chapter.url.substringAfterLast("/") val code = fetchChapterCode(chapter) ?: throw Exception("找不到章节地址,请尝试刷新章节列表")
val url = "$baseUrl/v2.0/apis/manga/reading?code=$code&v=v3.1818134" val url = "$baseUrl/v2.0/apis/manga/reading?code=$code&v=v3.1818134"
// Some chapters return 403 without this header // Some chapters return 403 without this header
val header = headersBuilder() val header = headersBuilder()
@ -264,4 +292,12 @@ class Happymh : HttpSource(), ConfigurableSource {
private inline fun <reified T> Response.parseAs(): T = use { private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromStream(it.body.byteStream()) json.decodeFromStream(it.body.byteStream())
} }
private fun ChapterByPageResponseData.isPageEnd(): Boolean {
return isEnd == 1 || items.isEmpty()
}
companion object {
private const val DUMMY_CHAPTER_MARK = "dummy-mark"
}
} }

View File

@ -22,6 +22,7 @@ data class MangaDto(
// Chapters // Chapters
@Serializable @Serializable
class ChapterByPageResponseDataItem( class ChapterByPageResponseDataItem(
val id: Long,
val chapterName: String, val chapterName: String,
val order: Int, val order: Int,
val codes: String, val codes: String,