MangaHub: Update DTO, update token refresh logic (#13033)

* MangaHub: Update DTO, update token refresh logic

* ktLint
This commit is contained in:
Vetle Ledaal 2022-08-16 23:16:10 +02:00 committed by GitHub
parent d47b7684ce
commit b193ecceb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 86 deletions

View File

@ -17,6 +17,7 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Cookie
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
@ -26,7 +27,9 @@ import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
@ -57,89 +60,47 @@ abstract class MangaHub(
private fun apiAuthInterceptor(chain: Interceptor.Chain): Response { private fun apiAuthInterceptor(chain: Interceptor.Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
parseApiKey(originalRequest).let { cdnApiKey = it }
val request = if (originalRequest.url.toString() == "$cdnApiUrl/graphql") { val cookie = client.cookieJar
val newRequest = originalRequest.newBuilder() .loadForRequest(baseUrl.toHttpUrl())
.header("x-mhub-access", cdnApiKey!!) .firstOrNull { it.name == "mhub_access" }
val request =
if (originalRequest.url.toString() == "$cdnApiUrl/graphql" && cookie != null) {
originalRequest.newBuilder()
.header("x-mhub-access", cookie.value)
.build() .build()
newRequest
} else { } else {
originalRequest originalRequest
} }
val originalResponse = chain.proceed(request) return chain.proceed(request)
parseApiKey(originalResponse).let { cdnApiKey = it } }
return if (request.url.toString() == "$cdnApiUrl/graphql") { private fun refreshApiKey(chapter: SChapter) {
var newResponse: Response? = null val now = Calendar.getInstance().time.time
runCatching { val url = "$baseUrl${chapter.url}".toHttpUrl()
val responseData = json val number = chapter.url
.decodeFromString<GraphQLDataDto<ChapterDto>>(originalResponse.peekBody(1 * 1024 * 1024).string()) .substringAfter("chapter-")
.substringBefore("/")
if ( val recently = "{\"${now - (0..120).random()}\":{\"mangaID\":${(1..42000).random()},\"number\":$number}}"
responseData.errors?.isNotEmpty() == true ||
responseData.data.chapter == null
) {
val buffer = okio.Buffer()
request.body!!.writeTo(buffer)
val requestBody = buffer.readUtf8()
val slug = PATTERN_SLUG.find(requestBody)?.groupValues?.get(1) client.cookieJar.saveFromResponse(
cdnApiKey = fetchCdnApiKey(slug) url,
listOf(
val newRequest = originalRequest.newBuilder() Cookie.Builder()
.header("x-mhub-access", cdnApiKey!!) .domain(url.host)
.name("recently")
.value(URLEncoder.encode(recently, "utf-8"))
.expiresAt(now + 2 * 60 * 60 * 24 * 31) // +2 months
.build() .build()
)
)
// Bypass rate limit for this request val request = GET("$url?reloadKey=1", headers)
newResponse = super.client.newCall(newRequest).execute() client.newCall(request).execute()
} }
}
newResponse ?: originalResponse
} else {
originalResponse
}
}
private var cdnApiKey: String? = null
get() {
if (field == null)
field = fetchCdnApiKey(null)
return field
}
set(value) {
if (value == null) return
field = value
}
private fun fetchCdnApiKey(slug: String?): String? {
val request = if (slug == null) {
GET("$baseUrl/?reloadKey=1", headers)
} else {
GET("$baseUrl/manga/$slug?reloadKey=1", headers)
}
// Bypass rate limit for this request
val response = super.client.newCall(request).execute()
return parseApiKey(response)
}
private fun parseApiKey(request: Request): String? =
request.header("Cookie")
?.split("; ")
?.mapNotNull { kv ->
val (k, v) = kv.split('=')
if (k == "mhub_access") v else null
}?.firstOrNull()
private fun parseApiKey(response: Response): String? =
response.headers("Set-Cookie")
.mapNotNull { kv ->
val (k, v) = kv.split("; ").first().split('=')
if (k == "mhub_access") v else null
}.firstOrNull()
override fun headersBuilder(): Headers.Builder { override fun headersBuilder(): Headers.Builder {
val defaultUserAgent = super.headersBuilder()["User-Agent"] val defaultUserAgent = super.headersBuilder()["User-Agent"]
@ -372,16 +333,22 @@ abstract class MangaHub(
return POST("$cdnApiUrl/graphql", jsonHeaders, body) return POST("$cdnApiUrl/graphql", jsonHeaders, body)
} }
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
super.fetchPageList(chapter)
.doOnError { refreshApiKey(chapter) }
.retry(1)
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val cdn = "$cdnImgUrl/file/imghub" val cdn = "$cdnImgUrl/file/imghub"
val chapterObject = json val chapterObject = json
.decodeFromString<GraphQLDataDto<ChapterDto>>(response.body!!.string()) .decodeFromString<GraphQLDataDto<ChapterDto>>(response.body!!.string())
if (chapterObject.data?.chapter == null) {
if (chapterObject.errors != null) { if (chapterObject.errors != null) {
val errors = chapterObject.errors.joinToString("\n") { it.message } val errors = chapterObject.errors.joinToString("\n") { it.message }
throw Exception(errors) throw Exception(errors)
} else if (chapterObject.data.chapter == null) { }
throw Exception("Unknown error while fetching pages") throw Exception("Unknown error while processing pages")
} }
val pagesObject = json val pagesObject = json
@ -503,17 +470,13 @@ abstract class MangaHub(
Genre("Yaoi", "yaoi"), Genre("Yaoi", "yaoi"),
Genre("Yuri", "yuri") Genre("Yuri", "yuri")
) )
companion object {
private val PATTERN_SLUG = ",slug:\\\\\"([^\\\\]+)\\\\\",".toRegex()
}
} }
// DTO // DTO
@Serializable @Serializable
data class GraphQLDataDto<T>( data class GraphQLDataDto<T>(
val errors: List<ErrorDto>? = null, val errors: List<ErrorDto>? = null,
val data: T val data: T? = null
) )
@Serializable @Serializable

View File

@ -9,7 +9,7 @@ class MangaHubGenerator : ThemeSourceGenerator {
override val themeClass = "MangaHub" override val themeClass = "MangaHub"
override val baseVersionCode: Int = 8 override val baseVersionCode: Int = 9
override val sources = listOf( override val sources = listOf(
SingleLang("1Manga.co", "https://1manga.co", "en", isNsfw = true, className = "OneMangaCo"), SingleLang("1Manga.co", "https://1manga.co", "en", isNsfw = true, className = "OneMangaCo"),