Refactor e-hentai API response parsing

This commit is contained in:
FooIbar authored and tarsin committed 2023-07-10 06:43:44 +08:00
1 parent 7a8ae183da
commit dac28b9b6a
13 files changed
+146 -130

No files matched your search

@@ -38,11 +38,11 @@ import com.hippo.ehviewer.client.parser.GalleryTokenApiParser
import com.hippo.ehviewer.client.parser.HomeParser
import com.hippo.ehviewer.client.parser.Limits
import com.hippo.ehviewer.client.parser.ProfileParser
import com.hippo.ehviewer.client.parser.RateGalleryParser
import com.hippo.ehviewer.client.parser.RateGalleryResult
import com.hippo.ehviewer.client.parser.SignInParser
import com.hippo.ehviewer.client.parser.TorrentParser
import com.hippo.ehviewer.client.parser.TorrentResult
import com.hippo.ehviewer.client.parser.VoteCommentParser
import com.hippo.ehviewer.client.parser.VoteCommentResult
import com.hippo.ehviewer.client.parser.VoteTagParser
import com.hippo.ehviewer.dailycheck.showEventNotification
import com.hippo.ehviewer.dailycheck.today
@@ -347,7 +347,13 @@ object EhEngine {
}.executeAndParsingWith(GalleryPageApiParser::parse)
}
suspend fun rateGallery(apiUid: Long, apiKey: String?, gid: Long, token: String?, rating: Float) = ehRequest(EhUrl.apiUrl, EhUrl.getGalleryDetailUrl(gid, token), EhUrl.origin) {
suspend fun rateGallery(
apiUid: Long,
apiKey: String?,
gid: Long,
token: String?,
rating: Float,
): RateGalleryResult = ehRequest(EhUrl.apiUrl, EhUrl.getGalleryDetailUrl(gid, token), EhUrl.origin) {
jsonBody {
put("method", "rategallery")
put("apiuid", apiUid)
@@ -356,7 +362,7 @@ object EhEngine {
put("token", token)
put("rating", ceil((rating * 2).toDouble()).toInt())
}
}.executeAndParsingWith(RateGalleryParser::parse)
}.executeAndParsingWith(String::parseAs)
suspend fun fillGalleryListByApi(galleryInfoList: List<GalleryInfo>, referer: String) = galleryInfoList.chunked(MAX_REQUEST_SIZE).parMap {
ehRequest(EhUrl.apiUrl, referer, EhUrl.origin) {
@@ -382,7 +388,7 @@ object EhEngine {
token: String?,
commentId: Long,
commentVote: Int,
) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
): VoteCommentResult = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
jsonBody {
put("method", "votecomment")
put("apiuid", apiUid)
@@ -392,7 +398,7 @@ object EhEngine {
put("comment_id", commentId)
put("comment_vote", commentVote)
}
}.executeAndParsingWith { VoteCommentParser.parse(this, commentVote) }
}.executeAndParsingWith(String::parseAs)
suspend fun voteTag(
apiUid: Long,
@@ -36,6 +36,7 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.executeAsync
import okio.BufferedSource
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -107,7 +108,9 @@ suspend inline fun <R> Request.executeNonCache(crossinline block: suspend Respon
suspend inline fun <reified T> Request.executeAndParseAs() = execute { parseAs<T>() }
inline fun <reified T> Response.parseAs(): T = json.decodeFromBufferedSource(body.source())
inline fun <reified T> Response.parseAs(): T = body.source().parseAs()
inline fun <reified T> BufferedSource.parseAs(): T = json.decodeFromBufferedSource(this)
inline fun <reified T> String.parseAs(): T = json.decodeFromString(this)
val json = Json { ignoreUnknownKeys = true }
@@ -32,12 +32,11 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.okio.decodeFromBufferedSource
import moe.tarsin.coroutines.runSuspendCatching
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.executeAsync
import okio.BufferedSource
import okio.HashingSink.Companion.sha1
import okio.blackholeSink
import okio.buffer
@@ -210,7 +209,7 @@ object EhTagDatabase : CoroutineScope {
val dir = AppConfig.getFilesDir("tag-translations")
val dataFile = File(dir, dataName).takeIf { it.exists() } ?: return
runSuspendCatching {
tagGroups = dataFile.source().buffer().use { Json.decodeFromBufferedSource(it) }
tagGroups = dataFile.source().buffer().use(BufferedSource::parseAs)
initialized = true
}.onFailure {
it.printStackTrace()
@@ -0,0 +1,6 @@
package com.hippo.ehviewer.client.parser
import kotlinx.serialization.Serializable
@Serializable
data class Error(val error: String)
@@ -19,32 +19,45 @@ import com.hippo.ehviewer.client.EhUtils.getCategory
import com.hippo.ehviewer.client.EhUtils.handleThumbUrlResolution
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.yorozuya.toFloatOrDefault
import com.hippo.ehviewer.yorozuya.toIntOrDefault
import org.json.JSONObject
import com.hippo.ehviewer.client.parseAs
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object GalleryApiParser {
fun parse(body: String, galleryInfoList: List<GalleryInfo>) {
val jo = JSONObject(body)
val ja = jo.getJSONArray("gmetadata")
for (i in 0 until ja.length()) {
val g = ja.getJSONObject(i)
val gid = g.getLong("gid")
val gi = galleryInfoList.find { it.gid == gid } ?: continue
gi.title = ParserUtils.trim(g.getString("title"))
gi.titleJpn = ParserUtils.trim(g.getString("title_jpn"))
gi.category = getCategory(g.getString("category"))
gi.thumbKey = getThumbKey(handleThumbUrlResolution(g.getString("thumb"))!!)
gi.uploader = g.getString("uploader")
gi.posted =
ParserUtils.formatDate(ParserUtils.parseLong(g.getString("posted"), 0) * 1000)
gi.rating = g.getString("rating").toFloatOrDefault(0.0f)
// tags
val tagJa = g.getJSONArray("tags")
gi.simpleTags = (0 until tagJa.length()).map { tagJa.getString(it) }
.let { arrayListOf<String>().apply { addAll(it) } }
gi.pages = g.getString("filecount").toIntOrDefault(0)
gi.generateSLang()
body.parseAs<Result>().items.forEach { item ->
val gi = galleryInfoList.find { it.gid == item.gid } ?: return@forEach
gi.apply {
title = item.title
titleJpn = item.titleJpn
category = getCategory(item.category)
thumbKey = getThumbKey(handleThumbUrlResolution(item.thumb)!!)
uploader = item.uploader
posted = ParserUtils.formatDate(item.posted * 1000)
rating = item.rating
simpleTags = item.tags
pages = item.pages
generateSLang()
}
}
}
@Serializable
data class Result(@SerialName("gmetadata") val items: List<Item>)
@Serializable
data class Item(
val gid: Long,
val title: String,
@SerialName("title_jpn")
val titleJpn: String,
val category: String,
val thumb: String,
val uploader: String,
val posted: Long,
@SerialName("filecount")
val pages: Int,
val rating: Float,
val tags: ArrayList<String>,
)
}
@@ -17,22 +17,26 @@
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.parseAs
import com.hippo.ehviewer.util.ExceptionUtils
import org.json.JSONArray
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object GalleryMultiPageViewerPTokenParser {
private const val IMAGE_LIST_STRING = "var imagelist = "
fun parse(body: String): List<String> {
val imagelist = body.substring(
body.indexOf("var imagelist = ") + 16,
body.indexOf(";", body.indexOf("var imagelist = ")),
)
val index = body.indexOf(IMAGE_LIST_STRING)
val imagelist = body.substring(index + IMAGE_LIST_STRING.length, body.indexOf(";", index))
return runCatching {
val ja = JSONArray(imagelist)
(0 until ja.length()).map { ja.getJSONObject(it).getString("k") }
imagelist.parseAs<List<Item>>().map(Item::token)
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
throw EhException(body)
throw ParseException("Parse pToken from MPV error", body, it)
}
}
@Serializable
data class Item(@SerialName("k") val token: String)
}
@@ -16,55 +16,44 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.parseAs
import com.hippo.ehviewer.yorozuya.unescapeXml
import org.json.JSONException
import org.json.JSONObject
import java.util.regex.Matcher
import java.util.regex.Pattern
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object GalleryPageApiParser {
private val PATTERN_IMAGE_URL = Pattern.compile("<img[^>]*src=\"([^\"]+)\" style")
private val PATTERN_SKIP_HATH_KEY = Pattern.compile("onclick=\"return nl\\('([^)]+)'\\)")
private val PATTERN_ORIGIN_IMAGE_URL =
Pattern.compile("<a href=\"([^\"]+)fullimg.php([^\"]+)\">")
private val PATTERN_IMAGE_URL = Regex("<img[^>]*src=\"([^\"]+)\" style")
private val PATTERN_SKIP_HATH_KEY = Regex("onclick=\"return nl\\('([^)]+)'\\)")
private val PATTERN_ORIGIN_IMAGE_URL = Regex("<a href=\"([^\"]+)fullimg.php([^\"]+)\">")
fun parse(body: String): Result {
return try {
var m: Matcher
val jo = JSONObject(body)
if (jo.has("error")) {
throw ParseException(jo.getString("error"), body)
}
val i3 = jo.getString("i3")
m = PATTERN_IMAGE_URL.matcher(i3)
val imageUrl = if (m.find()) {
m.group(1)!!.trim().unescapeXml()
} else {
null
}
val i6 = jo.getString("i6")
m = PATTERN_SKIP_HATH_KEY.matcher(i6)
val skipHathKey = if (m.find()) {
m.group(1)!!.trim().unescapeXml()
} else {
null
}
val i7 = jo.getString("i7")
m = PATTERN_ORIGIN_IMAGE_URL.matcher(i7)
val originImageUrl = if (m.find()) {
m.group(1)!!.unescapeXml() + "fullimg.php" + m.group(2)!!.unescapeXml()
} else {
null
}
if (!imageUrl.isNullOrEmpty()) {
Result(imageUrl, skipHathKey, originImageUrl)
} else {
throw ParseException("Parse image url and skip hath key error", body)
}
} catch (e: JSONException) {
throw ParseException("Can't parse json", body, e)
runCatching { body.parseAs<Error>() }.onSuccess { throw ParseException(it.error, body) }
val res = body.parseAs<JsonResult>()
val imageUrl = PATTERN_IMAGE_URL.find(res.imageUrl)?.run {
groupValues[1].trim().unescapeXml()
}
val skipHathKey = PATTERN_SKIP_HATH_KEY.find(res.skipHathKey)?.run {
groupValues[1].trim().unescapeXml()
}
val originImageUrl = PATTERN_ORIGIN_IMAGE_URL.find(res.originImageUrl)?.run {
groupValues[1].unescapeXml() + "fullimg.php" + groupValues[2].unescapeXml()
}
if (!imageUrl.isNullOrEmpty()) {
return Result(imageUrl, skipHathKey, originImageUrl)
} else {
throw ParseException("Parse image url and skip hath key error", body)
}
}
@Serializable
private data class JsonResult(
@SerialName("i3")
val imageUrl: String,
@SerialName("i6")
val skipHathKey: String,
@SerialName("i7")
val originImageUrl: String,
)
class Result(val imageUrl: String, val skipHathKey: String?, val originImageUrl: String?)
}
@@ -16,8 +16,10 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.parseAs
import com.hippo.ehviewer.util.ExceptionUtils
import org.json.JSONObject
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object GalleryTokenApiParser {
/**
@@ -31,12 +33,17 @@ object GalleryTokenApiParser {
* }
*/
fun parse(body: String): String {
val jo = JSONObject(body).getJSONArray("tokenlist").getJSONObject(0)
return runCatching {
jo.getString("token")
body.parseAs<Result>().tokenList[0].token
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
throw EhException(jo.getString("error"))
throw EhException(body.parseAs<Error>().error)
}
}
@Serializable
data class Result(@SerialName("tokenlist") val tokenList: List<Item>)
@Serializable
data class Item(val gid: Long, val token: String)
}
@@ -15,21 +15,13 @@
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.ParseException
import org.json.JSONException
import org.json.JSONObject
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object RateGalleryParser {
fun parse(body: String): Result {
return try {
val jsonObject = JSONObject(body)
val rating = jsonObject.getDouble("rating_avg").toFloat()
val ratingCount = jsonObject.getInt("rating_cnt")
Result(rating, ratingCount)
} catch (e: JSONException) {
throw ParseException("Can't parse rate gallery", body, e)
}
}
class Result(val rating: Float, val ratingCount: Int)
}
@Serializable
data class RateGalleryResult(
@SerialName("rating_avg")
val rating: Float,
@SerialName("rating_cnt")
val ratingCount: Int,
)
@@ -15,17 +15,15 @@
*/
package com.hippo.ehviewer.client.parser
import org.json.JSONObject
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object VoteCommentParser {
// {"comment_id":1253922,"comment_score":-19,"comment_vote":0}
fun parse(body: String, expectVote: Int): Result {
val jo = JSONObject(body)
val id = jo.getLong("comment_id")
val score = jo.getInt("comment_score")
val vote = jo.getInt("comment_vote")
return Result(id, score, vote, expectVote)
}
class Result(val id: Long, val score: Int, val vote: Int, val expectVote: Int)
}
@Serializable
data class VoteCommentResult(
@SerialName("comment_id")
val id: Long,
@SerialName("comment_score")
val score: Int,
@SerialName("comment_vote")
val vote: Int,
)
@@ -17,11 +17,11 @@
*/
package com.hippo.ehviewer.client.parser
import org.json.JSONObject
import com.hippo.ehviewer.client.parseAs
object VoteTagParser {
// {"error":"The tag \"neko\" is not allowed. Use character:neko or artist:neko"}
fun parse(body: String): String {
return JSONObject(body).optString("error")
fun parse(body: String): String? {
return runCatching { body.parseAs<Error>().error }.getOrNull()
}
}
@@ -63,7 +63,7 @@ import com.hippo.ehviewer.client.data.GalleryComment
import com.hippo.ehviewer.client.data.GalleryCommentList
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.hippo.ehviewer.client.parser.VoteCommentParser
import com.hippo.ehviewer.client.parser.VoteCommentResult
import com.hippo.ehviewer.dao.Filter
import com.hippo.ehviewer.dao.FilterMode
import com.hippo.ehviewer.databinding.ItemDrawerFavoritesBinding
@@ -341,11 +341,12 @@ class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefresh
runSuspendCatching {
EhEngine.voteComment(gd.apiUid, gd.apiKey, gd.gid, gd.token, id, vote)
}.onSuccess { result ->
val voteUp = vote > 0
showTip(
if (result.expectVote > 0) (if (0 != result.vote) R.string.vote_up_successfully else R.string.cancel_vote_up_successfully) else if (0 != result.vote) R.string.vote_down_successfully else R.string.cancel_vote_down_successfully,
if (voteUp) (if (0 != result.vote) R.string.vote_up_successfully else R.string.cancel_vote_up_successfully) else if (0 != result.vote) R.string.vote_down_successfully else R.string.cancel_vote_down_successfully,
LENGTH_SHORT,
)
withUIContext { onVoteCommentSuccess(result) }
withUIContext { onVoteCommentSuccess(result, voteUp) }
}.onFailure {
showTip(R.string.vote_failed, LENGTH_LONG)
}
@@ -698,7 +699,7 @@ class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefresh
updateView(true)
}
private fun onVoteCommentSuccess(result: VoteCommentParser.Result) {
private fun onVoteCommentSuccess(result: VoteCommentResult, voteUp: Boolean) {
if (mAdapter == null) {
return
}
@@ -721,7 +722,7 @@ class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefresh
// Update comment
val comment = mGalleryDetail!!.comments.comments[position]
comment.score = result.score
if (result.expectVote > 0) {
if (voteUp) {
comment.voteUpEd = 0 != result.vote
comment.voteDownEd = false
} else {
@@ -1193,11 +1193,9 @@ class GalleryDetailScene : BaseScene() {
runSuspendCatching {
EhEngine.voteTag(apiUid, apiKey, gid, token, tag, vote)
}.onSuccess { result ->
if (result.isNotEmpty()) {
result?.let {
showTip(result, LENGTH_SHORT)
} else {
showTip(R.string.tag_vote_successfully, LENGTH_SHORT)
}
} ?: showTip(R.string.tag_vote_successfully, LENGTH_SHORT)
}.onFailure {
showTip(R.string.vote_failed, LENGTH_LONG)
}