Fix chapters download in VIZ due to expiration time (#16085)

Fix chapters download due to expiration time.
This commit is contained in:
Alessandro Jean 2023-04-17 17:24:28 -03:00 committed by GitHub
parent 64edb4a916
commit a912752ea5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 51 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'VIZ Shonen Jump' extName = 'VIZ Shonen Jump'
pkgNameSuffix = 'en.vizshonenjump' pkgNameSuffix = 'en.vizshonenjump'
extClass = '.VizShonenJump' extClass = '.VizShonenJump'
extVersionCode = 15 extVersionCode = 16
} }
dependencies { dependencies {

View File

@ -4,32 +4,62 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import com.drew.imaging.ImageMetadataReader import com.drew.imaging.jpeg.JpegMetadataReader
import com.drew.metadata.exif.ExifSubIFDDirectory 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.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream import java.io.InputStream
class VizImageInterceptor : Interceptor { class VizImageInterceptor : Interceptor {
private val json: Json by injectLazy()
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
if (chain.request().url.queryParameter(SIGNATURE) == null) { if (!chain.request().url.toString().contains(IMAGE_URL_ENDPOINT)) {
return response 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) val body = image.toResponseBody(MEDIA_TYPE)
return response.newBuilder()
return imageResponse.newBuilder()
.body(body) .body(body)
.header("Content-Type", MEDIA_TYPE.toString())
.build() .build()
} }
private fun imageUrlParse(response: Response): String {
return response.use { json.decodeFromString<VizPageUrlDto>(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 { private fun InputStream.decodeImage(): ByteArray {
// See: https://stackoverflow.com/a/5924132 // See: https://stackoverflow.com/a/5924132
// See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603 // See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603
@ -110,7 +140,7 @@ class VizImageInterceptor : Interceptor {
} }
val output = ByteArrayOutputStream() val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.PNG, 100, output) result.compress(Bitmap.CompressFormat.JPEG, 95, output)
return output.toByteArray() return output.toByteArray()
} }
@ -129,7 +159,7 @@ class VizImageInterceptor : Interceptor {
} }
private fun ByteArrayInputStream.getImageData(): ImageData? { private fun ByteArrayInputStream.getImageData(): ImageData? {
val metadata = ImageMetadataReader.readMetadata(this) val metadata = JpegMetadataReader.readMetadata(this)
val sizeDir = metadata.directories.firstOrNull { val sizeDir = metadata.directories.firstOrNull {
it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) && it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) &&
@ -155,8 +185,10 @@ class VizImageInterceptor : Interceptor {
} }
companion object { companion object {
private const val SIGNATURE = "Signature" private const val IMAGE_URL_ENDPOINT = "get_manga_url"
private val MEDIA_TYPE = "image/png".toMediaTypeOrNull() 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_WIDTH_COUNT = 10
private const val CELL_HEIGHT_COUNT = 15 private const val CELL_HEIGHT_COUNT = 15

View File

@ -228,55 +228,30 @@ class VizShonenJump : ParsedHttpSource() {
.addQueryParameter("device_id", "3") .addQueryParameter("device_id", "3")
.addQueryParameter("manga_id", mangaId) .addQueryParameter("manga_id", mangaId)
.addQueryParameter("pages", it.toString()) .addQueryParameter("pages", it.toString())
.addEncodedQueryParameter("referer", document.location())
.toString() .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<VizPageUrlDto>()
.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 imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request { 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() val newHeaders = headersBuilder()
.add("Accept", "*/*") .add("Accept", ACCEPT_JSON)
.set("Referer", referer) .add("X-Client-Login", (loggedIn ?: false).toString())
.add("X-Requested-With", "XMLHttpRequest")
.build() .build()
return GET(newImageUrl, newHeaders) return GET(page.imageUrl!!, newHeaders)
} }
private fun checkIfIsLoggedIn(chain: Interceptor.Chain? = null) { private fun checkIfIsLoggedIn(chain: Interceptor.Chain? = null) {
@ -354,8 +329,8 @@ class VizShonenJump : ParsedHttpSource() {
companion object { companion object {
private const val ACCEPT_JSON = "application/json, text/javascript, */*; q=0.01" 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) " + 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" "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
private val DATE_FORMATTER by lazy { private val DATE_FORMATTER by lazy {
SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH) 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 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 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 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 REFRESH_LOGIN_LINKS_URL = "account/refresh_login_links"
private const val MANGA_AUTH_CHECK_URL = "manga/auth" private const val MANGA_AUTH_CHECK_URL = "manga/auth"