Commit 3f523e6f authored by Him188's avatar Him188

Long message

parent e495b91d
package net.mamoe.mirai.qqandroid.utils.cryptor
internal actual fun arraycopy(
src: ByteArray,
srcPos: Int,
dest: ByteArray,
destPos: Int,
length: Int
) = System.arraycopy(src, srcPos, dest, destPos, length)
\ No newline at end of file
......@@ -33,11 +33,13 @@ import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl
import net.mamoe.mirai.qqandroid.contact.QQImpl
import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend
import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl
import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.MultiMsg
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.list.FriendList
......@@ -45,6 +47,8 @@ import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.encodeToString
import kotlin.collections.asSequence
import kotlin.coroutines.CoroutineContext
import kotlin.math.absoluteValue
import kotlin.random.Random
@OptIn(MiraiInternalAPI::class)
internal expect class QQAndroidBot constructor(
......@@ -360,6 +364,34 @@ internal abstract class QQAndroidBotBase constructor(
return json.parse(GroupActiveData.serializer(), rep)
}
@LowLevelAPI
@MiraiExperimentalAPI
override suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) {
val source = MessageSourceFromSendFriend(
messageRandom = Random.nextInt().absoluteValue,
senderId = client.uin,
toUin = Group.calculateGroupUinByGroupCode(groupCode),
time = currentTimeSeconds,
groupId = groupCode,
originalMessage = message.asMessageChain(),
sequenceId = 0
// sourceMessage = message
)
// TODO: 2020/3/26 util 方法来添加单例元素
val toSend = buildMessageChain {
source.originalMessage.filter { it !is MessageSource }.forEach {
add(it)
}
add(source)
}
network.run {
val response = MultiMsg.ApplyUp.createForLongMessage(this@QQAndroidBotBase.client, toSend, groupCode)
.sendAndExpect<MultiMsg.ApplyUp.Response>()
println(response._miraiContentToString())
}
}
override suspend fun queryImageUrl(image: Image): String = when (image) {
is OnlineFriendImageImpl -> image.originUrl
is OnlineGroupImageImpl -> image.originUrl
......
......@@ -254,7 +254,7 @@ internal class MessageSourceFromSendGroup(
override val groupId: Long,
override val originalMessage: MessageChain
) : MessageSourceFromSend() {
private lateinit var sequenceIdDeferred: Deferred<Int>
internal lateinit var sequenceIdDeferred: Deferred<Int>
@OptIn(ExperimentalCoroutinesApi::class)
override val id: Long
......
......@@ -6,6 +6,7 @@
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file: OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class, LowLevelAPI::class, ExperimentalUnsignedTypes::class)
package net.mamoe.mirai.qqandroid.message
......@@ -218,6 +219,8 @@ private val atAllData = ImMsgBody.Elem(
)
)
private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> {
val elements = mutableListOf<ImMsgBody.Elem>()
......@@ -233,31 +236,49 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
fun transformOneMessage(it: Message) {
if (it is RichMessage) {
val content = MiraiPlatformUtils.zip(it.content.toByteArray())
when (it) {
is LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + content
)
)
)
is MergedForwardedMessage -> {
elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = 35,
template1 = byteArrayOf(1) + content
)
)
)
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN) // required
}
else -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (it) {
is XmlMessage -> 60
is JsonMessage -> 1
is MergedForwardedMessage -> 35
else -> error("unsupported RichMessage: ${it::class.simpleName}")
},
template1 = byteArrayOf(1) + content
)
)
)
}
}
when (it) {
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
is At -> {
elements.add(ImMsgBody.Elem(text = it.toJceData()))
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
}
is LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is RichMessage -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (it) {
is XmlMessage -> 60
is JsonMessage -> 1
else -> error("unsupported RichMessage")
},
template1 = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
......@@ -267,16 +288,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
is QuoteReplyToSend -> {
if (forGroup) {
check(it is QuoteReplyToSend.ToGroup) {
"sending a quote to group using QuoteReplyToSend.ToFriend"
"sending a quote to group using QuoteReplyToSend.ToFriend is prohibited"
}
if (it.sender is Member) {
transformOneMessage(it.createAt())
}
transformOneMessage(" ".toMessage())
transformOneMessage(PlainText(" "))
}
}
is QuoteReply,
is MessageSource -> {
is MessageSource,
-> {
}
else -> error("unsupported message type: ${it::class.simpleName}")
......@@ -358,7 +380,7 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain {
return buildMessageChain(elements.size + 1) {
+MessageSourceFromMsg(delegate = this@toMessageChain)
elements.joinToMessageChain(this)
}.removeAtIfHasQuoteReply()
}.cleanupRubbishMessageElements()
}
// These two functions are not identical, dont combine.
......@@ -369,11 +391,31 @@ internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
return buildMessageChain(elements.size + 1) {
+MessageSourceFromServer(delegate = this@toMessageChain)
elements.joinToMessageChain(this)
}.removeAtIfHasQuoteReply()
}.cleanupRubbishMessageElements()
}
private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
var last: SingleMessage? = null
return buildMessageChain(initialSize = this.count()) {
this@cleanupRubbishMessageElements.forEach { element ->
if (last == null) {
last = element
return@forEach
} else {
if (last is MergedForwardedMessage && element is PlainText) {
if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
last = element
return@forEach
}
}
}
add(element)
last = element
}
}
}
private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain =
this
/*
if (this.any<QuoteReply>()) {
var removed = false
......@@ -387,9 +429,6 @@ private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain =
}.asMessageChain()
} else this*/
@OptIn(
MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class
)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
this.forEach {
when {
......@@ -425,6 +464,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde
when (it.richMsg.serviceId) {
1 -> message.add(JsonMessage(content))
60 -> message.add(XmlMessage(content))
35 -> message.add(MergedForwardedMessage(content))
else -> {
@Suppress("DEPRECATION")
MiraiLogger.debug {
......
......@@ -101,7 +101,7 @@ internal open class QQAndroidClient(
var openAppId: Long = 715019303L
val apkVersionName: ByteArray get() = "8.2.7".toByteArray()
val buildVer: String get() = "8.2.7.4410"
val buildVer: String get() = "8.2.7.4410" // 8.2.0.1296
private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
......
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class LongMsg : ProtoBuf {
@Serializable
class MsgDeleteReq(
@ProtoId(1) val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val msgType: Int = 0
) : ProtoBuf
@Serializable
class MsgDeleteRsp(
@ProtoId(1) val result: Int = 0,
@ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class MsgDownReq(
@ProtoId(1) val srcUin: Int = 0,
@ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val msgType: Int = 0,
@ProtoId(4) val needCache: Int = 0
) : ProtoBuf
@Serializable
class MsgDownRsp(
@ProtoId(1) val result: Int = 0,
@ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val msgContent: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class MsgUpReq(
@ProtoId(1) val msgType: Int = 0,
@ProtoId(2) val dstUin: Long = 0L,
@ProtoId(3) val msgId: Int = 0,
@ProtoId(4) val msgContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val storeType: Int = 0,
@ProtoId(6) val msgUkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val needCache: Int = 0
) : ProtoBuf
@Serializable
class MsgUpRsp(
@ProtoId(1) val result: Int = 0,
@ProtoId(2) val msgId: Int = 0,
@ProtoId(3) val msgResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ReqBody(
@ProtoId(1) val subcmd: Int = 0,
@ProtoId(2) val termType: Int = 0,
@ProtoId(3) val platformType: Int = 0,
@ProtoId(4) val msgUpReq: List<LongMsg.MsgUpReq>? = null,
@ProtoId(5) val msgDownReq: List<LongMsg.MsgDownReq>? = null,
@ProtoId(6) val msgDelReq: List<LongMsg.MsgDeleteReq>? = null,
@ProtoId(10) val agentType: Int = 0
) : ProtoBuf
@Serializable
class RspBody(
@ProtoId(1) val subcmd: Int = 0,
@ProtoId(2) val msgUpRsp: List<LongMsg.MsgUpRsp>? = null,
@ProtoId(3) val msgDownRsp: List<LongMsg.MsgDownRsp>? = null,
@ProtoId(4) val msgDelRsp: List<LongMsg.MsgDeleteRsp>? = null
) : ProtoBuf
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class MsgTransmit : ProtoBuf {
@Serializable
class PbMultiMsgItem(
@ProtoId(1) val fileName: String = "",
@ProtoId(2) val buffer: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class PbMultiMsgNew(
@ProtoId(1) val msg: List<MsgComm.Msg>? = null
) : ProtoBuf
@Serializable
class PbMultiMsgTransmit(
@ProtoId(1) val msg: List<MsgComm.Msg>? = null,
@ProtoId(2) val pbItemList: List<MsgTransmit.PbMultiMsgItem>? = null
) : ProtoBuf
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Serializable
internal class MultiMsg : ProtoBuf {
@Serializable
class ExternMsg(
@ProtoId(1) val channelType: Int = 0
) : ProtoBuf
@Serializable
class MultiMsgApplyDownReq(
@ProtoId(1) val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val msgType: Int = 0,
@ProtoId(3) val srcUin: Long = 0L
) : ProtoBuf
@Serializable
class MultiMsgApplyDownRsp(
@ProtoId(1) val result: Int = 0,
@ProtoId(2) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val msgKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val uint32DownIp: List<Int>? = null,
@ProtoId(5) val uint32DownPort: List<Int>? = null,
@ProtoId(6) val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val msgExternInfo: MultiMsg.ExternMsg? = null,
@ProtoId(8) val bytesDownIpV6: List<ByteArray>? = null,
@ProtoId(9) val uint32DownV6Port: List<Int>? = null
) : ProtoBuf
@Serializable
class MultiMsgApplyUpReq(
@ProtoId(1) val dstUin: Long = 0L,
@ProtoId(2) val msgSize: Long = 0L,
@ProtoId(3) val msgMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val msgType: Int = 0,
@ProtoId(5) val applyId: Int = 0
) : ProtoBuf
@Serializable
class MultiMsgApplyUpRsp(
@ProtoId(1) val result: Int = 0,
@ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val msgUkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val uint32UpIp: List<Int>? = null,
@ProtoId(5) val uint32UpPort: List<Int>? = null,
@ProtoId(6) val blockSize: Long = 0L,
@ProtoId(7) val upOffset: Long = 0L,
@ProtoId(8) val applyId: Int = 0,
@ProtoId(9) val msgKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(10) val msgSig: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(11) val msgExternInfo: MultiMsg.ExternMsg? = null,
@ProtoId(12) val bytesUpIpV6: List<ByteArray>? = null,
@ProtoId(13) val uint32UpV6Port: List<Int>? = null
) : ProtoBuf
@Serializable
class ReqBody(
@ProtoId(1) val subcmd: Int = 0,
@ProtoId(2) val termType: Int = 0,
@ProtoId(3) val platformType: Int = 0,
@ProtoId(4) val netType: Int = 0,
@ProtoId(5) val buildVer: String = "",
@ProtoId(6) val multimsgApplyupReq: List<MultiMsg.MultiMsgApplyUpReq>? = null,
@ProtoId(7) val multimsgApplydownReq: List<MultiMsg.MultiMsgApplyDownReq>? = null,
@ProtoId(8) val buType: Int = 0,
@ProtoId(9) val reqChannelType: Int = 0
) : ProtoBuf
@Serializable
class RspBody(
@ProtoId(1) val subcmd: Int = 0,
@ProtoId(2) val multimsgApplyupRsp: List<MultiMsg.MultiMsgApplyUpRsp>? = null,
@ProtoId(3) val multimsgApplydownRsp: List<MultiMsg.MultiMsgApplyDownRsp>? = null
) : ProtoBuf
}
......@@ -11,9 +11,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.MultiMsg
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
......@@ -144,7 +145,8 @@ internal object KnownPacketFactories {
TroopManagement.EditGroupNametag,
TroopManagement.Kick,
Heartbeat.Alive,
PbMessageSvc.PbMsgWithDraw
PbMessageSvc.PbMsgWithDraw,
MultiMsg.ApplyUp
)
object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(
......
/*
* 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.utils.cryptor
import net.mamoe.mirai.utils.io.toUHexString
internal object MultiMsgCryptor {
private val impl = class_1457()
fun decrypt(data: ByteArray, offset: Int, length: Int, key: ByteArray): ByteArray {
return this.impl.method_67425(data, offset, length, key) ?: error("MultiMsgCryptor decypt failed: key=${key.toUHexString()}, data=${data.drop(offset).take(length).toByteArray().toUHexString()}")
}
fun decrypt(data: ByteArray, key: ByteArray): ByteArray {
return this.impl.method_67426(data, key) ?: error("MultiMsgCryptor decrypt failed: key=${key.toUHexString()}, data=${data.toUHexString()}")
}
fun enableResultRandom(enabled: Boolean) {
this.impl.method_67424(enabled)
}
fun encrypt(data: ByteArray, key: ByteArray): ByteArray {
return this.impl.method_67427(data, key)
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils.cryptor
internal actual fun arraycopy(
src: ByteArray,
srcPos: Int,
dest: ByteArray,
destPos: Int,
length: Int
) = System.arraycopy(src, srcPos, dest, destPos, length)
\ No newline at end of file
......@@ -13,6 +13,7 @@ import kotlinx.coroutines.Job
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.*
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
......@@ -139,6 +140,14 @@ interface LowLevelBotAPIAccessor {
@LowLevelAPI
@MiraiExperimentalAPI
suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData
/**
* 发送长消息
*/
@SinceMirai("0.31.0")
@LowLevelAPI
@MiraiExperimentalAPI
suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message)
}
/**
......
......@@ -15,7 +15,6 @@ package net.mamoe.mirai.message.data
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
/**
......@@ -28,26 +27,13 @@ class PlainText(val stringValue: String) :
Comparable<String> by stringValue,
CharSequence by stringValue {
@Suppress("unused")
constructor(charSequence: CharSequence) : this(charSequence.toString())
override operator fun contains(sub: String): Boolean = sub in stringValue
override fun toString(): String = stringValue
companion object Key : Message.Key<PlainText> {
@JvmStatic
val Empty = PlainText("")
@JvmStatic
val Null = PlainText("null")
inline fun of(value: String): PlainText {
return PlainText(value)
}
inline fun of(value: CharSequence): PlainText {
return PlainText(value)
}
}
companion object Key : Message.Key<PlainText>
}
/**
......
......@@ -33,6 +33,40 @@ interface RichMessage : MessageContent {
@SinceMirai("0.30.0")
companion object Templates : Message.Key<RichMessage> {
/**
* 合并转发.
*/
@MiraiExperimentalAPI
fun mergedForward(): Nothing {
TODO()
}
/**
* 长消息.
*
* @param brief 消息内容纯文本, 显示在图片的前面
*/
@MiraiExperimentalAPI
fun longMessage(brief: String, resId: String, time: Long): XmlMessage {
val template = """
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<msg serviceID="35" templateID="1" action="viewMultiMsg"
brief="$brief"
m_resid="$resId"
m_fileName="$time" sourceMsgId="0" url=""
flag="3" adverSign="0" multiMsgFlag="1">
<item layout="1">
<title>$brief…</title>
<hr hidden="false" style="0"/>
<summary>点击查看完整消息</summary>
</item>
<source name="聊天记录" icon="" action="" appid="-1"/>
</msg>
"""
return XmlMessage(template)
}
@MiraiExperimentalAPI
@SinceMirai("0.30.0")
fun share(url: String, title: String? = null, content: String? = null, coverUrl: String? = null): XmlMessage =
......@@ -107,6 +141,19 @@ class XmlMessage constructor(override val content: String) : RichMessage {
override fun toString(): String = content
}
/**
* 合并转发消息
*/
@SinceMirai("0.31.0")
@MiraiExperimentalAPI
class MergedForwardedMessage(override val content: String) : RichMessage {
companion object Key : Message.Key<XmlMessage>
// serviceId = 35
override fun toString(): String = content
}
/**
* 构造一条 XML 消息
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment