Some fixes for Manga Plus (#1146)

* Fix images not loading and duplicated content.

* Remove Log import from debug.

* Increase version.

* Fix 'Open in Browser' action.
This commit is contained in:
Alessandro Jean 2019-05-30 01:07:02 -03:00 committed by Eugene
parent 110190c239
commit 126a5ec612
2 changed files with 81 additions and 47 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Manga Plus by Shueisha' appName = 'Tachiyomi: Manga Plus by Shueisha'
pkgNameSuffix = 'en.mangaplus' pkgNameSuffix = 'en.mangaplus'
extClass = '.MangaPlus' extClass = '.MangaPlus'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.* import okhttp3.*
import rx.Observable import rx.Observable
import java.lang.Exception import java.lang.Exception
import java.util.UUID.randomUUID import java.util.UUID
class MangaPlus : HttpSource() { class MangaPlus : HttpSource() {
override val name = "Manga Plus by Shueisha" override val name = "Manga Plus by Shueisha"
@ -22,33 +22,37 @@ class MangaPlus : HttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val catalogHeaders = Headers.Builder().apply { private val catalogHeaders = Headers.Builder()
add("Origin", WEB_URL) .apply {
add("Referer", WEB_URL) add("Origin", WEB_URL)
add("User-Agent", USER_AGENT) add("Referer", WEB_URL)
add("SESSION-TOKEN", randomUUID().toString()) add("User-Agent", USER_AGENT)
}.build() add("SESSION-TOKEN", UUID.randomUUID().toString())
}
.build()
override val client = network.client.newBuilder().addInterceptor { override val client = network.client.newBuilder()
var request = it.request() .addInterceptor {
var request = it.request()
if (!request.url().queryParameterNames().contains("encryptionKey")) { if (!request.url().queryParameterNames().contains("encryptionKey")) {
return@addInterceptor it.proceed(request) return@addInterceptor it.proceed(request)
} }
val encryptionKey = request.url().queryParameter("encryptionKey")!! val encryptionKey = request.url().queryParameter("encryptionKey")!!
// Change the url and remove the encryptionKey to avoid detection. // Change the url and remove the encryptionKey to avoid detection.
val newUrl = request.url().newBuilder().removeAllQueryParameters("encryptionKey").build() val newUrl = request.url().newBuilder().removeAllQueryParameters("encryptionKey").build()
request = request.newBuilder().url(newUrl).build() request = request.newBuilder().url(newUrl).build()
val response = it.proceed(request) val response = it.proceed(request)
val image = decodeImage(encryptionKey, response.body()!!.bytes()) val image = decodeImage(encryptionKey, response.body()!!.bytes())
val body = ResponseBody.create(MediaType.parse("image/jpeg"), image) val body = ResponseBody.create(MediaType.parse("image/jpeg"), image)
response.newBuilder().body(body).build() response.newBuilder().body(body).build()
}.build() }
.build()
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/title_list/ranking", catalogHeaders) return GET("$baseUrl/title_list/ranking", catalogHeaders)
@ -60,13 +64,15 @@ class MangaPlus : HttpSource() {
if (result["success"] == null) if (result["success"] == null)
return MangasPage(emptyList(), false) return MangasPage(emptyList(), false)
val mangas = result["success"]["titleRankingView"]["titles"].array.map { val mangas = result["success"]["titleRankingView"]["titles"].array
SManga.create().apply { .filter { if (it.obj.has("language")) it["language"].int < 1 else true }
title = it["name"].string .map {
thumbnail_url = it["portraitImageUrl"].string SManga.create().apply {
url = "#/titles/${it["titleId"].int}" title = it["name"].string
} thumbnail_url = it["portraitImageUrl"].string
} url = "#/titles/${it["titleId"].int}"
}
}
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
@ -84,21 +90,21 @@ class MangaPlus : HttpSource() {
val mangas = result["success"]["webHomeView"]["groups"].array val mangas = result["success"]["webHomeView"]["groups"].array
.flatMap { it["titles"].array } .flatMap { it["titles"].array }
.mapNotNull { it["title"].obj } .mapNotNull { it["title"].obj }
.filter { if (it.obj.has("language")) it["language"].int < 1 else true }
.map { .map {
SManga.create().apply { SManga.create().apply {
title = it["name"].string title = it["name"].string
thumbnail_url = it["portraitImageUrl"].string thumbnail_url = removeDuration(it["portraitImageUrl"].string)
url = "#/titles/${it["titleId"].int}" url = "#/titles/${it["titleId"].int}"
} }
} }
.distinctBy { it.title }
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters)) return super.fetchSearchManga(page, query, filters)
.asObservableSuccess()
.map { searchMangaParse(it) }
.map { MangasPage(it.mangas.filter { m -> m.title.contains(query, true) }, it.hasNextPage) } .map { MangasPage(it.mangas.filter { m -> m.title.contains(query, true) }, it.hasNextPage) }
} }
@ -112,13 +118,15 @@ class MangaPlus : HttpSource() {
if (result["success"] == null) if (result["success"] == null)
return MangasPage(emptyList(), false) return MangasPage(emptyList(), false)
val mangas = result["success"]["allTitlesView"]["titles"].array.map { val mangas = result["success"]["allTitlesView"]["titles"].array
SManga.create().apply { .filter { if (it.obj.has("language")) it["language"].int < 1 else true }
title = it["name"].string .map {
thumbnail_url = it["portraitImageUrl"].string SManga.create().apply {
url = "#/titles/${it["titleId"].int}" title = it["name"].string
} thumbnail_url = removeDuration(it["portraitImageUrl"].string)
} url = "#/titles/${it["titleId"].int}"
}
}
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
@ -128,13 +136,23 @@ class MangaPlus : HttpSource() {
return GET("$baseUrl/title_detail?title_id=$mangaId", catalogHeaders) return GET("$baseUrl/title_detail?title_id=$mangaId", catalogHeaders)
} }
override fun mangaDetailsRequest(manga: SManga): Request = titleDetailsRequest(manga) // Workaround to allow "Open in browser" use the real URL.
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(titleDetailsRequest(manga))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
// Always returns the real URL for the "Open in browser".
override fun mangaDetailsRequest(manga: SManga): Request = GET(WEB_URL + manga.url, catalogHeaders)
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val result = response.asProto() val result = response.asProto()
if (result["success"] == null) if (result["success"] == null)
throw Exception("Some error happened when trying to obtain the title details.") throw Exception(result["error"]["englishPopup"]["body"].string)
val details = result["success"]["titleDetailView"].obj val details = result["success"]["titleDetailView"].obj
val title = details["title"].obj val title = details["title"].obj
@ -144,6 +162,7 @@ class MangaPlus : HttpSource() {
artist = title["author"].string artist = title["author"].string
description = details["overview"].string + "\n\n" + details["viewingPeriodDescription"].string description = details["overview"].string + "\n\n" + details["viewingPeriodDescription"].string
status = SManga.ONGOING status = SManga.ONGOING
thumbnail_url = removeDuration(title["portraitImageUrl"].string)
} }
} }
@ -153,7 +172,7 @@ class MangaPlus : HttpSource() {
val result = response.asProto() val result = response.asProto()
if (result["success"] == null) if (result["success"] == null)
return emptyList() throw Exception(result["error"]["englishPopup"]["body"].string)
val titleDetailView = result["success"]["titleDetailView"].obj val titleDetailView = result["success"]["titleDetailView"].obj
@ -183,7 +202,7 @@ class MangaPlus : HttpSource() {
val result = response.asProto() val result = response.asProto()
if (result["success"] == null) if (result["success"] == null)
return emptyList() throw Exception(result["error"]["englishPopup"]["body"].string)
return result["success"]["mangaViewer"]["pages"].array return result["success"]["mangaViewer"]["pages"].array
.mapNotNull { it["page"].obj } .mapNotNull { it["page"].obj }
@ -203,11 +222,14 @@ class MangaPlus : HttpSource() {
val newHeaders = Headers.Builder().apply { val newHeaders = Headers.Builder().apply {
add("Referer", WEB_URL) add("Referer", WEB_URL)
add("User-Agent", USER_AGENT) add("User-Agent", USER_AGENT)
}.build() }
return GET(page.imageUrl!!, newHeaders) return GET(page.imageUrl!!, newHeaders.build())
} }
// Maybe removing the duration parameter make the image accessible forever.
private fun removeDuration(url: String): String = url.substringBefore("&duration")
private fun decodeImage(encryptionKey: String, image: ByteArray): ByteArray { private fun decodeImage(encryptionKey: String, image: ByteArray): ByteArray {
val variablesSrc = """ val variablesSrc = """
var ENCRYPTION_KEY = "$encryptionKey"; var ENCRYPTION_KEY = "$encryptionKey";
@ -293,6 +315,7 @@ class MangaPlus : HttpSource() {
var Root = protobuf.Root; var Root = protobuf.Root;
var Type = protobuf.Type; var Type = protobuf.Type;
var Field = protobuf.Field; var Field = protobuf.Field;
var Enum = protobuf.Enum;
var Response = new Type("Response") var Response = new Type("Response")
.add(new Field("success", 1, "SuccessResult")) .add(new Field("success", 1, "SuccessResult"))
@ -303,6 +326,10 @@ class MangaPlus : HttpSource() {
.add(new Field("englishPopup", 2, "Popup")) .add(new Field("englishPopup", 2, "Popup"))
.add(new Field("spanishPopup", 3, "Popup")); .add(new Field("spanishPopup", 3, "Popup"));
var Popup = new Type("Popup")
.add(new Field("subject", 1, "string"))
.add(new Field("body", 2, "string"));
var SuccessResult = new Type("SuccessResult") var SuccessResult = new Type("SuccessResult")
.add(new Field("allTitlesView", 5, "AllTitlesView")) .add(new Field("allTitlesView", 5, "AllTitlesView"))
.add(new Field("titleRankingView", 6, "TitleRankingView")) .add(new Field("titleRankingView", 6, "TitleRankingView"))
@ -325,6 +352,7 @@ class MangaPlus : HttpSource() {
.add(new Field("viewingPeriodDescription", 7, "string")) .add(new Field("viewingPeriodDescription", 7, "string"))
.add(new Field("firstChapterList", 9, "Chapter", "repeated")) .add(new Field("firstChapterList", 9, "Chapter", "repeated"))
.add(new Field("lastChapterList", 10, "Chapter", "repeated")) .add(new Field("lastChapterList", 10, "Chapter", "repeated"))
.add(new Field("isSimulReleased", 14, "bool"))
.add(new Field("chaptersDescending", 17, "bool")); .add(new Field("chaptersDescending", 17, "bool"));
var MangaViewer = new Type("MangaViewer") var MangaViewer = new Type("MangaViewer")
@ -335,7 +363,11 @@ class MangaPlus : HttpSource() {
.add(new Field("name", 2, "string")) .add(new Field("name", 2, "string"))
.add(new Field("author", 3, "string")) .add(new Field("author", 3, "string"))
.add(new Field("portraitImageUrl", 4, "string")) .add(new Field("portraitImageUrl", 4, "string"))
.add(new Field("language", 7, "int32")); .add(new Field("landscapeImageUrl", 5, "string"))
.add(new Field("viewCount", 6, "uint32"))
.add(new Field("language", 7, "Language"));
var Language = new Enum("Language", {"english": 0, "spanish": 1});
var UpdatedTitleGroup = new Type("UpdatedTitleGroup") var UpdatedTitleGroup = new Type("UpdatedTitleGroup")
.add(new Field("groupName", 1, "string")) .add(new Field("groupName", 1, "string"))
@ -368,6 +400,7 @@ class MangaPlus : HttpSource() {
.define("mangaplus") .define("mangaplus")
.add(Response) .add(Response)
.add(ErrorResult) .add(ErrorResult)
.add(Popup)
.add(SuccessResult) .add(SuccessResult)
.add(TitleRankingView) .add(TitleRankingView)
.add(AllTitlesView) .add(AllTitlesView)
@ -375,6 +408,7 @@ class MangaPlus : HttpSource() {
.add(TitleDetailView) .add(TitleDetailView)
.add(MangaViewer) .add(MangaViewer)
.add(Title) .add(Title)
.add(Language)
.add(UpdatedTitleGroup) .add(UpdatedTitleGroup)
.add(UpdatedTitle) .add(UpdatedTitle)
.add(Chapter) .add(Chapter)