diff --git a/src/zh/happymh/build.gradle b/src/zh/happymh/build.gradle index f1304ccc6db..a972d90bff9 100644 --- a/src/zh/happymh/build.gradle +++ b/src/zh/happymh/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Happymh' extClass = '.Happymh' - extVersionCode = 14 + extVersionCode = 15 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/Happymh.kt b/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/Happymh.kt index 2ead01550ba..40307cce907 100644 --- a/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/Happymh.kt +++ b/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/Happymh.kt @@ -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 { - val comicId = response.request.url.pathSegments.last() - val document = response.asJsoup() - val script = document.selectFirst("mip-data > script:containsData(chapterList)")!!.data() - return json.decodeFromString(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().data + } + + private fun fetchChapterByPageObservable( + manga: SManga, + page: Int, + ): Observable { + 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> { + 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 } } diff --git a/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/dto/HappymhDto.kt b/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/dto/HappymhDto.kt index ac5ef9b8204..6811505c583 100644 --- a/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/dto/HappymhDto.kt +++ b/src/zh/happymh/src/eu/kanade/tachiyomi/extension/zh/happymh/dto/HappymhDto.kt @@ -21,10 +21,22 @@ data class MangaDto( // Chapters @Serializable -data class ChapterListDto(val chapterList: List) { - @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, + val isEnd: Int, +) + +@Serializable +class ChapterByPageResponse( + val data: ChapterByPageResponseData, +) // Pages @Serializable