From 88fc45c2d14895ba0ab7a76d4659e3c56a2b49b3 Mon Sep 17 00:00:00 2001 From: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> Date: Mon, 4 Apr 2022 08:43:15 -0300 Subject: [PATCH] Use MangaPlus JSON API instead of Protobuf. (#11326) --- src/all/mangaplus/AndroidManifest.xml | 8 + src/all/mangaplus/build.gradle | 2 +- .../extension/all/mangaplus/MangaPlus.kt | 39 ++--- .../extension/all/mangaplus/MangaPlusDto.kt | 138 ++++++++---------- .../all/mangaplus/MangaPlusUrlActivity.kt | 25 ++-- 5 files changed, 108 insertions(+), 104 deletions(-) diff --git a/src/all/mangaplus/AndroidManifest.xml b/src/all/mangaplus/AndroidManifest.xml index d4d44d41d..51dd67096 100644 --- a/src/all/mangaplus/AndroidManifest.xml +++ b/src/all/mangaplus/AndroidManifest.xml @@ -22,6 +22,14 @@ android:host="www.mangaplus.shueisha.co.jp" android:pathPattern="/titles/..*" android:scheme="https" /> + + diff --git a/src/all/mangaplus/build.gradle b/src/all/mangaplus/build.gradle index 9cdcb982c..3f3b66d85 100644 --- a/src/all/mangaplus/build.gradle +++ b/src/all/mangaplus/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'MANGA Plus by SHUEISHA' pkgNameSuffix = 'all.mangaplus' extClass = '.MangaPlusFactory' - extVersionCode = 28 + extVersionCode = 29 } dependencies { diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt index b089dff26..fceb92653 100644 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt +++ b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt @@ -15,8 +15,8 @@ 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.decodeFromByteArray -import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -29,6 +29,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.util.UUID abstract class MangaPlus( @@ -56,6 +57,8 @@ abstract class MangaPlus( .addInterceptor(SpecificHostRateLimitInterceptor(baseUrl.toHttpUrl(), 2)) .build() + private val json: Json by injectLazy() + private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } @@ -73,11 +76,11 @@ abstract class MangaPlus( .set("Referer", "$baseUrl/manga_list/hot") .build() - return GET("$API_URL/title_list/ranking", newHeaders) + return GET("$API_URL/title_list/ranking?format=json", newHeaders) } override fun popularMangaParse(response: Response): MangasPage { - val result = response.asProto() + val result = response.asMangaPlusResponse() if (result.success == null) throw Exception(result.error!!.langPopup.body) @@ -101,17 +104,18 @@ abstract class MangaPlus( .set("Referer", "$baseUrl/updates") .build() - return GET("$API_URL/web/web_homeV3?lang=$internalLang", newHeaders) + return GET("$API_URL/web/web_homeV3?lang=$internalLang&format=json", newHeaders) } override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.asProto() + val result = response.asMangaPlusResponse() if (result.success == null) throw Exception(result.error!!.langPopup.body) // Fetch all titles to get newer thumbnail URLs in the interceptor. - val popularResponse = client.newCall(popularMangaRequest(1)).execute().asProto() + val popularResponse = client.newCall(popularMangaRequest(1)).execute() + .asMangaPlusResponse() if (popularResponse.success != null) { titleList = popularResponse.success.titleRankingView!!.titles @@ -143,7 +147,7 @@ abstract class MangaPlus( } val filteredResult = it.mangas.filter { manga -> - manga.title.contains(query, true) + manga.title.contains(query.trim(), true) } MangasPage(filteredResult, it.hasNextPage) @@ -159,11 +163,11 @@ abstract class MangaPlus( .set("Referer", "$baseUrl/manga_list/all") .build() - return GET("$API_URL/title_list/allV2", newHeaders) + return GET("$API_URL/title_list/allV2?format=json", newHeaders) } override fun searchMangaParse(response: Response): MangasPage { - val result = response.asProto() + val result = response.asMangaPlusResponse() if (result.success == null) throw Exception(result.error!!.langPopup.body) @@ -206,7 +210,7 @@ abstract class MangaPlus( .set("Referer", "$baseUrl/titles/$titleId") .build() - return GET("$API_URL/title_detail?title_id=$titleId", newHeaders) + return GET("$API_URL/title_detail?title_id=$titleId&format=json", newHeaders) } // Workaround to allow "Open in browser" use the real URL. @@ -224,7 +228,7 @@ abstract class MangaPlus( } override fun mangaDetailsParse(response: Response): SManga { - val result = response.asProto() + val result = response.asMangaPlusResponse() if (result.success == null) throw Exception(result.error!!.langPopup.body) @@ -245,7 +249,7 @@ abstract class MangaPlus( override fun chapterListRequest(manga: SManga): Request = titleDetailsRequest(manga.url) override fun chapterListParse(response: Response): List { - val result = response.asProto() + val result = response.asMangaPlusResponse() if (result.success == null) throw Exception(result.error!!.langPopup.body) @@ -278,13 +282,14 @@ abstract class MangaPlus( .addQueryParameter("chapter_id", chapterId) .addQueryParameter("split", if (splitImages) "yes" else "no") .addQueryParameter("img_quality", imageQuality) + .addQueryParameter("format", "json") .toString() return GET(url, newHeaders) } override fun pageListParse(response: Response): List { - val result = response.asProto() + val result = response.asMangaPlusResponse() if (result.success == null) throw Exception(result.error!!.langPopup.body) @@ -292,7 +297,7 @@ abstract class MangaPlus( val referer = response.request.header("Referer")!! return result.success.mangaViewer!!.pages - .mapNotNull(MangaPlusPage::page) + .mapNotNull(MangaPlusPage::mangaPage) .mapIndexed { i, page -> val encryptionKey = if (page.encryptionKey == null) "" else "&encryptionKey=${page.encryptionKey}" @@ -418,8 +423,8 @@ abstract class MangaPlus( else -> englishPopup } - private fun Response.asProto(): MangaPlusResponse = use { - ProtoBuf.decodeFromByteArray(body!!.bytes()) + private fun Response.asMangaPlusResponse(): MangaPlusResponse = use { + json.decodeFromString(body!!.string()) } companion object { diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt index f6e6a383b..c047040d7 100644 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt +++ b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt @@ -1,66 +1,66 @@ package eu.kanade.tachiyomi.extension.all.mangaplus +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.protobuf.ProtoNumber @Serializable data class MangaPlusResponse( - @ProtoNumber(1) val success: SuccessResult? = null, - @ProtoNumber(2) val error: ErrorResult? = null + val success: SuccessResult? = null, + val error: ErrorResult? = null ) @Serializable data class ErrorResult( - @ProtoNumber(2) val englishPopup: Popup, - @ProtoNumber(3) val spanishPopup: Popup + val englishPopup: Popup, + val spanishPopup: Popup ) @Serializable data class Popup( - @ProtoNumber(1) val subject: String, - @ProtoNumber(2) val body: String + val subject: String, + val body: String ) @Serializable data class SuccessResult( - @ProtoNumber(1) val isFeaturedUpdated: Boolean? = false, - @ProtoNumber(6) val titleRankingView: TitleRankingView? = null, - @ProtoNumber(8) val titleDetailView: TitleDetailView? = null, - @ProtoNumber(10) val mangaViewer: MangaViewer? = null, - @ProtoNumber(25) val allTitlesViewV2: AllTitlesViewV2? = null, - @ProtoNumber(31) val webHomeViewV3: WebHomeViewV3? = null + val isFeaturedUpdated: Boolean? = false, + val titleRankingView: TitleRankingView? = null, + val titleDetailView: TitleDetailView? = null, + val mangaViewer: MangaViewer? = null, + val allTitlesViewV2: AllTitlesViewV2? = null, + val webHomeViewV3: WebHomeViewV3? = null ) @Serializable -data class TitleRankingView(@ProtoNumber(1) val titles: List = emptyList()) +data class TitleRankingView(val titles: List<Title> = emptyList()) @Serializable data class AllTitlesViewV2( - @ProtoNumber(1) val allTitlesGroup: List<AllTitlesGroup> = emptyList() + @SerialName("AllTitlesGroup") val allTitlesGroup: List<AllTitlesGroup> = emptyList() ) @Serializable data class AllTitlesGroup( - @ProtoNumber(1) val theTitle: String, - @ProtoNumber(2) val titles: List<Title> = emptyList() + val theTitle: String, + val titles: List<Title> = emptyList() ) @Serializable -data class WebHomeViewV3(@ProtoNumber(2) val groups: List<UpdatedTitleV2Group> = emptyList()) +data class WebHomeViewV3(val groups: List<UpdatedTitleV2Group> = emptyList()) @Serializable data class TitleDetailView( - @ProtoNumber(1) val title: Title, - @ProtoNumber(2) val titleImageUrl: String, - @ProtoNumber(3) val overview: String, - @ProtoNumber(4) val backgroundImageUrl: String, - @ProtoNumber(5) val nextTimeStamp: Int = 0, - @ProtoNumber(7) val viewingPeriodDescription: String = "", - @ProtoNumber(8) val nonAppearanceInfo: String = "", - @ProtoNumber(9) val firstChapterList: List<Chapter> = emptyList(), - @ProtoNumber(10) val lastChapterList: List<Chapter> = emptyList(), - @ProtoNumber(14) val isSimulReleased: Boolean = false, - @ProtoNumber(17) val chaptersDescending: Boolean = true + val title: Title, + val titleImageUrl: String, + val overview: String, + val backgroundImageUrl: String, + val nextTimeStamp: Int = 0, + val viewingPeriodDescription: String = "", + val nonAppearanceInfo: String = "", + val firstChapterList: List<Chapter> = emptyList(), + val lastChapterList: List<Chapter> = emptyList(), + val isSimulReleased: Boolean = false, + val chaptersDescending: Boolean = true ) { private val isWebtoon: Boolean get() = firstChapterList.all(Chapter::isVerticalOnly) && @@ -89,76 +89,62 @@ data class TitleDetailView( } @Serializable -data class MangaViewer(@ProtoNumber(1) val pages: List<MangaPlusPage> = emptyList()) +data class MangaViewer(val pages: List<MangaPlusPage> = emptyList()) @Serializable data class Title( - @ProtoNumber(1) val titleId: Int, - @ProtoNumber(2) val name: String, - @ProtoNumber(3) val author: String, - @ProtoNumber(4) val portraitImageUrl: String, - @ProtoNumber(5) val landscapeImageUrl: String, - @ProtoNumber(6) val viewCount: Int = 0, - @ProtoNumber(7) val language: Language? = Language.ENGLISH + val titleId: Int, + val name: String, + val author: String, + val portraitImageUrl: String, + val landscapeImageUrl: String, + val viewCount: Int = 0, + val language: Language? = Language.ENGLISH ) -@Serializable -enum class Language(val id: Int) { - @ProtoNumber(0) - ENGLISH(0), - - @ProtoNumber(1) - SPANISH(1), - - @ProtoNumber(2) - FRENCH(2), - - @ProtoNumber(3) - INDONESIAN(4), - - @ProtoNumber(4) - PORTUGUESE_BR(4), - - @ProtoNumber(5) - RUSSIAN(5), - - @ProtoNumber(6) - THAI(6) +enum class Language { + ENGLISH, + SPANISH, + FRENCH, + INDONESIAN, + PORTUGUESE_BR, + RUSSIAN, + THAI } @Serializable data class UpdatedTitleV2Group( - @ProtoNumber(1) val groupName: String, - @ProtoNumber(2) val titleGroups: List<OriginalTitleGroup> = emptyList() + val groupName: String, + val titleGroups: List<OriginalTitleGroup> = emptyList() ) @Serializable data class OriginalTitleGroup( - @ProtoNumber(1) val theTitle: String, - @ProtoNumber(3) val titles: List<UpdatedTitle> = emptyList() + val theTitle: String, + val titles: List<UpdatedTitle> = emptyList() ) @Serializable -data class UpdatedTitle(@ProtoNumber(1) val title: Title) +data class UpdatedTitle(val title: Title) @Serializable data class Chapter( - @ProtoNumber(1) val titleId: Int, - @ProtoNumber(2) val chapterId: Int, - @ProtoNumber(3) val name: String, - @ProtoNumber(4) val subTitle: String? = null, - @ProtoNumber(6) val startTimeStamp: Int, - @ProtoNumber(7) val endTimeStamp: Int, - @ProtoNumber(9) val isVerticalOnly: Boolean = false + val titleId: Int, + val chapterId: Int, + val name: String, + val subTitle: String? = null, + val startTimeStamp: Int, + val endTimeStamp: Int, + val isVerticalOnly: Boolean = false ) @Serializable -data class MangaPlusPage(@ProtoNumber(1) val page: MangaPage? = null) +data class MangaPlusPage(val mangaPage: MangaPage? = null) @Serializable data class MangaPage( - @ProtoNumber(1) val imageUrl: String, - @ProtoNumber(2) val width: Int, - @ProtoNumber(3) val height: Int, - @ProtoNumber(5) val encryptionKey: String? = null + val imageUrl: String, + val width: Int, + val height: Int, + val encryptionKey: String? = null ) diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusUrlActivity.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusUrlActivity.kt index 85bac6877..2d70c4a0a 100644 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusUrlActivity.kt +++ b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusUrlActivity.kt @@ -14,18 +14,23 @@ class MangaPlusUrlActivity : Activity() { val pathSegments = intent?.data?.pathSegments if (pathSegments != null && pathSegments.size > 1) { - val titleId = pathSegments[1] + val titleId = if (!pathSegments[1].equals("sns_share")) pathSegments[1] else + intent?.data?.getQueryParameter("title_id") - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", MangaPlus.PREFIX_ID_SEARCH + titleId) - putExtra("filter", packageName) - } + if (titleId != null) { + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", MangaPlus.PREFIX_ID_SEARCH + titleId) + putExtra("filter", packageName) + } - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("MangaPlusUrlActivity", e.toString()) + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("MangaPlusUrlActivity", e.toString()) + } + } else { + Log.e("MangaPlusUrlActivity", "Missing title ID from the URL") } } else { Log.e("MangaPlusUrlActivity", "Could not parse URI from intent $intent")