1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-24 20:43:33 +08:00

Support MessageReceipt.quoteReply for group

This commit is contained in:
Him188 2020-03-01 01:55:03 +08:00
parent f57d0242ad
commit 3748963a03
14 changed files with 456 additions and 294 deletions
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid
mirai-core/src/commonMain/kotlin/net.mamoe.mirai

View File

@ -20,6 +20,7 @@ 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.qqandroid.message.MessageSourceFromSendGroup
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
@ -79,10 +80,12 @@ internal class QQImpl(
bot.client,
id,
event.message
) { source = it }.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
) {
source = it
}.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
) { "send message failed" }
}
return MessageReceipt(source, this)
return MessageReceipt(source, this, null)
}
override suspend fun uploadImage(image: ExternalImage): Image = try {
@ -117,7 +120,14 @@ internal class QQImpl(
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
}
is LongConn.OffPicUp.Response.RequireUpload -> {
Http.postImage("0x6ff0070", bot.uin, null, imageInput = image.input, inputSize = image.inputSize, uKeyHex = response.uKey.toUHexString(""))
Http.postImage(
"0x6ff0070",
bot.uin,
null,
imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = response.uKey.toUHexString("")
)
//HighwayHelper.uploadImage(
// client = bot.client,
// serverIp = response.serverIp[0].toIpV4AddressString(),
@ -527,7 +537,8 @@ internal class GroupImpl(
override operator fun get(id: Long): Member {
return members.delegate.filteringGetOrNull { it.id == id } ?: throw NoSuchElementException("member $id not found in group $uin")
return members.delegate.filteringGetOrNull { it.id == id }
?: throw NoSuchElementException("member $id not found in group $uin")
}
override fun contains(id: Long): Boolean {
@ -544,21 +555,22 @@ internal class GroupImpl(
if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent")
}
lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSendGroup
lateinit var source: MessageSourceFromSendGroup
bot.network.run {
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
bot.client,
id,
event.message
) { source = it }.sendAndExpect()
) {
source = it
source.startWaitingSequenceId(this)
}.sendAndExpect()
check(
response is MessageSvc.PbSendMsg.Response.SUCCESS
) { "send message failed: $response" }
}
source.startWaitingSequenceId(this)
return MessageReceipt(source, this)
return MessageReceipt(source, this, botAsMember)
}
override suspend fun uploadImage(image: ExternalImage): Image = try {

View File

@ -14,6 +14,7 @@ import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo
@ -84,21 +85,28 @@ internal abstract class QQAndroidBotBase constructor(
return groups.delegate.getOrNull(uin)
}
override suspend fun queryGroupList(): Sequence<Long> {
@UseExperimental(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupList(): Sequence<Long> {
return network.run {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
}
override suspend fun queryGroupInfo(groupCode: Long): GroupInfo = network.run {
@UseExperimental(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo = network.run {
TroopManagement.GetGroupInfo(
client = bot.client,
groupCode = groupCode
).sendAndExpect<GroupInfoImpl>(retry = 2)
}
override suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo> =
@UseExperimental(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupMemberList(
groupUin: Long,
groupCode: Long,
ownerId: Long
): Sequence<MemberInfo> =
network.run {
var nextUin = 0L
var sequence = sequenceOf<MemberInfoImpl>()
@ -125,7 +133,7 @@ internal abstract class QQAndroidBotBase constructor(
}
override suspend fun recall(source: MessageSource) {
if (source.qqId != uin && source.groupId != 0L) {
if (source.senderId != uin && source.groupId != 0L) {
getGroup(source.groupId).checkBotPermissionOperator()
}
@ -136,7 +144,7 @@ internal abstract class QQAndroidBotBase constructor(
if (source.groupId == 0L) {
PbMessageSvc.PbMsgWithDraw.Friend(
bot.client,
source.qqId,
source.senderId,
source.sequenceId,
source.messageRandom,
source.time
@ -144,7 +152,7 @@ internal abstract class QQAndroidBotBase constructor(
} else {
MessageRecallEvent.GroupRecall(
bot,
source.qqId,
source.senderId,
source.id,
source.time.toInt(),
null,
@ -162,6 +170,7 @@ internal abstract class QQAndroidBotBase constructor(
}
}
@UseExperimental(LowLevelAPI::class)
override suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) {
network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response =
@ -172,6 +181,7 @@ internal abstract class QQAndroidBotBase constructor(
}
}
@UseExperimental(LowLevelAPI::class)
override suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long) {
network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response =

View File

@ -1,138 +0,0 @@
/*
* 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.message
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.messageRandom
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
internal inline class MessageSourceFromServer(
val delegate: ImMsgBody.SourceMsg
) : MessageSource {
override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF
override val id: Long
get() = (delegate.origSeqs?.firstOrNull() ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or
(delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.toInt()).toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
override val qqId: Long get() = delegate.senderUin
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
override fun toString(): String = ""
}
internal inline class MessageSourceFromMsg(
val delegate: MsgComm.Msg
) : MessageSource {
override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF
override val id: Long
get() = delegate.msgHead.msgSeq.toLong().shl(32) or
delegate.msgBody.richText.attr!!.random.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
override val qqId: Long get() = delegate.msgHead.fromUin
override val groupId: Long get() = delegate.msgHead.groupInfo?.groupCode ?: 0
fun toJceData(): ImMsgBody.SourceMsg {
return if (groupId == 0L) {
toJceDataImplForFriend()
} else toJceDataImplForGroup()
}
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = delegate.msgHead.toUin,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = delegate.msgHead.toUin, // group
msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime,
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = delegate.msgBody.richText.elems.also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
val groupUin = Group.calculateGroupUinByGroupCode(groupId)
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = groupUin,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = groupUin, // group
msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime,
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
groupInfo = MsgComm.GroupInfo(groupCode = groupId),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = delegate.msgBody.richText.elems.also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
override fun toString(): String = ""
}

View File

@ -0,0 +1,295 @@
/*
* 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.message
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.subscribingGetAsync
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.messageRandom
import net.mamoe.mirai.message.data.sequenceId
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
import net.mamoe.mirai.utils.MiraiExperimentalAPI
internal inline class MessageSourceFromServer(
val delegate: ImMsgBody.SourceMsg
) : MessageSource {
override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF
override val id: Long
get() = (delegate.origSeqs?.firstOrNull()
?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or
(delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.toInt()).toLong().and(0xFFFFFFFF)
override val toUin: Long get() = delegate.toUin
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
override val senderId: Long get() = delegate.senderUin
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
override fun toString(): String = ""
}
internal inline class MessageSourceFromMsg(
val delegate: MsgComm.Msg
) : MessageSource {
override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF
override val id: Long
get() = delegate.msgHead.msgSeq.toLong().shl(32) or
delegate.msgBody.richText.attr!!.random.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
override val toUin: Long get() = delegate.msgHead.toUin
override val senderId: Long get() = delegate.msgHead.fromUin
override val groupId: Long get() = delegate.msgHead.groupInfo?.groupCode ?: 0
fun toJceData(): ImMsgBody.SourceMsg {
return if (groupId == 0L) {
toJceDataImplForFriend()
} else toJceDataImplForGroup()
}
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = delegate.msgHead.toUin,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = delegate.msgHead.toUin, // group
msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime,
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = delegate.msgBody.richText.elems.also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
val groupUin = Group.calculateGroupUinByGroupCode(groupId)
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = groupUin,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = groupUin, // group
msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime,
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
groupInfo = MsgComm.GroupInfo(groupCode = groupId),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = delegate.msgBody.richText.elems.also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
override fun toString(): String = ""
}
internal abstract class MessageSourceFromSend : MessageSource {
abstract val sourceMessage: MessageChain
fun toJceData(): ImMsgBody.SourceMsg {
return if (groupId == 0L) {
toJceDataImplForFriend()
} else toJceDataImplForGroup()
}
private val elems by lazy {
sourceMessage.toRichTextElems(groupId != 0L)
}
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF)
return ImMsgBody.SourceMsg(
origSeqs = listOf(sequenceId),
senderUin = senderId,
toUin = toUin,
flag = 1,
elems = elems,
type = 0,
time = time.toInt(),
pbReserve = SourceMsg.ResvAttr(
origUids = messageUid
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = senderId, // qq
toUin = toUin, // group
msgType = 9, // 82?
c2cCmd = 11,
msgSeq = sequenceId,
msgTime = time.toInt(),
msgUid = messageUid, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(sequenceId),
senderUin = senderId,
toUin = toUin,
flag = 1,
elems = elems,
type = 0,
time = time.toInt(),
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = senderId, // qq
toUin = toUin, // group
msgType = 82, // 82?
c2cCmd = 1,
msgSeq = sequenceId,
msgTime = time.toInt(),
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
groupInfo = MsgComm.GroupInfo(groupCode = groupId),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
}
internal class MessageSourceFromSendFriend(
val messageRandom: Int,
override val time: Long,
override val senderId: Long,
override val toUin: Long,
override val groupId: Long,
val sequenceId: Int,
override val sourceMessage: MessageChain
) : MessageSourceFromSend() {
@UseExperimental(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceId.toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
override fun toString(): String {
return ""
}
}
internal class MessageSourceFromSendGroup(
val messageRandom: Int,
override val time: Long,
override val senderId: Long,
override val toUin: Long,
override val groupId: Long,
override val sourceMessage: MessageChain
) : MessageSourceFromSend() {
private lateinit var sequenceIdDeferred: Deferred<Int>
@UseExperimental(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
@UseExperimental(MiraiExperimentalAPI::class)
internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
sequenceIdDeferred =
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
timeoutMillis = 3000
) {
if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
it.sequenceId
} else null
}
}
override suspend fun ensureSequenceIdAvailable() {
sequenceIdDeferred.join()
}
override fun toString(): String {
return ""
}
}

View File

@ -226,6 +226,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
when (val source = this[QuoteReply].source) {
is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
is MessageSourceFromSend -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}")
}
}
@ -243,6 +244,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
is Face -> elements.add(ImMsgBody.Elem(face = it.toJceData()))
is QuoteReplyToSend -> {
if (forGroup) {
check(it is QuoteReplyToSend.ToGroup) {
"sending a quote to group suing QuoteReplyToSend.ToFriend"
}
if (it.sender is Member) {
transformOneMessage(it.createAt())
}

View File

@ -245,7 +245,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply {
groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl
if (this.delegate.groupName == null) {
@ -262,7 +262,11 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
this.delegate.groupCode = troopNum.groupCode
},
members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin)
members = bot._lowLevelQueryGroupMemberList(
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
))
)
}?.let {

View File

@ -78,6 +78,7 @@ internal class PbMessageSvc {
messageRandom: Int, // 921878719
time: Long
): OutgoingPacket = buildOutgoingUniPacket(client) {
val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF)
writeProtoBuf(
MsgSvc.PbMsgWithDrawReq.serializer(),
MsgSvc.PbMsgWithDrawReq(
@ -89,7 +90,7 @@ internal class PbMessageSvc {
fromUin = client.bot.uin,
toUin = toUin,
msgSeq = messageSequenceId,
msgUid = messageRandom.toLong() and 0xffffffff,
msgUid = messageUid,
msgTime = time and 0xffffffff,
routingHead = MsgSvc.RoutingHead(
c2c = MsgSvc.C2C(

View File

@ -10,9 +10,6 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
@ -21,16 +18,16 @@ import net.mamoe.mirai.data.Packet
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.subscribingGetAsync
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
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.message.MessageSourceFromSendFriend
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
@ -45,6 +42,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils._miraiContentToString
import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.math.absoluteValue
import kotlin.random.Random
@ -97,7 +95,7 @@ internal class MessageSvc {
syncFlag = syncFlag,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = client.c2cMessageSync.syncCookie
?: SyncCookie(time = msgTime + client.timeDifference).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
?: SyncCookie(time = msgTime).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
// syncFlag = client.c2cMessageSync.syncFlag,
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
//pubaccountCookie = client.c2cMessageSync.pubAccountCookie
@ -165,7 +163,7 @@ internal class MessageSvc {
bot = bot,
coroutineContext = bot.coroutineContext,
id = Group.calculateGroupCodeByGroupUin(msg.msgHead.fromUin),
groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply {
groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl
@ -183,7 +181,11 @@ internal class MessageSvc {
this.delegate.groupCode = troopNum.groupCode
},
members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin)
members = bot._lowLevelQueryGroupMemberList(
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
)
bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup)
@ -198,12 +200,15 @@ internal class MessageSvc {
override val specialTitle: String get() = ""
override val muteTimestamp: Int get() = 0
override val uin: Long get() = msg.msgHead.authUin
override val nick: String get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromNick
override val nick: String
get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() }
?: msg.msgHead.fromNick
}).also { group.members.delegate.addLast(it) })
}
}
}
166 -> {
println(msg._miraiContentToString())
return@mapNotNull when {
msg.msgHead.fromUin == bot.uin -> null
!bot.firstLoginSucceed -> null
@ -227,8 +232,13 @@ internal class MessageSvc {
override suspend fun QQAndroidBot.handle(packet: Response) {
when (packet.syncFlagFromServer) {
MsgSvc.SyncFlag.STOP,
MsgSvc.SyncFlag.START -> return
MsgSvc.SyncFlag.STOP -> return
MsgSvc.SyncFlag.START -> {
network.run {
PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendWithoutExpect()
}
return
}
MsgSvc.SyncFlag.CONTINUE -> {
network.run {
@ -266,62 +276,6 @@ internal class MessageSvc {
}
}
internal class MessageSourceFromSendFriend(
val messageRandom: Int,
override val time: Long,
override val qqId: Long,
override val groupId: Long,
val sequenceId: Int
) : MessageSource {
@UseExperimental(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceId.toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
override fun toString(): String {
return ""
}
}
internal class MessageSourceFromSendGroup(
val messageRandom: Int,
override val time: Long,
override val qqId: Long,
override val groupId: Long// ,
// override val sourceMessage: MessageChain
) : MessageSource {
private lateinit var sequenceIdDeferred: Deferred<Int>
@UseExperimental(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
@UseExperimental(MiraiExperimentalAPI::class)
fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
sequenceIdDeferred =
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
timeoutMillis = 3000
) {
if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
it.sequenceId
} else null
}
}
override suspend fun ensureSequenceIdAvailable() {
sequenceIdDeferred.join()
}
override fun toString(): String {
return ""
}
}
inline fun ToFriend(
client: QQAndroidClient,
toUin: Long,
@ -330,10 +284,12 @@ internal class MessageSvc {
): OutgoingPacket {
val source = MessageSourceFromSendFriend(
messageRandom = Random.nextInt().absoluteValue,
qqId = toUin,
senderId = client.uin,
toUin = toUin,
time = currentTimeSeconds + client.timeDifference,
groupId = 0,
sequenceId = client.atomicNextMessageSequenceId()
sequenceId = client.atomicNextMessageSequenceId(),
sourceMessage = message
)
sourceCallback(source)
return ToFriend(client, toUin, message, source)
@ -379,9 +335,11 @@ internal class MessageSvc {
val source = MessageSourceFromSendGroup(
messageRandom = Random.nextInt().absoluteValue,
qqId = client.uin,
senderId = client.uin,
toUin = Group.calculateGroupUinByGroupCode(groupCode),
time = currentTimeSeconds + client.timeDifference,
groupId = groupCode//,
groupId = groupCode,
sourceMessage = message//,
// sourceMessage = message
)
sourceCallback(source)

View File

@ -19,8 +19,6 @@ import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
@ -41,7 +39,7 @@ import kotlin.jvm.JvmStatic
* @see Contact 联系人
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
*/
@UseExperimental(MiraiInternalAPI::class)
@UseExperimental(MiraiInternalAPI::class, LowLevelAPI::class)
abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor {
companion object {
/**
@ -151,27 +149,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor {
?: throw NoSuchElementException("No such group $id for bot ${this.uin}")
}
/**
* 向服务器查询群列表. 返回值前 32 bits uin, 32 bits groupCode
*/
abstract suspend fun queryGroupList(): Sequence<Long>
/**
* 向服务器查询群资料. 获得的仅为当前时刻的资料.
* 请优先使用 [getGroup] 然后查看群资料.
*/
abstract suspend fun queryGroupInfo(groupCode: Long): GroupInfo
/**
* 向服务器查询群成员列表.
* 请优先使用 [getGroup], [Group.members] 查看群成员.
*
* 这个函数很慢. 请不要频繁使用.
*
* @see Group.calculateGroupUinByGroupCode 使用 groupCode 计算 groupUin
*/
abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
// endregion
// region network
@ -263,6 +240,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor {
*/
abstract fun close(cause: Throwable? = null)
@UseExperimental(LowLevelAPI::class)
final override fun toString(): String = "Bot(${uin})"
}

View File

@ -5,7 +5,7 @@ import net.mamoe.mirai.Bot
/**
* 群资料.
*
* 通过 [Bot.queryGroupInfo] 得到
* 通过 [Bot._lowLevelQueryGroupInfo] 得到
*/
interface GroupInfo {
/**

View File

@ -9,6 +9,9 @@
package net.mamoe.mirai
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@ -18,6 +21,7 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
* 使用低级的 API 无法带来任何安全和便捷保障.
* 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API.
*/
@Experimental
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class LowLevelAPI
@ -29,6 +33,31 @@ annotation class LowLevelAPI
@Suppress("FunctionName", "unused")
@LowLevelAPI
interface LowLevelBotAPIAccessor {
/**
* 向服务器查询群列表. 返回值前 32 bits uin, 32 bits groupCode
*/
@LowLevelAPI
suspend fun _lowLevelQueryGroupList(): Sequence<Long>
/**
* 向服务器查询群资料. 获得的仅为当前时刻的资料.
* 请优先使用 [Bot.getGroup] 然后查看群资料.
*/
@LowLevelAPI
suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo
/**
* 向服务器查询群成员列表.
* 请优先使用 [Bot.getGroup], [Group.members] 查看群成员.
*
* 这个函数很慢. 请不要频繁使用.
*
* @see Group.calculateGroupUinByGroupCode 使用 groupCode 计算 groupUin
*/
@LowLevelAPI
suspend fun _lowLevelQueryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
/**
* 撤回一条由机器人发送给好友的消息
* @param messageId [MessageSource.id]

View File

@ -12,13 +12,10 @@ package net.mamoe.mirai.message
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recallIn
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef
@ -36,7 +33,8 @@ import net.mamoe.mirai.utils.unsafeWeakRef
*/
open class MessageReceipt<C : Contact>(
val source: MessageSource,
target: C
target: C,
private val botAsMember: Member?
) {
init {
require(target is Group || target is QQ) { "target must be either Group or QQ" }
@ -47,6 +45,11 @@ open class MessageReceipt<C : Contact>(
*/
val target: C by target.unsafeWeakRef()
/**
* 是否为发送给群的消息的回执
*/
val isToGroup: Boolean = botAsMember != null
private val _isRecalled = atomic(false)
/**
@ -55,7 +58,6 @@ open class MessageReceipt<C : Contact>(
* @see Bot.recall
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/
@UseExperimental(MiraiExperimentalAPI::class)
suspend fun recall() {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
@ -75,10 +77,8 @@ open class MessageReceipt<C : Contact>(
* 在一段时间后撤回这条消息.. [recall] [recallIn] 只能被调用一次.
*
* @param millis 延迟时间, 单位为毫秒
*
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/
@UseExperimental(MiraiExperimentalAPI::class)
fun recallIn(millis: Long): Job {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
@ -91,27 +91,32 @@ open class MessageReceipt<C : Contact>(
}
/**
* 引用这条消息.
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
* @see MessageChain.quote 引用一条消息
*/
open suspend fun quote(): QuoteReplyToSend {
this.source.ensureSequenceIdAvailable()
@UseExperimental(LowLevelAPI::class)
return _unsafeQuote()
}
/**
* 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable].
* sequenceId 可用前就发送这条消息则会导致一个异常.
* 当且仅当用于存储而不用于发送时使用这个方法.
*
* @see MessageChain.quote 引用一条消息
*
* @throws IllegalStateException 当此消息不是群消息时
*/
@MiraiExperimentalAPI("unstable")
open fun quote(): QuoteReplyToSend {
val target = target
check(target is Group) { "quote is only available for GroupMessage" }
return this.source.quote(target.botAsMember)
@LowLevelAPI
@Suppress("FunctionName")
fun _unsafeQuote(): QuoteReplyToSend {
return this.source.quote(botAsMember as? QQ)
}
/**
* 引用这条消息并回复.
*
* @see MessageChain.quote 引用一条消息
*
* @throws IllegalStateException 当此消息不是群消息时
*/
@MiraiExperimentalAPI("unstable")
suspend fun quoteReply(message: MessageChain) {
target.sendMessage(this.quote() + message)
}
@ -138,12 +143,10 @@ inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceI
*/
inline val MessageReceipt<*>.sourceTime: Long get() = this.source.time
@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

@ -13,7 +13,7 @@
package net.mamoe.mirai.message.data
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.Group
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@ -50,19 +50,17 @@ interface MessageSource : Message, MessageMetadata {
val time: Long
/**
* 与这个消息相关的 [QQ] [QQ.id]
*
* 群消息时为发送人的 id (可能为 bot 自己). 好友消息时为消息发送目标好友的 id (不可能为 bot 自己)
* 发送人. 可以为机器人自己
*/
val qqId: Long
@Suppress("unused")
@Deprecated("使用 qqId. 此 API 将在不久后删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this.qqId"))
val senderId: Long
get() = qqId
/**
* 群号码, 0 时则来自好友消息
* 消息发送对象, 可以为一个群的 `uin` ( `id`)或一个好友, 或机器人自己
*/
val toUin: Long
/**
* 当群消息时为群 id, [Group.id], 好友消息时为 0
*/
val groupId: Long
@ -72,6 +70,8 @@ interface MessageSource : Message, MessageMetadata {
override fun toString(): String
}
interface GroupMessageSource : MessageSource
/**
* 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][MessageSource.ensureSequenceIdAvailable]
* @see MessageSource.id

View File

@ -40,16 +40,21 @@ open class QuoteReply
* 总是使用 [quote] 来构造实例.
*/
@UseExperimental(MiraiInternalAPI::class)
class QuoteReplyToSend
@MiraiInternalAPI constructor(source: MessageSource, val sender: QQ) : QuoteReply(source) {
fun createAt(): At = At(sender as Member)
sealed class QuoteReplyToSend
@MiraiInternalAPI constructor(source: MessageSource) : QuoteReply(source) {
class ToGroup(source: MessageSource, val sender: QQ) : QuoteReplyToSend(source) {
fun createAt(): At = At(sender as Member)
}
class ToFriend(source: MessageSource) : QuoteReplyToSend(source)
}
/**
* 引用这条消息.
* @see sender 消息发送人.
*/
@UseExperimental(MiraiInternalAPI::class)
fun MessageChain.quote(sender: QQ): QuoteReplyToSend {
fun MessageChain.quote(sender: QQ?): QuoteReplyToSend {
this.firstOrNull<MessageSource>()?.let {
return it.quote(sender)
}
@ -58,11 +63,12 @@ fun MessageChain.quote(sender: QQ): QuoteReplyToSend {
/**
* 引用这条消息.
* @see from 消息来源. 若是好友发送
*/
@UseExperimental(MiraiInternalAPI::class)
fun MessageSource.quote(sender: QQ): QuoteReplyToSend {
if (this.groupId != 0L) {
check(sender is Member) { "sender must be Member to quote a GroupMessage" }
}
return QuoteReplyToSend(this, sender)
fun MessageSource.quote(from: QQ?): QuoteReplyToSend {
return if (this.groupId != 0L) {
check(from is Member) { "sender must be Member to quote a GroupMessage" }
QuoteReplyToSend.ToGroup(this, from)
} else QuoteReplyToSend.ToFriend(this)
}