feat: creating, deleting comments | markdown, spoiler comments
This commit is contained in:
parent
129adc5825
commit
aaf9bdd00c
15 changed files with 672 additions and 157 deletions
|
@ -0,0 +1,273 @@
|
|||
package ani.dantotsu.connections.comments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.security.keystore.KeyProperties
|
||||
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.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
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
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object CommentsAPI {
|
||||
val address: String = "http://10.0.2.2:8081"
|
||||
val appSecret = BuildConfig.APP_SECRET
|
||||
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 parsed = try {
|
||||
Json.decodeFromString<CommentResponse>(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)
|
||||
}
|
||||
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)
|
||||
return null
|
||||
}
|
||||
val parsed = try {
|
||||
Json.decodeFromString<ReturnedComment>(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)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
suspend fun fetchAuthToken() {
|
||||
val url = "$address/authenticate"
|
||||
userId = generateUserId()
|
||||
val user = User(userId ?: return, Anilist.username ?: "")
|
||||
val body: FormBody = FormBody.Builder()
|
||||
.add("user_id", user.id)
|
||||
.add("username", user.username)
|
||||
.add("profile_picture_url", Anilist.avatar ?: "")
|
||||
.build()
|
||||
val request = requestBuilder()
|
||||
val json = request.post(url, requestBody = body)
|
||||
if (!json.text.startsWith("{")) return
|
||||
val parsed = try {
|
||||
Json.decodeFromString<AuthResponse>(json.text)
|
||||
} catch (e: Exception) {
|
||||
println("comment: $e")
|
||||
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<String, String> {
|
||||
return if (authToken != null) {
|
||||
mapOf(
|
||||
"appauth" to appSecret,
|
||||
"Authorization" to authToken!!
|
||||
)
|
||||
} else {
|
||||
mapOf(
|
||||
"appauth" to appSecret,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestBuilder(): Requests {
|
||||
return Requests(
|
||||
Injekt.get<NetworkHelper>().client,
|
||||
headerBuilder()
|
||||
)
|
||||
}
|
||||
|
||||
private fun errorReason(code: Int) {
|
||||
val error = when (code) {
|
||||
429 -> "Rate limited. :("
|
||||
else -> "Failed to connect"
|
||||
}
|
||||
snackString("Error $code: $error")
|
||||
}
|
||||
|
||||
@SuppressLint("GetInstance")
|
||||
private fun generateUserId(): String? {
|
||||
val anilistId = PrefManager.getVal(PrefName.AnilistUserId, null as String?) ?: return null
|
||||
val userIdEncryptKey = BuildConfig.USER_ID_ENCRYPT_KEY
|
||||
val keySpec = SecretKeySpec(userIdEncryptKey.toByteArray(), KeyProperties.KEY_ALGORITHM_AES)
|
||||
val cipher = Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/ECB/${KeyProperties.ENCRYPTION_PADDING_PKCS7}")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
|
||||
val encrypted = cipher.doFinal(anilistId.toByteArray())
|
||||
return encrypted.joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@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<Comment>,
|
||||
@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")
|
||||
val content: String,
|
||||
@SerialName("timestamp")
|
||||
val 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?
|
||||
)
|
||||
|
||||
@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<Boolean> {
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue