Commit 6cf563c8 authored by jiahua.liu's avatar jiahua.liu

Merge remote-tracking branch 'origin/master'

parents 5402505a 8b9cef05
...@@ -2,6 +2,15 @@ ...@@ -2,6 +2,15 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.22.0` 2020/2/24
### mirai-core
- 重构 `MessageChain`, 引入 `CombinedMessage`. (兼容大部分原 API)
- 新增 `MessageChainBuilder`, `buildMessageChain`
- `ExternalImage` 现在接收多种输入参数
### mirai-core-qqandroid
- 修复访问好友消息回执 `.sequenceId` 时抛出异常的问题
## `0.21.0` 2020/2/23 ## `0.21.0` 2020/2/23
- 支持好友消息的引用回复 - 支持好友消息的引用回复
- 更加结构化的 `QuoteReply` 架构, 支持引用任意群/好友消息回复给任意群/好友. - 更加结构化的 `QuoteReply` 架构, 支持引用任意群/好友消息回复给任意群/好友.
......
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.21.0 mirai_version=0.22.0
mirai_japt_version=1.1.0 mirai_japt_version=1.1.0
mirai_console_version=0.1.1
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
......
...@@ -173,14 +173,16 @@ fun main() { ...@@ -173,14 +173,16 @@ fun main() {
| ------------ | ------ | ----- | ----------- | -------------------------------- | | ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session | | sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 发送消息目标好友的QQ号 | | target | Long | false | 987654321 | 发送消息目标好友的QQ号 |
| quote | Long | true | 135798642 | 引用一条消息的messageId进行回复 |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 | | messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码 #### 响应: 返回统一状态码(并携带messageId)
```json5 ```json5
{ {
"code": 0, "code": 0,
"msg": "success" "msg": "success",
"messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
} }
``` ```
...@@ -211,52 +213,16 @@ fun main() { ...@@ -211,52 +213,16 @@ fun main() {
| ------------ | ------ | ----- | ----------- | -------------------------------- | | ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session | | sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 发送消息目标群的群号 | | target | Long | false | 987654321 | 发送消息目标群的群号 |
| quote | Long | true | 135798642 | 引用一条消息的messageId进行回复 |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 | | messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码 #### 响应: 返回统一状态码(并携带messageId)
```json5
{
"code": 0,
"msg": "success"
}
```
### 发送引用回复消息(仅支持群消息)
```
[POST] /sendQuoteMessage
```
使用此方法向指定的消息进行引用回复
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 引用消息的Message Source的Uid |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码
```json5 ```json5
{ {
"code": 0, "code": 0,
"msg": "success" "msg": "success",
"messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
} }
``` ```
...@@ -331,6 +297,39 @@ Content-Type:multipart/form-data ...@@ -331,6 +297,39 @@ Content-Type:multipart/form-data
### 撤回消息
```
[POST] /recall
```
使用此方法撤回指定消息。对于bot发送的消息,又2分钟时间限制。对于撤回群聊中群员的消息,需要有相应权限
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 需要撤回的消息的messageId |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 获取Bot收到的消息和事件 ### 获取Bot收到的消息和事件
``` ```
...@@ -370,7 +369,10 @@ Content-Type:multipart/form-data ...@@ -370,7 +369,10 @@ Content-Type:multipart/form-data
} }
},{ },{
"type": "FriendMessage", // 消息类型:GroupMessage或FriendMessage或各类Event "type": "FriendMessage", // 消息类型:GroupMessage或FriendMessage或各类Event
"messageChain": [{ // 消息链,是一个消息对象构成的数组 "messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain", "type": "Plain",
"text": "Miral牛逼" "text": "Miral牛逼"
}], }],
......
...@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI ...@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
sealed class BotEventDTO : EventDTO() sealed class BotEventDTO : EventDTO()
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(MiraiExperimentalAPI::class)
fun BotEvent.toDTO() = when(this) { suspend fun BotEvent.toDTO() = when(this) {
is MessagePacket<*, *> -> toDTO() is MessagePacket<*, *> -> toDTO()
else -> when(this) { else -> when(this) {
is BotOnlineEvent -> BotOnlineEventDTO(bot.uin) is BotOnlineEvent -> BotOnlineEventDTO(bot.uin)
......
...@@ -17,8 +17,9 @@ import net.mamoe.mirai.message.FriendMessage ...@@ -17,8 +17,9 @@ import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.message.uploadImage
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import java.net.URL
/* /*
* DTO data class * DTO data class
...@@ -57,7 +58,7 @@ data class PlainDTO(val text: String) : MessageDTO() ...@@ -57,7 +58,7 @@ data class PlainDTO(val text: String) : MessageDTO()
@Serializable @Serializable
@SerialName("Image") @SerialName("Image")
data class ImageDTO(val imageId: String) : MessageDTO() data class ImageDTO(val imageId: String? = null, val url: String? = null) : MessageDTO()
@Serializable @Serializable
@SerialName("Xml") @SerialName("Xml")
...@@ -84,42 +85,49 @@ sealed class MessageDTO : DTO ...@@ -84,42 +85,49 @@ sealed class MessageDTO : DTO
/* /*
Extend function Extend function
*/ */
fun MessagePacket<*, *>.toDTO() = when (this) { suspend fun MessagePacket<*, *>.toDTO() = when (this) {
is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender)) is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender)) is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
else -> IgnoreEventDTO else -> IgnoreEventDTO
}.apply { }.apply {
if (this is MessagePacketDTO) { messageChain = message.toDTOChain() } if (this is MessagePacketDTO) {
// else: `this` is bot event // 将MessagePacket中的所有Message转为DTO对象,并添加到messageChain
// foreachContent会忽略MessageSource,一次主动获取
messageChain = mutableListOf(messageDTO(message[MessageSource])).apply {
message.foreachContent { content -> messageDTO(content).takeUnless { it == UnknownMessageDTO }?.let(::add) }
}
// else: `this` is bot event
}
} }
fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply { suspend fun MessageChainDTO.toMessageChain(contact: Contact) =
foreachContent { content -> content.toDTO().takeUnless { it == UnknownMessageDTO }?.let(::add) } buildMessageChain { this@toMessageChain.forEach { it.toMessage(contact)?.let(::add) } }
}
fun MessageChainDTO.toMessageChain(contact: Contact) =
buildMessageChain { this@toMessageChain.forEach { add(it.toMessage(contact)) } }
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiExperimentalAPI::class) @UseExperimental(ExperimentalUnsignedTypes::class)
fun Message.toDTO() = when (this) { suspend fun MessagePacket<*, *>.messageDTO(message: Message) = when (message) {
is MessageSource -> MessageSourceDTO(id) is MessageSource -> MessageSourceDTO(message.id)
is At -> AtDTO(target, display) is At -> AtDTO(message.target, message.display)
is AtAll -> AtAllDTO(0L) is AtAll -> AtAllDTO(0L)
is Face -> FaceDTO(id) is Face -> FaceDTO(message.id)
is PlainText -> PlainDTO(stringValue) is PlainText -> PlainDTO(message.stringValue)
is Image -> ImageDTO(imageId) is Image -> ImageDTO(message.imageId, message.url())
is XMLMessage -> XmlDTO(stringValue) is XMLMessage -> XmlDTO(message.stringValue)
else -> UnknownMessageDTO else -> UnknownMessageDTO
} }
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class, MiraiExperimentalAPI::class) @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
fun MessageDTO.toMessage(contact: Contact) = when (this) { suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
is AtDTO -> At((contact as Group)[target]) is AtDTO -> At((contact as Group)[target])
is AtAllDTO -> AtAll is AtAllDTO -> AtAll
is FaceDTO -> Face(faceId) is FaceDTO -> Face(faceId)
is PlainDTO -> PlainText(text) is PlainDTO -> PlainText(text)
is ImageDTO -> Image(imageId) is ImageDTO -> when {
!imageId.isNullOrBlank() -> Image(imageId)
!url.isNullOrBlank() -> contact.uploadImage(URL(url))
else -> null
}
is XmlDTO -> XMLMessage(xml) is XmlDTO -> XMLMessage(xml)
is MessageSourceDTO, is UnknownMessageDTO -> PlainText("assert cannot reach") is MessageSourceDTO, is UnknownMessageDTO -> null
} }
...@@ -13,17 +13,17 @@ import net.mamoe.mirai.api.http.data.common.EventDTO ...@@ -13,17 +13,17 @@ import net.mamoe.mirai.api.http.data.common.EventDTO
import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO
import net.mamoe.mirai.api.http.data.common.toDTO import net.mamoe.mirai.api.http.data.common.toDTO
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.firstKey import net.mamoe.mirai.utils.firstKey
import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ConcurrentLinkedDeque
class MessageQueue : ConcurrentLinkedDeque<BotEvent>() { class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
val quoteCacheSize = 4096 val cacheSize = 4096
val quoteCache = LinkedHashMap<Long, GroupMessage>() val cache = LinkedHashMap<Long, MessagePacket<*, *>>()
fun fetch(size: Int): List<EventDTO> { suspend fun fetch(size: Int): List<EventDTO> {
var count = size var count = size
val ret = ArrayList<EventDTO>(count) val ret = ArrayList<EventDTO>(count)
...@@ -37,18 +37,20 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() { ...@@ -37,18 +37,20 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
} }
} }
// TODO: 等FriendMessage支持quote if (event is MessagePacket<*, *>) {
if (event is GroupMessage) {
addQuoteCache(event) addQuoteCache(event)
} }
} }
return ret return ret
} }
private fun addQuoteCache(msg: GroupMessage) { fun cache(messageId: Long) =
quoteCache[msg.message[MessageSource].id] = msg cache[messageId] ?: throw NoSuchElementException()
if (quoteCache.size > quoteCacheSize) {
quoteCache.remove(quoteCache.firstKey()) fun addQuoteCache(msg: MessagePacket<*, *>) {
cache[msg.message[MessageSource].id] = msg
if (cache.size > cacheSize) {
cache.remove(cache.firstKey())
} }
} }
} }
\ No newline at end of file
...@@ -63,7 +63,7 @@ private data class AuthRetDTO(val code: Int, val session: String) : DTO ...@@ -63,7 +63,7 @@ private data class AuthRetDTO(val code: Int, val session: String) : DTO
private data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO() private data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
private fun getBotOrThrow(qq: Long) = try { private fun getBotOrThrow(qq: Long) = try {
Bot.instanceWhose(qq) Bot.getInstance(qq)
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
throw NoSuchBotException throw NoSuchBotException
} }
\ No newline at end of file
...@@ -20,15 +20,19 @@ import io.ktor.response.respondText ...@@ -20,15 +20,19 @@ import io.ktor.response.respondText
import io.ktor.routing.post import io.ktor.routing.post
import io.ktor.routing.routing import io.ktor.routing.routing
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.api.http.AuthedSession import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.data.* import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.DTO
import net.mamoe.mirai.api.http.data.common.MessageChainDTO 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.VerifyDTO
import net.mamoe.mirai.api.http.data.common.toMessageChain import net.mamoe.mirai.api.http.data.common.toMessageChain
import net.mamoe.mirai.api.http.util.toJson import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.uploadImage import net.mamoe.mirai.message.uploadImage
import java.net.URL import java.net.URL
...@@ -42,23 +46,47 @@ fun Application.messageModule() { ...@@ -42,23 +46,47 @@ fun Application.messageModule() {
call.respondJson(fetch.toJson()) call.respondJson(fetch.toJson())
} }
suspend fun <C : Contact> sendMessage(
quote: QuoteReplyToSend?,
messageChain: MessageChain,
target: C
): MessageReceipt<out Contact> {
val send = if (quote == null) {
messageChain
} else {
(quote + messageChain).toChain()
}
return target.sendMessage(send)
}
miraiVerify<SendDTO>("/sendFriendMessage") { miraiVerify<SendDTO>("/sendFriendMessage") {
val quote = it.quote?.let { q ->
it.session.messageQueue.cache(q).run {
this[MessageSource].quote(sender)
}}
it.session.bot.getFriend(it.target).apply { it.session.bot.getFriend(it.target).apply {
sendMessage(it.messageChain.toMessageChain(this)) // this aka QQ val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
receipt.source.ensureSequenceIdAvailable()
it.session.messageQueue.addQuoteCache(FriendMessage(bot.selfQQ, receipt.source.toChain()))
call.respondDTO(SendRetDTO(messageId = receipt.source.id))
} }
} }
miraiVerify<SendDTO>("/sendGroupMessage") { miraiVerify<SendDTO>("/sendGroupMessage") {
val quote = it.quote?.let { q ->
it.session.messageQueue.cache(q).run {
this[MessageSource].quote(sender)
}}
it.session.bot.getGroup(it.target).apply { it.session.bot.getGroup(it.target).apply {
sendMessage(it.messageChain.toMessageChain(this)) // this aka Group val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
} receipt.source.ensureSequenceIdAvailable()
}
miraiVerify<SendDTO>("/sendQuoteMessage") { it.session.messageQueue.addQuoteCache(GroupMessage("", botPermission, botAsMember, receipt.source.toChain()))
it.session.messageQueue.quoteCache[it.target]?.apply { call.respondDTO(SendRetDTO(messageId = receipt.source.id))
quoteReply(it.messageChain.toMessageChain(group)) }
} ?: throw NoSuchElementException()
call.respondStateCode(StateCode.Success)
} }
miraiVerify<SendImageDTO>("sendImageMessage") { miraiVerify<SendImageDTO>("sendImageMessage") {
...@@ -101,8 +129,10 @@ fun Application.messageModule() { ...@@ -101,8 +129,10 @@ fun Application.messageModule() {
} }
miraiVerify<RecallDTO>("recall") { miraiVerify<RecallDTO>("recall") {
// TODO it.session.messageQueue.cache(it.target).apply {
call.respond(HttpStatusCode.NotFound, "未完成") it.session.bot.recall(get(MessageSource))
}
call.respondStateCode(StateCode.Success)
} }
} }
} }
...@@ -110,6 +140,7 @@ fun Application.messageModule() { ...@@ -110,6 +140,7 @@ fun Application.messageModule() {
@Serializable @Serializable
private data class SendDTO( private data class SendDTO(
override val sessionKey: String, override val sessionKey: String,
val quote: Long? = null,
val target: Long, val target: Long,
val messageChain: MessageChainDTO val messageChain: MessageChainDTO
) : VerifyDTO() ) : VerifyDTO()
...@@ -125,13 +156,13 @@ private data class SendImageDTO( ...@@ -125,13 +156,13 @@ private data class SendImageDTO(
@Serializable @Serializable
private class SendRetDTO( private class SendRetDTO(
val messageId: Long, val code: Int = 0,
@Transient val stateCode: StateCode = Success val msg: String = "success",
) : StateCode(stateCode.code, stateCode.msg) val messageId: Long
) : DTO
@Serializable @Serializable
private data class RecallDTO( private data class RecallDTO(
override val sessionKey: String, override val sessionKey: String,
val target: Long, val target: Long
val sender: Long
) : VerifyDTO() ) : VerifyDTO()
...@@ -32,7 +32,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() { ...@@ -32,7 +32,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
dependencies { dependencies {
api(project(":mirai-core")) api(project(":mirai-core"))
api(project(":mirai-core-qqandroid")) api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http")) // api(project(":mirai-api-http"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main")) runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
api(kotlin("serialization")) api(kotlin("serialization"))
......
...@@ -12,32 +12,26 @@ package net.mamoe.mirai.console ...@@ -12,32 +12,26 @@ package net.mamoe.mirai.console
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.MiraiHttpAPIServer
import net.mamoe.mirai.api.http.generateSessionKey
import net.mamoe.mirai.console.MiraiConsole.CommandProcessor.processNextCommandLine import net.mamoe.mirai.console.MiraiConsole.CommandProcessor.processNextCommandLine
import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.DefaultCommands
import net.mamoe.mirai.console.plugins.PluginManager import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.plugins.loadAsConfig import net.mamoe.mirai.console.plugins.loadAsConfig
import net.mamoe.mirai.console.plugins.withDefaultWrite import net.mamoe.mirai.console.plugins.withDefaultWrite
import net.mamoe.mirai.console.plugins.withDefaultWriteSave
import net.mamoe.mirai.console.utils.MiraiConsoleUI import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.console.utils.checkManager
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.utils.SimpleLogger
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import java.io.File import java.io.File
import java.security.Security
import java.util.*
object MiraiConsole { object MiraiConsole {
/** /**
* 发布的版本号 统一修改位置 * 发布的版本号 统一修改位置
*/ */
val version = "v0.01" const val version = "0.1.0"
var coreVersion = "v0.18.0" const val coreVersion = "v0.18.0"
val build = "Alpha" const val build = "Alpha"
/** /**
...@@ -194,13 +188,15 @@ object MiraiProperties { ...@@ -194,13 +188,15 @@ object MiraiProperties {
var HTTP_API_ENABLE: Boolean by config.withDefaultWrite { true } var HTTP_API_ENABLE: Boolean by config.withDefaultWrite { true }
var HTTP_API_PORT: Int by config.withDefaultWrite { 8080 } var HTTP_API_PORT: Int by config.withDefaultWrite { 8080 }
/*
var HTTP_API_AUTH_KEY: String by config.withDefaultWriteSave { var HTTP_API_AUTH_KEY: String by config.withDefaultWriteSave {
"InitKey" + generateSessionKey() "InitKey" + generateSessionKey()
} }*/
} }
object HTTPAPIAdaptar { object HTTPAPIAdaptar {
operator fun invoke() { operator fun invoke() {
/*
if (MiraiProperties.HTTP_API_ENABLE) { if (MiraiProperties.HTTP_API_ENABLE) {
if (MiraiProperties.HTTP_API_AUTH_KEY.startsWith("InitKey")) { if (MiraiProperties.HTTP_API_AUTH_KEY.startsWith("InitKey")) {
MiraiConsole.logger("请尽快更改初始生成的HTTP API AUTHKEY") MiraiConsole.logger("请尽快更改初始生成的HTTP API AUTHKEY")
...@@ -214,7 +210,7 @@ object HTTPAPIAdaptar { ...@@ -214,7 +210,7 @@ object HTTPAPIAdaptar {
MiraiProperties.HTTP_API_AUTH_KEY MiraiProperties.HTTP_API_AUTH_KEY
) )
MiraiConsole.logger("HTTPAPI启动完成; 端口= " + MiraiProperties.HTTP_API_PORT) MiraiConsole.logger("HTTPAPI启动完成; 端口= " + MiraiProperties.HTTP_API_PORT)
} }*/
} }
} }
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
package net.mamoe.mirai.console.plugins package net.mamoe.mirai.console.plugins
import net.mamoe.mirai.console.command.Command
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SimpleLogger import net.mamoe.mirai.utils.SimpleLogger
...@@ -303,7 +303,7 @@ object PluginManager { ...@@ -303,7 +303,7 @@ object PluginManager {
} }
return try { return try {
val subClass = pluginClass.asSubclass(PluginBase::class.java) val subClass = pluginClass.asSubclass(PluginBase::class.java)
val plugin: PluginBase = subClass.getDeclaredConstructor().newInstance() val plugin: PluginBase = subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().newInstance()
description.loaded = true description.loaded = true
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author) logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
logger.info(description.info) logger.info(description.info)
......
...@@ -542,7 +542,7 @@ internal class GroupImpl( ...@@ -542,7 +542,7 @@ internal class GroupImpl(
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by FriendMessageSendEvent")
} }
lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSend lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSendGroup
bot.network.run { bot.network.run {
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup( val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
bot.client, bot.client,
...@@ -579,6 +579,7 @@ internal class GroupImpl( ...@@ -579,6 +579,7 @@ internal class GroupImpl(
when (response) { when (response) {
is ImgStore.GroupPicUp.Response.Failed -> { is ImgStore.GroupPicUp.Response.Failed -> {
ImageUploadEvent.Failed(this@GroupImpl, image, response.resultCode, response.message).broadcast() ImageUploadEvent.Failed(this@GroupImpl, image, response.resultCode, response.message).broadcast()
if (response.message == "over file size max") throw OverFileSizeMaxException()
error("upload group image failed with reason ${response.message}") error("upload group image failed with reason ${response.message}")
} }
is ImgStore.GroupPicUp.Response.FileExists -> { is ImgStore.GroupPicUp.Response.FileExists -> {
......
...@@ -129,9 +129,15 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -129,9 +129,15 @@ internal abstract class QQAndroidBotBase constructor(
source.ensureSequenceIdAvailable() source.ensureSequenceIdAvailable()
network.run { network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response = val response: PbMessageSvc.PbMsgWithDraw.Response = if (source.groupId == 0L) {
PbMessageSvc.PbMsgWithDraw.Friend(bot.client, source.senderId, source.sequenceId, source.messageRandom, source.time)
.sendAndExpect()
} else {
PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageRandom) PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageRandom)
.sendAndExpect() .sendAndExpect()
}
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" } check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
} }
} }
......
...@@ -71,6 +71,35 @@ internal class PbMessageSvc { ...@@ -71,6 +71,35 @@ internal class PbMessageSvc {
) )
} }
fun Friend(
client: QQAndroidClient,
toUin: Long,
messageSequenceId: Int, // 56639
messageRandom: Int, // 921878719
time: Long,
messageType: Int = 0
): OutgoingPacket = buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbMsgWithDrawReq.serializer(),
MsgSvc.PbMsgWithDrawReq(
c2cWithDraw = listOf(
MsgSvc.PbC2CMsgWithDrawReq(
subCmd = 1,
msgInfo = listOf(
MsgSvc.PbC2CMsgWithDrawReq.MsgInfo(
fromUin = client.bot.uin,
toUin = toUin,
msgSeq = messageSequenceId,
msgUid = messageRandom.toLong() and 0xffffffff,
msgTime = time and 0xffffffff
)
)
)
)
)
)
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer()) val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer())
resp.groupWithDraw?.firstOrNull()?.let { resp.groupWithDraw?.firstOrNull()?.let {
......
...@@ -269,7 +269,28 @@ internal class MessageSvc { ...@@ -269,7 +269,28 @@ internal class MessageSvc {
} }
} }
internal class MessageSourceFromSend( internal class MessageSourceFromSendFriend(
val messageRandom: Int,
override val time: Long,
override val senderId: Long,
override val groupId: Long,
val sequenceId: Int
) : MessageSource {
@UseExperimental(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceId.toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
override fun toString(): String {
return ""
}
}
internal class MessageSourceFromSendGroup(
val messageRandom: Int, val messageRandom: Int,
override val time: Long, override val time: Long,
override val senderId: Long, override val senderId: Long,
...@@ -286,7 +307,7 @@ internal class MessageSvc { ...@@ -286,7 +307,7 @@ internal class MessageSvc {
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(MiraiExperimentalAPI::class)
fun startWaitingSequenceId(contact: Contact) { fun startWaitingSequenceId(contact: Contact) {
sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(timeoutMillis = 3000) { sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(timeoutMillis = 3000) {
if (it.messageRandom == this@MessageSourceFromSend.messageRandom) { if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
it.sequenceId it.sequenceId
} else null } else null
} }
...@@ -305,14 +326,14 @@ internal class MessageSvc { ...@@ -305,14 +326,14 @@ internal class MessageSvc {
client: QQAndroidClient, client: QQAndroidClient,
toUin: Long, toUin: Long,
message: MessageChain, message: MessageChain,
crossinline sourceCallback: (MessageSource) -> Unit crossinline sourceCallback: (MessageSourceFromSendFriend) -> Unit
): OutgoingPacket { ): OutgoingPacket {
val source = MessageSourceFromSend( val source = MessageSourceFromSendFriend(
messageRandom = Random.nextInt().absoluteValue, messageRandom = Random.nextInt().absoluteValue,
senderId = client.uin, senderId = client.uin,
time = currentTimeSeconds + client.timeDifference, time = currentTimeSeconds + client.timeDifference,
groupId = 0// groupId = 0,
// sourceMessage = message sequenceId = client.atomicNextMessageSequenceId()
) )
sourceCallback(source) sourceCallback(source)
return ToFriend(client, toUin, message, source) return ToFriend(client, toUin, message, source)
...@@ -326,7 +347,7 @@ internal class MessageSvc { ...@@ -326,7 +347,7 @@ internal class MessageSvc {
client: QQAndroidClient, client: QQAndroidClient,
toUin: Long, toUin: Long,
message: MessageChain, message: MessageChain,
source: MessageSourceFromSend source: MessageSourceFromSendFriend
): OutgoingPacket = buildOutgoingUniPacket(client) { ): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
...@@ -340,7 +361,7 @@ internal class MessageSvc { ...@@ -340,7 +361,7 @@ internal class MessageSvc {
elems = message.toRichTextElems(false) elems = message.toRichTextElems(false)
) )
), ),
msgSeq = client.atomicNextMessageSequenceId(), msgSeq = source.sequenceId,
msgRand = source.messageRandom, msgRand = source.messageRandom,
syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer()) syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer())
// msgVia = 1 // msgVia = 1
...@@ -353,10 +374,10 @@ internal class MessageSvc { ...@@ -353,10 +374,10 @@ internal class MessageSvc {
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long, groupCode: Long,
message: MessageChain, message: MessageChain,
sourceCallback: (MessageSourceFromSend) -> Unit sourceCallback: (MessageSourceFromSendGroup) -> Unit
): OutgoingPacket { ): OutgoingPacket {
val source = MessageSourceFromSend( val source = MessageSourceFromSendGroup(
messageRandom = Random.nextInt().absoluteValue, messageRandom = Random.nextInt().absoluteValue,
senderId = client.uin, senderId = client.uin,
time = currentTimeSeconds + client.timeDifference, time = currentTimeSeconds + client.timeDifference,
...@@ -375,7 +396,7 @@ internal class MessageSvc { ...@@ -375,7 +396,7 @@ internal class MessageSvc {
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long, groupCode: Long,
message: MessageChain, message: MessageChain,
source: MessageSourceFromSend source: MessageSourceFromSendGroup
): OutgoingPacket = buildOutgoingUniPacket(client) { ): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
......
...@@ -59,7 +59,7 @@ abstract class Bot : CoroutineScope { ...@@ -59,7 +59,7 @@ abstract class Bot : CoroutineScope {
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
*/ */
@JvmStatic @JvmStatic
fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq) fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq)
} }
/** /**
......
...@@ -60,7 +60,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -60,7 +60,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
it.get()?.let(block) it.get()?.let(block)
} }
fun instanceWhose(qq: Long): Bot { fun getInstance(qq: Long): Bot {
instances.forEach { instances.forEach {
it.get()?.let { bot -> it.get()?.let { bot ->
if (bot.uin == qq) { if (bot.uin == qq) {
......
...@@ -25,6 +25,7 @@ import net.mamoe.mirai.recall ...@@ -25,6 +25,7 @@ import net.mamoe.mirai.recall
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.WeakRefProperty import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
...@@ -74,6 +75,7 @@ interface Contact : CoroutineScope { ...@@ -74,6 +75,7 @@ interface Contact : CoroutineScope {
* @see ImageUploadEvent 图片发送完成事件 * @see ImageUploadEvent 图片发送完成事件
* *
* @throws EventCancelledException 当发送消息事件被取消 * @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
*/ */
suspend fun uploadImage(image: ExternalImage): Image suspend fun uploadImage(image: ExternalImage): Image
......
...@@ -31,7 +31,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef ...@@ -31,7 +31,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
* @see QQ.sendMessage 发送群消息, 返回回执(此对象) * @see QQ.sendMessage 发送群消息, 返回回执(此对象)
*/ */
open class MessageReceipt<C : Contact>( open class MessageReceipt<C : Contact>(
private val source: MessageSource, val source: MessageSource,
target: C target: C
) { ) {
init { init {
...@@ -48,7 +48,7 @@ open class MessageReceipt<C : Contact>( ...@@ -48,7 +48,7 @@ open class MessageReceipt<C : Contact>(
/** /**
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
* *
* @see Group.recall * @see Bot.recall
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/ */
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(MiraiExperimentalAPI::class)
...@@ -78,13 +78,9 @@ open class MessageReceipt<C : Contact>( ...@@ -78,13 +78,9 @@ open class MessageReceipt<C : Contact>(
fun recallIn(millis: Long): Job { fun recallIn(millis: Long): Job {
@Suppress("BooleanLiteralArgument") @Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) { if (_isRecalled.compareAndSet(false, true)) {
when (val contact = target) { return when (val contact = target) {
is Group -> { is QQ,
return contact.bot.recallIn(source, millis) is Group -> contact.bot.recallIn(source, millis)
}
is QQ -> {
TODO()
}
else -> error("Unknown contact type") else -> error("Unknown contact type")
} }
} else error("message is already or planned to be recalled") } else error("message is already or planned to be recalled")
......
...@@ -12,15 +12,18 @@ ...@@ -12,15 +12,18 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.Bot
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/** /**
* 消息源, 用于被引用. 它将由协议模块实现. * 消息源, 它存在于 [MessageChain] 中, 用于表示这个消息的来源.
* 消息源只用于 [QuoteReply] *
* 消息源只用于 [引用回复][QuoteReply] 或 [撤回][Bot.recall].
* *
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg` * `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
* *
* @see Bot.recall 撤回一条消息
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain] * @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
*/ */
interface MessageSource : Message, MessageMetadata { interface MessageSource : Message, MessageMetadata {
......
...@@ -161,7 +161,7 @@ internal class LockFreeLinkedListTest { ...@@ -161,7 +161,7 @@ internal class LockFreeLinkedListTest {
println("Check value") println("Check value")
value shouldBeEqualTo 6 value shouldBeEqualTo 6
println("Check size") println("Check size")
println(list.getLinkStructure()) // println(list.getLinkStructure())
list.size shouldBeEqualTo 6 list.size shouldBeEqualTo 6
} }
...@@ -174,7 +174,7 @@ internal class LockFreeLinkedListTest { ...@@ -174,7 +174,7 @@ internal class LockFreeLinkedListTest {
println("Check value") println("Check value")
value shouldBeEqualTo 2 value shouldBeEqualTo 2
println("Check size") println("Check size")
println(list.getLinkStructure()) // println(list.getLinkStructure())
list.size shouldBeEqualTo 5 list.size shouldBeEqualTo 5
} }
...@@ -198,7 +198,7 @@ internal class LockFreeLinkedListTest { ...@@ -198,7 +198,7 @@ internal class LockFreeLinkedListTest {
println("Check value") println("Check value")
value shouldBeEqualTo 2 value shouldBeEqualTo 2
println("Check size") println("Check size")
println(list.getLinkStructure()) // println(list.getLinkStructure())
list.size shouldBeEqualTo 1 list.size shouldBeEqualTo 1
} }
/* /*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment