mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-23 22:00:10 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
6cf563c8e5
@ -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
|
||||
- 支持好友消息的引用回复
|
||||
- 更加结构化的 `QuoteReply` 架构, 支持引用任意群/好友消息回复给任意群/好友.
|
||||
|
@ -1,8 +1,9 @@
|
||||
# style guide
|
||||
kotlin.code.style=official
|
||||
# config
|
||||
mirai_version=0.21.0
|
||||
mirai_version=0.22.0
|
||||
mirai_japt_version=1.1.0
|
||||
mirai_console_version=0.1.1
|
||||
kotlin.incremental.multiplatform=true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
# kotlin
|
||||
|
@ -173,14 +173,16 @@ fun main() {
|
||||
| ------------ | ------ | ----- | ----------- | -------------------------------- |
|
||||
| sessionKey | String | false | YourSession | 已经激活的Session |
|
||||
| target | Long | false | 987654321 | 发送消息目标好友的QQ号 |
|
||||
| quote | Long | true | 135798642 | 引用一条消息的messageId进行回复 |
|
||||
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
|
||||
|
||||
#### 响应: 返回统一状态码
|
||||
#### 响应: 返回统一状态码(并携带messageId)
|
||||
|
||||
```json5
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success"
|
||||
"msg": "success",
|
||||
"messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
|
||||
}
|
||||
```
|
||||
|
||||
@ -211,52 +213,16 @@ fun main() {
|
||||
| ------------ | ------ | ----- | ----------- | -------------------------------- |
|
||||
| sessionKey | String | false | YourSession | 已经激活的Session |
|
||||
| target | Long | false | 987654321 | 发送消息目标群的群号 |
|
||||
| quote | Long | true | 135798642 | 引用一条消息的messageId进行回复 |
|
||||
| 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
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success"
|
||||
"msg": "success",
|
||||
"messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
|
||||
}
|
||||
```
|
||||
|
||||
@ -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收到的消息和事件
|
||||
|
||||
```
|
||||
@ -370,7 +369,10 @@ Content-Type:multipart/form-data
|
||||
}
|
||||
},{
|
||||
"type": "FriendMessage", // 消息类型:GroupMessage或FriendMessage或各类Event
|
||||
"messageChain": [{ // 消息链,是一个消息对象构成的数组
|
||||
"messageChain": [{ // 消息链,是一个消息对象构成的数组
|
||||
"type": "Source",
|
||||
"uid": 123456
|
||||
},{
|
||||
"type": "Plain",
|
||||
"text": "Miral牛逼"
|
||||
}],
|
||||
|
@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
sealed class BotEventDTO : EventDTO()
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
fun BotEvent.toDTO() = when(this) {
|
||||
suspend fun BotEvent.toDTO() = when(this) {
|
||||
is MessagePacket<*, *> -> toDTO()
|
||||
else -> when(this) {
|
||||
is BotOnlineEvent -> BotOnlineEventDTO(bot.uin)
|
||||
|
@ -17,8 +17,9 @@ import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.MessagePacket
|
||||
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 java.net.URL
|
||||
|
||||
/*
|
||||
* DTO data class
|
||||
@ -57,7 +58,7 @@ data class PlainDTO(val text: String) : MessageDTO()
|
||||
|
||||
@Serializable
|
||||
@SerialName("Image")
|
||||
data class ImageDTO(val imageId: String) : MessageDTO()
|
||||
data class ImageDTO(val imageId: String? = null, val url: String? = null) : MessageDTO()
|
||||
|
||||
@Serializable
|
||||
@SerialName("Xml")
|
||||
@ -84,42 +85,49 @@ sealed class MessageDTO : DTO
|
||||
/*
|
||||
Extend function
|
||||
*/
|
||||
fun MessagePacket<*, *>.toDTO() = when (this) {
|
||||
suspend fun MessagePacket<*, *>.toDTO() = when (this) {
|
||||
is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
|
||||
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
|
||||
else -> IgnoreEventDTO
|
||||
}.apply {
|
||||
if (this is MessagePacketDTO) { messageChain = message.toDTOChain() }
|
||||
// else: `this` is bot event
|
||||
if (this is MessagePacketDTO) {
|
||||
// 将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 {
|
||||
foreachContent { content -> content.toDTO().takeUnless { it == UnknownMessageDTO }?.let(::add) }
|
||||
}
|
||||
suspend fun MessageChainDTO.toMessageChain(contact: Contact) =
|
||||
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)
|
||||
fun Message.toDTO() = when (this) {
|
||||
is MessageSource -> MessageSourceDTO(id)
|
||||
is At -> AtDTO(target, display)
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
suspend fun MessagePacket<*, *>.messageDTO(message: Message) = when (message) {
|
||||
is MessageSource -> MessageSourceDTO(message.id)
|
||||
is At -> AtDTO(message.target, message.display)
|
||||
is AtAll -> AtAllDTO(0L)
|
||||
is Face -> FaceDTO(id)
|
||||
is PlainText -> PlainDTO(stringValue)
|
||||
is Image -> ImageDTO(imageId)
|
||||
is XMLMessage -> XmlDTO(stringValue)
|
||||
is Face -> FaceDTO(message.id)
|
||||
is PlainText -> PlainDTO(message.stringValue)
|
||||
is Image -> ImageDTO(message.imageId, message.url())
|
||||
is XMLMessage -> XmlDTO(message.stringValue)
|
||||
else -> UnknownMessageDTO
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class, MiraiExperimentalAPI::class)
|
||||
fun MessageDTO.toMessage(contact: Contact) = when (this) {
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
|
||||
is AtDTO -> At((contact as Group)[target])
|
||||
is AtAllDTO -> AtAll
|
||||
is FaceDTO -> Face(faceId)
|
||||
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 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
|
||||
import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO
|
||||
import net.mamoe.mirai.api.http.data.common.toDTO
|
||||
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.utils.firstKey
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
|
||||
class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
|
||||
|
||||
val quoteCacheSize = 4096
|
||||
val quoteCache = LinkedHashMap<Long, GroupMessage>()
|
||||
val cacheSize = 4096
|
||||
val cache = LinkedHashMap<Long, MessagePacket<*, *>>()
|
||||
|
||||
fun fetch(size: Int): List<EventDTO> {
|
||||
suspend fun fetch(size: Int): List<EventDTO> {
|
||||
var count = size
|
||||
|
||||
val ret = ArrayList<EventDTO>(count)
|
||||
@ -37,18 +37,20 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 等FriendMessage支持quote
|
||||
if (event is GroupMessage) {
|
||||
if (event is MessagePacket<*, *>) {
|
||||
addQuoteCache(event)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun addQuoteCache(msg: GroupMessage) {
|
||||
quoteCache[msg.message[MessageSource].id] = msg
|
||||
if (quoteCache.size > quoteCacheSize) {
|
||||
quoteCache.remove(quoteCache.firstKey())
|
||||
fun cache(messageId: Long) =
|
||||
cache[messageId] ?: throw NoSuchElementException()
|
||||
|
||||
fun addQuoteCache(msg: MessagePacket<*, *>) {
|
||||
cache[msg.message[MessageSource].id] = msg
|
||||
if (cache.size > cacheSize) {
|
||||
cache.remove(cache.firstKey())
|
||||
}
|
||||
}
|
||||
}
|
@ -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 fun getBotOrThrow(qq: Long) = try {
|
||||
Bot.instanceWhose(qq)
|
||||
Bot.getInstance(qq)
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw NoSuchBotException
|
||||
}
|
@ -20,15 +20,19 @@ import io.ktor.response.respondText
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.routing.routing
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
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.DTO
|
||||
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.toMessageChain
|
||||
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 java.net.URL
|
||||
|
||||
@ -42,23 +46,47 @@ fun Application.messageModule() {
|
||||
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") {
|
||||
val quote = it.quote?.let { q ->
|
||||
it.session.messageQueue.cache(q).run {
|
||||
this[MessageSource].quote(sender)
|
||||
}}
|
||||
|
||||
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") {
|
||||
it.session.bot.getGroup(it.target).apply {
|
||||
sendMessage(it.messageChain.toMessageChain(this)) // this aka Group
|
||||
}
|
||||
}
|
||||
val quote = it.quote?.let { q ->
|
||||
it.session.messageQueue.cache(q).run {
|
||||
this[MessageSource].quote(sender)
|
||||
}}
|
||||
|
||||
miraiVerify<SendDTO>("/sendQuoteMessage") {
|
||||
it.session.messageQueue.quoteCache[it.target]?.apply {
|
||||
quoteReply(it.messageChain.toMessageChain(group))
|
||||
} ?: throw NoSuchElementException()
|
||||
call.respondStateCode(StateCode.Success)
|
||||
it.session.bot.getGroup(it.target).apply {
|
||||
val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
|
||||
receipt.source.ensureSequenceIdAvailable()
|
||||
|
||||
it.session.messageQueue.addQuoteCache(GroupMessage("", botPermission, botAsMember, receipt.source.toChain()))
|
||||
call.respondDTO(SendRetDTO(messageId = receipt.source.id))
|
||||
}
|
||||
}
|
||||
|
||||
miraiVerify<SendImageDTO>("sendImageMessage") {
|
||||
@ -101,8 +129,10 @@ fun Application.messageModule() {
|
||||
}
|
||||
|
||||
miraiVerify<RecallDTO>("recall") {
|
||||
// TODO
|
||||
call.respond(HttpStatusCode.NotFound, "未完成")
|
||||
it.session.messageQueue.cache(it.target).apply {
|
||||
it.session.bot.recall(get(MessageSource))
|
||||
}
|
||||
call.respondStateCode(StateCode.Success)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,6 +140,7 @@ fun Application.messageModule() {
|
||||
@Serializable
|
||||
private data class SendDTO(
|
||||
override val sessionKey: String,
|
||||
val quote: Long? = null,
|
||||
val target: Long,
|
||||
val messageChain: MessageChainDTO
|
||||
) : VerifyDTO()
|
||||
@ -125,13 +156,13 @@ private data class SendImageDTO(
|
||||
|
||||
@Serializable
|
||||
private class SendRetDTO(
|
||||
val messageId: Long,
|
||||
@Transient val stateCode: StateCode = Success
|
||||
) : StateCode(stateCode.code, stateCode.msg)
|
||||
val code: Int = 0,
|
||||
val msg: String = "success",
|
||||
val messageId: Long
|
||||
) : DTO
|
||||
|
||||
@Serializable
|
||||
private data class RecallDTO(
|
||||
override val sessionKey: String,
|
||||
val target: Long,
|
||||
val sender: Long
|
||||
val target: Long
|
||||
) : VerifyDTO()
|
||||
|
@ -32,7 +32,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
|
||||
dependencies {
|
||||
api(project(":mirai-core"))
|
||||
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/build/classes/kotlin/jvm/main"))
|
||||
api(kotlin("serialization"))
|
||||
|
@ -12,32 +12,26 @@ package net.mamoe.mirai.console
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
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.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.loadAsConfig
|
||||
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.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 java.io.File
|
||||
import java.security.Security
|
||||
import java.util.*
|
||||
|
||||
|
||||
object MiraiConsole {
|
||||
/**
|
||||
* 发布的版本号 统一修改位置
|
||||
*/
|
||||
val version = "v0.01"
|
||||
var coreVersion = "v0.18.0"
|
||||
val build = "Alpha"
|
||||
const val version = "0.1.0"
|
||||
const val coreVersion = "v0.18.0"
|
||||
const val build = "Alpha"
|
||||
|
||||
|
||||
/**
|
||||
@ -194,13 +188,15 @@ object MiraiProperties {
|
||||
|
||||
var HTTP_API_ENABLE: Boolean by config.withDefaultWrite { true }
|
||||
var HTTP_API_PORT: Int by config.withDefaultWrite { 8080 }
|
||||
/*
|
||||
var HTTP_API_AUTH_KEY: String by config.withDefaultWriteSave {
|
||||
"InitKey" + generateSessionKey()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
object HTTPAPIAdaptar {
|
||||
operator fun invoke() {
|
||||
/*
|
||||
if (MiraiProperties.HTTP_API_ENABLE) {
|
||||
if (MiraiProperties.HTTP_API_AUTH_KEY.startsWith("InitKey")) {
|
||||
MiraiConsole.logger("请尽快更改初始生成的HTTP API AUTHKEY")
|
||||
@ -214,7 +210,7 @@ object HTTPAPIAdaptar {
|
||||
MiraiProperties.HTTP_API_AUTH_KEY
|
||||
)
|
||||
MiraiConsole.logger("HTTPAPI启动完成; 端口= " + MiraiProperties.HTTP_API_PORT)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,2 +0,0 @@
|
||||
package net.mamoe.mirai.console
|
||||
|
@ -9,9 +9,9 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugins
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.SimpleLogger
|
||||
@ -303,7 +303,7 @@ object PluginManager {
|
||||
}
|
||||
return try {
|
||||
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
|
||||
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
|
||||
logger.info(description.info)
|
||||
|
@ -542,7 +542,7 @@ internal class GroupImpl(
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSend
|
||||
lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSendGroup
|
||||
bot.network.run {
|
||||
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
|
||||
bot.client,
|
||||
@ -579,6 +579,7 @@ internal class GroupImpl(
|
||||
when (response) {
|
||||
is ImgStore.GroupPicUp.Response.Failed -> {
|
||||
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}")
|
||||
}
|
||||
is ImgStore.GroupPicUp.Response.FileExists -> {
|
||||
|
@ -129,9 +129,15 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
source.ensureSequenceIdAvailable()
|
||||
|
||||
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)
|
||||
.sendAndExpect()
|
||||
}
|
||||
|
||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer())
|
||||
resp.groupWithDraw?.firstOrNull()?.let {
|
||||
|
@ -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,
|
||||
override val time: Long,
|
||||
override val senderId: Long,
|
||||
@ -286,7 +307,7 @@ internal class MessageSvc {
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
fun startWaitingSequenceId(contact: Contact) {
|
||||
sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(timeoutMillis = 3000) {
|
||||
if (it.messageRandom == this@MessageSourceFromSend.messageRandom) {
|
||||
if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
|
||||
it.sequenceId
|
||||
} else null
|
||||
}
|
||||
@ -305,14 +326,14 @@ internal class MessageSvc {
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
message: MessageChain,
|
||||
crossinline sourceCallback: (MessageSource) -> Unit
|
||||
crossinline sourceCallback: (MessageSourceFromSendFriend) -> Unit
|
||||
): OutgoingPacket {
|
||||
val source = MessageSourceFromSend(
|
||||
val source = MessageSourceFromSendFriend(
|
||||
messageRandom = Random.nextInt().absoluteValue,
|
||||
senderId = client.uin,
|
||||
time = currentTimeSeconds + client.timeDifference,
|
||||
groupId = 0//
|
||||
// sourceMessage = message
|
||||
groupId = 0,
|
||||
sequenceId = client.atomicNextMessageSequenceId()
|
||||
)
|
||||
sourceCallback(source)
|
||||
return ToFriend(client, toUin, message, source)
|
||||
@ -326,7 +347,7 @@ internal class MessageSvc {
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
message: MessageChain,
|
||||
source: MessageSourceFromSend
|
||||
source: MessageSourceFromSendFriend
|
||||
): 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())
|
||||
|
||||
@ -340,7 +361,7 @@ internal class MessageSvc {
|
||||
elems = message.toRichTextElems(false)
|
||||
)
|
||||
),
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgSeq = source.sequenceId,
|
||||
msgRand = source.messageRandom,
|
||||
syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer())
|
||||
// msgVia = 1
|
||||
@ -353,10 +374,10 @@ internal class MessageSvc {
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
message: MessageChain,
|
||||
sourceCallback: (MessageSourceFromSend) -> Unit
|
||||
sourceCallback: (MessageSourceFromSendGroup) -> Unit
|
||||
): OutgoingPacket {
|
||||
|
||||
val source = MessageSourceFromSend(
|
||||
val source = MessageSourceFromSendGroup(
|
||||
messageRandom = Random.nextInt().absoluteValue,
|
||||
senderId = client.uin,
|
||||
time = currentTimeSeconds + client.timeDifference,
|
||||
@ -375,7 +396,7 @@ internal class MessageSvc {
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
message: MessageChain,
|
||||
source: MessageSourceFromSend
|
||||
source: MessageSourceFromSendGroup
|
||||
): 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())
|
||||
|
||||
|
@ -59,7 +59,7 @@ abstract class Bot : CoroutineScope {
|
||||
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
|
||||
*/
|
||||
@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(
|
||||
it.get()?.let(block)
|
||||
}
|
||||
|
||||
fun instanceWhose(qq: Long): Bot {
|
||||
fun getInstance(qq: Long): Bot {
|
||||
instances.forEach {
|
||||
it.get()?.let { bot ->
|
||||
if (bot.uin == qq) {
|
||||
|
@ -25,6 +25,7 @@ import net.mamoe.mirai.recall
|
||||
import net.mamoe.mirai.recallIn
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
@ -74,6 +75,7 @@ interface Contact : CoroutineScope {
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
|
||||
*/
|
||||
suspend fun uploadImage(image: ExternalImage): Image
|
||||
|
||||
|
@ -31,7 +31,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
* @see QQ.sendMessage 发送群消息, 返回回执(此对象)
|
||||
*/
|
||||
open class MessageReceipt<C : Contact>(
|
||||
private val source: MessageSource,
|
||||
val source: MessageSource,
|
||||
target: C
|
||||
) {
|
||||
init {
|
||||
@ -48,7 +48,7 @@ open class MessageReceipt<C : Contact>(
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Group.recall
|
||||
* @see Bot.recall
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
@ -78,13 +78,9 @@ open class MessageReceipt<C : Contact>(
|
||||
fun recallIn(millis: Long): Job {
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
if (_isRecalled.compareAndSet(false, true)) {
|
||||
when (val contact = target) {
|
||||
is Group -> {
|
||||
return contact.bot.recallIn(source, millis)
|
||||
}
|
||||
is QQ -> {
|
||||
TODO()
|
||||
}
|
||||
return when (val contact = target) {
|
||||
is QQ,
|
||||
is Group -> contact.bot.recallIn(source, millis)
|
||||
else -> error("Unknown contact type")
|
||||
}
|
||||
} else error("message is already or planned to be recalled")
|
||||
|
@ -12,15 +12,18 @@
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* 消息源, 用于被引用. 它将由协议模块实现.
|
||||
* 消息源只用于 [QuoteReply]
|
||||
* 消息源, 它存在于 [MessageChain] 中, 用于表示这个消息的来源.
|
||||
*
|
||||
* 消息源只用于 [引用回复][QuoteReply] 或 [撤回][Bot.recall].
|
||||
*
|
||||
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
|
||||
*
|
||||
* @see Bot.recall 撤回一条消息
|
||||
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
|
||||
*/
|
||||
interface MessageSource : Message, MessageMetadata {
|
||||
|
@ -161,7 +161,7 @@ internal class LockFreeLinkedListTest {
|
||||
println("Check value")
|
||||
value shouldBeEqualTo 6
|
||||
println("Check size")
|
||||
println(list.getLinkStructure())
|
||||
// println(list.getLinkStructure())
|
||||
list.size shouldBeEqualTo 6
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ internal class LockFreeLinkedListTest {
|
||||
println("Check value")
|
||||
value shouldBeEqualTo 2
|
||||
println("Check size")
|
||||
println(list.getLinkStructure())
|
||||
// println(list.getLinkStructure())
|
||||
list.size shouldBeEqualTo 5
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ internal class LockFreeLinkedListTest {
|
||||
println("Check value")
|
||||
value shouldBeEqualTo 2
|
||||
println("Check size")
|
||||
println(list.getLinkStructure())
|
||||
// println(list.getLinkStructure())
|
||||
list.size shouldBeEqualTo 1
|
||||
}
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user