From a912752ea59c8664e81b993a892678ff1639f09a Mon Sep 17 00:00:00 2001 From: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> Date: Mon, 17 Apr 2023 17:24:28 -0300 Subject: [PATCH] Fix chapters download in VIZ due to expiration time (#16085) Fix chapters download due to expiration time. --- src/en/vizshonenjump/build.gradle | 2 +- .../en/vizshonenjump/VizImageInterceptor.kt | 50 ++++++++++++++--- .../en/vizshonenjump/VizShonenJump.kt | 56 +++++-------------- 3 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/en/vizshonenjump/build.gradle b/src/en/vizshonenjump/build.gradle index 6b43c3790..793c0613f 100644 --- a/src/en/vizshonenjump/build.gradle +++ b/src/en/vizshonenjump/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'VIZ Shonen Jump' pkgNameSuffix = 'en.vizshonenjump' extClass = '.VizShonenJump' - extVersionCode = 15 + extVersionCode = 16 } dependencies { 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 d76205b18..0a4697b9f 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 @@ -4,32 +4,62 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Rect -import com.drew.imaging.ImageMetadataReader +import com.drew.imaging.jpeg.JpegMetadataReader import com.drew.metadata.exif.ExifSubIFDDirectory +import eu.kanade.tachiyomi.network.GET +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody.Companion.toResponseBody +import uy.kohesive.injekt.injectLazy import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream +import java.io.IOException import java.io.InputStream class VizImageInterceptor : Interceptor { + private val json: Json by injectLazy() + override fun intercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) - if (chain.request().url.queryParameter(SIGNATURE) == null) { + if (!chain.request().url.toString().contains(IMAGE_URL_ENDPOINT)) { return response } - val image = response.body.byteStream().decodeImage() + val imageUrl = imageUrlParse(response) + val imageResponse = chain.proceed(imageRequest(imageUrl)) + + val image = imageResponse.body.byteStream().decodeImage() val body = image.toResponseBody(MEDIA_TYPE) - return response.newBuilder() + + return imageResponse.newBuilder() .body(body) + .header("Content-Type", MEDIA_TYPE.toString()) .build() } + private fun imageUrlParse(response: Response): String { + return response.use { json.decodeFromString(it.body.string()) } + .data?.values?.firstOrNull() ?: throw IOException(FAILED_TO_FETCH_PAGE_URL) + } + + private fun imageRequest(url: String): Request { + val headers = Headers.Builder() + .add("Accept", "*/*") + .add("Origin", "https://www.viz.com") + .add("Referer", "https://www.viz.com/") + .add("User-Agent", VizShonenJump.USER_AGENT) + .build() + + return GET(url, headers) + } + private fun InputStream.decodeImage(): ByteArray { // See: https://stackoverflow.com/a/5924132 // See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603 @@ -110,7 +140,7 @@ class VizImageInterceptor : Interceptor { } val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) + result.compress(Bitmap.CompressFormat.JPEG, 95, output) return output.toByteArray() } @@ -129,7 +159,7 @@ class VizImageInterceptor : Interceptor { } private fun ByteArrayInputStream.getImageData(): ImageData? { - val metadata = ImageMetadataReader.readMetadata(this) + val metadata = JpegMetadataReader.readMetadata(this) val sizeDir = metadata.directories.firstOrNull { it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) && @@ -155,8 +185,10 @@ class VizImageInterceptor : Interceptor { } companion object { - private const val SIGNATURE = "Signature" - private val MEDIA_TYPE = "image/png".toMediaTypeOrNull() + private const val IMAGE_URL_ENDPOINT = "get_manga_url" + private val MEDIA_TYPE = "image/jpeg".toMediaType() + + private const val FAILED_TO_FETCH_PAGE_URL = "Something went wrong while trying to fetch page." private const val CELL_WIDTH_COUNT = 10 private const val CELL_HEIGHT_COUNT = 15 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 0e9c8e4dc..bcd9a60c2 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 @@ -228,55 +228,30 @@ class VizShonenJump : ParsedHttpSource() { .addQueryParameter("device_id", "3") .addQueryParameter("manga_id", mangaId) .addQueryParameter("pages", it.toString()) - .addEncodedQueryParameter("referer", document.location()) .toString() - Page(it, imageUrl) + // The image URL is actually fetched in the interceptor to avoid the short + // time expiration it have. Using the interceptor will guarantee the requests + // always follow the expected order, even when downloading: + // imageUrlRequest -> imageRequest -> decryption + // By using the url field of page, while downloading through the app it will + // do a batch call to get all imageUrl's first and then starts downloading it, + // but this takes time and the imageUrl's will be already expired. The reader + // doesn't face this issue as it follows the expected request order. + Page(it, imageUrl = imageUrl) } } - override fun imageUrlRequest(page: Page): Request { - val url = page.url.toHttpUrlOrNull()!! - val referer = url.queryParameter("referer")!! - val newUrl = url.newBuilder() - .removeAllEncodedQueryParameters("referer") - .toString() - - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("X-Client-Login", (loggedIn ?: false).toString()) - .add("X-Requested-With", "XMLHttpRequest") - .set("Referer", referer) - .build() - - return GET(newUrl, newHeaders) - } - - override fun imageUrlParse(response: Response): String { - val referer = response.request.header("Referer")!! - val pageUrl = response.parseAs() - .data?.values?.firstOrNull() ?: throw Exception(FAILED_TO_FETCH_PAGE_URL) - - return pageUrl.toHttpUrl().newBuilder() - .addEncodedQueryParameter("referer", referer) - .toString() - } - override fun imageUrlParse(document: Document) = "" override fun imageRequest(page: Page): Request { - val imageUrl = page.imageUrl!!.toHttpUrlOrNull()!! - val referer = imageUrl.queryParameter("referer")!! - val newImageUrl = imageUrl.newBuilder() - .removeAllEncodedQueryParameters("referer") - .toString() - val newHeaders = headersBuilder() - .add("Accept", "*/*") - .set("Referer", referer) + .add("Accept", ACCEPT_JSON) + .add("X-Client-Login", (loggedIn ?: false).toString()) + .add("X-Requested-With", "XMLHttpRequest") .build() - return GET(newImageUrl, newHeaders) + return GET(page.imageUrl!!, newHeaders) } private fun checkIfIsLoggedIn(chain: Interceptor.Chain? = null) { @@ -354,8 +329,8 @@ 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/106.0.0.0 Safari/537.36" + const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" private val DATE_FORMATTER by lazy { SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH) @@ -364,7 +339,6 @@ 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"