From 7b4cd430a1adfcb15040d3ec37b6f84a969b8ffc Mon Sep 17 00:00:00 2001 From: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> Date: Sun, 9 Oct 2022 14:14:34 -0300 Subject: [PATCH] Update VIZ endpoints (#13788) * Update VIZ endpoints. * Reword the page fetch error. Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- src/en/vizshonenjump/README.md | 53 ++++++++++++++++ src/en/vizshonenjump/build.gradle | 4 +- .../en/vizshonenjump/VizImageInterceptor.kt | 12 ++-- .../en/vizshonenjump/VizShonenJump.kt | 63 ++++++++----------- .../en/vizshonenjump/VizShonenJumpDto.kt | 28 +++++++++ 5 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 src/en/vizshonenjump/README.md create mode 100644 src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJumpDto.kt diff --git a/src/en/vizshonenjump/README.md b/src/en/vizshonenjump/README.md new file mode 100644 index 000000000..94f9854ff --- /dev/null +++ b/src/en/vizshonenjump/README.md @@ -0,0 +1,53 @@ +# VIZ Shonen Jump + +Table of Content +- [FAQ](#FAQ) + - [Why are some chapters missing?](#why-are-some-chapters-missing) +- [Guides](#Guides) + - [Reading subscription-locked chapters](#reading-subscription-locked-chapters) + +Don't find the question you are looking for? Go check out our general FAQs and Guides +over at [Extension FAQ] or [Getting Started]. + +[Extension FAQ]: https://tachiyomi.org/help/faq/#extensions +[Getting Started]: https://tachiyomi.org/help/guides/getting-started/#installation + +## FAQ + +### Why are some chapters missing? + +VIZ Shonen Jump is a paid subscription-based service and you need an active monthly subscription to +be able to read most of the chapters available in their service. These locked chapters will be +filtered out from the chapter list by default if you're not signed in. To sign in with +your existing account, follow the guide available below. + +## Guides + +### Reading subscription-locked chapters + +The **VIZ Shonen Jump** sources allows the reading of paid chapters if you have +an active paid subscription on their service. + +> **Warning** +> Although the extension supports sign in and reading subscription-locked chapters, +> it's more a convenience. **We are not responsible if your account get banned** or something +> related, as using other clients that are not their official app or website goes against +> their Terms of Service. **It's recommended to use their official app or website instead**, +> use this feature in the extension at your own risk. + +Follow the following steps to be able to sign in and get access to the locked chapters: + +1. Open the popular or latest section of the source. +2. Open the WebView by clicking the button with a globe icon. +3. Do the login with your existing account *(read the observations section)*. +4. Close the WebView and refresh the chapter list of the titles + you want to read the already paid chapters. + +#### Observations + +- You may sometime face the *"Your session has expired"* error. To fix it, + you just need to open the WebView, await for the website to completely load and + sign in again if you're not logged anymore. After that, you can close the + WebView and try again. +- The extension **will not** bypass any payment requirement. You still do need + to have an active paid subscription to be able to read the locked chapters. diff --git a/src/en/vizshonenjump/build.gradle b/src/en/vizshonenjump/build.gradle index fc8dc46d9..6b43c3790 100644 --- a/src/en/vizshonenjump/build.gradle +++ b/src/en/vizshonenjump/build.gradle @@ -6,11 +6,11 @@ ext { extName = 'VIZ Shonen Jump' pkgNameSuffix = 'en.vizshonenjump' extClass = '.VizShonenJump' - extVersionCode = 14 + extVersionCode = 15 } dependencies { - implementation 'com.drewnoakes:metadata-extractor:2.14.0' + implementation 'com.drewnoakes:metadata-extractor:2.18.0' } apply from: "$rootDir/common.gradle" diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt index 54ac90bfb..70371154f 100644 --- a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt +++ b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt @@ -22,23 +22,23 @@ class VizImageInterceptor : Interceptor { if (chain.request().url.queryParameter(SIGNATURE) == null) return response - val image = decodeImage(response.body!!.byteStream()) + val image = response.body!!.byteStream().decodeImage() val body = image.toResponseBody(MEDIA_TYPE) return response.newBuilder() .body(body) .build() } - private fun decodeImage(image: InputStream): ByteArray { + private fun InputStream.decodeImage(): ByteArray { // See: https://stackoverflow.com/a/5924132 // See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603 val byteOutputStream = ByteArrayOutputStream() - image.copyTo(byteOutputStream) + copyTo(byteOutputStream) val byteInputStreamForImage = ByteArrayInputStream(byteOutputStream.toByteArray()) val byteInputStreamForMetadata = ByteArrayInputStream(byteOutputStream.toByteArray()) - val imageData = getImageData(byteInputStreamForMetadata) + val imageData = byteInputStreamForMetadata.getImageData() ?: return byteOutputStream.toByteArray() val input = BitmapFactory.decodeStream(byteInputStreamForImage) @@ -127,8 +127,8 @@ class VizImageInterceptor : Interceptor { drawBitmap(from, srcRect, dstRect, null) } - private fun getImageData(inputStream: InputStream): ImageData? { - val metadata = ImageMetadataReader.readMetadata(inputStream) + private fun ByteArrayInputStream.getImageData(): ImageData? { + val metadata = ImageMetadataReader.readMetadata(this) val sizeDir = metadata.directories.firstOrNull { it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) && diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt index 10aa9402d..7574272de 100644 --- a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt +++ b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt @@ -9,15 +9,11 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.int -import kotlinx.serialization.json.intOrNull -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive import okhttp3.CacheControl import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -27,6 +23,7 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable import uy.kohesive.injekt.injectLazy +import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -226,17 +223,16 @@ class VizShonenJump : ParsedHttpSource() { .substringAfterLast("/") .substringBefore("?") - return IntRange(0, pageCount) - .map { - val imageUrl = "$baseUrl/manga/get_manga_url".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("device_id", "3") - .addQueryParameter("manga_id", mangaId) - .addQueryParameter("page", it.toString()) - .addEncodedQueryParameter("referer", document.location()) - .toString() + return IntRange(0, pageCount).map { + val imageUrl = "$baseUrl/manga/get_manga_url".toHttpUrl().newBuilder() + .addQueryParameter("device_id", "3") + .addQueryParameter("manga_id", mangaId) + .addQueryParameter("pages", it.toString()) + .addEncodedQueryParameter("referer", document.location()) + .toString() - Page(it, imageUrl) - } + Page(it, imageUrl) + } } override fun imageUrlRequest(page: Page): Request { @@ -247,6 +243,7 @@ class VizShonenJump : ParsedHttpSource() { .toString() val newHeaders = headersBuilder() + .add("Accept", ACCEPT_JSON) .add("X-Client-Login", (loggedIn ?: false).toString()) .add("X-Requested-With", "XMLHttpRequest") .set("Referer", referer) @@ -256,10 +253,11 @@ class VizShonenJump : ParsedHttpSource() { } override fun imageUrlParse(response: Response): String { - val cdnUrl = response.body!!.string() val referer = response.request.header("Referer")!! + val pageUrl = response.parseAs() + .data?.values?.firstOrNull() ?: throw Exception(FAILED_TO_FETCH_PAGE_URL) - return cdnUrl.toHttpUrlOrNull()!!.newBuilder() + return pageUrl.toHttpUrl().newBuilder() .addEncodedQueryParameter("referer", referer) .toString() } @@ -274,6 +272,7 @@ class VizShonenJump : ParsedHttpSource() { .toString() val newHeaders = headersBuilder() + .add("Accept", "*/*") .set("Referer", referer) .build() @@ -324,15 +323,9 @@ class VizShonenJump : ParsedHttpSource() { .addQueryParameter("manga_id", mangaId) .toString() val authCheckRequest = GET(authCheckUrl, authCheckHeaders) - val authCheckResponse = chain.proceed(authCheckRequest) - val authCheckJson = json.parseToJsonElement(authCheckResponse.body!!.string()).jsonObject + val authCheckResponse = chain.proceed(authCheckRequest).parseAs() - authCheckResponse.close() - - if ( - authCheckJson["ok"]!!.jsonPrimitive.int == 1 && - authCheckJson["archive_info"]!!.jsonObject["ok"]!!.jsonPrimitive.int == 1 - ) { + if (authCheckResponse.ok == 1 && authCheckResponse.archiveInfo?.ok == 1) { val newChapterUrl = chain.request().url.newBuilder() .removeAllQueryParameters("locked") .build() @@ -343,18 +336,15 @@ class VizShonenJump : ParsedHttpSource() { return chain.proceed(newChapterRequest) } - if ( - authCheckJson["archive_info"]!!.jsonObject["err"] is JsonObject && - authCheckJson["archive_info"]!!.jsonObject["err"]!!.jsonObject["code"]?.jsonPrimitive?.intOrNull == 4 && - loggedIn == true - ) { - throw Exception(SESSION_EXPIRED) + if (authCheckResponse.archiveInfo?.error?.code == 4) { + throw IOException(SESSION_EXPIRED) } - val errorMessage = authCheckJson["archive_info"]!!.jsonObject["err"]?.jsonObject - ?.get("msg")?.jsonPrimitive?.contentOrNull ?: AUTH_CHECK_FAILED + throw IOException(authCheckResponse.archiveInfo?.error?.message ?: AUTH_CHECK_FAILED) + } - throw Exception(errorMessage) + private inline fun Response.parseAs(): T = use { + json.decodeFromString(it.body?.string().orEmpty()) } private fun String.toDate(): Long { @@ -365,7 +355,7 @@ class VizShonenJump : ParsedHttpSource() { companion object { private const val ACCEPT_JSON = "application/json, text/javascript, */*; q=0.01" private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36" private val DATE_FORMATTER by lazy { SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH) @@ -374,6 +364,7 @@ class VizShonenJump : ParsedHttpSource() { private const val COUNTRY_NOT_SUPPORTED = "Your country is not supported by the service." private const val SESSION_EXPIRED = "Your session has expired, please log in through WebView again." private const val AUTH_CHECK_FAILED = "Something went wrong in the auth check." + private const val FAILED_TO_FETCH_PAGE_URL = "Something went wrong while trying to fetch page." private const val REFRESH_LOGIN_LINKS_URL = "account/refresh_login_links" private const val MANGA_AUTH_CHECK_URL = "manga/auth" diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJumpDto.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJumpDto.kt new file mode 100644 index 000000000..585e85738 --- /dev/null +++ b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJumpDto.kt @@ -0,0 +1,28 @@ +package eu.kanade.tachiyomi.extension.en.vizshonenjump + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class VizPageUrlDto( + val ok: Int = 0, + val data: Map? = null +) + +@Serializable +data class VizMangaAuthDto( + val ok: Int = 0, + @SerialName("archive_info") val archiveInfo: VizArchiveInfoDto? = null +) + +@Serializable +data class VizArchiveInfoDto( + val ok: Int = 0, + @SerialName("err") val error: VizErrorDto? = null, +) + +@Serializable +data class VizErrorDto( + val code: Int, + @SerialName("msg") val message: String? = null +)