mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 02:20:14 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
170418b529
@ -69,7 +69,7 @@ JDK要求6以上
|
||||
### 4 Try Bot
|
||||
|
||||
- 在src/main文件夹下新建文件夹,命名为```kotlin```
|
||||
- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Packages```) 包名为```net.mamoe.mirai.simpleloader```
|
||||
- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Package```) 包名为```net.mamoe.mirai.simpleloader```
|
||||
|
||||
- 在包下新建kotlin文件```MyLoader.kt```
|
||||
|
||||
@ -103,9 +103,9 @@ suspend fun main() {
|
||||
- 本例的功能中,在任意群内任意成员发送包含“舔”字或“刘老板”字样的消息,MiraiBot会回复“刘老板太强了”
|
||||
|
||||
|
||||
|
||||
至此,简单的入门已经结束,下面可根据不同的需求参阅wiki进行功能的添加。
|
||||
|
||||
下面,可以尝试对不同事件进行监听[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)
|
||||
|
||||
### 此外,还可以使用Maven作为包管理工具
|
||||
本项目推荐使用gradle,因此不提供详细入门指导
|
||||
|
114
docs/guide_subscribe_events.md
Normal file
114
docs/guide_subscribe_events.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Mirai Guide - Subscribe Events
|
||||
|
||||
由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-02-21```,对应版本```0.17.0```
|
||||
|
||||
本页面采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](mirai-japt/README.md)
|
||||
|
||||
本页面是[Mirai Guide - Getting Started](/docs/guide_getting_started.md)的后续Guide
|
||||
|
||||
## 消息事件-Message Event
|
||||
|
||||
首先我们来回顾上一个Guide的源码
|
||||
|
||||
```kotlin
|
||||
suspend fun main() {
|
||||
val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L
|
||||
val password = "your_password"//Bot的密码
|
||||
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
|
||||
miraiBot.subscribeMessages {
|
||||
"你好" reply "你好!"
|
||||
case("at me") {
|
||||
reply(sender.at() + " 给爷爬 ")
|
||||
}
|
||||
|
||||
(contains("舔") or contains("刘老板")) {
|
||||
"刘老板太强了".reply()
|
||||
}
|
||||
}
|
||||
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
|
||||
}
|
||||
```
|
||||
|
||||
在本例中,```miraiBot```是一个Bot对象,让其登录,然后对```Message Event```进行了监听。
|
||||
|
||||
对于``````Message Event``````,```Mirai```提供了较其他Event更强大的[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140),本例也采用了[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140)。其他具体使用方法可以参考[Wiki:Message Event](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin#Message-Event)部分。
|
||||
|
||||
|
||||
|
||||
## 事件-Event
|
||||
|
||||
上一节中提到的```Message Event```仅仅是众多```Event```的这一种,其他```Event```有群员加入群,离开群,私聊等等...
|
||||
|
||||
具体doc暂不提供,现可翻阅源码[**BotEvents.kt**](https://github.com/mamoe/mirai/blob/master/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt),查看注释。当前事件仍在扩充中,可能有一定不足。
|
||||
|
||||
下面我们开始示例对一些事件进行监听。
|
||||
|
||||
|
||||
|
||||
## 尝试监听事件-Try Subscribing Events
|
||||
|
||||
### 监听加群事件
|
||||
|
||||
在代码中的```miraiBot.join()```前添加
|
||||
|
||||
```kotlin
|
||||
miraiBot.subscribeAlways<MemberJoinEvent> {
|
||||
it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!")
|
||||
}
|
||||
```
|
||||
|
||||
本段语句监听了加入群的事件。
|
||||
|
||||
### 监听禁言事件
|
||||
|
||||
在代码中添加
|
||||
|
||||
```kotlin
|
||||
miraiBot.subscribeAlways<MemberMuteEvent> (){
|
||||
it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")
|
||||
}
|
||||
```
|
||||
|
||||
在被禁言后,Bot将发送恭喜语句。
|
||||
|
||||
### 添加后的可执行代码
|
||||
|
||||
至此,当前的代码为
|
||||
|
||||
```kotlin
|
||||
package net.mamoe.mirai.simpleloader
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.alsoLogin
|
||||
import net.mamoe.mirai.event.subscribeMessages
|
||||
import net.mamoe.mirai.contact.nameCardOrNick
|
||||
import net.mamoe.mirai.contact.sendMessage
|
||||
import net.mamoe.mirai.event.events.MemberJoinEvent
|
||||
import net.mamoe.mirai.event.events.MemberMuteEvent
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
|
||||
suspend fun main() {
|
||||
val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L
|
||||
val password = "your_password"//Bot的密码
|
||||
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
|
||||
miraiBot.subscribeMessages {
|
||||
"你好" reply "你好!"
|
||||
case("at me") {
|
||||
reply(sender.at() + " 给爷爬 ")
|
||||
}
|
||||
|
||||
(contains("舔") or contains("刘老板")) {
|
||||
"刘老板太强了".reply()
|
||||
}
|
||||
}
|
||||
miraiBot.subscribeAlways<MemberJoinEvent> {
|
||||
it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!")
|
||||
}
|
||||
miraiBot.subscribeAlways<MemberMuteEvent> (){
|
||||
it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")
|
||||
}
|
||||
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
|
||||
}
|
||||
```
|
||||
|
@ -16,14 +16,13 @@ import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.data.CustomFaceFromFile
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.NotOnlineImageFromFile
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
|
||||
import net.mamoe.mirai.qqandroid.network.highway.postImage
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
@ -66,7 +65,7 @@ internal class QQImpl(
|
||||
override val nick: String
|
||||
get() = friendInfo.nick
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> {
|
||||
val event = FriendMessageSendEvent(this, message).broadcast()
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
@ -80,6 +79,7 @@ internal class QQImpl(
|
||||
).sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
) { "send message failed" }
|
||||
}
|
||||
return MessageReceipt(message, this)
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
@ -325,6 +325,24 @@ internal class GroupImpl(
|
||||
|
||||
override lateinit var owner: Member
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
override val botAsMember: Member by lazy {
|
||||
Member(object : MemberInfo {
|
||||
override val nameCard: String
|
||||
get() = bot.nick // TODO: 2020/2/21 机器人在群内的昵称获取
|
||||
override val permission: MemberPermission
|
||||
get() = botPermission
|
||||
override val specialTitle: String
|
||||
get() = "" // TODO: 2020/2/21 获取机器人在群里的头衔
|
||||
override val muteTimestamp: Int
|
||||
get() = botMuteRemaining
|
||||
override val uin: Long
|
||||
get() = bot.uin
|
||||
override val nick: String
|
||||
get() = bot.nick
|
||||
})
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
override lateinit var botPermission: MemberPermission
|
||||
|
||||
@ -340,6 +358,9 @@ internal class GroupImpl(
|
||||
override val members: ContactList<Member> = ContactList(members.mapNotNull {
|
||||
if (it.uin == bot.uin) {
|
||||
botPermission = it.permission
|
||||
if (it.permission == MemberPermission.OWNER) {
|
||||
owner = botAsMember
|
||||
}
|
||||
null
|
||||
} else Member(it).also { member ->
|
||||
if (member.permission == MemberPermission.OWNER) {
|
||||
@ -475,6 +496,20 @@ internal class GroupImpl(
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override suspend fun recall(source: MessageSource) {
|
||||
if (source.senderId != bot.uin) {
|
||||
checkBotPermissionOperator()
|
||||
}
|
||||
|
||||
source.ensureSequenceIdAvailable()
|
||||
|
||||
bot.network.run {
|
||||
val response = PbMessageSvc.PbMsgWithDraw.Group(bot.client, this@GroupImpl.id, source.sequenceId, source.messageUid.toInt())
|
||||
.sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
override fun Member(memberInfo: MemberInfo): Member {
|
||||
return MemberImpl(
|
||||
@ -498,7 +533,7 @@ internal class GroupImpl(
|
||||
return members.delegate.filteringGetOrNull { it.id == id }
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group> {
|
||||
check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" }
|
||||
val event = GroupMessageSendEvent(this, message).broadcast()
|
||||
if (event.isCancelled) {
|
||||
@ -514,6 +549,11 @@ internal class GroupImpl(
|
||||
response is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
) { "send message failed: $response" }
|
||||
}
|
||||
|
||||
((message.last() as MessageSource) as MessageSvc.PbSendMsg.MessageSourceFromSend)
|
||||
.startWaitingSequenceId(this)
|
||||
|
||||
return MessageReceipt(message, this)
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
|
@ -22,6 +22,12 @@ internal inline class MessageSourceFromServer(
|
||||
val delegate: ImMsgBody.SourceMsg
|
||||
) : MessageSource {
|
||||
override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF
|
||||
override val sequenceId: Int get() = delegate.origSeqs?.firstOrNull() ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override val messageUid: Long get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
|
||||
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.senderUin
|
||||
@ -34,6 +40,11 @@ internal inline class MessageSourceFromMsg(
|
||||
val delegate: MsgComm.Msg
|
||||
) : MessageSource {
|
||||
override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF
|
||||
override val sequenceId: Int get() = delegate.msgHead.msgSeq
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override val messageUid: Long get() = delegate.msgBody.richText.attr!!.random.toLong()
|
||||
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.msgHead.fromUin
|
||||
|
@ -15,6 +15,7 @@ import kotlinx.io.core.readUInt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiDebugAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.io.discardExact
|
||||
@ -27,12 +28,13 @@ internal fun At.toJceData(): ImMsgBody.Text {
|
||||
return ImMsgBody.Text(
|
||||
str = text,
|
||||
attr6Buf = buildPacket {
|
||||
writeShort(1)
|
||||
writeShort(0)
|
||||
writeShort(text.length.toShort())
|
||||
writeByte(1)
|
||||
writeInt(target.toInt())
|
||||
writeShort(0)
|
||||
// MessageForText$AtTroopMemberInfo
|
||||
writeShort(1) // const
|
||||
writeShort(0) // startPos
|
||||
writeShort(text.length.toShort()) // textLen
|
||||
writeByte(0) // flag, may=1
|
||||
writeInt(target.toInt()) // uin
|
||||
writeShort(0) // const
|
||||
}.readBytes()
|
||||
)
|
||||
}
|
||||
@ -206,7 +208,15 @@ notOnlineImage=NotOnlineImage#2050019814 {
|
||||
private val atAllData = ImMsgBody.Elem(
|
||||
text = ImMsgBody.Text(
|
||||
str = "@全体成员",
|
||||
attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes()
|
||||
attr6Buf = buildPacket {
|
||||
// MessageForText$AtTroopMemberInfo
|
||||
writeShort(1) // const
|
||||
writeShort(0) // startPos
|
||||
writeShort("@全体成员".length.toShort()) // textLen
|
||||
writeByte(1) // flag, may=1
|
||||
writeInt(0) // uin
|
||||
writeShort(0) // const
|
||||
}.readBytes()
|
||||
)
|
||||
)
|
||||
|
||||
@ -224,7 +234,7 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
|
||||
this.forEach {
|
||||
when (it) {
|
||||
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
|
||||
is At -> elements.add(ImMsgBody.Elem(text = it.toJceData()))
|
||||
is At -> elements.add(ImMsgBody.Elem(text = it.toJceData())).also { elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) }
|
||||
is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
|
||||
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
|
||||
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
|
||||
@ -249,7 +259,7 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
|
||||
internal class CustomFaceFromServer(
|
||||
internal val delegate: ImMsgBody.CustomFace
|
||||
) : CustomFace() {
|
||||
override val filepath: String get() = delegate.filePath
|
||||
override val filepath: String = delegate.filePath
|
||||
override val fileId: Int get() = delegate.fileId
|
||||
override val serverIp: Int get() = delegate.serverIp
|
||||
override val serverPort: Int get() = delegate.serverPort
|
||||
@ -265,14 +275,14 @@ internal class CustomFaceFromServer(
|
||||
override val size: Int get() = delegate.size
|
||||
override val original: Int get() = delegate.origin
|
||||
override val pbReserve: ByteArray get() = delegate.pbReserve
|
||||
override val imageId: String get() = delegate.filePath
|
||||
override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is CustomFaceFromServer && other.filepath == this.filepath && other.md5.contentEquals(this.md5)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filepath.hashCode() + 31 * md5.hashCode()
|
||||
return imageId.hashCode() + 31 * md5.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,7 +306,7 @@ internal class NotOnlineImageFromServer(
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return resourceId.hashCode() + 31 * md5.hashCode()
|
||||
return imageId.hashCode() + 31 * md5.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.io.ProtoBuf
|
||||
|
||||
class MsgRevokeUserDef : ProtoBuf {
|
||||
@Serializable
|
||||
class MsgInfoUserDef(
|
||||
@SerialId(1) val longMessageFlag: Int = 0,
|
||||
@SerialId(2) val longMsgInfo: List<MsgInfoDef>? = null,
|
||||
@SerialId(3) val fileUuid: List<String> = listOf()
|
||||
) : ProtoBuf {
|
||||
@Serializable
|
||||
class MsgInfoDef(
|
||||
@SerialId(1) val msgSeq: Int = 0,
|
||||
@SerialId(2) val longMsgId: Int = 0,
|
||||
@SerialId(3) val longMsgNum: Int = 0,
|
||||
@SerialId(4) val longMsgIndex: Int = 0
|
||||
) : ProtoBuf
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class UinTypeUserDef(
|
||||
@SerialId(1) val fromUinType: Int = 0,
|
||||
@SerialId(2) val fromGroupCode: Long = 0L,
|
||||
@SerialId(3) val fileUuid: List<String> = listOf()
|
||||
) : ProtoBuf
|
||||
}
|
@ -14,6 +14,7 @@ import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
@ -138,7 +139,8 @@ internal object KnownPacketFactories {
|
||||
TroopManagement.GetGroupInfo,
|
||||
TroopManagement.EditGroupNametag,
|
||||
TroopManagement.Kick,
|
||||
Heartbeat.Alive
|
||||
Heartbeat.Alive,
|
||||
PbMessageSvc.PbMsgWithDraw
|
||||
)
|
||||
|
||||
object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgRevokeUserDef
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
|
||||
|
||||
internal class PbMessageSvc {
|
||||
object PbMsgWithDraw : OutgoingPacketFactory<PbMsgWithDraw.Response>(
|
||||
"PbMessageSvc.PbMsgWithDraw"
|
||||
) {
|
||||
sealed class Response : Packet {
|
||||
object Success : Response() {
|
||||
override fun toString(): String {
|
||||
return "PbMessageSvc.PbMsgWithDraw.Response.Success"
|
||||
}
|
||||
}
|
||||
|
||||
data class Failed(
|
||||
val result: Int,
|
||||
val errorMessage: String
|
||||
) : Response()
|
||||
}
|
||||
|
||||
// 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00
|
||||
fun Group(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
messageSequenceId: Int, // 56639
|
||||
messageRandom: Int, // 921878719
|
||||
messageType: Int = 0
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbMsgWithDrawReq.serializer(),
|
||||
MsgSvc.PbMsgWithDrawReq(
|
||||
groupWithDraw = listOf(
|
||||
MsgSvc.PbGroupMsgWithDrawReq(
|
||||
subCmd = 1,
|
||||
groupType = 0, // 普通群
|
||||
groupCode = groupCode,
|
||||
msgList = listOf(
|
||||
MsgSvc.PbGroupMsgWithDrawReq.MessageInfo(
|
||||
msgSeq = messageSequenceId,
|
||||
msgRandom = messageRandom,
|
||||
msgType = messageType
|
||||
)
|
||||
),
|
||||
userdef = MsgRevokeUserDef.MsgInfoUserDef(
|
||||
longMessageFlag = 0
|
||||
).toByteArray(MsgRevokeUserDef.MsgInfoUserDef.serializer())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer())
|
||||
resp.groupWithDraw?.firstOrNull()?.let {
|
||||
if (it.result != 0) {
|
||||
return Response.Failed(it.result, it.errmsg)
|
||||
}
|
||||
return Response.Success
|
||||
}
|
||||
resp.c2cWithDraw?.firstOrNull()?.let {
|
||||
if (it.result != 0) {
|
||||
return Response.Failed(it.result, it.errmsg)
|
||||
}
|
||||
return Response.Success
|
||||
}
|
||||
return Response.Failed(-1, "No response")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,18 +9,25 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.ListeningStatus
|
||||
import net.mamoe.mirai.event.events.BotJoinGroupEvent
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
import net.mamoe.mirai.event.events.MemberJoinEvent
|
||||
import net.mamoe.mirai.event.subscribe
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.addOrRemove
|
||||
import net.mamoe.mirai.qqandroid.GroupImpl
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
|
||||
@ -256,12 +263,49 @@ internal class MessageSvc {
|
||||
override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
|
||||
}
|
||||
|
||||
/**
|
||||
* 121: 被限制? 个别号才不能发
|
||||
*/
|
||||
data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
|
||||
override fun toString(): String =
|
||||
"MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageSourceFromSend(
|
||||
override val messageUid: Long,
|
||||
override val time: Long,
|
||||
override val senderId: Long,
|
||||
override val groupId: Long,
|
||||
override val sourceMessage: MessageChain
|
||||
) : MessageSource {
|
||||
lateinit var sequenceIdDeferred: CompletableDeferred<Int>
|
||||
|
||||
fun startWaitingSequenceId(contact: Contact) {
|
||||
sequenceIdDeferred = CompletableDeferred()
|
||||
contact.subscribe<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt> { event ->
|
||||
if (event.messageRandom == messageUid.toInt()) {
|
||||
sequenceIdDeferred.complete(event.sequenceId)
|
||||
return@subscribe ListeningStatus.STOPPED
|
||||
}
|
||||
|
||||
return@subscribe ListeningStatus.LISTENING
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
override val sequenceId: Int
|
||||
get() = sequenceIdDeferred.getCompleted()
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
sequenceIdDeferred.join()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友消息
|
||||
*/
|
||||
@ -271,9 +315,17 @@ internal class MessageSvc {
|
||||
toUin: Long,
|
||||
message: MessageChain
|
||||
): 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())
|
||||
|
||||
val source = MessageSourceFromSend(
|
||||
messageUid = Random.nextInt().absoluteValue.toLong() and 0xffffffff,
|
||||
senderId = client.uin,
|
||||
time = currentTimeSeconds + client.timeDifference,
|
||||
groupId = 0,
|
||||
sourceMessage = message
|
||||
)
|
||||
message.addOrRemove(source)
|
||||
|
||||
///return@buildOutgoingUniPacket
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
|
||||
@ -285,8 +337,8 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgRand = Random.nextInt().absoluteValue,
|
||||
syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
|
||||
msgRand = source.messageUid.toInt(),
|
||||
syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer())
|
||||
// msgVia = 1
|
||||
)
|
||||
)
|
||||
@ -302,11 +354,18 @@ internal class MessageSvc {
|
||||
message: MessageChain
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
|
||||
val source = MessageSourceFromSend(
|
||||
messageUid = Random.nextInt().absoluteValue.toLong(),
|
||||
senderId = client.uin,
|
||||
time = currentTimeSeconds + client.timeDifference,
|
||||
groupId = groupCode,
|
||||
sourceMessage = message
|
||||
)
|
||||
|
||||
///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())
|
||||
|
||||
// DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString())
|
||||
|
||||
val seq = client.atomicNextMessageSequenceId()
|
||||
///return@buildOutgoingUniPacket
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
|
||||
@ -317,12 +376,14 @@ internal class MessageSvc {
|
||||
elems = message.toRichTextElems()
|
||||
)
|
||||
),
|
||||
msgSeq = seq,
|
||||
msgRand = Random.nextInt().absoluteValue,
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgRand = source.messageUid.toInt(),
|
||||
syncCookie = EMPTY_BYTE_ARRAY,
|
||||
msgVia = 1
|
||||
)
|
||||
)
|
||||
|
||||
message.addOrRemove(source)
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
|
@ -19,6 +19,7 @@ import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
import net.mamoe.mirai.data.NoPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.qqandroid.GroupImpl
|
||||
@ -46,9 +47,18 @@ internal class OnlinePush {
|
||||
/**
|
||||
* 接受群消息
|
||||
*/
|
||||
internal object PbPushGroupMsg : IncomingPacketFactory<GroupMessage?>("OnlinePush.PbPushGroupMsg") {
|
||||
internal object PbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
|
||||
internal class SendGroupMessageReceipt(
|
||||
val messageRandom: Int,
|
||||
val sequenceId: Int
|
||||
) : Packet, Event {
|
||||
override fun toString(): String {
|
||||
return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalStdlibApi::class)
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): GroupMessage? {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
|
||||
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
|
||||
if (!bot.firstLoginSucceed) return null
|
||||
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
|
||||
@ -56,7 +66,7 @@ internal class OnlinePush {
|
||||
val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
|
||||
|
||||
if (pbPushMsg.msg.msgHead.fromUin == bot.uin) {
|
||||
return null
|
||||
return SendGroupMessageReceipt(pbPushMsg.msg.msgBody.richText.attr!!.random, pbPushMsg.msg.msgHead.msgSeq)
|
||||
}
|
||||
|
||||
val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode)
|
||||
|
@ -107,6 +107,8 @@ fun main() {
|
||||
* 顶层方法. TCP 切掉头后直接来这里
|
||||
*/
|
||||
fun ByteReadPacket.decodeMultiClientToServerPackets() {
|
||||
DebugLogger.enable()
|
||||
PacketLogger.enable()
|
||||
println("=======================处理客户端到服务器=======================")
|
||||
var count = 0
|
||||
while (remaining != 0L) {
|
||||
|
@ -25,7 +25,7 @@ fun main() {
|
||||
println(
|
||||
File(
|
||||
"""
|
||||
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\statsvc\getonline
|
||||
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\msgrevoke
|
||||
""".trimIndent()
|
||||
)
|
||||
.generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n")
|
||||
|
@ -84,7 +84,7 @@ abstract class Bot : CoroutineScope {
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
val nick: String
|
||||
get() = TODO("bot 昵称获取")
|
||||
get() = ""// TODO("bot 昵称获取")
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
@ -175,8 +175,6 @@ abstract class Bot : CoroutineScope {
|
||||
*/
|
||||
abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
|
||||
|
||||
// TODO 目前还不能构造群对象. 这将在以后支持
|
||||
|
||||
// endregion
|
||||
|
||||
// region network
|
||||
|
@ -18,6 +18,7 @@ import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
@ -40,19 +41,24 @@ interface Contact : CoroutineScope {
|
||||
*
|
||||
* 对于 [QQ], `uin` 与 `id` 是相同的意思.
|
||||
* 对于 [Group], `groupCode` 与 `id` 是相同的意思.
|
||||
*
|
||||
* @see QQ.id
|
||||
* @see Group.id
|
||||
*/
|
||||
val id: Long
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
* 向这个对象发送消息. 发送成功后 [message] 中会添加 [MessageSource], 此后可以 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
suspend fun sendMessage(message: MessageChain)
|
||||
suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact>
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
@ -88,4 +94,4 @@ interface Contact : CoroutineScope {
|
||||
|
||||
suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain())
|
||||
|
||||
suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain())
|
||||
suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.toMessage())
|
@ -40,6 +40,15 @@ class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedLis
|
||||
fun containsAll(elements: Collection<C>): Boolean = elements.all { contains(it) }
|
||||
fun isEmpty(): Boolean = delegate.isEmpty()
|
||||
inline fun forEach(block: (C) -> Unit) = delegate.forEach(block)
|
||||
fun first(): C {
|
||||
forEach { return it }
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
fun firstOrNull(): C? {
|
||||
forEach { return it }
|
||||
return null
|
||||
}
|
||||
|
||||
override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
|
||||
}
|
||||
|
@ -11,11 +11,22 @@
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.quote
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
@ -83,10 +94,18 @@ interface Group : Contact, CoroutineScope {
|
||||
override val id: Long
|
||||
|
||||
/**
|
||||
* 群主
|
||||
* 群主.
|
||||
*
|
||||
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
|
||||
*/
|
||||
val owner: Member
|
||||
|
||||
/**
|
||||
* [Bot] 在群内的 [Member] 实例
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
val botAsMember: Member
|
||||
|
||||
/**
|
||||
* 机器人被禁言还剩余多少秒
|
||||
*
|
||||
@ -133,6 +152,17 @@ interface Group : Contact, CoroutineScope {
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun quit(): Boolean
|
||||
|
||||
/**
|
||||
* 撤回这条消息.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
* @see Group.recall (扩展函数) 接受参数 [MessageChain]
|
||||
*/
|
||||
suspend fun recall(source: MessageSource)
|
||||
|
||||
/**
|
||||
* 构造一个 [Member].
|
||||
* 非特殊情况请不要使用这个函数. 优先使用 [get].
|
||||
@ -142,6 +172,19 @@ interface Group : Contact, CoroutineScope {
|
||||
@JvmName("newMember")
|
||||
fun Member(memberInfo: MemberInfo): Member
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息. 发送成功后 [message] 中会添加 [MessageSource], 此后可以 [引用回复][quote] 或 [撤回][recall] 这条消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group>
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
@ -184,7 +227,52 @@ interface Group : Contact, CoroutineScope {
|
||||
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回这条消息.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
* @see Group.recall
|
||||
*/
|
||||
suspend inline fun Group.recall(message: MessageChain) = this.recall(message[MessageSource])
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.
|
||||
*
|
||||
* @param millis 延迟的时间, 单位为毫秒
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @see recall
|
||||
*/
|
||||
fun Group.recallIn(
|
||||
message: MessageSource,
|
||||
millis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
||||
kotlinx.coroutines.delay(millis)
|
||||
recall(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.
|
||||
*
|
||||
* @param millis 延迟的时间, 单位为毫秒
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @see recall
|
||||
*/
|
||||
fun Group.recallIn(
|
||||
message: MessageChain,
|
||||
millis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
||||
kotlinx.coroutines.delay(millis)
|
||||
recall(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回机器人是否正在被禁言
|
||||
*
|
||||
* @see Group.botMuteRemaining 剩余禁言时间
|
||||
*/
|
||||
val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0
|
||||
|
@ -16,6 +16,12 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
@ -68,4 +74,17 @@ interface QQ : Contact, CoroutineScope {
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryRemark(): FriendNameRemark
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息. 发送成功后 [message] 中会添加 [MessageSource], 此后可以 [撤回][recall] 这条消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
|
||||
}
|
@ -62,4 +62,15 @@ interface GroupInfo {
|
||||
* 机器人被禁言还剩时间, 秒.
|
||||
*/
|
||||
val botMuteRemaining: Int
|
||||
|
||||
/*
|
||||
/**
|
||||
* 机器人的头衔
|
||||
*/
|
||||
val botSpecialTitle: String
|
||||
|
||||
/**
|
||||
* 机器人的昵称
|
||||
*/
|
||||
val botNameCard: String*/
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
@ -10,12 +10,11 @@
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
import kotlin.jvm.JvmName
|
||||
@ -60,6 +59,11 @@ class GroupMessage(
|
||||
@JvmName("reply2")
|
||||
suspend inline fun MessageChain.quoteReply() = quoteReply(this)
|
||||
|
||||
suspend inline fun MessageChain.recall() = group.recall(this)
|
||||
suspend inline fun MessageSource.recall() = group.recall(this)
|
||||
inline fun MessageSource.recallIn(delay: Long) = group.recallIn(this, delay)
|
||||
inline fun MessageChain.recallIn(delay: Long) = group.recallIn(this, delay)
|
||||
|
||||
override fun toString(): String =
|
||||
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
|
||||
}
|
@ -35,7 +35,7 @@ expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>(bot: Bot)
|
||||
/**
|
||||
* 仅内部使用, 请使用 [MessagePacket]
|
||||
*/ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
|
||||
@MiraiInternalAPI
|
||||
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : Packet, BotEvent {
|
||||
/**
|
||||
@ -73,20 +73,19 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
|
||||
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
|
||||
*/
|
||||
suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
|
||||
suspend inline fun reply(message: MessageChain): MessageReceipt<TSubject> = subject.sendMessage(message) as MessageReceipt<TSubject>
|
||||
|
||||
suspend inline fun reply(message: Message) = subject.sendMessage(message.toChain())
|
||||
suspend inline fun reply(plain: String) = subject.sendMessage(plain.singleChain())
|
||||
suspend inline fun reply(message: Message): MessageReceipt<TSubject> = subject.sendMessage(message.toChain()) as MessageReceipt<TSubject>
|
||||
suspend inline fun reply(plain: String): MessageReceipt<TSubject> = subject.sendMessage(plain.toMessage().toChain()) as MessageReceipt<TSubject>
|
||||
|
||||
@JvmName("reply1")
|
||||
suspend inline fun String.reply() = reply(this)
|
||||
suspend inline fun String.reply(): MessageReceipt<TSubject> = reply(this)
|
||||
|
||||
@JvmName("reply1")
|
||||
suspend inline fun Message.reply() = reply(this)
|
||||
suspend inline fun Message.reply(): MessageReceipt<TSubject> = reply(this)
|
||||
|
||||
@JvmName("reply1")
|
||||
suspend inline fun MessageChain.reply() = reply(this)
|
||||
|
||||
suspend inline fun MessageChain.reply(): MessageReceipt<TSubject> = reply(this)
|
||||
// endregion
|
||||
|
||||
// region
|
||||
@ -113,9 +112,9 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
/**
|
||||
* 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException]
|
||||
*/
|
||||
inline fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage"))
|
||||
fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage"))
|
||||
|
||||
inline fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error("`At.member` can only be used in GroupMessage")
|
||||
fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error("`At.member` can only be used in GroupMessage")
|
||||
|
||||
// endregion
|
||||
|
||||
@ -135,16 +134,4 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
*/
|
||||
suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
|
||||
// endregion
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.target)"))
|
||||
fun At.qq(): QQ = bot.getFriend(this.target)
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.toLong())"))
|
||||
fun Int.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0).toLong())
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this)"))
|
||||
fun Long.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0))
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getGroup(this)"))
|
||||
fun Long.group(): Group = bot.getGroup(this)
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
|
||||
/**
|
||||
* 发送消息后得到的回执. 可用于撤回.
|
||||
*
|
||||
* 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问.
|
||||
*
|
||||
* @see Group.sendMessage 发送群消息, 返回回执(此对象)
|
||||
* @see QQ.sendMessage 发送群消息, 返回回执(此对象)
|
||||
*/
|
||||
open class MessageReceipt<C : Contact>(
|
||||
val originalMessage: MessageChain,
|
||||
target: C
|
||||
) {
|
||||
init {
|
||||
require(target is Group || target is QQ) { "target must be either Group or QQ" }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送目标, 为 [Group] 或 [QQ]
|
||||
*/
|
||||
val target: C by target.unsafeWeakRef()
|
||||
|
||||
private val _isRecalled = atomic(false)
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Group.recall
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
suspend fun recall() {
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
if (_isRecalled.compareAndSet(false, true)) {
|
||||
when (val contact = target) {
|
||||
is Group -> {
|
||||
contact.recall(originalMessage)
|
||||
}
|
||||
is QQ -> {
|
||||
TODO()
|
||||
}
|
||||
else -> error("Unknown contact type")
|
||||
}
|
||||
} else error("message is already or planned to be recalled")
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @param millis 延迟时间, 单位为毫秒
|
||||
*
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
fun recallIn(millis: Long): Job {
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
if (_isRecalled.compareAndSet(false, true)) {
|
||||
when (val contact = target) {
|
||||
is Group -> {
|
||||
return contact.recallIn(originalMessage, millis)
|
||||
}
|
||||
is QQ -> {
|
||||
TODO()
|
||||
}
|
||||
else -> error("Unknown contact type")
|
||||
}
|
||||
} else error("message is already or planned to be recalled")
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息. 仅群消息能被引用
|
||||
*
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*
|
||||
* @throws IllegalStateException 当此消息不是群消息时
|
||||
*/
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
open fun quote(): MessageChain {
|
||||
val target = target
|
||||
check(target is Group) { "quote is only available for GroupMessage" }
|
||||
return this.originalMessage.quote(target.botAsMember)
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息并回复. 仅群消息能被引用
|
||||
*
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*
|
||||
* @throws IllegalStateException 当此消息不是群消息时
|
||||
*/
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
suspend fun quoteReply(message: MessageChain) {
|
||||
target.sendMessage(this.quote() + message)
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
suspend inline fun MessageReceipt<out Contact>.quoteReply(message: Message) {
|
||||
return this.quoteReply(message.toChain())
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
suspend inline fun MessageReceipt<out Contact>.quoteReply(message: String) {
|
||||
return this.quoteReply(message.toMessage().toChain())
|
||||
}
|
||||
|
@ -16,7 +16,9 @@ import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* "@全体成员"
|
||||
* "@全体成员".
|
||||
*
|
||||
* 非会员每天只能发送 10 次 [AtAll]. 超出部分会被以普通文字看待.
|
||||
*
|
||||
* @see At at 单个群成员
|
||||
*/
|
||||
@ -26,7 +28,7 @@ object AtAll : Message, Message.Key<AtAll> {
|
||||
// 自动为消息补充 " "
|
||||
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
if(tail is PlainText && tail.stringValue.startsWith(' ')){
|
||||
if (tail is PlainText && tail.stringValue.startsWith(' ')) {
|
||||
return super.followedBy(tail)
|
||||
}
|
||||
return super.followedBy(PlainText(" ")) + tail
|
||||
|
@ -13,9 +13,6 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.message.data.NullMessageChain.toString
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -68,6 +65,15 @@ interface MessageChain : Message, MutableList<Message> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 先删除同类型的消息, 再添加 [message]
|
||||
*/
|
||||
fun <T : Message> MessageChain.addOrRemove(message: T) {
|
||||
val clazz = message::class
|
||||
this.removeAll { clazz.isInstance(it) }
|
||||
this.add(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage]
|
||||
*/
|
||||
@ -132,6 +138,16 @@ fun MessageChain(vararg messages: Message): MessageChain =
|
||||
if (messages.isEmpty()) EmptyMessageChain()
|
||||
else MessageChainImpl(messages.toMutableList())
|
||||
|
||||
/**
|
||||
* 构造 [MessageChain] 的快速途径 (无 [Array] 创建)
|
||||
* 若仅提供一个参数, 请考虑使用 [Message.toChain] 以优化性能
|
||||
*/
|
||||
@JvmName("newChain")
|
||||
@JsName("newChain")
|
||||
@Suppress("FunctionName")
|
||||
fun MessageChain(message: Message): MessageChain =
|
||||
MessageChainImpl(mutableListOf(message))
|
||||
|
||||
/**
|
||||
* 构造 [MessageChain]
|
||||
*/
|
||||
@ -141,30 +157,6 @@ fun MessageChain(vararg messages: Message): MessageChain =
|
||||
fun MessageChain(messages: Iterable<Message>): MessageChain =
|
||||
MessageChainImpl(messages.toMutableList())
|
||||
|
||||
/**
|
||||
* 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain]
|
||||
*
|
||||
* 参数 [delegate] 不能为 [MessageChain] 的实例, 否则将会抛出异常.
|
||||
* 使用 [Message.toChain] 将帮助提前处理这个问题.
|
||||
*
|
||||
* @param delegate 所构造的单元素 [MessageChain] 代表的 [Message]
|
||||
* @throws IllegalArgumentException 当 [delegate] 为 [MessageChain] 的实例时
|
||||
*
|
||||
* @see Message.toChain receiver 模式
|
||||
*/
|
||||
@JvmName("newSingleMessageChain")
|
||||
@JsName("newChain")
|
||||
@MiraiExperimentalAPI
|
||||
@UseExperimental(ExperimentalContracts::class)
|
||||
@Suppress("FunctionName")
|
||||
fun SingleMessageChain(delegate: Message): MessageChain {
|
||||
contract {
|
||||
returns() implies (delegate !is MessageChain)
|
||||
}
|
||||
require(delegate !is MessageChain) { "delegate for SingleMessageChain should not be any instance of MessageChain" }
|
||||
return SingleMessageChainImpl(delegate)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 得到包含 [this] 的 [MessageChain].
|
||||
@ -387,97 +379,3 @@ internal inline class MessageChainImpl constructor(
|
||||
// endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个成员的不可修改的 [MessageChain].
|
||||
*
|
||||
* 在连接时将会把它当做一个普通 [Message] 看待, 但它不能被 [plusAssign]
|
||||
*/
|
||||
@PublishedApi
|
||||
internal inline class SingleMessageChainImpl(
|
||||
private val delegate: Message
|
||||
) : Message, MutableList<Message>,
|
||||
MessageChain {
|
||||
|
||||
// region Message override
|
||||
override operator fun contains(sub: String): Boolean = delegate.contains(sub)
|
||||
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" }
|
||||
return if (tail is MessageChain) tail.apply { followedBy(delegate) }
|
||||
else MessageChain(delegate, tail)
|
||||
}
|
||||
|
||||
override fun plusAssign(message: Message) =
|
||||
throw UnsupportedOperationException("SingleMessageChainImpl cannot be plusAssigned")
|
||||
|
||||
override fun toString(): String = delegate.toString()
|
||||
// endregion
|
||||
|
||||
// region MutableList override
|
||||
override fun containsAll(elements: Collection<Message>): Boolean = elements.all { it == delegate }
|
||||
|
||||
override operator fun get(index: Int): Message = if (index == 0) delegate else throw NoSuchElementException()
|
||||
override fun indexOf(element: Message): Int = if (delegate == element) 0 else -1
|
||||
override fun isEmpty(): Boolean = false
|
||||
override fun lastIndexOf(element: Message): Int = if (delegate == element) 0 else -1
|
||||
override fun add(element: Message): Boolean = throw UnsupportedOperationException()
|
||||
override fun add(index: Int, element: Message) = throw UnsupportedOperationException()
|
||||
override fun addAll(index: Int, elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun addAll(elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun clear() = throw UnsupportedOperationException()
|
||||
override fun listIterator(): MutableListIterator<Message> = object : MutableListIterator<Message> {
|
||||
private var hasNext = true
|
||||
override fun hasPrevious(): Boolean = !hasNext
|
||||
override fun nextIndex(): Int = if (hasNext) 0 else -1
|
||||
override fun previous(): Message =
|
||||
if (hasPrevious()) {
|
||||
hasNext = true
|
||||
delegate
|
||||
} else throw NoSuchElementException()
|
||||
|
||||
override fun previousIndex(): Int = if (!hasNext) 0 else -1
|
||||
override fun add(element: Message) = throw UnsupportedOperationException()
|
||||
override fun hasNext(): Boolean = hasNext
|
||||
override fun next(): Message =
|
||||
if (hasNext) {
|
||||
hasNext = false
|
||||
delegate
|
||||
} else throw NoSuchElementException()
|
||||
|
||||
override fun remove() = throw UnsupportedOperationException()
|
||||
override fun set(element: Message) = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun listIterator(index: Int): MutableListIterator<Message> =
|
||||
if (index == 0) listIterator() else throw UnsupportedOperationException()
|
||||
|
||||
override fun remove(element: Message): Boolean = throw UnsupportedOperationException()
|
||||
override fun removeAll(elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun removeAt(index: Int): Message = throw UnsupportedOperationException()
|
||||
override fun retainAll(elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun set(index: Int, element: Message): Message = throw UnsupportedOperationException()
|
||||
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> {
|
||||
return if (fromIndex == 0) when (toIndex) {
|
||||
1 -> mutableListOf<Message>(this)
|
||||
0 -> mutableListOf()
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
else throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun iterator(): MutableIterator<Message> = object : MutableIterator<Message> {
|
||||
private var hasNext = true
|
||||
override fun hasNext(): Boolean = hasNext
|
||||
override fun next(): Message =
|
||||
if (hasNext) {
|
||||
hasNext = false
|
||||
delegate
|
||||
} else throw NoSuchElementException()
|
||||
|
||||
override fun remove() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override operator fun contains(element: Message): Boolean = element == delegate
|
||||
override val size: Int get() = 1
|
||||
// endregion
|
||||
}
|
@ -26,6 +26,18 @@ import kotlin.jvm.JvmName
|
||||
interface MessageSource : Message {
|
||||
companion object Key : Message.Key<MessageSource>
|
||||
|
||||
/**
|
||||
* 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][ensureSequenceIdAvailable]
|
||||
*/
|
||||
val sequenceId: Int
|
||||
|
||||
/**
|
||||
* 等待 [sequenceId] 获取, 确保其可用.
|
||||
*
|
||||
* 若原消息发送失败, 这个方法会等待最多 3 秒随后抛出 [IllegalStateException]
|
||||
*/
|
||||
suspend fun ensureSequenceIdAvailable()
|
||||
|
||||
/**
|
||||
* 实际上是个随机数, 但服务器确实是用它当做 uid
|
||||
*/
|
||||
@ -42,7 +54,7 @@ interface MessageSource : Message {
|
||||
val senderId: Long
|
||||
|
||||
/**
|
||||
* 群号码
|
||||
* 群号码, 为 0 时则来自好友消息
|
||||
*/
|
||||
val groupId: Long
|
||||
|
||||
|
@ -38,15 +38,4 @@ inline class PlainText(val stringValue: String) : Message {
|
||||
* 构造 [PlainText]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.toMessage(): PlainText = PlainText(this)
|
||||
|
||||
/**
|
||||
* 得到包含作为 [PlainText] 的 [this] 的 [MessageChain].
|
||||
*
|
||||
* @return 唯一成员且不可修改的 [SingleMessageChainImpl]
|
||||
*
|
||||
* @see SingleMessageChain
|
||||
* @see SingleMessageChainImpl
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.singleChain(): MessageChain = SingleMessageChainImpl(this.toMessage())
|
||||
inline fun String.toMessage(): PlainText = PlainText(this)
|
@ -51,9 +51,39 @@ class ExternalImage(
|
||||
filename: String
|
||||
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename)
|
||||
|
||||
fun generateUUID(md5: ByteArray): String{
|
||||
fun generateUUID(md5: ByteArray): String {
|
||||
return "${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}"
|
||||
}
|
||||
|
||||
fun generateImageId(md5: ByteArray, imageType: Int): String {
|
||||
return """{${generateUUID(md5)}}.${determineFormat(imageType)}"""
|
||||
}
|
||||
|
||||
fun determineImageType(format: String): Int {
|
||||
return when (format) {
|
||||
"jpg" -> 1000
|
||||
"png" -> 1001
|
||||
"webp" -> 1002
|
||||
"bmp" -> 1005
|
||||
"gig" -> 2000
|
||||
"apng" -> 2001
|
||||
"sharpp" -> 1004
|
||||
else -> 1000 // unsupported, just make it jpg
|
||||
}
|
||||
}
|
||||
|
||||
fun determineFormat(imageType: Int): String {
|
||||
return when (imageType) {
|
||||
1000 -> "jpg"
|
||||
1001 -> "png"
|
||||
1002 -> "webp"
|
||||
1005 -> "bmp"
|
||||
2000 -> "gig"
|
||||
2001 -> "apng"
|
||||
1004 -> "sharpp"
|
||||
else -> "jpg" // unsupported, just make it jpg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val format: String =
|
||||
@ -73,16 +103,7 @@ class ExternalImage(
|
||||
* SHARPP: 1004
|
||||
*/
|
||||
val imageType: Int
|
||||
get() = when (format) {
|
||||
"jpg" -> 1000
|
||||
"png" -> 1001
|
||||
"webp" -> 1002
|
||||
"bmp" -> 1005
|
||||
"gig" -> 2000
|
||||
"apng" -> 2001
|
||||
"sharpp" -> 1004
|
||||
else -> 1000 // unsupported, just make it jpg
|
||||
}
|
||||
get() = determineImageType(format)
|
||||
|
||||
override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
|
||||
|
||||
|
@ -9,6 +9,7 @@ import net.mamoe.mirai.event.events.EventCancelledException;
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent;
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent;
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent;
|
||||
import net.mamoe.mirai.message.MessageReceipt;
|
||||
import net.mamoe.mirai.message.data.Image;
|
||||
import net.mamoe.mirai.message.data.Message;
|
||||
import net.mamoe.mirai.message.data.MessageChain;
|
||||
@ -42,8 +43,7 @@ public interface BlockingContact {
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
// kotlin bug
|
||||
void sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
MessageReceipt<? extends Contact> sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -53,7 +53,7 @@ public interface BlockingContact {
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
void sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
MessageReceipt<? extends Contact> sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -63,7 +63,7 @@ public interface BlockingContact {
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
void sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
MessageReceipt<? extends Contact> sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
|
@ -3,6 +3,9 @@ package net.mamoe.mirai.japt;
|
||||
import net.mamoe.mirai.contact.*;
|
||||
import net.mamoe.mirai.data.MemberInfo;
|
||||
import net.mamoe.mirai.event.events.*;
|
||||
import net.mamoe.mirai.message.MessageReceipt;
|
||||
import net.mamoe.mirai.message.data.Message;
|
||||
import net.mamoe.mirai.message.data.MessageChain;
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -152,6 +155,36 @@ public interface BlockingGroup extends BlockingContact {
|
||||
@Nullable
|
||||
BlockingMember getMemberOrNull(long id);
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<Group> sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<Group> sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<Group> sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 检查此 id 的群成员是否存在
|
||||
*/
|
||||
|
@ -1,8 +1,14 @@
|
||||
package net.mamoe.mirai.japt;
|
||||
|
||||
import net.mamoe.mirai.contact.QQ;
|
||||
import net.mamoe.mirai.data.FriendNameRemark;
|
||||
import net.mamoe.mirai.data.PreviousNameList;
|
||||
import net.mamoe.mirai.data.Profile;
|
||||
import net.mamoe.mirai.event.events.EventCancelledException;
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent;
|
||||
import net.mamoe.mirai.message.MessageReceipt;
|
||||
import net.mamoe.mirai.message.data.Message;
|
||||
import net.mamoe.mirai.message.data.MessageChain;
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -47,4 +53,35 @@ public interface BlockingQQ extends BlockingContact {
|
||||
@MiraiExperimentalAPI(message = "还未支持")
|
||||
@NotNull
|
||||
FriendNameRemark queryRemark();
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<QQ> sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<QQ> sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<QQ> sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import net.mamoe.mirai.japt.BlockingBot
|
||||
import net.mamoe.mirai.japt.BlockingGroup
|
||||
import net.mamoe.mirai.japt.BlockingMember
|
||||
import net.mamoe.mirai.japt.BlockingQQ
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
@ -35,9 +36,9 @@ internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ {
|
||||
override fun getId(): Long = delegate.id
|
||||
override fun getNick(): String = delegate.nick
|
||||
|
||||
override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun sendMessage(messages: MessageChain): MessageReceipt<QQ> = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String): MessageReceipt<QQ> = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message): MessageReceipt<QQ> = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) }
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ -51,9 +52,9 @@ internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ {
|
||||
}
|
||||
|
||||
internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup {
|
||||
override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun sendMessage(messages: MessageChain): MessageReceipt<Group> = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String): MessageReceipt<Group> = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message): MessageReceipt<Group> = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun getOwner(): BlockingMember = delegate.owner.blocking()
|
||||
@MiraiExperimentalAPI
|
||||
override fun newMember(memberInfo: MemberInfo): Member = delegate.Member(memberInfo)
|
||||
|
Loading…
Reference in New Issue
Block a user