From f6d8d415cc940524c56283d9f167ee827a716cfe Mon Sep 17 00:00:00 2001 From: Lorg0n Date: Thu, 17 Oct 2024 17:21:53 +0300 Subject: [PATCH] feat: add core Hikka tracker classes and DTOs for API requests --- .../tachiyomi/data/track/hikka/Hikka.kt | 177 ++++++++++++++++++ .../tachiyomi/data/track/hikka/HikkaApi.kt | 177 ++++++++++++++++++ .../data/track/hikka/HikkaInterceptor.kt | 88 +++++++++ .../tachiyomi/data/track/hikka/HikkaUtils.kt | 33 ++++ .../data/track/hikka/dto/HKAuthTokenInfo.kt | 13 ++ .../data/track/hikka/dto/HKClient.kt | 14 ++ .../tachiyomi/data/track/hikka/dto/HKManga.kt | 40 ++++ .../data/track/hikka/dto/HKMangaPagination.kt | 9 + .../tachiyomi/data/track/hikka/dto/HKOAuth.kt | 17 ++ .../data/track/hikka/dto/HKPagination.kt | 10 + .../tachiyomi/data/track/hikka/dto/HKRead.kt | 34 ++++ .../tachiyomi/data/track/hikka/dto/HKUser.kt | 16 ++ 12 files changed, 628 insertions(+) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaApi.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaInterceptor.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaUtils.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKAuthTokenInfo.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKClient.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKManga.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKMangaPagination.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKOAuth.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKPagination.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKRead.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKUser.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt new file mode 100644 index 000000000..efaaaa390 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt @@ -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 { + 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 { + 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 { + 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(trackPreferences.trackToken(this).get()) + } catch (e: Exception) { + null + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaApi.kt new file mode 100644 index 000000000..f3bcc5874 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaApi.kt @@ -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() + } + } + } + + 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() + } + } + } + + suspend fun searchManga(query: String): List { + 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() + .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() + .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() + .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) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaInterceptor.kt new file mode 100644 index 000000000..220b30224 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaInterceptor.kt @@ -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") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaUtils.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaUtils.kt new file mode 100644 index 000000000..42a3b0f16 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/HikkaUtils.kt @@ -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) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKAuthTokenInfo.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKAuthTokenInfo.kt new file mode 100644 index 000000000..13f8233d3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKAuthTokenInfo.kt @@ -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, + val expiration: Long, + val used: Long +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKClient.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKClient.kt new file mode 100644 index 000000000..98af1651a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKClient.kt @@ -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 +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKManga.kt new file mode 100644 index 000000000..8f7990ba6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKManga.kt @@ -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 = "" + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKMangaPagination.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKMangaPagination.kt new file mode 100644 index 000000000..77641cbc2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKMangaPagination.kt @@ -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 +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKOAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKOAuth.kt new file mode 100644 index 000000000..7b9caec5d --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKOAuth.kt @@ -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 + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKPagination.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKPagination.kt new file mode 100644 index 000000000..d5b056175 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKPagination.kt @@ -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 +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKRead.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKRead.kt new file mode 100644 index 000000000..80093a278 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKRead.kt @@ -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}" + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKUser.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKUser.kt new file mode 100644 index 000000000..5541eea70 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/dto/HKUser.kt @@ -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 +)