diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt index d5286443..d761564f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt @@ -1,13 +1,16 @@ package org.koitharu.kotatsu.parsers.site import okhttp3.Headers +import okhttp3.Response import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import org.jsoup.HttpStatusException import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -15,6 +18,7 @@ import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSONTo import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet +import java.net.HttpURLConnection import java.net.URLDecoder import java.text.DateFormat import java.text.SimpleDateFormat @@ -109,10 +113,12 @@ internal class RemangaParser( val domain = getDomain() val slug = manga.url.find(regexLastUrlPath) ?: throw ParseException("Cannot obtain slug from ${manga.url}") - val data = context.httpGet( - url = "https://api.$domain/api/titles/$slug/", - headers = getApiHeaders(), - ).parseJson() + val data = catch401 { + context.httpGet( + url = "https://api.$domain/api/titles/$slug/", + headers = getApiHeaders(), + ) + }.parseJson() val content = try { data.getJSONObject("content") } catch (e: JSONException) { @@ -166,7 +172,9 @@ internal class RemangaParser( override suspend fun getPages(chapter: MangaChapter): List { val referer = "https://${getDomain()}/" - val content = context.httpGet(chapter.url.withDomain(subdomain = "api"), getApiHeaders()).parseJson() + val content = catch401 { + context.httpGet(chapter.url.withDomain(subdomain = "api"), getApiHeaders()) + }.parseJson() .getJSONObject("content") val pages = content.optJSONArray("pages") if (pages == null) { @@ -205,10 +213,12 @@ internal class RemangaParser( } override suspend fun getUsername(): String { - val jo = context.httpGet( - url = "https://api.${getDomain()}/api/users/current/", - headers = getApiHeaders(), - ).parseJson() + val jo = catch401 { + context.httpGet( + url = "https://api.${getDomain()}/api/users/current/", + headers = getApiHeaders(), + ) + }.parseJson() return jo.getJSONObject("content").getString("username") } @@ -246,10 +256,12 @@ internal class RemangaParser( val result = ArrayList(100) var page = 1 while (true) { - val content = context.httpGet( - url = "https://api.$domain/api/titles/chapters/?branch_id=$branchId&page=$page&count=100", - headers = getApiHeaders(), - ).parseJson().getJSONArray("content") + val content = catch401 { + context.httpGet( + url = "https://api.$domain/api/titles/chapters/?branch_id=$branchId&page=$page&count=100", + headers = getApiHeaders(), + ) + }.parseJson().getJSONArray("content") val len = content.length() if (len == 0) { break @@ -262,4 +274,14 @@ internal class RemangaParser( } return result } + + private inline fun catch401(block: () -> Response): Response = try { + block() + } catch (e: HttpStatusException) { + if (e.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) { + throw AuthRequiredException(source) + } else { + throw e + } + } } \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/ContinuationCallCallback.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/ContinuationCallCallback.kt new file mode 100644 index 00000000..599dba87 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/ContinuationCallCallback.kt @@ -0,0 +1,36 @@ +package org.koitharu.kotatsu.parsers.util + +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.CompletionHandler +import okhttp3.Call +import okhttp3.Callback +import okhttp3.Response +import java.io.IOException +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +internal class ContinuationCallCallback( + private val call: Call, + private val continuation: CancellableContinuation, +) : Callback, CompletionHandler { + + override fun onResponse(call: Call, response: Response) { + if (continuation.isActive) { + continuation.resume(response) + } + } + + override fun onFailure(call: Call, e: IOException) { + if (!call.isCanceled() && continuation.isActive) { + continuation.resumeWithException(e) + } + } + + override fun invoke(cause: Throwable?) { + runCatching { + call.cancel() + }.onFailure { e -> + cause?.addSuppressed(e) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/HttpExt.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/HttpExt.kt index 01b73a2a..a84d62fb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/HttpExt.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/HttpExt.kt @@ -2,31 +2,12 @@ package org.koitharu.kotatsu.parsers.util import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.Call -import okhttp3.Callback import okhttp3.Response -import java.io.IOException -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -suspend fun Call.await() = suspendCancellableCoroutine { cont -> - this.enqueue( - object : Callback { - override fun onFailure(call: Call, e: IOException) { - if (cont.isActive) { - cont.resumeWithException(e) - } - } - - override fun onResponse(call: Call, response: Response) { - if (cont.isActive) { - cont.resume(response) - } - } - }, - ) - cont.invokeOnCancellation { - this.cancel() - } +suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation -> + val callback = ContinuationCallCallback(this, continuation) + enqueue(callback) + continuation.invokeOnCancellation(callback) } val Response.mimeType: String?