komikku/app/src/main/java/exh/md/handlers/FollowsHandler.kt

225 lines
8.1 KiB
Kotlin
Raw Normal View History

package exh.md.handlers
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
2020-10-28 00:52:32 +01:00
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.md.handlers.serializers.FollowsPageResult
import exh.md.handlers.serializers.Result
import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil
import exh.metadata.metadata.MangaDexSearchMetadata
import exh.util.floor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
2020-11-06 20:24:28 +01:00
import kotlinx.serialization.decodeFromString
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
class FollowsHandler(val client: OkHttpClient, val headers: Headers, val preferences: PreferencesHelper, private val useLowQualityCovers: Boolean) {
/**
* fetch follows by page
*/
2020-12-26 20:22:55 +01:00
suspend fun fetchFollows(): MangasPage {
return client.newCall(followsListRequest())
2020-12-26 20:22:55 +01:00
.await()
.let { response ->
followsParseMangaPage(response)
}
}
/**
* Parse follows api to manga page
* used when multiple follows
*/
private fun followsParseMangaPage(response: Response, forceHd: Boolean = false): MetadataMangasPage {
2020-11-01 03:06:32 +01:00
val followsPageResult = try {
MdUtil.jsonParser.decodeFromString(
response.body?.string().orEmpty()
2020-11-01 03:06:32 +01:00
)
} catch (e: Exception) {
2020-11-25 21:57:05 +01:00
XLog.tag("FollowsHandler").enableStackTrace(2).e("error parsing follows", e)
2020-11-06 20:24:28 +01:00
FollowsPageResult()
}
2020-11-01 03:06:32 +01:00
if (followsPageResult.result.isEmpty()) {
return MetadataMangasPage(emptyList(), false, emptyList())
}
val lowQualityCovers = if (forceHd) false else useLowQualityCovers
2020-11-01 03:06:32 +01:00
val follows = followsPageResult.result.map {
followFromElement(it, lowQualityCovers)
}
val comparator = compareBy<Pair<SManga, MangaDexSearchMetadata>> { it.second.follow_status }.thenBy { it.first.title }
val result = follows.sortedWith(comparator)
return MetadataMangasPage(result.map { it.first }, false, result.map { it.second })
}
2020-12-12 18:36:29 +01:00
/**
* fetch follow status used when fetching status for 1 manga
*/
private fun followStatusParse(response: Response): Track {
2020-11-01 03:06:32 +01:00
val followsPageResult = try {
MdUtil.jsonParser.decodeFromString(
response.body?.string().orEmpty()
2020-11-01 03:06:32 +01:00
)
} catch (e: Exception) {
2020-11-25 21:57:05 +01:00
XLog.tag("FollowsHandler").enableStackTrace(2).e("error parsing follows", e)
2020-11-06 20:24:28 +01:00
FollowsPageResult()
}
val track = Track.create(TrackManager.MDLIST)
2020-11-01 03:06:32 +01:00
if (followsPageResult.result.isEmpty()) {
track.status = FollowStatus.UNFOLLOWED.int
} else {
2020-11-01 03:06:32 +01:00
val follow = followsPageResult.result.first()
track.status = follow.follow_type
2020-11-01 03:06:32 +01:00
if (followsPageResult.result[0].chapter.isNotBlank()) {
track.last_chapter_read = follow.chapter.toFloat().floor()
}
2020-10-28 00:52:32 +01:00
track.tracking_url = MdUtil.baseUrl + follow.manga_id.toString()
track.title = follow.title
}
return track
}
2020-12-12 18:36:29 +01:00
/**
* build Request for follows page
*/
private fun followsListRequest(): Request {
2020-10-28 00:52:32 +01:00
return GET("${MdUtil.baseUrl}${MdUtil.followsAllApi}", headers, CacheControl.FORCE_NETWORK)
}
/**
* Parse result element to manga
*/
private fun followFromElement(result: Result, lowQualityCovers: Boolean): Pair<SManga, MangaDexSearchMetadata> {
val manga = SManga.create()
manga.title = MdUtil.cleanString(result.title)
manga.url = "/manga/${result.manga_id}/"
manga.thumbnail_url = MdUtil.formThumbUrl(manga.url, lowQualityCovers)
return manga to MangaDexSearchMetadata().apply {
2020-11-01 03:06:32 +01:00
title = manga.title
mdUrl = manga.url
thumbnail_url = manga.thumbnail_url
follow_status = FollowStatus.fromInt(result.follow_type)?.int
}
}
/**
* Change the status of a manga
*/
suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean {
return withContext(Dispatchers.IO) {
val response: Response =
if (followStatus == FollowStatus.UNFOLLOWED) {
client.newCall(
GET(
2020-10-28 00:52:32 +01:00
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=manga_unfollow&id=$mangaID&type=$mangaID",
headers,
CacheControl.FORCE_NETWORK
)
)
2020-10-28 00:52:32 +01:00
.await()
} else {
val status = followStatus.int
client.newCall(
GET(
2020-10-28 00:52:32 +01:00
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=manga_follow&id=$mangaID&type=$status",
headers,
CacheControl.FORCE_NETWORK
)
)
2020-10-28 00:52:32 +01:00
.await()
}
2020-11-01 03:06:32 +01:00
withContext(Dispatchers.IO) { response.body?.string().isNullOrEmpty() }
}
}
suspend fun updateReadingProgress(track: Track): Boolean {
return withContext(Dispatchers.IO) {
2020-10-28 00:52:32 +01:00
val mangaID = MdUtil.getMangaId(track.tracking_url)
val formBody = FormBody.Builder()
.add("chapter", track.last_chapter_read.toString())
2020-11-25 21:57:05 +01:00
XLog.tag("FollowsHandler").d("chapter to update %s", track.last_chapter_read.toString())
val response = client.newCall(
POST(
2020-10-28 00:52:32 +01:00
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=edit_progress&id=$mangaID",
headers,
formBody.build()
)
2020-10-28 00:52:32 +01:00
).await()
2020-10-28 00:52:32 +01:00
client.newCall(
GET(
2020-10-28 00:52:32 +01:00
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=manga_rating&id=$mangaID&rating=${track.score.toInt()}",
headers
)
2020-11-06 20:24:28 +01:00
).await()
2020-11-01 03:06:32 +01:00
withContext(Dispatchers.IO) { response.body?.string().isNullOrEmpty() }
}
}
suspend fun updateRating(track: Track): Boolean {
return withContext(Dispatchers.IO) {
2020-10-28 00:52:32 +01:00
val mangaID = MdUtil.getMangaId(track.tracking_url)
val response = client.newCall(
GET(
2020-10-28 00:52:32 +01:00
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=manga_rating&id=$mangaID&rating=${track.score.toInt()}",
headers
)
)
2020-10-28 00:52:32 +01:00
.await()
2020-11-01 03:06:32 +01:00
withContext(Dispatchers.IO) { response.body?.string().isNullOrEmpty() }
}
}
/**
* fetch all manga from all possible pages
*/
suspend fun fetchAllFollows(forceHd: Boolean): List<Pair<SManga, MangaDexSearchMetadata>> {
return withContext(Dispatchers.IO) {
val listManga = mutableListOf<Pair<SManga, MangaDexSearchMetadata>>()
2020-10-28 00:52:32 +01:00
val response = client.newCall(followsListRequest()).await()
val mangasPage = followsParseMangaPage(response, forceHd)
listManga.addAll(
mangasPage.mangas.mapIndexed { index, sManga ->
sManga to mangasPage.mangasMetadata[index] as MangaDexSearchMetadata
}
)
listManga
}
}
suspend fun fetchTrackingInfo(url: String): Track {
return withContext(Dispatchers.IO) {
val request = GET(
2020-10-28 00:52:32 +01:00
"${MdUtil.baseUrl}${MdUtil.followsMangaApi}" + MdUtil.getMangaId(url),
headers,
CacheControl.FORCE_NETWORK
)
2020-10-28 00:52:32 +01:00
val response = client.newCall(request).await()
val track = followStatusParse(response)
track
}
}
}