Happymh: Fix chapter list parsing (#6572)

* Happymh: Fix chapter list parsing

* Update src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/Happymh.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
AlphaBoom 2024-12-12 14:23:53 +08:00 committed by Draff
parent 4cc9b24137
commit eb41438fb3
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 79 additions and 23 deletions

View File

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

View File

@ -4,7 +4,8 @@ import android.app.Application
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.zh.happymh.dto.ChapterListDto
import eu.kanade.tachiyomi.extension.zh.happymh.dto.ChapterByPageResponse
import eu.kanade.tachiyomi.extension.zh.happymh.dto.ChapterByPageResponseData
import eu.kanade.tachiyomi.extension.zh.happymh.dto.PageListResponseDto
import eu.kanade.tachiyomi.extension.zh.happymh.dto.PopularResponseDto
import eu.kanade.tachiyomi.network.GET
@ -17,17 +18,18 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.asResponseBody
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@ -38,6 +40,7 @@ class Happymh : HttpSource(), ConfigurableSource {
override val name: String = "嗨皮漫画"
override val lang: String = "zh"
override val supportsLatest: Boolean = true
override val baseUrl: String = "https://m.happymh.com"
private val json: Json by injectLazy()
@ -54,7 +57,10 @@ class Happymh : HttpSource(), ConfigurableSource {
private val rewriteOctetStream: Interceptor = Interceptor { chain ->
val originalResponse: Response = chain.proceed(chain.request())
if (originalResponse.headers("Content-Type").contains("application/octet-stream") && originalResponse.request.url.toString().contains(".jpg")) {
if (originalResponse.headers("Content-Type")
.contains("application/octet-stream") && originalResponse.request.url.toString()
.contains(".jpg")
) {
val orgBody = originalResponse.body.source()
val newBody = orgBody.asResponseBody("image/jpeg".toMediaType())
originalResponse.newBuilder()
@ -139,24 +145,62 @@ class Happymh : HttpSource(), ConfigurableSource {
author = document.selectFirst("div.mg-property > p.mg-sub-title:nth-of-type(2)")!!.text()
artist = author
genre = document.select("div.mg-property > p.mg-cate > a").eachText().joinToString(", ")
description = document.selectFirst("div.manga-introduction > mip-showmore#showmore")!!.text()
description =
document.selectFirst("div.manga-introduction > mip-showmore#showmore")!!.text()
}
// Chapters
override fun chapterListParse(response: Response): List<SChapter> {
val comicId = response.request.url.pathSegments.last()
val document = response.asJsoup()
val script = document.selectFirst("mip-data > script:containsData(chapterList)")!!.data()
return json.decodeFromString<ChapterListDto>(script).chapterList.map {
SChapter.create().apply {
val chapterId = it.id
url = "/reads/$comicId/$chapterId"
name = it.chapterName
private fun fetchChapterByPage(manga: SManga, page: Int): ChapterByPageResponseData {
val code = manga.url.substringAfterLast("/")
val url = "$baseUrl/v2.0/apis/manga/chapterByPage".toHttpUrl().newBuilder()
.addQueryParameter("code", code)
.addQueryParameter("lang", "cn")
.addQueryParameter("order", "asc")
.addQueryParameter("page", "$page")
.build()
return client.newCall(GET(url, headers)).execute().parseAs<ChapterByPageResponse>().data
}
private fun fetchChapterByPageObservable(
manga: SManga,
page: Int,
): Observable<ChapterByPageResponseData> {
return Observable.just(fetchChapterByPage(manga, page)).concatMap {
if (it.isEnd == 1 || it.items.isEmpty()) {
Observable.just(it)
} else {
Observable.just(it).concatWith(fetchChapterByPageObservable(manga, page + 1))
}
}
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return Observable
.defer {
fetchChapterByPageObservable(manga, 1)
}
.map {
it.items.map {
SChapter.create().apply {
name = it.chapterName
url = "/mangaread/${it.codes}"
chapter_number = it.order.toFloat()
}
}
}
.toList()
.map { it.flatten().sortedByDescending { chapter -> chapter.chapter_number } }
.map {
// remove order mark
it.onEach { chapter ->
chapter.chapter_number = 0f
}
}
}
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun getChapterUrl(chapter: SChapter): String {
return baseUrl + chapter.url
}
@ -164,13 +208,12 @@ class Happymh : HttpSource(), ConfigurableSource {
// Pages
override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url.startsWith("/v2.0/apis/manga/read")) {
if (!chapter.url.contains("mangaread")) {
// Old format is detected
throw Exception("请刷新章节列表")
}
val comicId = chapter.url.substringAfter("/reads/").substringBefore("/")
val chapterId = chapter.url.substringAfterLast("/")
val url = "$baseUrl/v2.0/apis/manga/reading?code=$comicId&cid=$chapterId&v=v3.1613134"
val code = chapter.url.substringAfterLast("/")
val url = "$baseUrl/v2.0/apis/manga/reading?code=$code&v=v3.1818134"
// Some chapters return 403 without this header
val header = headersBuilder()
.add("X-Requested-With", "XMLHttpRequest")
@ -210,7 +253,8 @@ class Happymh : HttpSource(), ConfigurableSource {
Headers.headersOf("User-Agent", newValue as String)
true
} catch (e: Throwable) {
Toast.makeText(context, "User Agent 无效:${e.message}", Toast.LENGTH_LONG).show()
Toast.makeText(context, "User Agent 无效:${e.message}", Toast.LENGTH_LONG)
.show()
false
}
}

View File

@ -21,10 +21,22 @@ data class MangaDto(
// Chapters
@Serializable
data class ChapterListDto(val chapterList: List<ChapterDto>) {
@Serializable
data class ChapterDto(val id: String, val chapterName: String)
}
class ChapterByPageResponseDataItem(
val chapterName: String,
val order: Int,
val codes: String,
)
@Serializable
class ChapterByPageResponseData(
val items: List<ChapterByPageResponseDataItem>,
val isEnd: Int,
)
@Serializable
class ChapterByPageResponse(
val data: ChapterByPageResponseData,
)
// Pages
@Serializable