diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md index fb539bbc2..550b63e75 100644 --- a/mirai-api-http/README_CH.md +++ b/mirai-api-http/README_CH.md @@ -220,6 +220,75 @@ fun main() { +### 发送图片消息(通过URL) + +``` +[POST] /sendGroupMessage +``` + +使用此方法向指定群发送消息 + +#### 请求 + +```json5 +{ + "sessionKey": "YourSession", + "target": 987654321, + "qq": 1234567890, + "group": 987654321, + "urls": [ + "https://xxx.yyy.zzz/", + "https://aaa.bbb.ccc/" + ] +} +``` + +| 名字 | 类型 | 可选 | 举例 | 说明 | +| ------------ | ------ | ----- | ----------- | ---------------------------------- | +| sessionKey | String | false | YourSession | 已经激活的Session | +| target | Long | true | 987654321 | 发送对象的QQ号或群号,可能存在歧义 | +| qq | Long | true | 123456789 | 发送对象的QQ号 | +| group | Long | true | 987654321 | 发送对象的群号 | +| urls | Array | false | [] | 是一个url字符串构成的数组 | + +#### 响应: 图片的imageId数组 + +```json5 +[ + "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.jpg", + "{YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY}.jpg" +] +``` + + + +### 图片文件上传 + +``` +[POST] /sendGroupMessage +``` + +使用此方法上传图片文件至服务器并返回ImageId + +#### 请求 + +Content-Type:multipart/form-data + +| 名字 | 类型 | 可选 | 举例 | 说明 | +| ------------ | ------ | ----- | ----------- | ---------------------------------- | +| sessionKey | String | false | YourSession | 已经激活的Session | +| type | String | false | "friend " | "friend" 或 "group" | +| img | File | false | - | 图片文件 | + + +#### 响应: 图片的imageId(好友图片与群聊图片Id不同) + +``` +{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.jpg +``` + + + ### 获取Bot收到的消息 ``` diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt index fc57a8470..ccee7a996 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt @@ -18,6 +18,7 @@ import io.ktor.features.DefaultHeaders import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode +import io.ktor.http.content.PartData import io.ktor.request.receive import io.ktor.response.defaultTextContentType import io.ktor.response.respondText @@ -182,3 +183,20 @@ internal inline fun PipelineContext.paramOrNu else -> error(name::class.simpleName + " is not supported") } as R ?: illegalParam(R::class.simpleName, name) + +/** + * multi part + */ +internal fun List.value(name: String) = + try { + (filter { it.name == name }[0] as PartData.FormItem).value + } catch (e: Exception) { + throw IllegalParamException("参数格式错误") + } + +internal fun List.file(name: String) = + try { + filter { it.name == name }[0] as? PartData.FileItem + } catch (e: Exception) { + throw IllegalParamException("参数格式错误") + } \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt index 252fff813..6a41fe76c 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt @@ -11,14 +11,25 @@ package net.mamoe.mirai.api.http.route import io.ktor.application.Application import io.ktor.application.call +import io.ktor.http.content.readAllParts +import io.ktor.http.content.streamProvider +import io.ktor.request.receiveMultipart +import io.ktor.response.respondText +import io.ktor.routing.post import io.ktor.routing.routing import kotlinx.serialization.Serializable -import net.mamoe.mirai.api.http.data.StateCode +import net.mamoe.mirai.api.http.AuthedSession +import net.mamoe.mirai.api.http.SessionManager +import net.mamoe.mirai.api.http.data.* import net.mamoe.mirai.api.http.data.common.MessageChainDTO import net.mamoe.mirai.api.http.data.common.VerifyDTO import net.mamoe.mirai.api.http.data.common.toDTO import net.mamoe.mirai.api.http.data.common.toMessageChain import net.mamoe.mirai.api.http.util.toJson +import net.mamoe.mirai.contact.toList +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.uploadImage +import java.net.URL fun Application.messageModule() { routing { @@ -41,6 +52,42 @@ fun Application.messageModule() { call.respondStateCode(StateCode.Success) } + miraiVerify("sendImageMessage") { + val bot = it.session.bot + val contact = when { + it.target != null -> bot[it.target] + it.qq != null -> bot.getFriend(it.qq) + it.group != null -> bot.getGroup(it.group) + else -> throw IllegalParamException("target、qq、group不可全为null") + } + val ls = it.urls.map { url -> contact.uploadImage(URL(url)) } + contact.sendMessage(MessageChain(ls)) + call.respondJson(ls.map { image -> image.imageId }.toJson()) + } + + // TODO: 重构 + post("uploadImage") { + val parts = call.receiveMultipart().readAllParts() + val sessionKey = parts.value("sessionKey") + if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException + val session = try { + SessionManager[sessionKey] as AuthedSession + } catch (e: TypeCastException) { throw NotVerifiedSessionException } + + val type = parts.value("type") + parts.file("img")?.apply { + val image = streamProvider().use { + when(type) { + "group" -> session.bot.groups.toList().random().uploadImage(it) + "friend" -> session.bot.qqs.toList().random().uploadImage(it) + else -> null + } + } + image?.apply { + call.respondText(imageId) + } ?: throw IllegalAccessException("图片上传错误") + } ?: throw IllegalAccessException("未知错误") + } } } @@ -49,4 +96,14 @@ private data class SendDTO( override val sessionKey: String, val target: Long, val messageChain: MessageChainDTO -) : VerifyDTO() \ No newline at end of file +) : VerifyDTO() + +@Serializable +private data class SendImageDTO( + override val sessionKey: String, + val target: Long? = null, + val qq: Long? = null, + val group: Long? = null, + val urls: List +) : VerifyDTO() + diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index ca368b55f..5723cebe4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -32,7 +32,11 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie import net.mamoe.mirai.qqandroid.network.protocol.packet.* +import net.mamoe.mirai.qqandroid.message.toMessageChain +import net.mamoe.mirai.qqandroid.message.toRichTextElems +import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.math.absoluteValue import kotlin.random.Random @@ -44,13 +48,16 @@ internal class MessageSvc { internal object PushNotify : IncomingPacketFactory("MessageSvc.PushNotify") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify { discardExact(4) // don't remove - return decodeUniPacket(RequestPushNotify.serializer()) } override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? { network.run { - return PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds) + return PbGetMsg( + client, + MsgSvc.SyncFlag.START, + packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds + ) } } } @@ -129,6 +136,10 @@ internal class MessageSvc { val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull { when (it.msgHead.msgType) { + 33 -> { + println("GroupUin" + it.msgHead.fromUin + "新群员" + it.msgHead.authUin + " 出现了[" + it.msgHead.authNick + "] 添加刷新") + null + } 166 -> { when { it.msgHead.fromUin == bot.uin -> null diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index b14e0ffb3..ce8f11441 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -111,9 +111,9 @@ internal class OnlinePush { val groupUin = content.fromUin val target = var7 if (this.readByte().toInt() == 1) { - println("群" + groupUin + "新增管理员" + target) + println("群uin" + groupUin + "新增管理员" + target) } else { - println("群" + groupUin + "减少管理员" + target) + println("群uin" + groupUin + "减少管理员" + target) } } } @@ -122,8 +122,9 @@ internal class OnlinePush { if (readByte().toInt() == 1) { val target = readUInt().toLong() val groupUin = content.fromUin - println("群" + groupUin + "t掉了" + target) + println("群uin" + groupUin + "t掉了" + target) } + } } } @@ -200,6 +201,10 @@ internal class OnlinePush { } } } + 4352 -> { + println(msgInfo.contentToString()) + println(msgInfo.vMsg.toUHexString()) + } else -> { println("unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " ") } @@ -207,8 +212,6 @@ internal class OnlinePush { } else if (msgInfo.shMsgType.toInt() == 528) { val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer()) println(content.contentToString()) - } else if (msgInfo.shMsgType.toInt() == 4352) { - println("4352") } else { println("unknown shtype ${msgInfo.shMsgType.toInt()}") }