Merge remote-tracking branch 'origin/master'

This commit is contained in:
ryoii 2020-02-21 21:21:18 +08:00
commit 170418b529
34 changed files with 850 additions and 220 deletions

View File

@ -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因此不提供详细入门指导

View 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 离线, 避免主线程退出
}
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -107,6 +107,8 @@ fun main() {
* 顶层方法. TCP 切掉头后直接来这里
*/
fun ByteReadPacket.decodeMultiClientToServerPackets() {
DebugLogger.enable()
PacketLogger.enable()
println("=======================处理客户端到服务器=======================")
var count = 0
while (remaining != 0L) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,4 +62,15 @@ interface GroupInfo {
* 机器人被禁言还剩时间, .
*/
val botMuteRemaining: Int
/*
/**
* 机器人的头衔
*/
val botSpecialTitle: String
/**
* 机器人的昵称
*/
val botNameCard: String*/
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
/**
* 上传一个图片以备发送.

View File

@ -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 的群成员是否存在
*/

View File

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

View File

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