Fix manga plus chapters

This commit is contained in:
Jobobby04 2023-04-09 22:07:24 -04:00
parent 81283dc5cf
commit d013578fb5
3 changed files with 61 additions and 55 deletions

View File

@ -194,6 +194,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return runAsObservable { pageHandler.fetchPageList(chapter, usePort443Only(), dataSaver(), delegate) }
}
override suspend fun getPageList(chapter: SChapter): List<Page> {
return pageHandler.fetchPageList(chapter, usePort443Only(), dataSaver(), delegate)
}
override fun fetchImage(page: Page): Observable<Response> {
val call = pageHandler.getImageCall(page)
return call?.asObservableSuccess() ?: super.fetchImage(page)

View File

@ -1,28 +1,27 @@
package exh.md.dto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class MangaPlusResponse(
@ProtoNumber(1) val success: SuccessResult? = null,
val success: SuccessResult? = null,
)
@Serializable
data class SuccessResult(
@ProtoNumber(10) val mangaViewer: MangaViewer? = null,
val mangaViewer: MangaViewer? = null,
)
@Serializable
data class MangaViewer(@ProtoNumber(1) val pages: List<MangaPlusPage> = emptyList())
data class MangaViewer(val pages: List<MangaPlusPage> = emptyList())
@Serializable
data class MangaPlusPage(@ProtoNumber(1) val page: MangaPage? = null)
data class MangaPlusPage(val mangaPage: MangaPage? = null)
@Serializable
data class MangaPage(
@ProtoNumber(1) val imageUrl: String,
@ProtoNumber(2) val width: Int,
@ProtoNumber(3) val height: Int,
@ProtoNumber(5) val encryptionKey: String? = null,
val imageUrl: String,
val width: Int,
val height: Int,
val encryptionKey: String? = null,
)

View File

@ -2,21 +2,26 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.Page
import exh.md.dto.MangaPlusPage
import exh.md.dto.MangaPlusResponse
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import uy.kohesive.injekt.injectLazy
import java.util.UUID
class MangaPlusHandler(currentClient: OkHttpClient) {
val baseUrl = "https://jumpg-webapi.tokyo-cdn.com/api"
val json: Json by injectLazy()
val headers = Headers.Builder()
.add("Origin", WEB_URL)
.add("Referer", WEB_URL)
@ -24,7 +29,9 @@ class MangaPlusHandler(currentClient: OkHttpClient) {
.add("SESSION-TOKEN", UUID.randomUUID().toString()).build()
val client: OkHttpClient = currentClient.newBuilder()
.addInterceptor { imageIntercept(it) }
.addInterceptor(::imageIntercept)
.rateLimitHost(API_URL.toHttpUrl(), 1)
.rateLimitHost(WEB_URL.toHttpUrl(), 2)
.build()
suspend fun fetchPageList(chapterId: String): List<Page> {
@ -33,72 +40,68 @@ class MangaPlusHandler(currentClient: OkHttpClient) {
}
private fun pageListRequest(chapterId: String): Request {
return GET(
"$baseUrl/manga_viewer?chapter_id=$chapterId&split=yes&img_quality=super_high",
headers,
)
val newHeaders = headers.newBuilder()
.set("Referer", "$WEB_URL/viewer/$chapterId")
.build()
val url = "$API_URL/manga_viewer".toHttpUrl().newBuilder()
.addQueryParameter("chapter_id", chapterId)
.addQueryParameter("split", "yes")
.addQueryParameter("img_quality", "super_high")
.addQueryParameter("format", "json")
.toString()
return GET(url, newHeaders)
}
private fun pageListParse(response: Response): List<Page> {
val result = ProtoBuf.decodeFromByteArray<MangaPlusResponse>(response.body.bytes())
val result = json.decodeFromString<MangaPlusResponse>(response.body.string())
if (result.success == null) {
throw Exception("error getting images")
}
val referer = response.request.header("Referer")!!
return result.success.mangaViewer!!.pages
.mapNotNull { it.page }
.mapNotNull(MangaPlusPage::mangaPage)
.mapIndexed { i, page ->
val encryptionKey =
if (page.encryptionKey == null) "" else "&encryptionKey=${page.encryptionKey}"
Page(i, "", "${page.imageUrl}$encryptionKey")
val encryptionKey = if (page.encryptionKey == null) "" else "#${page.encryptionKey}"
Page(i, referer, page.imageUrl + encryptionKey)
}
}
private fun imageIntercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (!request.url.queryParameterNames.contains("encryptionKey")) {
return chain.proceed(request)
}
val encryptionKey = request.url.queryParameter("encryptionKey")!!
// Change the url and remove the encryptionKey to avoid detection.
val newUrl = request.url.newBuilder().removeAllQueryParameters("encryptionKey").build()
request = request.newBuilder().url(newUrl).build()
val request = chain.request()
val response = chain.proceed(request)
val encryptionKey = request.url.fragment
val image = decodeImage(encryptionKey, response.body.bytes())
val body = image.toResponseBody("image/jpeg".toMediaTypeOrNull())
return response.newBuilder().body(body).build()
if (encryptionKey.isNullOrEmpty()) {
return response
}
private fun decodeImage(encryptionKey: String, image: ByteArray): ByteArray {
val keyStream = HEX_GROUP
.findAll(encryptionKey)
.map { it.groupValues[1].toInt(16) }
.toList()
val contentType = response.headers["Content-Type"] ?: "image/jpeg"
val image = response.body.bytes().decodeXorCipher(encryptionKey)
val body = image.toResponseBody(contentType.toMediaTypeOrNull())
val content = image
.map { it.toInt() }
.toIntArray()
val blockSizeInBytes = keyStream.size
content.forEachIndexed { i, value ->
content[i] = value xor keyStream[i % blockSizeInBytes]
return response.newBuilder()
.body(body)
.build()
}
return ByteArray(content.size) { pos -> content[pos].toByte() }
private fun ByteArray.decodeXorCipher(key: String): ByteArray {
val keyStream = key.chunked(2)
.map { it.toInt(16) }
return mapIndexed { i, byte -> byte.toInt() xor keyStream[i % keyStream.size] }
.map(Int::toByte)
.toByteArray()
}
companion object {
private const val WEB_URL = "https://mangaplus.shueisha.co.jp"
private const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"
private val HEX_GROUP = "(.{1,2})".toRegex()
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
private const val API_URL = "https://jumpg-webapi.tokyo-cdn.com/api"
}
}