mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
feat: add core Hikka tracker classes and DTOs for API requests
This commit is contained in:
parent
68d24dff27
commit
f6d8d415cc
12 changed files with 628 additions and 0 deletions
177
app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt
Normal file
177
app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class Hikka(id: Long) : BaseTracker(id, "Hikka"), DeletableTracker {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val READING = 0L
|
||||||
|
const val COMPLETED = 1L
|
||||||
|
const val ON_HOLD = 2L
|
||||||
|
const val DROPPED = 3L
|
||||||
|
const val PLAN_TO_READ = 4L
|
||||||
|
const val REREADING = 5L
|
||||||
|
|
||||||
|
private val SCORE_LIST = IntRange(0, 10)
|
||||||
|
.map(Int::toString)
|
||||||
|
.toImmutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
private val interceptor by lazy { HikkaInterceptor(this) }
|
||||||
|
private val api by lazy { HikkaApi(id, client, interceptor) }
|
||||||
|
|
||||||
|
override fun getLogoColor(): Int {
|
||||||
|
return Color.rgb(0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLogo(): Int {
|
||||||
|
return R.drawable.ic_tracker_hikka
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatusList(): List<Long> {
|
||||||
|
return listOf(
|
||||||
|
READING,
|
||||||
|
COMPLETED,
|
||||||
|
ON_HOLD,
|
||||||
|
DROPPED,
|
||||||
|
PLAN_TO_READ,
|
||||||
|
REREADING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||||
|
READING -> MR.strings.reading
|
||||||
|
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||||
|
COMPLETED -> MR.strings.completed
|
||||||
|
ON_HOLD -> MR.strings.on_hold
|
||||||
|
DROPPED -> MR.strings.dropped
|
||||||
|
REREADING -> MR.strings.repeating
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReadingStatus(): Long {
|
||||||
|
return READING
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRereadingStatus(): Long {
|
||||||
|
return REREADING
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCompletionStatus(): Long {
|
||||||
|
return COMPLETED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getScoreList(): ImmutableList<String> {
|
||||||
|
return SCORE_LIST
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun displayScore(track: Track): String {
|
||||||
|
return track.score.toInt().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
didReadChapter: Boolean,
|
||||||
|
): eu.kanade.tachiyomi.data.database.models.Track {
|
||||||
|
if (track.status != COMPLETED) {
|
||||||
|
if (didReadChapter) {
|
||||||
|
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||||
|
track.status = COMPLETED
|
||||||
|
} else if (track.status != REREADING) {
|
||||||
|
track.status = READING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return api.updateUserManga(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun bind(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
hasReadChapters: Boolean,
|
||||||
|
): eu.kanade.tachiyomi.data.database.models.Track {
|
||||||
|
val remoteTrack = api.getManga(track)
|
||||||
|
|
||||||
|
track.copyPersonalFrom(remoteTrack)
|
||||||
|
track.library_id = remoteTrack.library_id
|
||||||
|
|
||||||
|
if (track.status != COMPLETED) {
|
||||||
|
val isRereading = track.status == REREADING
|
||||||
|
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||||
|
}
|
||||||
|
|
||||||
|
return update(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun add(track: eu.kanade.tachiyomi.data.database.models.Track): eu.kanade.tachiyomi.data.database.models.Track {
|
||||||
|
return api.addUserManga(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<TrackSearch> {
|
||||||
|
return api.searchManga(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun refresh(track: eu.kanade.tachiyomi.data.database.models.Track): eu.kanade.tachiyomi.data.database.models.Track {
|
||||||
|
val remoteTrack = api.updateUserManga(track)
|
||||||
|
track.copyPersonalFrom(remoteTrack)
|
||||||
|
track.total_chapters = remoteTrack.total_chapters
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun login(username: String, password: String) = login(password)
|
||||||
|
|
||||||
|
suspend fun login(code: String) {
|
||||||
|
try {
|
||||||
|
val oauth = HKOAuth(code, System.currentTimeMillis() / 1000 + 30 * 60)
|
||||||
|
interceptor.setAuth(oauth)
|
||||||
|
val reference = api.getCurrentUser().reference
|
||||||
|
saveCredentials(reference, oauth.accessToken)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(track: Track) {
|
||||||
|
api.deleteManga(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logout() {
|
||||||
|
super.logout()
|
||||||
|
trackPreferences.trackToken(this).delete()
|
||||||
|
interceptor.setAuth(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIfAuthExpired(): Boolean {
|
||||||
|
return trackPreferences.trackAuthExpired(this).get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAuthExpired() {
|
||||||
|
trackPreferences.trackAuthExpired(this).set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveOAuth(oAuth: HKOAuth?) {
|
||||||
|
trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadOAuth(): HKOAuth? {
|
||||||
|
return try {
|
||||||
|
json.decodeFromString<HKOAuth>(trackPreferences.trackToken(this).get())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKAuthTokenInfo
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKMangaPagination
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKManga
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKRead
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKUser
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.PUT
|
||||||
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class HikkaApi(
|
||||||
|
private val trackId: Long,
|
||||||
|
private val client: OkHttpClient,
|
||||||
|
interceptor: HikkaInterceptor,
|
||||||
|
) {
|
||||||
|
suspend fun getCurrentUser(): HKUser {
|
||||||
|
return withIOContext {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("${BASE_API_URL}/user/me")
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(request)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<HKUser>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getTokenInfo(): HKAuthTokenInfo {
|
||||||
|
return withIOContext {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("${BASE_API_URL}/auth/token/info")
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(request)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<HKAuthTokenInfo>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun searchManga(query: String): List<TrackSearch> {
|
||||||
|
return withIOContext {
|
||||||
|
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
|
.appendQueryParameter("page", "1")
|
||||||
|
.appendQueryParameter("size", "50")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("media_type", buildJsonArray { })
|
||||||
|
put("status", buildJsonArray { })
|
||||||
|
put("only_translated", false)
|
||||||
|
put("magazines", buildJsonArray { })
|
||||||
|
put("genres", buildJsonArray { })
|
||||||
|
put("score", buildJsonArray {
|
||||||
|
add(0)
|
||||||
|
add(10)
|
||||||
|
})
|
||||||
|
put("query", query)
|
||||||
|
put("sort", buildJsonArray {
|
||||||
|
add("score:asc")
|
||||||
|
add("scored_by:asc")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(POST(url.toString(), body=payload.toString().toRequestBody(jsonMime)))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<HKMangaPagination>()
|
||||||
|
.list
|
||||||
|
.map { it.toTrack(trackId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getManga(track: Track): TrackSearch {
|
||||||
|
return withIOContext {
|
||||||
|
val slug = track.tracking_url.split("/")[4]
|
||||||
|
|
||||||
|
val url = "$BASE_API_URL/manga/${slug}".toUri().buildUpon()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(GET(url.toString()))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<HKManga>()
|
||||||
|
.toTrack(trackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteManga(track: tachiyomi.domain.track.model.Track) {
|
||||||
|
return withIOContext {
|
||||||
|
val slug = track.remoteUrl.split("/")[4]
|
||||||
|
|
||||||
|
val url = "$BASE_API_URL/read/manga/${slug}".toUri().buildUpon()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
authClient.newCall(DELETE(url.toString()))
|
||||||
|
.awaitSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addUserManga(track: Track): Track {
|
||||||
|
return withIOContext {
|
||||||
|
val slug = track.tracking_url.split("/")[4]
|
||||||
|
|
||||||
|
val url = "$BASE_API_URL/read/manga/${slug}".toUri().buildUpon()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("note", "")
|
||||||
|
put("chapters", track.last_chapter_read.toInt())
|
||||||
|
put("volumes", 0)
|
||||||
|
put("rereads", 0)
|
||||||
|
put("score", track.score.toInt())
|
||||||
|
put("status", track.toApiStatus())
|
||||||
|
}
|
||||||
|
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(PUT(url.toString(), body=payload.toString().toRequestBody(jsonMime)))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<HKRead>()
|
||||||
|
.toTrack(trackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateUserManga(track: Track): Track = addUserManga(track)
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val BASE_API_URL = "https://hikka.io/api"
|
||||||
|
const val BASE_URL = "https://hikka.io"
|
||||||
|
private const val SCOPE = "readlist,read:user-details"
|
||||||
|
private const val REFERENCE = "49eda83d-baa6-45f8-9936-b2a41d944da4"
|
||||||
|
|
||||||
|
fun authUrl(): Uri = "$BASE_URL/oauth".toUri().buildUpon()
|
||||||
|
.appendQueryParameter("reference", REFERENCE)
|
||||||
|
.appendQueryParameter("scope", SCOPE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun refreshTokenRequest(oauth: HKOAuth): Request {
|
||||||
|
val headers = Headers.Builder()
|
||||||
|
.add("auth", oauth.accessToken)
|
||||||
|
.add("Cookie", "auth=${oauth.accessToken}")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET("$BASE_API_URL/auth/token/info", headers = headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.dto.HKOAuth
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.json.JSONObject
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class HikkaInterceptor(private val hikka: Hikka) : Interceptor {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
private var oauth: HKOAuth? = hikka.loadOAuth()
|
||||||
|
private val tokenExpired get() = hikka.getIfAuthExpired()
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
if (tokenExpired) {
|
||||||
|
throw HKTokenExpired()
|
||||||
|
}
|
||||||
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
|
if (oauth?.isExpired() == true) {
|
||||||
|
refreshToken(chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oauth == null) {
|
||||||
|
throw IOException("Hikka.io: User is not authenticated")
|
||||||
|
}
|
||||||
|
|
||||||
|
val authRequest = originalRequest.newBuilder()
|
||||||
|
.addHeader("auth", oauth!!.accessToken)
|
||||||
|
.addHeader("Cookie", "auth=${oauth!!.accessToken}")
|
||||||
|
.addHeader("accept", "application/json")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Log.println(Log.WARN, "interceptor", "Set Auth Request Headers: " + authRequest.headers)
|
||||||
|
|
||||||
|
return chain.proceed(authRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user authenticates with MyAnimeList for the first time. Sets the refresh token
|
||||||
|
* and the oauth object.
|
||||||
|
*/
|
||||||
|
fun setAuth(oauth: HKOAuth?) {
|
||||||
|
this.oauth = oauth
|
||||||
|
hikka.saveOAuth(oauth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshToken(chain: Interceptor.Chain): HKOAuth = synchronized(this) {
|
||||||
|
if (tokenExpired) throw HKTokenExpired()
|
||||||
|
oauth?.takeUnless { it.isExpired() }?.let { return@synchronized it }
|
||||||
|
|
||||||
|
val response = try {
|
||||||
|
chain.proceed(HikkaApi.refreshTokenRequest(oauth!!))
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
throw HKTokenRefreshFailed()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.code == 401) {
|
||||||
|
hikka.setAuthExpired()
|
||||||
|
throw HKTokenExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
return runCatching {
|
||||||
|
if (response.isSuccessful && oauth != null) {
|
||||||
|
val responseBody = response.body?.string() ?: return@runCatching null
|
||||||
|
val jsonObject = JSONObject(responseBody)
|
||||||
|
|
||||||
|
val secret = oauth!!.accessToken
|
||||||
|
val expiration = jsonObject.getLong("expiration")
|
||||||
|
|
||||||
|
HKOAuth(secret, expiration)
|
||||||
|
} else {
|
||||||
|
response.close()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.getOrNull()?.also {
|
||||||
|
this.oauth = it
|
||||||
|
hikka.saveOAuth(it)
|
||||||
|
} ?: throw HKTokenRefreshFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HKTokenRefreshFailed : IOException("Hikka.io: Failed to refresh account token")
|
||||||
|
class HKTokenExpired : IOException("Hikka.io: Login has expired")
|
|
@ -0,0 +1,33 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
fun Track.toApiStatus() = when (status) {
|
||||||
|
Hikka.READING -> "reading"
|
||||||
|
Hikka.COMPLETED -> "completed"
|
||||||
|
Hikka.ON_HOLD -> "on_hold"
|
||||||
|
Hikka.DROPPED -> "dropped"
|
||||||
|
Hikka.PLAN_TO_READ -> "planned"
|
||||||
|
Hikka.REREADING -> "completed"
|
||||||
|
else -> throw NotImplementedError("To Api: Unknown status: $status")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toTrackStatus(status: String) = when (status) {
|
||||||
|
"reading" -> Hikka.READING
|
||||||
|
"completed" -> Hikka.COMPLETED
|
||||||
|
"on_hold" -> Hikka.ON_HOLD
|
||||||
|
"dropped" -> Hikka.DROPPED
|
||||||
|
"planned" -> Hikka.PLAN_TO_READ
|
||||||
|
"rewatching" -> Hikka.REREADING
|
||||||
|
else -> throw NotImplementedError("To Track: Unknown status: $status")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stringToNumber(input: String): Long {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val hash = digest.digest(input.toByteArray())
|
||||||
|
|
||||||
|
return hash.copyOfRange(0, 8).fold(0L) { acc, byte ->
|
||||||
|
acc shl 8 or (byte.toLong() and 0xff)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKAuthTokenInfo(
|
||||||
|
val reference: String,
|
||||||
|
val created: Long,
|
||||||
|
val client: HKClient,
|
||||||
|
val scope: List<String>,
|
||||||
|
val expiration: Long,
|
||||||
|
val used: Long
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKClient(
|
||||||
|
val reference: String,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val verified: Boolean,
|
||||||
|
val user: HKUser,
|
||||||
|
val created: Long,
|
||||||
|
val updated: Long
|
||||||
|
)
|
|
@ -0,0 +1,40 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.HikkaApi
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.stringToNumber
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKManga(
|
||||||
|
@SerialName("data_type") val dataType: String,
|
||||||
|
@SerialName("title_original") val titleOriginal: String,
|
||||||
|
@SerialName("media_type") val mediaType: String,
|
||||||
|
@SerialName("title_ua") val titleUa: String? = null,
|
||||||
|
@SerialName("title_en") val titleEn: String? = null,
|
||||||
|
val chapters: Int? = null,
|
||||||
|
val volumes: Int? = null,
|
||||||
|
@SerialName("translated_ua") val translatedUa: Boolean,
|
||||||
|
val status: String,
|
||||||
|
val image: String,
|
||||||
|
val year: Int,
|
||||||
|
@SerialName("scored_by") val scoredBy: Int,
|
||||||
|
val score: Double,
|
||||||
|
val slug: String
|
||||||
|
) {
|
||||||
|
fun toTrack(trackId: Long): TrackSearch {
|
||||||
|
return TrackSearch.create(trackId).apply {
|
||||||
|
remote_id = stringToNumber(this@HKManga.slug)
|
||||||
|
title = this@HKManga.titleUa ?: this@HKManga.titleEn ?: this@HKManga.titleOriginal
|
||||||
|
total_chapters = this@HKManga.chapters?.toLong() ?: 0
|
||||||
|
cover_url = this@HKManga.image
|
||||||
|
summary = ""
|
||||||
|
score = this@HKManga.score
|
||||||
|
tracking_url = HikkaApi.BASE_URL + "/manga/${this@HKManga.slug}"
|
||||||
|
publishing_status = this@HKManga.status
|
||||||
|
publishing_type = "manga"
|
||||||
|
start_date = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKMangaPagination(
|
||||||
|
val pagination: HKPagination,
|
||||||
|
val list: List<HKManga>
|
||||||
|
)
|
|
@ -0,0 +1,17 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKOAuth(
|
||||||
|
@SerialName("secret")
|
||||||
|
val accessToken: String,
|
||||||
|
|
||||||
|
@SerialName("expiration")
|
||||||
|
val expiration: Long,
|
||||||
|
) {
|
||||||
|
fun isExpired(): Boolean {
|
||||||
|
return (expiration - 1000) < System.currentTimeMillis() / 1000
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKPagination(
|
||||||
|
val total: Int,
|
||||||
|
val pages: Int,
|
||||||
|
val page: Int
|
||||||
|
)
|
|
@ -0,0 +1,34 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.HikkaApi
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.stringToNumber
|
||||||
|
import eu.kanade.tachiyomi.data.track.hikka.toTrackStatus
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKRead(
|
||||||
|
val reference: String,
|
||||||
|
val note: String,
|
||||||
|
val updated: Long,
|
||||||
|
val created: Long,
|
||||||
|
val status: String,
|
||||||
|
val chapters: Int,
|
||||||
|
val volumes: Int,
|
||||||
|
val rereads: Int,
|
||||||
|
val score: Int,
|
||||||
|
val content: HKManga
|
||||||
|
) {
|
||||||
|
fun toTrack(trackId: Long): TrackSearch {
|
||||||
|
return TrackSearch.create(trackId).apply {
|
||||||
|
title = this@HKRead.content.titleUa ?: this@HKRead.content.titleEn ?: this@HKRead.content.titleOriginal
|
||||||
|
remote_id = stringToNumber(this@HKRead.content.slug)
|
||||||
|
total_chapters = this@HKRead.content.chapters?.toLong() ?: 0
|
||||||
|
library_id = stringToNumber(this@HKRead.content.slug)
|
||||||
|
last_chapter_read = this@HKRead.chapters.toDouble()
|
||||||
|
score = this@HKRead.score.toDouble()
|
||||||
|
status = toTrackStatus(this@HKRead.status)
|
||||||
|
tracking_url = HikkaApi.BASE_URL + "/manga/${this@HKRead.content.slug}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HKUser(
|
||||||
|
val reference: String,
|
||||||
|
val updated: Long,
|
||||||
|
val created: Long,
|
||||||
|
val description: String,
|
||||||
|
val username: String,
|
||||||
|
val cover: String,
|
||||||
|
val active: Boolean,
|
||||||
|
val avatar: String,
|
||||||
|
val role: String
|
||||||
|
)
|
Loading…
Reference in a new issue