komikku/app/src/main/java/exh/md/handlers/MangaPlusHandler.kt
AntsyLich 335f0956b4
Remove detekt (mihonapp/mihon#1130)
Annoying. More annoying in this project.

(cherry picked from commit 777ae2461e1eb277a3aa0c998ff69e4f100387a1)
2024-08-26 00:08:37 +07:00

113 lines
3.9 KiB
Kotlin

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.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 json: Json by injectLazy()
val headers = Headers.Builder()
.add("Origin", WEB_URL)
.add("Referer", WEB_URL)
.add("User-Agent", USER_AGENT)
.add("SESSION-TOKEN", UUID.randomUUID().toString()).build()
val client: OkHttpClient = currentClient.newBuilder()
.addInterceptor(::imageIntercept)
.rateLimitHost(API_URL.toHttpUrl(), 1)
.rateLimitHost(WEB_URL.toHttpUrl(), 2)
.build()
suspend fun fetchPageList(chapterId: String, dataSaver: Boolean): List<Page> {
val response = client.newCall(pageListRequest(chapterId.substringAfterLast("/"), dataSaver)).awaitSuccess()
return pageListParse(response)
}
private fun pageListRequest(chapterId: String, dataSaver: Boolean): Request {
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",
if (dataSaver) {
"low"
} else {
"super_high"
},
)
.addQueryParameter("format", "json")
.toString()
return GET(url, newHeaders)
}
private fun pageListParse(response: Response): List<Page> {
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(MangaPlusPage::mangaPage)
.mapIndexed { i, page ->
val encryptionKey = if (page.encryptionKey == null) "" else "#${page.encryptionKey}"
Page(i, referer, page.imageUrl + encryptionKey)
}
}
private fun imageIntercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val encryptionKey = request.url.fragment
if (encryptionKey.isNullOrEmpty()) {
return response
}
val contentType = response.headers["Content-Type"] ?: "image/jpeg"
val image = response.body.bytes().decodeXorCipher(encryptionKey)
val body = image.toResponseBody(contentType.toMediaTypeOrNull())
return response.newBuilder()
.body(body)
.build()
}
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/109.0.0.0 Safari/537.36"
private const val API_URL = "https://jumpg-webapi.tokyo-cdn.com/api"
}
}