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>
This commit is contained in:
Alessandro Jean 2022-10-09 14:14:34 -03:00 committed by GitHub
parent 008e56a237
commit 7b4cd430a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 44 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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) &&

View File

@ -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<VizPageUrlDto>()
.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<VizMangaAuthDto>()
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 <reified T> 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"

View File

@ -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<Int, String>? = 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
)