Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-02-25 17:32:45 +08:00
commit 6cf563c8e5
22 changed files with 251 additions and 146 deletions

View File

@ -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` 架构, 支持引用任意群/好友消息回复给任意群/好友.

View File

@ -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

View File

@ -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-Typemultipart/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-Typemultipart/form-data
}
},{
"type": "FriendMessage", // 消息类型GroupMessage或FriendMessage或各类Event
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain",
"text": "Miral牛逼"
}],

View File

@ -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)

View File

@ -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
}

View File

@ -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())
}
}
}

View File

@ -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
}

View File

@ -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()

View File

@ -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"))

View File

@ -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)
}
}*/
}
}

View File

@ -1,2 +0,0 @@
package net.mamoe.mirai.console

View File

@ -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)

View File

@ -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 -> {

View File

@ -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" }
}
}

View File

@ -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 {

View File

@ -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())

View File

@ -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)
}
/**

View File

@ -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) {

View File

@ -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

View File

@ -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")

View File

@ -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 {

View File

@ -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
}
/*