Jelajahi Sumber

Bump to 1.7.5, Support mirai 1.2.1

Update dependencies
Add voice support (via url, path)

Signed-off-by: Hieuzest <[email protected]>
Hieuzest 5 tahun lalu
induk
melakukan
4b56bb4648

+ 6 - 6
gradle.properties

@@ -1,22 +1,22 @@
 # build
-httpVersion=v1.7.4
+httpVersion=v1.7.5
 # style guide
 kotlin.code.style=official
 # config
-miraiVersion=1.1.0
+miraiVersion=1.2.1
 miraiConsoleVersion=0.5.2
 kotlin.incremental.multiplatform=true
 kotlin.parallel.tasks.in.project=true
 # kotlin
-kotlinVersion=1.3.71
+kotlinVersion=1.4.0
 # kotlin libraries
-serializationVersion=0.20.0
-coroutinesVersion=1.3.5
+serializationVersion=1.0.0-RC
+coroutinesVersion=1.3.9
 atomicFuVersion=0.14.2
 kotlinXIoVersion=0.1.16
 coroutinesIoVersion=0.1.16
 # utility
-ktorVersion=1.3.2
+ktorVersion=1.4.0
 klockVersion=1.7.0
 # gradle plugin
 protobufJavaVersion=3.10.0

+ 2 - 2
mirai-api-http/build.gradle.kts

@@ -41,12 +41,12 @@ kotlin {
             compileOnly("net.mamoe:mirai-core-qqandroid:$miraiVersion")
             compileOnly("net.mamoe:mirai-console:$miraiConsoleVersion")
 
-
             compileOnly(kotlin("stdlib-jdk8", kotlinVersion))
             compileOnly(kotlin("stdlib-jdk7", kotlinVersion))
             compileOnly(kotlin("reflect", kotlinVersion))
 
             api(ktor("server-cio"))
+            api(ktor("client"))
             api(ktor("http-jvm"))
             api(ktor("websockets"))
             compileOnly(kotlinx("io-jvm"))
@@ -90,7 +90,7 @@ kotlin {
             compileOnly(kotlinx("io"))
             compileOnly(kotlinx("coroutines-io", coroutinesIoVersion))
             compileOnly(kotlinx("coroutines-core", coroutinesVersion))
-            compileOnly(kotlinx("serialization-runtime", serializationVersion))
+            compileOnly(kotlinx("serialization-core", serializationVersion))
             implementation(ktor("server-core"))
             implementation(ktor("http"))
         }

+ 10 - 0
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/HttpApiPluginBase.kt

@@ -108,4 +108,14 @@ object HttpApiPluginBase : PluginBase() {
         async {
             image(name).apply { writeBytes(data) }
         }
+
+    private val voiceFold: File = File(dataFolder, "voices").apply { mkdirs() }
+
+    internal fun voice(voiceName: String) = File(voiceFold, voiceName)
+
+    fun saveVoiceAsync(name: String, data: ByteArray) =
+        async {
+            voice(name).apply { writeBytes(data) }
+        }
+
 }

+ 22 - 2
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt

@@ -77,6 +77,13 @@ data class FlashImageDTO(
     val path: String? = null
 ) : MessageDTO()
 
+@Serializable
+@SerialName("Voice")
+data class VoiceDTO(
+    val url: String? = null,
+    val path: String? = null
+) : MessageDTO()
+
 @Serializable
 @SerialName("Xml")
 data class XmlDTO(val xml: String) : MessageDTO()
@@ -160,6 +167,7 @@ suspend fun Message.toDTO() = when (this) {
     is PlainText -> PlainDTO(content)
     is Image -> ImageDTO(imageId, queryUrl())
     is FlashImage -> FlashImageDTO(image.imageId, image.queryUrl())
+    is Voice -> VoiceDTO(url, fileName)
     is ServiceMessage -> XmlDTO(content)
     is LightApp -> AppDTO(content)
     is QuoteReply -> QuoteDTO(source.id, source.fromId, source.targetId,
@@ -185,7 +193,7 @@ suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
     is PlainDTO -> PlainText(text)
     is ImageDTO -> when {
         !imageId.isNullOrBlank() -> Image(imageId)
-        !url.isNullOrBlank() -> contact.uploadImage(withContext(Dispatchers.IO) { URL(url) })
+        !url.isNullOrBlank() -> contact.uploadImage(withContext(Dispatchers.IO) { URL(url).openStream() })
         !path.isNullOrBlank() -> with(HttpApiPluginBase.image(path)) {
             if (exists()) {
                 contact.uploadImage(this)
@@ -195,7 +203,7 @@ suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
     }
     is FlashImageDTO -> when {
         !imageId.isNullOrBlank() -> Image(imageId)
-        !url.isNullOrBlank() -> contact.uploadImage(withContext(Dispatchers.IO) { URL(url) })
+        !url.isNullOrBlank() -> contact.uploadImage(withContext(Dispatchers.IO) { URL(url).openStream() })
         !path.isNullOrBlank() -> with(HttpApiPluginBase.image(path)) {
             if (exists()) {
                 contact.uploadImage(this)
@@ -203,6 +211,18 @@ suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
         }
         else -> null
     }?.flash()
+    is VoiceDTO -> when {
+        contact !is Group -> null
+        !url.isNullOrBlank() -> contact.uploadVoice(withContext(Dispatchers.IO) { URL(url).openStream() }).also {
+            println("${it.url} ${it.fileName} ${it.fileSize} ${it.md5}")
+        }
+        !path.isNullOrBlank() -> with(HttpApiPluginBase.voice(path)) {
+            if (exists()) {
+                contact.uploadVoice(this.inputStream())
+            } else throw NoSuchFileException(this)
+        }
+        else -> null
+    }
     is XmlDTO -> ServiceMessage(60, xml)
     is JsonDTO -> ServiceMessage(1, json)
     is AppDTO -> LightApp(content)

+ 1 - 5
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/CacheQueue.kt

@@ -17,11 +17,7 @@ class CacheQueue : LinkedHashMap<Int, OnlineMessageSource>() {
 
     override fun get(key: Int): OnlineMessageSource = super.get(key) ?: throw NoSuchElementException()
 
-    override fun put(key: Int, value: OnlineMessageSource): OnlineMessageSource? = super.put(key, value).also {
-        if (size > cacheSize) {
-            remove(this.entries.first().key)
-        }
-    }
+    override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, OnlineMessageSource>?): Boolean = size > cacheSize
 
     fun add(source: OnlineMessageSource) {
         put(source.id, source)

+ 45 - 2
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt

@@ -213,7 +213,7 @@ fun Application.messageModule() {
                 it.group != null -> bot.getGroup(it.group)
                 else -> throw IllegalParamException("target、qq、group不可全为null")
             }
-            val ls = it.urls.map { url -> contact.uploadImage(URL(url)) }
+            val ls = it.urls.map { url -> contact.uploadImage(URL(url).openStream()) }
             val receipt = contact.sendMessage(buildMessageChain { addAll(ls) })
 
             it.session.cacheQueue.add(receipt.source)
@@ -223,7 +223,7 @@ fun Application.messageModule() {
         // TODO: 重构
         miraiMultiPart("uploadImage") { session, parts ->
 
-            var path: String? = null
+            var path: String?
 
             val type = parts.value("type")
             parts.file("img")?.apply {
@@ -256,6 +256,40 @@ fun Application.messageModule() {
             } ?: throw IllegalAccessException("未知错误")
         }
 
+        miraiMultiPart("uploadVoice") { session, parts ->
+
+            var path: String?
+
+            val type = parts.value("type")
+            parts.file("voice")?.apply {
+
+                val voice = streamProvider().use {
+                    // originalFileName assert not null
+                    val newFile = HttpApiPluginBase.saveVoiceAsync(
+                            originalFileName ?: generateSessionKey(), it.readBytes())
+
+                    when (type) {
+                        "group" -> session.bot.groups.firstOrNull()?.uploadVoice(newFile.await().inputStream())
+                        else -> null
+                    }.apply {
+                        // 使用apply不影响when返回
+                        path = newFile.await().absolutePath
+
+                    }
+                }
+
+                voice?.apply {
+                    call.respondDTO(UploadVoiceRetDTO(
+                            url,
+                            fileName,
+                            fileSize,
+                            path
+                    ))
+                } ?: throw IllegalAccessException("语音上传错误")
+
+            } ?: throw IllegalAccessException("未知错误")
+        }
+
         /**
          * 撤回消息
          */
@@ -301,6 +335,15 @@ private class UploadImageRetDTO(
     val path: String?
 ) : DTO
 
+@Serializable
+@Suppress("unused")
+private class UploadVoiceRetDTO(
+    val url: String?,
+    val fileName: String,
+    val fileSize: Long,
+    val path: String?
+) : DTO
+
 @Serializable
 private data class RecallDTO(
     override val sessionKey: String,

+ 4 - 0
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/service/report/ReportConfig.kt

@@ -40,20 +40,24 @@ class ReportConfig(config: Config) {
     /**
      *  群消息子配置
      */
+    @Suppress("UNCHECKED_CAST")
     val groupMessage = ReportMessageConfig(serviceConfig.getOrDefault("groupMessage", emptyMap<String, Any>()) as Map<String, Any>)
 
     /**
      *  好友消息子配置
      */
+    @Suppress("UNCHECKED_CAST")
     val friendMessage = ReportMessageConfig(serviceConfig.getOrDefault("friendMessage", emptyMap<String, Any>()) as Map<String, Any>)
 
     /**
      *  临时消息子配置
      */
+    @Suppress("UNCHECKED_CAST")
     val tempMessage = ReportMessageConfig(serviceConfig.getOrDefault("tempMessage", emptyMap<String, Any>()) as Map<String, Any>)
 
     /**
      *  事件消息子配置
      */
+    @Suppress("UNCHECKED_CAST")
     val eventMessage = ReportMessageConfig(serviceConfig.getOrDefault("eventMessage", emptyMap<String, Any>()) as Map<String, Any>)
 }

+ 48 - 52
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt

@@ -12,91 +12,87 @@ package net.mamoe.mirai.api.http.util
 import kotlinx.serialization.*
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.polymorphic
 import net.mamoe.mirai.api.http.data.common.*
 import kotlin.reflect.KClass
 
 // 解析失败时直接返回null,由路由判断响应400状态
-@OptIn(ImplicitReflectionSerializer::class, UnstableDefault::class)
 inline fun <reified T : Any> String.jsonParseOrNull(
     serializer: DeserializationStrategy<T>? = null
 ): T? = try {
-    if (serializer == null) MiraiJson.json.parse(this) else Json.parse(this)
+    if (serializer == null) Json.decodeFromString(this) else Json.decodeFromString(this)
 } catch (e: Exception) {
     null
 }
 
 
-@OptIn(ImplicitReflectionSerializer::class, UnstableDefault::class)
 inline fun <reified T : Any> T.toJson(
     serializer: SerializationStrategy<T>? = null
-): String = if (serializer == null) MiraiJson.json.stringify(this)
-else MiraiJson.json.stringify(serializer, this)
+): String = if (serializer == null) MiraiJson.json.encodeToString(this)
+else MiraiJson.json.encodeToString(serializer, this)
 
 
 // 序列化列表时,stringify需要使用的泛型是T,而非List<T>
 // 因为使用的stringify的stringify(objs: List<T>)重载
-@OptIn(ImplicitReflectionSerializer::class, UnstableDefault::class)
 inline fun <reified T : Any> List<T>.toJson(
     serializer: SerializationStrategy<List<T>>? = null
-): String = if (serializer == null) MiraiJson.json.stringify(this)
-else MiraiJson.json.stringify(serializer, this)
+): String = if (serializer == null) MiraiJson.json.encodeToString(this)
+else MiraiJson.json.encodeToString(serializer, this)
 
 
 /**
  * Json解析规则,需要注册支持的多态的类
  */
 object MiraiJson {
-    @OptIn(ImplicitReflectionSerializer::class)
-    @UnstableDefault
-    val json = Json {
+
+val json = Json {
 
         isLenient = true
         ignoreUnknownKeys = true
 
-        @Suppress("UNCHECKED_CAST")
-        serialModule = SerializersModule {
+//        @Suppress("UNCHECKED_CAST")
+        serializersModule = SerializersModule {
 
             polymorphic(EventDTO::class) {
 
-                GroupMessagePacketDTO::class with GroupMessagePacketDTO.serializer()
-                FriendMessagePacketDTO::class with FriendMessagePacketDTO.serializer()
-                TempMessagePacketDto::class with TempMessagePacketDto.serializer()
-
-
-                /*
-                 * BotEventDTO为sealed Class,以BotEventDTO为接收者的函数可以自动进行多态序列化
-                 * 这里通过向EventDTO为接收者的方法进行所有事件类型的多态注册
-                 */
-                BotEventDTO::class.sealedSubclasses.forEach {
-                    val clazz = it as KClass<BotEventDTO>
-                    clazz with clazz.serializer()
-                }
-
-//                BotOnlineEventDTO::class with BotOnlineEventDTO.serializer()
-//                BotOfflineEventActiveDTO::class with BotOfflineEventActiveDTO.serializer()
-//                BotOfflineEventForceDTO::class with BotOfflineEventForceDTO.serializer()
-//                BotOfflineEventDroppedDTO::class with BotOfflineEventDroppedDTO.serializer()
-//                BotReloginEventDTO::class with BotReloginEventDTO.serializer()
-//                GroupRecallEventDTO::class with GroupRecallEventDTO.serializer()
-//                FriendRecallEventDTO::class with FriendRecallEventDTO.serializer()
-//                BotGroupPermissionChangeEventDTO::class with BotGroupPermissionChangeEventDTO.serializer()
-//                BotMuteEventDTO::class with BotMuteEventDTO.serializer()
-//                BotUnmuteEventDTO::class with BotUnmuteEventDTO.serializer()
-//                BotJoinGroupEventDTO::class with BotJoinGroupEventDTO.serializer()
-//                GroupNameChangeEventDTO::class with GroupNameChangeEventDTO.serializer()
-//                GroupEntranceAnnouncementChangeEventDTO::class with GroupEntranceAnnouncementChangeEventDTO.serializer()
-//                GroupMuteAllEventDTO::class with GroupMuteAllEventDTO.serializer()
-//                GroupAllowAnonymousChatEventDTO::class with GroupAllowAnonymousChatEventDTO.serializer()
-//                GroupAllowConfessTalkEventDTO::class with GroupAllowConfessTalkEventDTO.serializer()
-//                GroupAllowMemberInviteEventDTO::class with GroupAllowMemberInviteEventDTO.serializer()
-//                MemberJoinEventDTO::class with MemberJoinEventDTO.serializer()
-//                MemberLeaveEventKickDTO::class with MemberLeaveEventKickDTO.serializer()
-//                MemberLeaveEventQuitDTO::class with MemberLeaveEventQuitDTO.serializer()
-//                MemberCardChangeEventDTO::class with MemberCardChangeEventDTO.serializer()
-//                MemberSpecialTitleChangeEventDTO::class with MemberSpecialTitleChangeEventDTO.serializer()
-//                MemberPermissionChangeEventDTO::class with MemberPermissionChangeEventDTO.serializer()
-//                MemberMuteEventDTO::class with MemberMuteEventDTO.serializer()
-//                MemberUnmuteEventDTO::class with MemberUnmuteEventDTO.serializer()
+                subclass(GroupMessagePacketDTO::class, GroupMessagePacketDTO.serializer())
+                subclass(FriendMessagePacketDTO::class, FriendMessagePacketDTO.serializer())
+                subclass(TempMessagePacketDto::class, TempMessagePacketDto.serializer())
+
+//                /*
+//                 * BotEventDTO为sealed Class,以BotEventDTO为接收者的函数可以自动进行多态序列化
+//                 * 这里通过向EventDTO为接收者的方法进行所有事件类型的多态注册
+//                 */
+//                BotEventDTO::class.sealedSubclasses.forEach {
+//                    val clazz = it as KClass<BotEventDTO>
+//                    subclass(clazz, clazz.serializer())
+//                }
+
+                subclass(BotOnlineEventDTO::class, BotOnlineEventDTO.serializer())
+                subclass(BotOfflineEventActiveDTO::class, BotOfflineEventActiveDTO.serializer())
+                subclass(BotOfflineEventForceDTO::class, BotOfflineEventForceDTO.serializer())
+                subclass(BotOfflineEventDroppedDTO::class, BotOfflineEventDroppedDTO.serializer())
+                subclass(BotReloginEventDTO::class, BotReloginEventDTO.serializer())
+                subclass(GroupRecallEventDTO::class, GroupRecallEventDTO.serializer())
+                subclass(FriendRecallEventDTO::class, FriendRecallEventDTO.serializer())
+                subclass(BotGroupPermissionChangeEventDTO::class, BotGroupPermissionChangeEventDTO.serializer())
+                subclass(BotMuteEventDTO::class, BotMuteEventDTO.serializer())
+                subclass(BotUnmuteEventDTO::class, BotUnmuteEventDTO.serializer())
+                subclass(BotJoinGroupEventDTO::class, BotJoinGroupEventDTO.serializer())
+                subclass(GroupNameChangeEventDTO::class, GroupNameChangeEventDTO.serializer())
+                subclass(GroupEntranceAnnouncementChangeEventDTO::class, GroupEntranceAnnouncementChangeEventDTO.serializer())
+                subclass(GroupMuteAllEventDTO::class, GroupMuteAllEventDTO.serializer())
+                subclass(GroupAllowAnonymousChatEventDTO::class, GroupAllowAnonymousChatEventDTO.serializer())
+                subclass(GroupAllowConfessTalkEventDTO::class, GroupAllowConfessTalkEventDTO.serializer())
+                subclass(GroupAllowMemberInviteEventDTO::class, GroupAllowMemberInviteEventDTO.serializer())
+                subclass(MemberJoinEventDTO::class, MemberJoinEventDTO.serializer())
+                subclass(MemberLeaveEventKickDTO::class, MemberLeaveEventKickDTO.serializer())
+                subclass(MemberLeaveEventQuitDTO::class, MemberLeaveEventQuitDTO.serializer())
+                subclass(MemberCardChangeEventDTO::class, MemberCardChangeEventDTO.serializer())
+                subclass(MemberSpecialTitleChangeEventDTO::class, MemberSpecialTitleChangeEventDTO.serializer())
+                subclass(MemberPermissionChangeEventDTO::class, MemberPermissionChangeEventDTO.serializer())
+                subclass(MemberMuteEventDTO::class, MemberMuteEventDTO.serializer())
+                subclass(MemberUnmuteEventDTO::class, MemberUnmuteEventDTO.serializer())
             }
         }
     }

+ 1 - 1
mirai-api-http/src/main/resources/plugin.yml

@@ -1,5 +1,5 @@
 name: MiraiAPIHTTP
 path: net.mamoe.mirai.api.http.HttpApiPluginBase
-version: v1.7.4
+version: v1.7.5
 info: Mirai HTTP API Server Plugin
 author: "ryoii"