MangaHub: Add getting API Key back (#16392)

This commit is contained in:
Slowlife 2023-05-10 20:40:49 +07:00 committed by GitHub
parent 628a076e32
commit 169a8dfbe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 17 deletions

View File

@ -15,7 +15,10 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import okhttp3.Cookie
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.OkHttpClient
@ -24,7 +27,9 @@ import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
@ -39,8 +44,12 @@ abstract class MangaHub(
override val supportsLatest = true
private var baseApiUrl = "https://api.mghubcdn.com"
private var baseCdnUrl = "https://imgx.mghubcdn.com"
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(::uaIntercept)
.addInterceptor(::apiAuthInterceptor)
.rateLimit(1)
.build()
@ -56,9 +65,6 @@ abstract class MangaHub(
open val json: Json by injectLazy()
private var baseApiUrl = "https://api.mghubcdn.com"
private var baseCdnUrl = "https://imgx.mghubcdn.com"
private var userAgent: String? = null
private var checkedUa = false
@ -88,6 +94,63 @@ abstract class MangaHub(
return chain.proceed(chain.request())
}
private fun apiAuthInterceptor(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val cookie = client.cookieJar
.loadForRequest(baseUrl.toHttpUrl())
.firstOrNull { it.name == "mhub_access" }
val request =
if (originalRequest.url.toString() == "$baseApiUrl/graphql" && cookie != null) {
originalRequest.newBuilder()
.header("x-mhub-access", cookie.value)
.build()
} else {
originalRequest
}
return chain.proceed(request)
}
private fun refreshApiKey(chapter: SChapter) {
val now = Calendar.getInstance().time.time
val slug = "$baseUrl${chapter.url}"
.toHttpUrlOrNull()
?.pathSegments
?.get(1)
val url = if (slug != null) {
"$baseUrl/manga/$slug".toHttpUrl()
} else {
baseUrl.toHttpUrl()
}
// Set required cookie (for cache busting?)
val recently = buildJsonObject {
putJsonObject((now - (0..3600).random()).toString()) {
put("mangaID", (1..42_000).random())
put("number", (1..20).random())
}
}.toString()
client.cookieJar.saveFromResponse(
url,
listOf(
Cookie.Builder()
.domain(url.host)
.name("recently")
.value(URLEncoder.encode(recently, "utf-8"))
.expiresAt(now + 2 * 60 * 60 * 24 * 31) // +2 months
.build(),
),
)
val request = GET("$url?reloadKey=1", headers)
client.newCall(request).execute()
}
// popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/popular/page/$page", headers)
@ -301,23 +364,55 @@ abstract class MangaHub(
.toRequestBody()
val newHeaders = headersBuilder()
.set("Accept", "application/json")
.set("Content-Type", "application/json")
.set("Origin", baseUrl)
.set("Sec-Fetch-Dest", "empty")
.set("Sec-Fetch-Mode", "cors")
.set("Sec-Fetch-Site", "cross-site")
.removeAll("Upgrade-Insecure-Requests")
.build()
return POST("$baseApiUrl/graphql", newHeaders, body)
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
super.fetchPageList(chapter)
.doOnError { refreshApiKey(chapter) }
.retry(1)
override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used")
override fun pageListParse(response: Response): List<Page> {
val pagesString = json.decodeFromString<ApiChapterPagesResponse>(response.body.string()).data.chapter.pages
val pages = json.decodeFromString<ApiChapterPages>(pagesString)
val chapterObject = json.decodeFromString<ApiChapterPagesResponse>(response.body.string())
if (chapterObject.data?.chapter == null) {
if (chapterObject.errors != null) {
val errors = chapterObject.errors.joinToString("\n") { it.message }
throw Exception(errors)
}
throw Exception("Unknown error while processing pages")
}
val pages = json.decodeFromString<ApiChapterPages>(chapterObject.data.chapter.pages)
return pages.i.mapIndexed { i, page ->
Page(i, "", "$baseCdnUrl/${pages.p}$page")
}
}
// Image
override fun imageUrlRequest(page: Page): Request {
val newHeaders = headersBuilder()
.set("Accept", "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
.set("Sec-Fetch-Dest", "image")
.set("Sec-Fetch-Mode", "no-cors")
.set("Sec-Fetch-Site", "cross-site")
.removeAll("Upgrade-Insecure-Requests")
.build()
return GET(page.url, newHeaders)
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
// filters

View File

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

View File

@ -14,9 +14,15 @@ val PAGES_QUERY = buildQuery {
""".trimIndent()
}
@Serializable
data class ApiErrorMessages(
val message: String,
)
@Serializable
data class ApiChapterPagesResponse(
val data: ApiChapterData,
val data: ApiChapterData?,
val errors: List<ApiErrorMessages>?,
)
@Serializable