package ani.dantotsu.connections.comments import ani.dantotsu.BuildConfig import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import com.lagradost.nicehttp.Requests import eu.kanade.tachiyomi.network.NetworkHelper import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json import okhttp3.FormBody import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get object CommentsAPI { val address: String = "https://1224665.xyz:443" var authToken: String? = null var userId: String? = null var isBanned: Boolean = false var isAdmin: Boolean = false var isMod: Boolean = false suspend fun getCommentsForId(id: Int, page: Int = 1): CommentResponse? { val url = "$address/comments/$id/$page" val request = requestBuilder() val json = request.get(url) if (!json.text.startsWith("{")) return null val res = json.code == 200 if (!res && json.code != 404) { errorReason(json.code, json.text) } val parsed = try { Json.decodeFromString(json.text) } catch (e: Exception) { println("comments: $e") return null } return parsed } suspend fun vote(commentId: Int, voteType: Int): Boolean { val url = "$address/comments/vote/$commentId/$voteType" val request = requestBuilder() val json = request.post(url) val res = json.code == 200 if (!res) { errorReason(json.code, json.text) } return res } suspend fun comment(mediaId: Int, parentCommentId: Int?, content: String): Comment? { val url = "$address/comments" val body = FormBody.Builder() .add("user_id", userId ?: return null) .add("media_id", mediaId.toString()) .add("content", content) parentCommentId?.let { body.add("parent_comment_id", it.toString()) } val request = requestBuilder() val json = request.post(url, requestBody = body.build()) val res = json.code == 200 if (!res) { errorReason(json.code, json.text) return null } val parsed = try { Json.decodeFromString(json.text) } catch (e: Exception) { println("comment: $e") snackString("Failed to parse comment") return null } return Comment( parsed.id, parsed.userId, parsed.mediaId, parsed.parentCommentId, parsed.content, parsed.timestamp, parsed.deleted, 0, 0, null, Anilist.username ?: "", Anilist.avatar ) } suspend fun deleteComment(commentId: Int): Boolean { val url = "$address/comments/$commentId" val request = requestBuilder() val json = request.delete(url) val res = json.code == 200 if (!res) { errorReason(json.code, json.text) } return res } suspend fun editComment(commentId: Int, content: String): Boolean { val url = "$address/comments/$commentId" val body = FormBody.Builder() .add("content", content) .build() val request = requestBuilder() val json = request.put(url, requestBody = body) val res = json.code == 200 if (!res) { errorReason(json.code, json.text) } return res } suspend fun banUser(userId: String): Boolean { val url = "$address/ban/$userId" val request = requestBuilder() val json = request.post(url) val res = json.code == 200 if (!res) { errorReason(json.code, json.text) } return res } suspend fun fetchAuthToken() { val url = "$address/authenticate" val token = PrefManager.getVal(PrefName.AnilistToken, null as String?) ?: return val body: FormBody = FormBody.Builder() .add("token", token) .build() val request = requestBuilder() val json = request.post(url, requestBody = body) if (!json.text.startsWith("{")) return val parsed = try { Json.decodeFromString(json.text) } catch (e: Exception) { snackString("Failed to login to comments API: ${e.printStackTrace()}") return } authToken = parsed.authToken userId = parsed.user.id isBanned = parsed.user.isBanned ?: false isAdmin = parsed.user.isAdmin ?: false isMod = parsed.user.isMod ?: false } private fun headerBuilder(): Map { return if (authToken != null) { mapOf( "appauth" to BuildConfig.APP_SECRET, "Authorization" to authToken!! ) } else { mapOf( "appauth" to BuildConfig.APP_SECRET, ) } } private fun requestBuilder(): Requests { return Requests( Injekt.get().client, headerBuilder() ) } private fun errorReason(code: Int, reason: String? = null) { val error = when (code) { 429 -> "Rate limited. :(" else -> "Failed to connect" } snackString("Error $code: ${reason ?: error}") } } @Serializable data class AuthResponse( @SerialName("authToken") val authToken: String, @SerialName("user") val user: User ) @Serializable data class User( @SerialName("user_id") val id: String, @SerialName("username") val username: String, @SerialName("profile_picture_url") val profilePictureUrl: String? = null, @SerialName("is_banned") @Serializable(with = NumericBooleanSerializer::class) val isBanned: Boolean? = null, @SerialName("is_mod") @Serializable(with = NumericBooleanSerializer::class) val isAdmin: Boolean? = null, @SerialName("is_admin") @Serializable(with = NumericBooleanSerializer::class) val isMod: Boolean? = null ) @Serializable data class CommentResponse( @SerialName("comments") val comments: List, @SerialName("totalPages") val totalPages: Int ) @Serializable data class Comment( @SerialName("comment_id") val commentId: Int, @SerialName("user_id") val userId: String, @SerialName("media_id") val mediaId: Int, @SerialName("parent_comment_id") val parentCommentId: Int?, @SerialName("content") var content: String, @SerialName("timestamp") var timestamp: String, @SerialName("deleted") @Serializable(with = NumericBooleanSerializer::class) val deleted: Boolean?, @SerialName("upvotes") var upvotes: Int, @SerialName("downvotes") var downvotes: Int, @SerialName("user_vote_type") var userVoteType: Int?, @SerialName("username") val username: String, @SerialName("profile_picture_url") val profilePictureUrl: String?, @SerialName("is_mod") @Serializable(with = NumericBooleanSerializer::class) val isMod: Boolean? = null, @SerialName("is_admin") @Serializable(with = NumericBooleanSerializer::class) val isAdmin: Boolean? = null ) @Serializable data class ReturnedComment( @SerialName("id") var id: Int, @SerialName("comment_id") var commentId: Int?, @SerialName("user_id") val userId: String, @SerialName("media_id") val mediaId: Int, @SerialName("parent_comment_id") val parentCommentId: Int? = null, @SerialName("content") val content: String, @SerialName("timestamp") val timestamp: String, @SerialName("deleted") @Serializable(with = NumericBooleanSerializer::class) val deleted: Boolean?, ) object NumericBooleanSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("NumericBoolean", PrimitiveKind.INT) override fun serialize(encoder: Encoder, value: Boolean) { encoder.encodeInt(if (value) 1 else 0) } override fun deserialize(decoder: Decoder): Boolean { return decoder.decodeInt() != 0 } }