Merge pull request #170 from mamoe/long-message

Support long message in general `sendMessage`
This commit is contained in:
Him188 2020-03-29 14:29:33 +08:00 committed by GitHub
commit f7040c18fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 282 additions and 373 deletions

View File

@ -32,9 +32,9 @@ internal actual class ECDHKeyPairImpl(
} }
@Suppress("FunctionName") @Suppress("FunctionName")
actual fun ECDH() = ECDH(ECDH.generateKeyPair()) internal actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object { actual companion object {
@Suppress("ObjectPropertyName") @Suppress("ObjectPropertyName")
private var _isECDHAvailable: Boolean = false // because `runCatching` has no contract. private var _isECDHAvailable: Boolean = false // because `runCatching` has no contract.

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@ -29,12 +31,12 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.* import net.mamoe.mirai.data.*
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.MessageRecallEvent import net.mamoe.mirai.event.events.MessageRecallEvent
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl
import net.mamoe.mirai.qqandroid.contact.QQImpl import net.mamoe.mirai.qqandroid.contact.QQImpl
import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend
import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl
import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
@ -162,6 +164,7 @@ internal abstract class QQAndroidBotBase constructor(
TODO("not implemented") TODO("not implemented")
} }
@ExperimentalMessageSource
override suspend fun recall(source: MessageSource) { override suspend fun recall(source: MessageSource) {
if (source.senderId != uin && source.groupId != 0L) { if (source.senderId != uin && source.groupId != 0L) {
getGroup(source.groupId).checkBotPermissionOperator() getGroup(source.groupId).checkBotPermissionOperator()
@ -368,34 +371,21 @@ internal abstract class QQAndroidBotBase constructor(
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
override suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) { override suspend fun _lowLevelSendLongGroupMessage(groupCode: Long, message: Message): MessageReceipt<Group> {
val chain = message.asMessageChain() val chain = message.asMessageChain()
check(chain.toString().length <= 4500 && chain.count { it is Image } <= 50) { "message is too large" } check(chain.toString().length <= 4500 && chain.count { it is Image } <= 50) { "message is too large. Allow up to 4500 chars or 50 images" }
val group = getGroup(groupCode) val group = getGroup(groupCode)
val source = MessageSourceFromSendFriend( val time = currentTimeSeconds
messageRandom = Random.nextInt().absoluteValue,
senderId = client.uin,
toUin = Group.calculateGroupUinByGroupCode(groupCode),
time = currentTimeSeconds,
groupId = groupCode,
originalMessage = chain,
sequenceId = client.atomicNextMessageSequenceId()
// sourceMessage = message
)
// TODO: 2020/3/26 util 方法来添加单例元素
val toSend = buildMessageChain(chain.size) {
source.originalMessage.forEach {
if (it !is MessageSource) {
add(it)
}
}
add(source)
}
network.run { network.run {
val data = toSend.calculateValidationDataForGroup(group) val data = chain.calculateValidationDataForGroup(
sequenceId = client.atomicNextMessageSequenceId(),
time = time.toInt(),
random = Random.nextInt().absoluteValue.toUInt(),
groupCode,
group.botAsMember.nameCardOrNick
)
val response = val response =
MultiMsg.ApplyUp.createForGroupLongMessage( MultiMsg.ApplyUp.createForGroupLongMessage(
@ -441,17 +431,15 @@ internal abstract class QQAndroidBotBase constructor(
} }
} }
group.sendMessage( return group.sendMessage(
RichMessage.longMessage( RichMessage.longMessage(
brief = toSend.joinToString(limit = 30) { brief = chain.toString().let { // already cached
when (it) { if (it.length > 27) {
is PlainText -> it.stringValue it.take(27) + "..."
is At -> it.toString() } else it
else -> ""
}
}, },
resId = resId, resId = resId,
timeSeconds = source.time timeSeconds = time
) )
) )
} }

View File

@ -23,9 +23,7 @@ import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.OfflineGroupImage
import net.mamoe.mirai.message.data.asMessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
@ -273,13 +271,25 @@ internal class GroupImpl(
return members.delegate.filteringGetOrNull { it.id == id } return members.delegate.filteringGetOrNull { it.id == id }
} }
@OptIn(MiraiExperimentalAPI::class)
@JvmSynthetic @JvmSynthetic
override suspend fun sendMessage(message: Message): MessageReceipt<Group> { override suspend fun sendMessage(message: Message): MessageReceipt<Group> {
check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" } check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" }
val event = GroupMessageSendEvent(this, message.asMessageChain()).broadcast() val event = GroupMessageSendEvent(this, message.asMessageChain()).broadcast()
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by GroupMessageSendEvent")
} }
if (message !is LongMessage) {
val length = event.message.toString().length
if (length > 4000
|| event.message.count { it is Image } > 3
|| (event.message.any<QuoteReply>() && (event.message.any<Image>() || length > 100))
) {
return bot._lowLevelSendLongGroupMessage(this.id, message)
}
}
lateinit var source: MessageSourceFromSendGroup lateinit var source: MessageSourceFromSendGroup
bot.network.run { bot.network.run {
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup( val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.contact package net.mamoe.mirai.qqandroid.contact
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -8,9 +8,12 @@
*/ */
@file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.contact package net.mamoe.mirai.qqandroid.contact
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
@ -36,6 +39,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -46,12 +51,23 @@ internal inline class FriendInfoImpl(
override val uin: Long get() = jceFriendInfo.friendUin override val uin: Long get() = jceFriendInfo.friendUin
} }
@OptIn(ExperimentalContracts::class)
internal fun QQ.checkIsQQImpl(): QQImpl {
contract {
returns() implies (this@checkIsQQImpl is QQImpl)
}
check(this is QQImpl) { "A QQ instance is not instance of QQImpl. Don't interlace two protocol implementations together!" }
return this
}
internal class QQImpl( internal class QQImpl(
bot: QQAndroidBot, bot: QQAndroidBot,
override val coroutineContext: CoroutineContext, override val coroutineContext: CoroutineContext,
override val id: Long, override val id: Long,
private val friendInfo: FriendInfo private val friendInfo: FriendInfo
) : QQ() { ) : QQ() {
var lastMessageSequence: AtomicInt = atomic(-1)
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val nick: String override val nick: String
get() = friendInfo.nick get() = friendInfo.nick
@ -67,12 +83,12 @@ internal class QQImpl(
bot.network.run { bot.network.run {
check( check(
MessageSvc.PbSendMsg.ToFriend( MessageSvc.PbSendMsg.ToFriend(
bot.client, bot.client,
id, id,
event.message event.message
) { ) {
source = it source = it
} }
.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS .sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
) { "send message failed" } ) { "send message failed" }
} }

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -146,7 +148,7 @@ internal abstract class MessageSourceFromSend : MessageSource {
} }
private val elems by lazy { private val elems by lazy {
originalMessage.toRichTextElems(groupId != 0L) originalMessage.toRichTextElems(groupId != 0L, true)
} }
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg { private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {

View File

@ -7,6 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file: OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class, LowLevelAPI::class, ExperimentalUnsignedTypes::class) @file: OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class, LowLevelAPI::class, ExperimentalUnsignedTypes::class)
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
@ -56,44 +57,6 @@ internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage {
) )
} }
/*
CustomFace#24412994 {
guid=<Empty ByteArray>
filePath={01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png
shortcut=
buffer=<Empty ByteArray>
flag=00 00 00 00
oldData=15 36 20 39 32 6B 41 31 00 38 37 32 66 30 36 36 30 33 61 65 31 30 33 62 37 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 30 31 45 39 34 35 31 42 2D 37 30 45 44 2D 45 41 45 33 2D 42 33 37 43 2D 31 30 31 46 31 45 45 42 46 35 42 35 7D 2E 70 6E 67 41
fileId=0x872F0660(-2026961312)
serverIp=0x3AE103B7(987825079)
serverPort=0x00000050(80)
fileType=0x00000000(0)
signature=<Empty ByteArray>
useful=0x00000001(1)
md5=01 E9 45 1B 70 ED EA E3 B3 7C 10 1F 1E EB F5 B5
thumbUrl=/gchatpic_new/1040400290/1041235568-2268005984-01E9451B70EDEAE3B37C101F1EEBF5B5/198?term=2
bigUrl=
origUrl=/gchatpic_new/1040400290/1041235568-2268005984-01E9451B70EDEAE3B37C101F1EEBF5B5/0?term=2
bizType=0x00000000(0)
repeatIndex=0x00000000(0)
repeatImage=0x00000000(0)
imageType=0x00000000(0)
index=0x00000000(0)
width=0x0000015F(351)
height=0x000000EB(235)
source=0x00000000(0)
size=0x0000057C(1404)
origin=0x00000000(0)
thumbWidth=0x000000C6(198)
thumbHeight=0x00000084(132)
showLen=0x00000000(0)
downloadLen=0x00000000(0)
_400Url=/gchatpic_new/1040400290/1041235568-2268005984-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2
_400Width=0x0000015F(351)
_400Height=0x000000EB(235)
pbReserve=<Empty ByteArray>
}
*/
internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes() internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes()
internal fun Face.toJceData(): ImMsgBody.Face { internal fun Face.toJceData(): ImMsgBody.Face {
@ -133,76 +96,6 @@ internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace {
private val oldData: ByteArray = private val oldData: ByteArray =
"15 36 20 39 32 6B 41 31 00 38 37 32 66 30 36 36 30 33 61 65 31 30 33 62 37 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 30 31 45 39 34 35 31 42 2D 37 30 45 44 2D 45 41 45 33 2D 42 33 37 43 2D 31 30 31 46 31 45 45 42 46 35 42 35 7D 2E 70 6E 67 41".hexToBytes() "15 36 20 39 32 6B 41 31 00 38 37 32 66 30 36 36 30 33 61 65 31 30 33 62 37 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 30 31 45 39 34 35 31 42 2D 37 30 45 44 2D 45 41 45 33 2D 42 33 37 43 2D 31 30 31 46 31 45 45 42 46 35 42 35 7D 2E 70 6E 67 41".hexToBytes()
/*
customFace=CustomFace#2050019814 {
guid=<Empty ByteArray>
filePath=5F6C522DEAC4F36C0ED8EF362660EFD6.png
shortcut=
buffer=<Empty ByteArray>
flag=<Empty ByteArray>
oldData=<Empty ByteArray>
fileId=0xB40AF10E(-1274351346)
serverIp=0xB703E13A(-1224482502)
serverPort=0x00000050(80)
fileType=0x00000042(66)
signature=6B 44 61 76 72 79 68 79 57 67 70 52 41 45 78 49
useful=0x00000001(1)
md5=5F 6C 52 2D EA C4 F3 6C 0E D8 EF 36 26 60 EF D6
thumbUrl=
bigUrl=
origUrl=
bizType=0x00000005(5)
repeatIndex=0x00000000(0)
repeatImage=0x00000000(0)
imageType=0x000003E9(1001)
index=0x00000000(0)
width=0x0000005F(95)
height=0x00000054(84)
source=0x00000067(103)
size=0x000006E2(1762)
origin=0x00000000(0)
thumbWidth=0x00000000(0)
thumbHeight=0x00000000(0)
showLen=0x00000000(0)
downloadLen=0x00000000(0)
_400Url=
_400Width=0x00000000(0)
_400Height=0x00000000(0)
pbReserve=08 01 10 00 32 00 4A 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05
}
notOnlineImage=NotOnlineImage#2050019814 {
filePath=41AEF2D4B5BD24CF3791EFC5FEB67D60.jpg
fileLen=0x00000350(848)
downloadPath=/f2b7e5c0-acb3-4e83-aa5c-c8383840cc91
oldVerSendFile=<Empty ByteArray>
imgType=0x000003E8(1000)
previewsImage=<Empty ByteArray>
picMd5=41 AE F2 D4 B5 BD 24 CF 37 91 EF C5 FE B6 7D 60
picHeight=0x00000032(50)
picWidth=0x00000033(51)
resId=/f2b7e5c0-acb3-4e83-aa5c-c8383840cc91
flag=<Empty ByteArray>
thumbUrl=
original=0x00000000(0)
bigUrl=
origUrl=
bizType=0x00000005(5)
result=0x00000000(0)
index=0x00000000(0)
opFaceBuf=<Empty ByteArray>
oldPicMd5=false
thumbWidth=0x00000000(0)
thumbHeight=0x00000000(0)
fileId=0x00000000(0)
showLen=0x00000000(0)
downloadLen=0x00000000(0)
_400Url=
_400Width=0x00000000(0)
_400Height=0x00000000(0)
pbReserve=08 01 10 00 32 00 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05
}
*/
private val atAllData = ImMsgBody.Elem( private val atAllData = ImMsgBody.Elem(
text = ImMsgBody.Text( text = ImMsgBody.Text(
@ -222,7 +115,7 @@ private val atAllData = ImMsgBody.Elem(
private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> { internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: Boolean): MutableList<ImMsgBody.Elem> {
val elements = mutableListOf<ImMsgBody.Elem>() val elements = mutableListOf<ImMsgBody.Elem>()
if (this.any<QuoteReply>()) { if (this.any<QuoteReply>()) {
@ -310,28 +203,34 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
} }
this.forEach(::transformOneMessage) this.forEach(::transformOneMessage)
when { if (withGeneralFlags) {
longTextResId != null -> { when {
elements.add( longTextResId != null -> {
ImMsgBody.Elem( elements.add(
generalFlags = ImMsgBody.GeneralFlags( ImMsgBody.Elem(
longTextFlag = 1, generalFlags = ImMsgBody.GeneralFlags(
longTextResid = longTextResId!!, longTextFlag = 1,
pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes() longTextResid = longTextResId!!,
), pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
),
)
) )
) }
this.any<RichMessage>() -> {
// 08 09 78 00 A0 01 81 DC 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 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_RICH_MESSAGE)))
}
else -> elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_ELSE)))
} }
this.any<RichMessage>() -> {
// 08 09 78 00 A0 01 81 DC 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 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes())))
}
else -> elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes())))
} }
return elements return elements
} }
private val PB_RESERVE_FOR_RICH_MESSAGE =
"08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()
private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()
internal class OnlineGroupImageImpl( internal class OnlineGroupImageImpl(
internal val delegate: ImMsgBody.CustomFace internal val delegate: ImMsgBody.CustomFace
) : OnlineGroupImage() { ) : OnlineGroupImage() {
@ -416,15 +315,10 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
var last: SingleMessage? = null var last: SingleMessage? = null
return buildMessageChain(initialSize = this.count()) { return buildMessageChain(initialSize = this.count()) {
this@cleanupRubbishMessageElements.forEach { element -> this@cleanupRubbishMessageElements.forEach { element ->
if (last == null) { if (last is LongMessage && element is PlainText) {
last = element if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
return@forEach last = element
} else { return@forEach
if (last is LongMessage && element is PlainText) {
if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
last = element
return@forEach
}
} }
} }
@ -443,19 +337,6 @@ internal inline fun <reified R> Iterable<*>.firstIsInstance(): R {
throw NoSuchElementException("Collection contains no element matching the predicate.") throw NoSuchElementException("Collection contains no element matching the predicate.")
} }
/*
if (this.any<QuoteReply>()) {
var removed = false
this.filter {
if (it is At && !removed) {
false
} else {
removed = true
true
}
}.asMessageChain()
} else this*/
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) { internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
this.forEach { this.forEach {
when { when {
@ -467,9 +348,6 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde
if (it.text.attr6Buf.isEmpty()) { if (it.text.attr6Buf.isEmpty()) {
message.add(it.text.str.toMessage()) message.add(it.text.str.toMessage())
} else { } else {
// 00 01 00 00 00 05 01 00 00 00 00 00 00 all
// 00 01 00 00 00 0A 00 3E 03 3F A2 00 00 one/nick
// 00 01 00 00 00 07 00 44 71 47 90 00 00 one/groupCard
val id: Long val id: Long
it.text.attr6Buf.read { it.text.attr6Buf.read {
discardExact(7) discardExact(7)

View File

@ -27,14 +27,17 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA
import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.qqandroid.utils.io.useBytes import net.mamoe.mirai.qqandroid.utils.io.useBytes
import net.mamoe.mirai.qqandroid.utils.io.withUse import net.mamoe.mirai.qqandroid.utils.io.withUse
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey import net.mamoe.mirai.utils.io.toInt
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -171,9 +174,6 @@ internal object KnownPacketFactories {
?: IncomingFactories.firstOrNull { it.receivingCommandName == commandName } ?: IncomingFactories.firstOrNull { it.receivingCommandName == commandName }
} }
/**
* full packet without length
*/
// do not inline. Exceptions thrown will not be reported correctly // do not inline. Exceptions thrown will not be reported correctly
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -183,22 +183,12 @@ internal object KnownPacketFactories {
val flag1 = readInt() val flag1 = readInt()
PacketLogger.verbose { "开始处理一个包" } PacketLogger.verbose { "开始处理一个包" }
PacketLogger.verbose { "flag1(0A/0B) = ${flag1.toUByte().toUHexString()}" }
val flag2 = readByte().toInt() val flag2 = readByte().toInt()
PacketLogger.verbose {
"包类型(flag2) = $flag2. (可能是 ${when (flag2) {
2 -> "OicqRequest"
1 -> "Uni/ProtoBuf"
0 -> "Heartbeat"
else -> "未知"
}})"
}
val flag3 = readByte().toInt() val flag3 = readByte().toInt()
check(flag3 == 0) { check(flag3 == 0) {
"Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. Remaining=${this.readBytes() "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " +
.toUHexString()}" "Remaining=${this.readBytes().toUHexString()}"
} }
readString(readInt() - 4)// uinAccount readString(readInt() - 4)// uinAccount
@ -209,14 +199,11 @@ internal object KnownPacketFactories {
kotlin.runCatching { kotlin.runCatching {
when (flag2) { when (flag2) {
2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size) 2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size)
.also { PacketLogger.verbose { "成功使用 16 zero 解密" } }
1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size) 1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size)
.also { PacketLogger.verbose { "成功使用 d2Key 解密" } }
0 -> data 0 -> data
else -> error("") else -> error("")
} }
}.getOrElse { }.getOrElse {
PacketLogger.verbose { "失败, 尝试其他各种key" }
bot.client.tryDecryptOrNull(data, size) { it } bot.client.tryDecryptOrNull(data, size) { it }
}?.toReadPacket()?.let { decryptedData -> }?.toReadPacket()?.let { decryptedData ->
when (flag1) { when (flag1) {
@ -236,7 +223,7 @@ internal object KnownPacketFactories {
} else { } else {
handleIncomingPacket(it, bot, flag2, consumer) handleIncomingPacket(it, bot, flag2, consumer)
} }
} ?: inline { } ?: kotlin.run {
PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" } PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" }
return return
} }
@ -295,8 +282,6 @@ internal object KnownPacketFactories {
} }
} }
private inline fun <R> inline(block: () -> R): R = block()
class IncomingPacket<T : Packet?>( class IncomingPacket<T : Packet?>(
val packetFactory: PacketFactory<T>?, val packetFactory: PacketFactory<T>?,
val sequenceId: Int, val sequenceId: Int,
@ -358,9 +343,7 @@ internal object KnownPacketFactories {
} }
} }
} }
8 -> { 8 -> input
input
}
else -> error("unknown dataCompressed flag: $dataCompressed") else -> error("unknown dataCompressed flag: $dataCompressed")
} }

View File

@ -46,34 +46,30 @@ internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal fun MessageChain.calculateValidationDataForGroup( internal fun MessageChain.calculateValidationDataForGroup(
group: Group sequenceId: Int,
time: Int,
random: UInt,
groupCode: Long,
botMemberNameCard: String
): MessageValidationData { ): MessageValidationData {
// top_package.akkv#method_42702 val richTextElems = this.toRichTextElems(true, false)
val source: MessageSource by this.orElse { error("internal error: calculateValidationData: cannot find MessageSource, chain=${this._miraiContentToString()}") }
check(source is MessageSourceFromSendGroup || source is MessageSourceFromSendFriend) {
"internal error: calculateValidationData: MessageSource must be "
}
val richTextElems = this.toRichTextElems(source is MessageSourceFromSendGroup)
.filterNot { it.generalFlags != null }
val msgTransmit = MsgTransmit.PbMultiMsgTransmit( val msgTransmit = MsgTransmit.PbMultiMsgTransmit(
msg = listOf( msg = listOf(
MsgComm.Msg( MsgComm.Msg(
msgHead = MsgComm.MsgHead( msgHead = MsgComm.MsgHead(
fromUin = group.bot.uin, fromUin = 1040400290,
msgSeq = source.sequenceId, msgSeq = sequenceId,
msgTime = source.time.toInt(), msgTime = time,
msgUid = 0x01000000000000000L or source.messageRandom.toLong(), // TODO: 2020/3/26 CHECK IT msgUid = 0x01000000000000000L or random.toLong(),
mutiltransHead = MsgComm.MutilTransHead( mutiltransHead = MsgComm.MutilTransHead(
status = 0, status = 0,
msgId = 1 msgId = 1
), ),
msgType = 82, // troop msgType = 82, // troop
groupInfo = MsgComm.GroupInfo( groupInfo = MsgComm.GroupInfo(
groupCode = group.id, groupCode = groupCode,
groupCard = "Cinnamon"// group.botAsMember.nameCard, // Cinnamon groupCard = botMemberNameCard, // Cinnamon
), ),
isSrcMsg = false isSrcMsg = false
), ),
@ -91,69 +87,6 @@ internal fun MessageChain.calculateValidationDataForGroup(
return MessageValidationData(MiraiPlatformUtils.gzip(bytes)) return MessageValidationData(MiraiPlatformUtils.gzip(bytes))
} }
/*
=======================处理客户端到服务器=======================
flag1=0x0000000B(11), flag2=1, sequenceId = 00 00 E0 90, flag3=00, // 解密 bodyouter by D2 key
Packet 20:02:51 : ByteReadPacket outer body decrypted=00 00 00 28 00 00 00 18 4D 65 73 73 61 67 65 53 76 63 2E 50 62 53 65 6E 64 4D 73 67 00 00 00 08 8A 51 B1 25 00 00 00 04 00 00 02 7A 0A 08 12 06 08 F6 DD 96 FC 03 12 07 08 01 10 00 18 F2 46 1A D5 04 0A D2 04 12 A9 03 62 A6 03 0A A1 03 01 78 9C 7D 91 4B 4F DB 40 10 C7 BF CA 6A 2F 3E 81 1F 21 0D 91 6C 23 F1 48 15 CA A3 28 04 89 5C AA C5 1E 9B 15 6B 3B F5 AE 97 38 27 E8 05 04 5C B9 21 71 A1 95 B8 D0 1E 2A 24 54 F5 CB 20 25 F0 31 18 1B AA DE 90 46 AB 9D 9D D9 F9 CF 6F C6 5D 18 25 82 68 C8 25 CF 52 CF B0 67 2D 83 40 1A 64 21 4F 63 CF E8 6F 77 66 E6 0D 22 15 4B 43 26 B2 14 3C A3 04 69 90 05 DF 4D 64 4C 24 E4 9A 07 D0 5D F6 68 A3 49 89 82 64 28 98 AA 7D 9B 12 16 A8 AA 26 D5 1C 0E D7 0B A1 F8 BA 8C 29 D9 CB 39 44 18 77 1A 68 8E 8D 16 49 A9 A5 0E AB 83 E9 10 4D 6B 4A 92 2F 39 48 1E 7A 74 73 BB BD B1 16 7C B4 07 65 B9 37 E2 6D 4B 1F 9A 81 B5 3B B6 16 57 F9 4A F0 69 D5 71 3E 2C 2D F6 0F 76 3A 5D 25 C7 73 5B 03 D9 1F 8D 8B AF 83 9E 39 6A 44 9F 75 6F 4D 55 A5 22 2E 60 83 25 80 B2 CD F9 A6 DD 68 B5 5A 4E DB B6 28 91 59 91 07 80 7D 75 51 09 FD 22 17 1E A5 24 12 2C 46 24 44 08 71 32 3D 1E A7 75 34 79 83 E8 D4 61 9B FA 2E 47 64 22 58 99 15 EA F5 41 71 25 C0 7F 1F EE F1 E8 87 6B BE 26 BA FB 39 D9 E7 61 08 28 10 31 21 01 5B 52 A5 80 5A CE F4 5D 59 24 09 CB 4B FF E9 DB C3 E4 E4 CF F4 FA FB D3 D5 F9 E4 EE 62 7A F9 7B 7A 7F 3A 3D FE E9 9A FF 32 5C B3 EA 05 7F D4 44 24 AD 69 9F 8F CF 26 37 B7 CF 77 BF 26 7F 2F 29 E1 41 B5 8D FF 7B C1 DB 70 58 8D 78 C6 AE C5 4C 5C A9 FF 02 0F 69 BA A1 10 23 12 4D 0A 4B 0A 49 E4 BD A0 E7 9A 84 51 51 E6 9A 82 E4 B8 8D E6 94 AF E6 8C 81 E6 9F A5 E7 9C 8B 5B E8 BD AC E5 8F 91 E5 A4 9A E6 9D A1 E6 B6 88 E6 81 AF 5D EF BC 8C E8 AF B7 E6 9C 9F E5 BE 85 E5 90 8E E7 BB AD E7 89 88 E6 9C AC E3 80 82 12 55 AA 02 52 30 01 3A 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 92 3F 28 E9 9B 97 86 04 40 00
// 尝试解 Uni
// head
Packet Debug 20:02:51 : head=00 00 00 18 4D 65 73 73 61 67 65 53 76 63 2E 50 62 53 65 6E 64 4D 73 67 00 00 00 08 8A 51 B1 25 00 00 00 04
Packet 20:02:51 : commandName=MessageSvc.PbSendMsg
MessageSvc.PbSendMsg
unknown4Bytes=8A 51 B1 25
extraData=
Packet Debug 20:02:51 : Real body=0A 08 12 06 08 F6 DD 96 FC 03 12 07 08 01 10 00 18 F2 46 1A D5 04 0A D2 04 12 A9 03 62 A6 03 0A A1 03 01 78 9C 7D 91 4B 4F DB 40 10 C7 BF CA 6A 2F 3E 81 1F 21 0D 91 6C 23 F1 48 15 CA A3 28 04 89 5C AA C5 1E 9B 15 6B 3B F5 AE 97 38 27 E8 05 04 5C B9 21 71 A1 95 B8 D0 1E 2A 24 54 F5 CB 20 25 F0 31 18 1B AA DE 90 46 AB 9D 9D D9 F9 CF 6F C6 5D 18 25 82 68 C8 25 CF 52 CF B0 67 2D 83 40 1A 64 21 4F 63 CF E8 6F 77 66 E6 0D 22 15 4B 43 26 B2 14 3C A3 04 69 90 05 DF 4D 64 4C 24 E4 9A 07 D0 5D F6 68 A3 49 89 82 64 28 98 AA 7D 9B 12 16 A8 AA 26 D5 1C 0E D7 0B A1 F8 BA 8C 29 D9 CB 39 44 18 77 1A 68 8E 8D 16 49 A9 A5 0E AB 83 E9 10 4D 6B 4A 92 2F 39 48 1E 7A 74 73 BB BD B1 16 7C B4 07 65 B9 37 E2 6D 4B 1F 9A 81 B5 3B B6 16 57 F9 4A F0 69 D5 71 3E 2C 2D F6 0F 76 3A 5D 25 C7 73 5B 03 D9 1F 8D 8B AF 83 9E 39 6A 44 9F 75 6F 4D 55 A5 22 2E 60 83 25 80 B2 CD F9 A6 DD 68 B5 5A 4E DB B6 28 91 59 91 07 80 7D 75 51 09 FD 22 17 1E A5 24 12 2C 46 24 44 08 71 32 3D 1E A7 75 34 79 83 E8 D4 61 9B FA 2E 47 64 22 58 99 15 EA F5 41 71 25 C0 7F 1F EE F1 E8 87 6B BE 26 BA FB 39 D9 E7 61 08 28 10 31 21 01 5B 52 A5 80 5A CE F4 5D 59 24 09 CB 4B FF E9 DB C3 E4 E4 CF F4 FA FB D3 D5 F9 E4 EE 62 7A F9 7B 7A 7F 3A 3D FE E9 9A FF 32 5C B3 EA 05 7F D4 44 24 AD 69 9F 8F CF 26 37 B7 CF 77 BF 26 7F 2F 29 E1 41 B5 8D FF 7B C1 DB 70 58 8D 78 C6 AE C5 4C 5C A9 FF 02 0F 69 BA A1 10 23 12 4D 0A 4B 0A 49 E4 BD A0 E7 9A 84 51 51 E6 9A 82 E4 B8 8D E6 94 AF E6 8C 81 E6 9F A5 E7 9C 8B 5B E8 BD AC E5 8F 91 E5 A4 9A E6 9D A1 E6 B6 88 E6 81 AF 5D EF BC 8C E8 AF B7 E6 9C 9F E5 BE 85 E5 90 8E E7 BB AD E7 89 88 E6 9C AC E3 80 82 12 55 AA 02 52 30 01 3A 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 92 3F 28 E9 9B 97 86 04 40 00
Packet 20:02:51 : ByteReadPacket uni packet=
Packet 20:02:51 : =======================共有 1 个包=======================
=======================处理服务器到客户端客户端=======================
=======================处理服务器到客户端客户端=======================
Packet 20:02:51 : ByteReadPacket 正在处理=00 00 00 0B 01 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 0B 1C 26 01 C3 F8 46 01 ED 8D 1E C8 86 C1 62 89 9C F4 16 57 67 99 4F 39 E7 69 4F 74 33 6E 92 89 74 49 09 84 19 10 6F 3C 81 DA 0C 92 DD 04 B7 60 C9 DB C8 4F F8 60 57 A2 3F CF 95 5F 01 F8 0A 79 E7 28 B9 6A F6 AD 0A 71 BA 54 F8 8C DF AF CF D3
Packet 20:02:51 : flag1(0A/0B) = 0B
Packet 20:02:51 : 包类型(flag2) = 1. (可能是 uni)
Packet 20:02:51 : 成功使用 d2Key 解密
Packet 20:02:51 : ByteReadPacket sso/uni body==00 00 00 34 00 00 E0 90 00 00 00 00 00 00 00 04 00 00 00 18 4D 65 73 73 61 67 65 53 76 63 2E 50 62 53 65 6E 64 4D 73 67 00 00 00 08 8A 51 B1 25 00 00 00 00 00 00 00 0C 08 00 18 EA 90 ED F3 05
Packet 20:02:51 : sequenceId = 57488
Packet 20:02:51 : sso(inner)extraData =
Packet Debug 20:02:51 : commandName=MessageSvc.PbSendMsg
Packet 20:02:51 : 不是oicq response(可能是 UNI/PB)= 00 00 00 0C 08 00 18 EA 90 ED F3 05
Packet 20:02:51 : =======================共有 0 个包=======================
*/
/*
=======================处理客户端到服务器=======================
flag1=0x0000000B(11), flag2=1, sequenceId = 00 00 E0 8D, flag3=00, // 解密 bodyouter by D2 key
Packet 20:02:50 : ByteReadPacket outer body decrypted=00 00 00 24 00 00 00 14 4D 75 6C 74 69 4D 73 67 2E 41 70 70 6C 79 55 70 00 00 00 08 8A 51 B1 25 00 00 00 04 00 00 00 3B 08 01 10 05 18 09 20 03 2A 0A 38 2E 32 2E 30 2E 31 32 39 36 32 1F 08 F6 DD 96 FC 03 10 CF 05 1A 10 BB 45 B9 71 2C F4 D3 06 5D A7 A7 A2 FF D4 62 D2 20 03 28 00 40 01
// 尝试解 Uni
// head
Packet Debug 20:02:50 : head=00 00 00 14 4D 75 6C 74 69 4D 73 67 2E 41 70 70 6C 79 55 70 00 00 00 08 8A 51 B1 25 00 00 00 04
Packet 20:02:50 : commandName=MultiMsg.ApplyUp
MultiMsg.ApplyUp
unknown4Bytes=8A 51 B1 25
extraData=
Packet Debug 20:02:50 : Real body=08 01 10 05 18 09 20 03 2A 0A 38 2E 32 2E 30 2E 31 32 39 36 32 1F 08 F6 DD 96 FC 03 10 CF 05 1A 10 BB 45 B9 71 2C F4 D3 06 5D A7 A7 A2 FF D4 62 D2 20 03 28 00 40 01
Packet 20:02:50 : ByteReadPacket uni packet=
Packet 20:02:50 : =======================共有 1 个包=======================
=======================处理服务器到客户端客户端=======================
Packet 20:02:50 : ByteReadPacket 正在处理=00 00 00 0B 01 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 8A B2 8A B1 DA C9 60 28 D8 55 AB 39 B9 07 A6 D8 BA F2 55 87 C2 C9 29 08 53 CC AF 99 3F 22 26 1F 66 01 09 60 F2 2A 3C F1 A4 DC 74 5A 27 1C 47 E2 F0 7E 57 0C 9B 50 7D 0D 52 A3 17 BB B7 8D 9B 62 3A B3 E2 65 6D 7C 74 24 79 11 A5 23 78 83 63 35 8C C9 34 4A D9 CD 61 4D 0D 73 74 DF 49 F3 AD 65 2D 1A 87 14 2F 03 5F 0B 16 1F 87 CE 2A 53 3E 9F 8F CF 0F B8 C3 6B E1 6C 42 46 0D 59 F2 89 7E 8A 47 A8 CC 52 C0 E7 5C E4 CD 00 A0 00 61 FA AF 95 C1 C4 1B 8C C3 24 48 A5 4D 4F D7 59 38 F1 AE 4A 3B 18 7E 52 96 D5 2D 5D 67 D0 B8 0C BC F0 FD 3E 45 2C 7F 2E 1B AC FF F1 86 04 9B 8E 16 DF 7F C0 1C 25 13 36 21 D8 87 B1 FA BA 6E D2 DA E3 02 D2 31 45 9D 61 D4 43 07 F6 B5 D3 B0 6D 72 8B 83 FA B5 90 A7 BA 7A 32 2C 28 96 67 AC AB 42 37 EF 51 5B A1 A8 2D 17 93 F9 2C 22 51 6C 49 0A ED 38 AF 88 A1 E4 C7 09 BC DA 11 3F 46 DF D3 60 51 0E 92 89 56 D6 0D B4 66 DC 74 77 64 42 95 56 BE 89 61 75 CB F7 8C 33 D4 6B 40 4F 07 43 5B D9 A4 38 E1 DC 2A 0D 4D D6 8D 2B F5 E4 A2 45 3D EF 77 E5 24 F5 09 5E 1C 9C 14 CA 33 4D 3D 63 83 2E 38 94 13 1D 7A 0D 62 DB 89 0D 27 8D E2 58 5D 24 25 BC 9F D3 E3 3A 55 F2 FB 93 69 61 F0 25 E6 7F 7F B6 25 87 33 5B 5F 35 C1 E0 C4 6E 25 41 A0 12 B5 E6 DA 1A C9 F4 20 31 86 D3 B2 C9 D3 2D 96 40 92 BC BD 38 AD D6 94 E9 25 14 12 2D B6 32 6E D5 37 7D C6 E3 A8 E5 1E AD 97 52 FA DD CC 7E 96 5A E0 CB AF 79 4B CB BC E3 9F 57 4C 94 C7 9D 58 83 D0 11 41 BD E6 9C E1 98 7B BB 5B
Packet 20:02:50 : flag1(0A/0B) = 0B
Packet 20:02:50 : 包类型(flag2) = 1. (可能是 uni)
Packet 20:02:50 : 成功使用 d2Key 解密
Packet 20:02:50 : ByteReadPacket sso/uni body==00 00 00 30 00 00 E0 8D 00 00 00 00 00 00 00 04 00 00 00 14 4D 75 6C 74 69 4D 73 67 2E 41 70 70 6C 79 55 70 00 00 00 08 8A 51 B1 25 00 00 00 00 00 00 01 88 08 01 12 FF 02 08 00 12 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 1A 98 01 1B 76 62 FB B2 C6 24 C3 1F 39 47 0D 45 5C 77 BD 0C 8F 69 FB C8 4F D8 76 83 26 60 EA A3 24 BC FD F6 C8 B4 64 DA 47 9D 6C 1A FA F4 EF 02 FC A4 76 1F 87 EB FF 51 62 20 E9 1F 74 6B 2F 7B 7C 53 EC 6D A2 53 AC 2B 93 B4 79 83 6D E6 D8 86 E1 D5 E2 4D EE 75 03 A3 3B 72 EB 0A 3E 13 3A 80 70 EF CC B4 0D F9 42 E3 DF 5F 7A 4C 36 BC 3B 9C 31 5A B1 40 B4 5B 49 26 CE 65 BD 2F 86 8D 9D 0C 34 1B 5E 32 6E EF 60 4B E1 60 7F 1A 98 CF 14 42 85 A6 F8 BE A5 EE A7 A6 C7 9E 11 20 FB AE FA 95 0A 20 B7 87 A4 8F 0E 20 FB AE FA 9D 0A 20 E5 B6 95 B0 0A 28 50 28 90 3F 28 BB 03 28 50 40 00 4A 10 4E 64 43 67 6D 61 71 35 6D 52 73 43 53 38 41 58 52 68 AF 63 72 0B 4D 5B 17 6E D8 35 C1 D3 3F C8 D7 FC F0 A8 0A 67 4D B5 A6 B3 B7 E2 E1 9F 96 68 D3 BC AD 4A 6A 20 72 E8 D2 44 C3 8B 93 60 F3 3C 4B 46 83 E4 75 A2 3C 72 A4 F7 31 D9 88 89 23 34 9A AF EF FC 17 29 5D 6C D0 2B F1 63 D5 9F E2 B9 B5 49 D2 62 E3 D0 F9 19 C5 0D 20 AF 78 D5 34 7E BB B7 E2 8E 5C 69 F4 38 38 E7
Packet 20:02:50 : sequenceId = 57485
Packet 20:02:50 : sso(inner)extraData =
Packet Debug 20:02:50 : commandName=MultiMsg.ApplyUp
找不到包 PacketFactory
Packet 20:02:50 : 传递给 PacketFactory 的数据 = 00 00 01 88 08 01 12 FF 02 08 00 12 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 1A 98 01 1B 76 62 FB B2 C6 24 C3 1F 39 47 0D 45 5C 77 BD 0C 8F 69 FB C8 4F D8 76 83 26 60 EA A3 24 BC FD F6 C8 B4 64 DA 47 9D 6C 1A FA F4 EF 02 FC A4 76 1F 87 EB FF 51 62 20 E9 1F 74 6B 2F 7B 7C 53 EC 6D A2 53 AC 2B 93 B4 79 83 6D E6 D8 86 E1 D5 E2 4D EE 75 03 A3 3B 72 EB 0A 3E 13 3A 80 70 EF CC B4 0D F9 42 E3 DF 5F 7A 4C 36 BC 3B 9C 31 5A B1 40 B4 5B 49 26 CE 65 BD 2F 86 8D 9D 0C 34 1B 5E 32 6E EF 60 4B E1 60 7F 1A 98 CF 14 42 85 A6 F8 BE A5 EE A7 A6 C7 9E 11 20 FB AE FA 95 0A 20 B7 87 A4 8F 0E 20 FB AE FA 9D 0A 20 E5 B6 95 B0 0A 28 50 28 90 3F 28 BB 03 28 50 40 00 4A 10 4E 64 43 67 6D 61 71 35 6D 52 73 43 53 38 41 58 52 68 AF 63 72 0B 4D 5B 17 6E D8 35 C1 D3 3F C8 D7 FC F0 A8 0A 67 4D B5 A6 B3 B7 E2 E1 9F 96 68 D3 BC AD 4A 6A 20 72 E8 D2 44 C3 8B 93 60 F3 3C 4B 46 83 E4 75 A2 3C 72 A4 F7 31 D9 88 89 23 34 9A AF EF FC 17 29 5D 6C D0 2B F1 63 D5 9F E2 B9 B5 49 D2 62 E3 D0 F9 19 C5 0D 20 AF 78 D5 34 7E BB B7 E2 8E 5C 69 F4 38 38 E7
Packet 20:02:50 : 不是oicq response(可能是 UNI/PB)=
Packet 20:02:50 : =======================共有 0 个包=======================
*/
internal class MultiMsg { internal class MultiMsg {
object ApplyUp : OutgoingPacketFactory<ApplyUp.Response>("MultiMsg.ApplyUp") { object ApplyUp : OutgoingPacketFactory<ApplyUp.Response>("MultiMsg.ApplyUp") {
@ -188,9 +121,7 @@ internal class MultiMsg {
applyId = 0, applyId = 0,
dstUin = dstUin, dstUin = dstUin,
msgMd5 = messageData.md5, msgMd5 = messageData.md5,
msgSize = messageData.data.size.toLong().also { msgSize = messageData.data.size.toLong(),
println("data.size = $it")
},
msgType = 3 // TODO 3 for group? msgType = 3 // TODO 3 for group?
), ),
), ),
@ -203,23 +134,6 @@ internal class MultiMsg {
) )
} }
/*
RspBody#195600860 {
multimsgApplyupRsp=[MultiMsgApplyUpRsp#314337396 {
applyId=0x00000000(0)
blockSize=0x0000000000000000(0)
msgKey=4E 64 43 67 6D 61 71 35 6D 52 73 43 53 38 41 58
msgResid=4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74
msgSig=AF 63 72 0B 4D 5B 17 6E D8 35 C1 D3 3F C8 D7 FC F0 A8 0A 67 4D B5 A6 B3 B7 E2 E1 9F 96 68 D3 BC AD 4A 6A 20 72 E8 D2 44 C3 8B 93 60 F3 3C 4B 46 83 E4 75 A2 3C 72 A4 F7 31 D9 88 89 23 34 9A AF EF FC 17 29 5D 6C D0 2B F1 63 D5 9F E2 B9 B5 49 D2 62 E3 D0 F9 19 C5 0D 20 AF 78 D5 34 7E BB B7 E2 8E 5C 69 F4 38 38 E7
msgUkey=1B 76 62 FB B2 C6 24 C3 1F 39 47 0D 45 5C 77 BD 0C 8F 69 FB C8 4F D8 76 83 26 60 EA A3 24 BC FD F6 C8 B4 64 DA 47 9D 6C 1A FA F4 EF 02 FC A4 76 1F 87 EB FF 51 62 20 E9 1F 74 6B 2F 7B 7C 53 EC 6D A2 53 AC 2B 93 B4 79 83 6D E6 D8 86 E1 D5 E2 4D EE 75 03 A3 3B 72 EB 0A 3E 13 3A 80 70 EF CC B4 0D F9 42 E3 DF 5F 7A 4C 36 BC 3B 9C 31 5A B1 40 B4 5B 49 26 CE 65 BD 2F 86 8D 9D 0C 34 1B 5E 32 6E EF 60 4B E1 60 7F 1A 98 CF 14 42 85 A6 F8 BE A5 EE A7 A6 C7 9E 11
result=0x00000000(0)
uint32UpIp=[0xA2BE977B(-1564567685), 0xE1E903B7(-504822857), 0xA3BE977B(-1547790469), 0xA6055B65(-1509598363)]
uint32UpPort=[0x00000050(80), 0x00001F90(8080), 0x000001BB(443), 0x00000050(80)]
upOffset=0x0000000000000000(0)
}]
subcmd=0x00000001(1)
}
*/
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val body = readProtoBuf(MultiMsg.RspBody.serializer()) val body = readProtoBuf(MultiMsg.RspBody.serializer())
val response = body.multimsgApplyupRsp!!.first() val response = body.multimsgApplyupRsp!!.first()

View File

@ -11,6 +11,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.atomicfu.loop
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
@ -19,16 +20,15 @@ import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsQQImpl
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
@ -37,6 +37,8 @@ import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup
import net.mamoe.mirai.qqandroid.message.toMessageChain import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.message.toRichTextElems import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
@ -195,9 +197,8 @@ internal class MessageSvc {
bot.groups.delegate.addLast(newGroup) bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup) return@mapNotNull BotJoinGroupEvent(newGroup)
} else { } else {
if (group == null) { group ?: return@mapNotNull null
return@mapNotNull null
}
if (group.members.contains(msg.msgHead.authUin)) { if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null return@mapNotNull null
} }
@ -207,21 +208,30 @@ internal class MessageSvc {
override val specialTitle: String get() = "" override val specialTitle: String get() = ""
override val muteTimestamp: Int get() = 0 override val muteTimestamp: Int get() = 0
override val uin: Long get() = msg.msgHead.authUin override val uin: Long get() = msg.msgHead.authUin
override val nick: String override val nick: String = msg.msgHead.authNick.takeIf { it.isNotEmpty() }
get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromNick
?: msg.msgHead.fromNick
}).also { group.members.delegate.addLast(it) }) }).also { group.members.delegate.addLast(it) })
} }
} }
166 -> { 166 -> {
val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null
return@mapNotNull when { friend.checkIsQQImpl()
msg.msgHead.fromUin == bot.uin -> null
!bot.firstLoginSucceed -> null if (msg.msgHead.fromUin == bot.uin || !bot.firstLoginSucceed) {
else -> FriendMessage( return@mapNotNull null
friend, }
msg.toMessageChain()
) friend.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
println("bigger")
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
println("set ok")
return@mapNotNull FriendMessage(
friend,
msg.toMessageChain()
)
}
} else return@mapNotNull null
} }
} }
else -> return@mapNotNull null else -> return@mapNotNull null
@ -319,7 +329,7 @@ internal class MessageSvc {
contentHead = MsgComm.ContentHead(pkgNum = 1), contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody( msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText( richText = ImMsgBody.RichText(
elems = message.toRichTextElems(false) elems = message.toRichTextElems(false, true)
) )
), ),
msgSeq = source.sequenceId, msgSeq = source.sequenceId,
@ -372,7 +382,7 @@ internal class MessageSvc {
contentHead = MsgComm.ContentHead(pkgNum = 1), contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody( msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText( richText = ImMsgBody.RichText(
elems = message.toRichTextElems(true) elems = message.toRichTextElems(true, true)
) )
), ),
msgSeq = client.atomicNextMessageSequenceId(), msgSeq = client.atomicNextMessageSequenceId(),

View File

@ -21,7 +21,7 @@ expect interface ECDHPublicKey {
internal expect class ECDHKeyPairImpl : ECDHKeyPair internal expect class ECDHKeyPairImpl : ECDHKeyPair
interface ECDHKeyPair { internal interface ECDHKeyPair {
val privateKey: ECDHPrivateKey val privateKey: ECDHPrivateKey
val publicKey: ECDHPublicKey val publicKey: ECDHPublicKey
@ -43,7 +43,7 @@ interface ECDHKeyPair {
/** /**
* 椭圆曲线密码, ECDH 加密 * 椭圆曲线密码, ECDH 加密
*/ */
expect class ECDH(keyPair: ECDHKeyPair) { internal expect class ECDH(keyPair: ECDHKeyPair) {
val keyPair: ECDHKeyPair val keyPair: ECDHKeyPair
/** /**
@ -74,9 +74,9 @@ expect class ECDH(keyPair: ECDHKeyPair) {
} }
@Suppress("FunctionName") @Suppress("FunctionName")
expect fun ECDH(): ECDH internal expect fun ECDH(): ECDH
val initialPublicKey internal val initialPublicKey
get() = ECDH.constructPublicKey("3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8".chunkedHexToBytes()) get() = ECDH.constructPublicKey("3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8".chunkedHexToBytes())
private val commonHeadFor02 = "302E301006072A8648CE3D020106052B8104001F031A00".chunkedHexToBytes() private val commonHeadFor02 = "302E301006072A8648CE3D020106052B8104001F031A00".chunkedHexToBytes()
private val commonHeadForNot02 = "3046301006072A8648CE3D020106052B8104001F033200".chunkedHexToBytes() private val commonHeadForNot02 = "3046301006072A8648CE3D020106052B8104001F033200".chunkedHexToBytes()
@ -86,7 +86,7 @@ private val byteArray_04 = byteArrayOf(0x04)
private val head1 = "302E301006072A8648CE3D020106052B8104001F031A00".chunkedHexToBytes() private val head1 = "302E301006072A8648CE3D020106052B8104001F031A00".chunkedHexToBytes()
private val head2 = "3046301006072A8648CE3D020106052B8104001F03320004".chunkedHexToBytes() private val head2 = "3046301006072A8648CE3D020106052B8104001F03320004".chunkedHexToBytes()
fun ByteArray.adjustToPublicKey(): ECDHPublicKey { internal fun ByteArray.adjustToPublicKey(): ECDHPublicKey {
val head = if (this.size < 30) head1 else head2 val head = if (this.size < 30) head1 else head2
return ECDH.constructPublicKey(head + this) return ECDH.constructPublicKey(head + this)

View File

@ -23,7 +23,7 @@ import kotlin.random.Random
/** /**
* 解密错误 * 解密错误
*/ */
class DecryptionFailedException : Exception { internal class DecryptionFailedException : Exception {
constructor() : super() constructor() : super()
constructor(message: String?) : super(message) constructor(message: String?) : super(message)
} }
@ -34,7 +34,7 @@ class DecryptionFailedException : Exception {
* **注意**: 此为 Mirai 内部 API. 它可能会在任何时刻被改变. * **注意**: 此为 Mirai 内部 API. 它可能会在任何时刻被改变.
*/ */
@MiraiInternalAPI @MiraiInternalAPI
object TEA { internal object TEA {
// TODO: 2020/2/28 使用 stream 式输入以避免缓存 // TODO: 2020/2/28 使用 stream 式输入以避免缓存
/** /**

View File

@ -20,7 +20,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLength: Int) { internal fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLength: Int) {
if (array.size <= maxLength) { if (array.size <= maxLength) {
writeShort(array.size.toShort()) writeShort(array.size.toShort())
writeFully(array) writeFully(array)
@ -32,13 +32,13 @@ fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLe
} }
} }
inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int { internal inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int {
this.writeShort(byteArray.size.toShort()) this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray) this.writeFully(byteArray)
return byteArray.size return byteArray.size
} }
inline fun BytePacketBuilder.writeIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit): Int = internal inline fun BytePacketBuilder.writeIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit): Int =
BytePacketBuilder().apply(builder).build().use { BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag) if (tag != null) writeUByte(tag)
val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL) val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL)
@ -47,7 +47,7 @@ inline fun BytePacketBuilder.writeIntLVPacket(tag: UByte? = null, lengthOffset:
return length.toInt() return length.toInt()
} }
inline fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit): Int = internal inline fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit): Int =
BytePacketBuilder().apply(builder).build().use { BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag) if (tag != null) writeUByte(tag)
val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL) val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL)
@ -56,18 +56,16 @@ inline fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset
return length.toInt() return length.toInt()
} }
inline fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray()) internal inline fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray())
fun BytePacketBuilder.writeHex(uHex: String) { internal fun BytePacketBuilder.writeHex(uHex: String) {
uHex.split(" ").forEach { uHex.split(" ").forEach {
if (it.isNotBlank()) { if (it.isNotBlank()) {
writeUByte(it.toUByte(16)) writeUByte(it.toUByte(16))
} }
} }
} }
/**
* 会使用 [ByteArrayPool] 缓存
*/
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = internal inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
TEA.encrypt(BytePacketBuilder().apply(encoder).build(), key) { decrypted -> writeFully(decrypted) } TEA.encrypt(BytePacketBuilder().apply(encoder).build(), key) { decrypted -> writeFully(decrypted) }

View File

@ -32,10 +32,10 @@ internal actual class ECDHKeyPairImpl(
} }
@Suppress("FunctionName") @Suppress("FunctionName")
actual fun ECDH() = internal actual fun ECDH() =
ECDH(ECDH.generateKeyPair()) ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object { actual companion object {
actual val isECDHAvailable: Boolean actual val isECDHAvailable: Boolean

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
@ -152,6 +153,7 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
* @see _lowLevelRecallFriendMessage 低级 API * @see _lowLevelRecallFriendMessage 低级 API
* @see _lowLevelRecallGroupMessage 低级 API * @see _lowLevelRecallGroupMessage 低级 API
*/ */
@ExperimentalMessageSource
@JvmSynthetic @JvmSynthetic
actual abstract suspend fun recall(source: MessageSource) actual abstract suspend fun recall(source: MessageSource)

View File

@ -5,6 +5,7 @@ import net.mamoe.mirai.contact.PermissionDeniedException
import net.mamoe.mirai.contact.recall import net.mamoe.mirai.contact.recall
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
@ -61,6 +62,7 @@ actual abstract class BotJavaFriendlyAPI actual constructor() {
* *
* @see Bot.recall (扩展函数) 接受参数 [MessageChain] * @see Bot.recall (扩展函数) 接受参数 [MessageChain]
*/ */
@ExperimentalMessageSource
@JvmName("recall") @JvmName("recall")
fun __recallBlockingForJava__(source: MessageSource) { fun __recallBlockingForJava__(source: MessageSource) {
runBlocking { recall(source) } runBlocking { recall(source) }
@ -88,6 +90,7 @@ actual abstract class BotJavaFriendlyAPI actual constructor() {
* @param millis 延迟的时间, 单位为毫秒 * @param millis 延迟的时间, 单位为毫秒
* @see recall * @see recall
*/ */
@ExperimentalMessageSource
@JvmName("recallIn") @JvmName("recallIn")
fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) { fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) {
runBlocking { recallIn(source, millis) } runBlocking { recallIn(source, millis) }
@ -148,6 +151,7 @@ actual abstract class BotJavaFriendlyAPI actual constructor() {
/** /**
* 异步调用 [__recallBlockingForJava__] * 异步调用 [__recallBlockingForJava__]
*/ */
@ExperimentalMessageSource
@JvmName("recallAsync") @JvmName("recallAsync")
fun __recallAsyncForJava__(source: MessageSource): Future<Unit> { fun __recallAsyncForJava__(source: MessageSource): Future<Unit> {
return future { recall(source) } return future { recall(source) }

View File

@ -54,6 +54,8 @@ actual abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI() {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -48,6 +48,8 @@ actual abstract class ContactJavaFriendlyAPI {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -125,6 +125,8 @@ actual abstract class Group : Contact(), CoroutineScope {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -110,6 +110,8 @@ actual abstract class Member : MemberJavaFriendlyAPI() {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -78,6 +78,8 @@ actual abstract class QQ : Contact(), CoroutineScope {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -29,7 +29,8 @@ import net.mamoe.mirai.utils.unsafeWeakRef
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
actual open class MessageReceipt<C : Contact> actual constructor( actual open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::class)
actual constructor(
actual val source: MessageSource, actual val source: MessageSource,
target: C, target: C,
private val botAsMember: Member? private val botAsMember: Member?
@ -56,6 +57,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
* @see Bot.recall * @see Bot.recall
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/ */
@OptIn(ExperimentalMessageSource::class)
actual suspend fun recall() { actual suspend fun recall() {
@Suppress("BooleanLiteralArgument") @Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) { if (_isRecalled.compareAndSet(false, true)) {
@ -82,7 +84,8 @@ actual open class MessageReceipt<C : Contact> actual constructor(
if (_isRecalled.compareAndSet(false, true)) { if (_isRecalled.compareAndSet(false, true)) {
return when (val contact = target) { return when (val contact = target) {
is QQ, is QQ,
is Group -> contact.bot.recallIn(source, millis) is Group,
-> contact.bot.recallIn(source, millis)
else -> error("Unknown contact type") else -> error("Unknown contact type")
} }
} else error("message is already or planned to be recalled") } else error("message is already or planned to be recalled")
@ -92,6 +95,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息. * [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
* @see MessageChain.quote 引用一条消息 * @see MessageChain.quote 引用一条消息
*/ */
@OptIn(ExperimentalMessageSource::class)
actual open suspend fun quote(): QuoteReplyToSend { actual open suspend fun quote(): QuoteReplyToSend {
this.source.ensureSequenceIdAvailable() this.source.ensureSequenceIdAvailable()
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
@ -105,6 +109,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
* *
* @see MessageChain.quote 引用一条消息 * @see MessageChain.quote 引用一条消息
*/ */
@OptIn(ExperimentalMessageSource::class)
@LowLevelAPI @LowLevelAPI
@Suppress("FunctionName") @Suppress("FunctionName")
actual fun _unsafeQuote(): QuoteReplyToSend { actual fun _unsafeQuote(): QuoteReplyToSend {

View File

@ -19,6 +19,7 @@ import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
@ -33,6 +34,7 @@ import kotlin.jvm.JvmSynthetic
/** /**
* 登录, 返回 [this] * 登录, 返回 [this]
*/ */
@JvmSynthetic
suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() } suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
// 任何人都能看到这个方法 // 任何人都能看到这个方法
@ -167,6 +169,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
* @see _lowLevelRecallFriendMessage 低级 API * @see _lowLevelRecallFriendMessage 低级 API
* @see _lowLevelRecallGroupMessage 低级 API * @see _lowLevelRecallGroupMessage 低级 API
*/ */
@ExperimentalMessageSource
@JvmSynthetic @JvmSynthetic
abstract suspend fun recall(source: MessageSource) abstract suspend fun recall(source: MessageSource)
@ -223,6 +226,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
* @throws PermissionDeniedException [Bot] 无权限操作时 * @throws PermissionDeniedException [Bot] 无权限操作时
* @see Bot.recall * @see Bot.recall
*/ */
@JvmSynthetic
suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[MessageSource]) suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[MessageSource])
/** /**
@ -233,6 +237,7 @@ suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[Messa
* @param coroutineContext 额外的 [CoroutineContext] * @param coroutineContext 额外的 [CoroutineContext]
* @see recall * @see recall
*/ */
@JvmSynthetic
inline fun Bot.recallIn( inline fun Bot.recallIn(
source: MessageSource, source: MessageSource,
millis: Long, millis: Long,
@ -249,6 +254,7 @@ inline fun Bot.recallIn(
* @param coroutineContext 额外的 [CoroutineContext] * @param coroutineContext 额外的 [CoroutineContext]
* @see recall * @see recall
*/ */
@JvmSynthetic
inline fun Bot.recallIn( inline fun Bot.recallIn(
message: MessageChain, message: MessageChain,
millis: Long, millis: Long,
@ -265,15 +271,20 @@ inline fun Bot.recallIn(
* *
* @param cause 原因. null 时视为正常关闭, null 时视为异常关闭 * @param cause 原因. null 时视为正常关闭, null 时视为异常关闭
*/ */
@JvmSynthetic
suspend inline fun Bot.closeAndJoin(cause: Throwable? = null) { suspend inline fun Bot.closeAndJoin(cause: Throwable? = null) {
close(cause) close(cause)
coroutineContext[Job]?.join() coroutineContext[Job]?.join()
} }
@JvmSynthetic
inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id) inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id)
@JvmSynthetic
inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id) inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id)
@JvmSynthetic
inline fun Bot.getFriendOrNull(id: Long): QQ? = this.friends.getOrNull(id) inline fun Bot.getFriendOrNull(id: Long): QQ? = this.friends.getOrNull(id)
@JvmSynthetic
inline fun Bot.getGroupOrNull(id: Long): Group? = this.groups.getOrNull(id) inline fun Bot.getGroupOrNull(id: Long): Group? = this.groups.getOrNull(id)

View File

@ -57,6 +57,8 @@ expect abstract class Contact() : CoroutineScope, ContactJavaFriendlyAPI {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -129,6 +129,8 @@ expect abstract class Group() : Contact, CoroutineScope {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -132,6 +132,8 @@ expect abstract class Member() : MemberJavaFriendlyAPI {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -87,6 +87,8 @@ expect abstract class QQ() : Contact, CoroutineScope {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -13,6 +13,7 @@ import kotlinx.coroutines.Job
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.* import net.mamoe.mirai.data.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
@ -137,6 +138,7 @@ interface LowLevelBotAPIAccessor {
/** /**
* 获取群活跃信息 * 获取群活跃信息
*/ */
@SinceMirai("0.29.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData
@ -147,7 +149,7 @@ interface LowLevelBotAPIAccessor {
@SinceMirai("0.31.0") @SinceMirai("0.31.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) suspend fun _lowLevelSendLongGroupMessage(groupCode: Long, message: Message): MessageReceipt<Group>
} }
/** /**

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.message package net.mamoe.mirai.message
@ -153,6 +153,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
/** /**
* 引用这个消息 * 引用这个消息
*/ */
@ExperimentalMessageSource
inline fun MessageChain.quote(): QuoteReplyToSend = this.quote(sender) inline fun MessageChain.quote(): QuoteReplyToSend = this.quote(sender)
operator fun <M : Message> get(at: Message.Key<M>): M { operator fun <M : Message> get(at: Message.Key<M>): M {

View File

@ -15,12 +15,16 @@ import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
import kotlin.jvm.JvmSynthetic
/** /**
* 发送消息后得到的回执. 可用于撤回. * 发送消息后得到的回执. 可用于撤回.
* *
* 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问. * 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问.
* *
* @param source 指代发送出去的消息
* @param target 消息发送对象
*
* @see Group.sendMessage 发送群消息, 返回回执此对象 * @see Group.sendMessage 发送群消息, 返回回执此对象
* @see QQ.sendMessage 发送群消息, 返回回执此对象 * @see QQ.sendMessage 发送群消息, 返回回执此对象
* *
@ -28,11 +32,15 @@ import net.mamoe.mirai.recallIn
* @see MessageReceipt.sourceSequenceId 源序列号 * @see MessageReceipt.sourceSequenceId 源序列号
* @see MessageReceipt.sourceTime 源时间 * @see MessageReceipt.sourceTime 源时间
*/ */
expect open class MessageReceipt<C : Contact>( expect open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::class) constructor(
source: MessageSource, source: MessageSource,
target: C, target: C,
botAsMember: Member? botAsMember: Member?
) { ) {
/**
* 指代发送出去的消息
*/
@ExperimentalMessageSource
val source: MessageSource val source: MessageSource
/** /**
@ -90,6 +98,8 @@ expect open class MessageReceipt<C : Contact>(
* *
* @see MessageSource.id * @see MessageSource.id
*/ */
@get:JvmSynthetic
@ExperimentalMessageSource
inline val MessageReceipt<*>.sourceId: Long get() = this.source.id inline val MessageReceipt<*>.sourceId: Long get() = this.source.id
/** /**
@ -97,6 +107,8 @@ inline val MessageReceipt<*>.sourceId: Long get() = this.source.id
* *
* @see MessageSource.sequenceId * @see MessageSource.sequenceId
*/ */
@get:JvmSynthetic
@ExperimentalMessageSource
inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceId inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceId
/** /**
@ -104,6 +116,8 @@ inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceI
* *
* @see MessageSource.time * @see MessageSource.time
*/ */
@get:JvmSynthetic
@ExperimentalMessageSource
inline val MessageReceipt<*>.sourceTime: Long get() = this.source.time inline val MessageReceipt<*>.sourceTime: Long get() = this.source.time
suspend inline fun MessageReceipt<out Contact>.quoteReply(message: Message) { suspend inline fun MessageReceipt<out Contact>.quoteReply(message: Message) {

View File

@ -37,7 +37,7 @@ interface Image : Message, MessageContent {
* 图片的 id. 只需要有这个 id 即可发送图片. * 图片的 id. 只需要有这个 id 即可发送图片.
* *
* 示例: * 示例:
* 好友图片的 id: `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * 好友图片的 id: `/f8f1ab55-bf8e-4236-b55e-955848d7069f` `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206`
* 群图片的 id: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png` * 群图片的 id: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png`
*/ */
val imageId: String val imageId: String
@ -53,7 +53,7 @@ interface Image : Message, MessageContent {
@JsName("newImage") @JsName("newImage")
@JvmName("newImage") @JvmName("newImage")
fun Image(imageId: String): Image = when (imageId.length) { fun Image(imageId: String): Image = when (imageId.length) {
37 -> OfflineFriendImage(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f 54, 37 -> OfflineFriendImage(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f
42 -> OfflineGroupImage(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png 42 -> OfflineGroupImage(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png
else -> throw IllegalArgumentException("Bad imageId, expecting length=37 or 42, got ${imageId.length}") else -> throw IllegalArgumentException("Bad imageId, expecting length=37 or 42, got ${imageId.length}")
} }
@ -211,7 +211,7 @@ abstract class OnlineGroupImage : GroupImage(), OnlineImage
/** /**
* 好友图片 * 好友图片
* *
* [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度)
*/ // NotOnlineImage */ // NotOnlineImage
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
sealed class FriendImage : AbstractImage() { sealed class FriendImage : AbstractImage() {

View File

@ -127,6 +127,7 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@OptIn(ExperimentalMessageSource::class)
@JvmSynthetic @JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) { fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
@ -415,7 +416,10 @@ internal class MessageChainImplByIterable constructor(
) : Message, Iterable<SingleMessage>, MessageChain { ) : Message, Iterable<SingleMessage>, MessageChain {
override val size: Int by lazy { delegate.count() } override val size: Int by lazy { delegate.count() }
override fun iterator(): Iterator<SingleMessage> = delegate.iterator() override fun iterator(): Iterator<SingleMessage> = delegate.iterator()
override fun toString(): String = this.delegate.joinToString("") { it.toString() } var toStringTemp: String? = null
override fun toString(): String =
toStringTemp ?: this.delegate.joinToString("") { it.toString() }.also { toStringTemp = it }
override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) }
} }
@ -428,7 +432,10 @@ internal class MessageChainImplByCollection constructor(
) : Message, Iterable<SingleMessage>, MessageChain { ) : Message, Iterable<SingleMessage>, MessageChain {
override val size: Int get() = delegate.size override val size: Int get() = delegate.size
override fun iterator(): Iterator<SingleMessage> = delegate.iterator() override fun iterator(): Iterator<SingleMessage> = delegate.iterator()
override fun toString(): String = this.delegate.joinToString("") { it.toString() } var toStringTemp: String? = null
override fun toString(): String =
toStringTemp ?: this.delegate.joinToString("") { it.toString() }.also { toStringTemp = it }
override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) }
} }
@ -446,7 +453,10 @@ internal class MessageChainImplBySequence constructor(
*/ */
private val collected: List<SingleMessage> by lazy { delegate.toList() } private val collected: List<SingleMessage> by lazy { delegate.toList() }
override fun iterator(): Iterator<SingleMessage> = collected.iterator() override fun iterator(): Iterator<SingleMessage> = collected.iterator()
override fun toString(): String = this.collected.joinToString("") { it.toString() } var toStringTemp: String? = null
override fun toString(): String =
toStringTemp ?: this.collected.joinToString("") { it.toString() }.also { toStringTemp = it }
override operator fun contains(sub: String): Boolean = collected.any { it.contains(sub) } override operator fun contains(sub: String): Boolean = collected.any { it.contains(sub) }
} }

View File

@ -19,6 +19,12 @@ import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
/**
* MessageSource 正计划于 0.32 0.33 或之后进行 API 不兼容的重写.
*/
@RequiresOptIn(message = "MessageSource 正计划于 0.32 或 0.33 或之后进行 API 不兼容的重写", level = RequiresOptIn.Level.WARNING)
annotation class ExperimentalMessageSource
/** /**
* 消息源, 它存在于 [MessageChain] , 用于表示这个消息的来源. * 消息源, 它存在于 [MessageChain] , 用于表示这个消息的来源.
* *
@ -29,6 +35,7 @@ import kotlin.jvm.JvmSynthetic
* @see Bot.recall 撤回一条消息 * @see Bot.recall 撤回一条消息
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain] * @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
*/ */
@ExperimentalMessageSource
interface MessageSource : Message, MessageMetadata { interface MessageSource : Message, MessageMetadata {
companion object Key : Message.Key<MessageSource> companion object Key : Message.Key<MessageSource>
@ -82,6 +89,7 @@ interface MessageSource : Message, MessageMetadata {
* 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][MessageSource.ensureSequenceIdAvailable] * 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][MessageSource.ensureSequenceIdAvailable]
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageSource.sequenceId: Int inline val MessageSource.sequenceId: Int
get() = (this.id shr 32).toInt() get() = (this.id shr 32).toInt()
@ -90,6 +98,7 @@ inline val MessageSource.sequenceId: Int
* 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. * 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分.
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageSource.messageRandom: Int inline val MessageSource.messageRandom: Int
get() = this.id.toInt() get() = this.id.toInt()
@ -98,24 +107,33 @@ inline val MessageSource.messageRandom: Int
/** /**
* 消息 id. * 消息 id.
*
* 仅接收到的消息才可以获取这个 id.
*
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageChain.id: Long inline val MessageChain.id: Long
get() = this[MessageSource].id get() = this[MessageSource].id
/** /**
* 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一. * 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一.
*
* 仅接收到的消息才可以获取这个序列号.
*
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageChain.sequenceId: Int inline val MessageChain.sequenceId: Int
get() = this[MessageSource].sequenceId get() = this.getOrNull(MessageSource)?.sequenceId ?: error("Only MessageChain from server has sequenceId")
/** /**
* 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. * 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分.
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageChain.messageRandom: Int inline val MessageChain.messageRandom: Int
get() = this[MessageSource].messageRandom get() = this.getOrNull(MessageSource)?.messageRandom ?: error("Only MessageChain from server has sequenceId")

View File

@ -29,6 +29,7 @@ import kotlin.jvm.JvmName
* 总是使用 [quote] 来构造这个实例. * 总是使用 [quote] 来构造这个实例.
*/ */
open class QuoteReply open class QuoteReply
@OptIn(ExperimentalMessageSource::class)
@MiraiInternalAPI constructor(val source: MessageSource) : Message, MessageMetadata { @MiraiInternalAPI constructor(val source: MessageSource) : Message, MessageMetadata {
companion object Key : Message.Key<QuoteReply> companion object Key : Message.Key<QuoteReply>
@ -39,7 +40,7 @@ open class QuoteReply
* 用于发送的引用回复. * 用于发送的引用回复.
* 总是使用 [quote] 来构造实例. * 总是使用 [quote] 来构造实例.
*/ */
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, ExperimentalMessageSource::class)
sealed class QuoteReplyToSend sealed class QuoteReplyToSend
@MiraiInternalAPI constructor(source: MessageSource) : QuoteReply(source) { @MiraiInternalAPI constructor(source: MessageSource) : QuoteReply(source) {
class ToGroup(source: MessageSource, val sender: QQ) : QuoteReplyToSend(source) { class ToGroup(source: MessageSource, val sender: QQ) : QuoteReplyToSend(source) {
@ -53,6 +54,7 @@ sealed class QuoteReplyToSend
* 引用这条消息. * 引用这条消息.
* @see sender 消息发送人. * @see sender 消息发送人.
*/ */
@ExperimentalMessageSource
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
fun MessageChain.quote(sender: QQ?): QuoteReplyToSend { fun MessageChain.quote(sender: QQ?): QuoteReplyToSend {
this.firstOrNull<MessageSource>()?.let { this.firstOrNull<MessageSource>()?.let {
@ -65,6 +67,7 @@ fun MessageChain.quote(sender: QQ?): QuoteReplyToSend {
* 引用这条消息. * 引用这条消息.
* @see from 消息来源. 若是好友发送 * @see from 消息来源. 若是好友发送
*/ */
@ExperimentalMessageSource
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
fun MessageSource.quote(from: QQ?): QuoteReplyToSend { fun MessageSource.quote(from: QQ?): QuoteReplyToSend {
return if (this.groupId != 0L) { return if (this.groupId != 0L) {

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
@ -162,6 +163,7 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
* @see _lowLevelRecallFriendMessage 低级 API * @see _lowLevelRecallFriendMessage 低级 API
* @see _lowLevelRecallGroupMessage 低级 API * @see _lowLevelRecallGroupMessage 低级 API
*/ */
@ExperimentalMessageSource
@JvmSynthetic @JvmSynthetic
actual abstract suspend fun recall(source: MessageSource) actual abstract suspend fun recall(source: MessageSource)

View File

@ -5,6 +5,7 @@ import net.mamoe.mirai.contact.PermissionDeniedException
import net.mamoe.mirai.contact.recall import net.mamoe.mirai.contact.recall
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
@ -61,6 +62,7 @@ actual abstract class BotJavaFriendlyAPI actual constructor() {
* *
* @see Bot.recall (扩展函数) 接受参数 [MessageChain] * @see Bot.recall (扩展函数) 接受参数 [MessageChain]
*/ */
@ExperimentalMessageSource
@JvmName("recall") @JvmName("recall")
fun __recallBlockingForJava__(source: MessageSource) { fun __recallBlockingForJava__(source: MessageSource) {
runBlocking { recall(source) } runBlocking { recall(source) }
@ -88,6 +90,7 @@ actual abstract class BotJavaFriendlyAPI actual constructor() {
* @param millis 延迟的时间, 单位为毫秒 * @param millis 延迟的时间, 单位为毫秒
* @see recall * @see recall
*/ */
@ExperimentalMessageSource
@JvmName("recallIn") @JvmName("recallIn")
fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) { fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) {
runBlocking { recallIn(source, millis) } runBlocking { recallIn(source, millis) }
@ -148,6 +151,7 @@ actual abstract class BotJavaFriendlyAPI actual constructor() {
/** /**
* 异步调用 [__recallBlockingForJava__] * 异步调用 [__recallBlockingForJava__]
*/ */
@ExperimentalMessageSource
@JvmName("recallAsync") @JvmName("recallAsync")
fun __recallAsyncForJava__(source: MessageSource): Future<Unit> { fun __recallAsyncForJava__(source: MessageSource): Future<Unit> {
return future { recall(source) } return future { recall(source) }

View File

@ -53,6 +53,8 @@ actual abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI() {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -48,6 +48,8 @@ actual abstract class ContactJavaFriendlyAPI {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -127,6 +127,8 @@ actual abstract class Group : Contact(), CoroutineScope {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -118,6 +118,8 @@ actual abstract class Member : MemberJavaFriendlyAPI() {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -78,6 +78,8 @@ actual abstract class QQ : Contact(), CoroutineScope {
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
* *
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
* *

View File

@ -29,7 +29,8 @@ import net.mamoe.mirai.utils.unsafeWeakRef
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
actual open class MessageReceipt<C : Contact> actual constructor( actual open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::class)
actual constructor(
actual val source: MessageSource, actual val source: MessageSource,
target: C, target: C,
private val botAsMember: Member? private val botAsMember: Member?
@ -56,6 +57,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
* @see Bot.recall * @see Bot.recall
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/ */
@OptIn(ExperimentalMessageSource::class)
actual suspend fun recall() { actual suspend fun recall() {
@Suppress("BooleanLiteralArgument") @Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) { if (_isRecalled.compareAndSet(false, true)) {
@ -84,6 +86,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息. * [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
* @see MessageChain.quote 引用一条消息 * @see MessageChain.quote 引用一条消息
*/ */
@OptIn(ExperimentalMessageSource::class)
actual open suspend fun quote(): QuoteReplyToSend { actual open suspend fun quote(): QuoteReplyToSend {
this.source.ensureSequenceIdAvailable() this.source.ensureSequenceIdAvailable()
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
@ -97,6 +100,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
* *
* @see MessageChain.quote 引用一条消息 * @see MessageChain.quote 引用一条消息
*/ */
@OptIn(ExperimentalMessageSource::class)
@LowLevelAPI @LowLevelAPI
@Suppress("FunctionName") @Suppress("FunctionName")
actual fun _unsafeQuote(): QuoteReplyToSend { actual fun _unsafeQuote(): QuoteReplyToSend {

View File

@ -116,7 +116,7 @@ suspend fun InputStream.uploadAsImage(contact: Contact): OfflineImage =
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend fun File.uploadAsImage(contact: Contact): OfflineImage { suspend fun File.uploadAsImage(contact: Contact): OfflineImage {
require(this.exists() && this.canRead()) require(this.isFile && this.exists() && this.canRead()) { "file ${this.path} is not readable" }
return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
} }