mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-24 02:40:13 +08:00
Merge pull request #170 from mamoe/long-message
Support long message in general `sendMessage`
This commit is contained in:
commit
f7040c18fb
@ -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.
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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(),
|
||||||
|
@ -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)
|
||||||
|
@ -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 式输入以避免缓存
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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) }
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
|
@ -54,6 +54,8 @@ actual abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI() {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -48,6 +48,8 @@ actual abstract class ContactJavaFriendlyAPI {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -125,6 +125,8 @@ actual abstract class Group : Contact(), CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -110,6 +110,8 @@ actual abstract class Member : MemberJavaFriendlyAPI() {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -78,6 +78,8 @@ actual abstract class QQ : Contact(), CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
@ -57,6 +57,8 @@ expect abstract class Contact() : CoroutineScope, ContactJavaFriendlyAPI {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -129,6 +129,8 @@ expect abstract class Group() : Contact, CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -87,6 +87,8 @@ expect abstract class QQ() : Contact, CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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() {
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
|
@ -53,6 +53,8 @@ actual abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI() {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -48,6 +48,8 @@ actual abstract class ContactJavaFriendlyAPI {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -127,6 +127,8 @@ actual abstract class Group : Contact(), CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -118,6 +118,8 @@ actual abstract class Member : MemberJavaFriendlyAPI() {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -78,6 +78,8 @@ actual abstract class QQ : Contact(), CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
|
* 单条消息最大可发送 4500 字符或 50 张图片.
|
||||||
|
*
|
||||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||||
*
|
*
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user