C2C message send support

This commit is contained in:
Him188 2020-02-02 23:21:42 +08:00
parent 6ac9acc640
commit 5d46d1c4be
11 changed files with 134 additions and 83 deletions

View File

@ -3,8 +3,11 @@ package net.mamoe.mirai.qqandroid
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.MiraiInternalAPI
internal actual class QQAndroidBot actual constructor(
@UseExperimental(MiraiInternalAPI::class)
internal actual class QQAndroidBot
actual constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration

View File

@ -18,16 +18,15 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.QQImpl
import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.unsafeWeakRef
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.Volatile
@ -107,6 +106,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
override suspend fun init() {
// delay(5000)
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {

View File

@ -145,7 +145,7 @@ internal class MsgComm : ProtoBuf {
@SerialId(1) val lastReadTime: Int = 0,
@SerialId(2) val peerUin: Long = 0L,
@SerialId(3) val msgCompleted: Int = 0,
@SerialId(4) val msg: List<Msg>,
@SerialId(4) val msg: List<Msg>? = null,
@SerialId(5) val unreadMsgNum: Int = 0,
@SerialId(8) val c2cType: Int = 0,
@SerialId(9) val serviceType: Int = 0,

View File

@ -3,14 +3,15 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import kotlin.math.absoluteValue
import kotlin.random.Random
@Serializable
class SyncCookie(
@SerialId(1) val time1: Long? = null, // 1580277992
@SerialId(2) val time: Long, // 1580277992
@SerialId(3) val unknown1: Long = Random.nextLong(),// 678328038
@SerialId(4) val unknown2: Long = Random.nextLong(), // 1687142153
@SerialId(3) val unknown1: Long = Random.nextLong().absoluteValue,// 678328038
@SerialId(4) val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153
@SerialId(5) val const1: Long = const1_, // 1458467940
@SerialId(11) val const2: Long = const2_, // 2683038258
@SerialId(12) val unknown3: Long = 0x1d,
@ -18,8 +19,8 @@ class SyncCookie(
@SerialId(14) val unknown4: Long = 0
) : ProtoBuf
private val const1_: Long = Random.nextLong()
private val const2_: Long = Random.nextLong()
private val const1_: Long = Random.nextLong().absoluteValue
private val const2_: Long = Random.nextLong().absoluteValue
/*
@Serializable

View File

@ -4,6 +4,7 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot
@ -29,7 +30,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.math.absoluteValue
import kotlin.random.Random
@ -39,14 +39,14 @@ internal class MessageSvc {
*/
internal object PushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
discardExact(4)
discardExact(4) // don't remove
return decodeUniPacket(RequestPushNotify.serializer())
}
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
network.run {
return PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: 0)
return PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds)
}
}
}
@ -57,9 +57,6 @@ internal class MessageSvc {
*/
@UseExperimental(MiraiInternalAPI::class)
internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
val EXTRA_DATA =
"08 00 12 33 6D 6F 64 65 6C 3A 78 69 67 6F 6D 69 20 36 3B 6F 73 3A 32 32 3B 76 65 72 73 69 6F 6E 3A 76 32 6D 61 6E 3A 78 69 61 6F 6D 69 73 79 73 3A 4C 4D 59 34 38 5A 18 E4 E1 A4 FF FE 2D 20 E9 E1 A4 FF FE 2D 28 A8 E1 A4 FF FE 2D 30 99 E1 A4 FF FE 2D".hexToBytes()
operator fun invoke(
client: QQAndroidClient,
syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
@ -67,7 +64,7 @@ internal class MessageSvc {
): OutgoingPacket = buildOutgoingUniPacket(
client
) {
println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
//println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(),
MsgSvc.PbGetMsgReq(
@ -96,7 +93,11 @@ internal class MessageSvc {
* 不要直接 expect 这个 class. 它可能
*/
@MiraiInternalAPI
open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: MutableList<FriendMessage>) : MultiPacket<FriendMessage>(delegate) {
open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: MutableList<FriendMessage>) : MultiPacket<FriendMessage>(delegate),
BroadcastControllable {
override val shouldBroadcast: Boolean
get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP
override fun toString(): String {
return "MessageSvc.PbGetMsg.Response($syncFlagFromServer=$syncFlagFromServer, messages=List(size=${this.size}))"
}
@ -120,7 +121,7 @@ internal class MessageSvc {
return GetMsgSuccess(mutableListOf())
}
val messages = resp.uinPairMsgs.asSequence().flatMap { it.msg.asSequence() }.mapNotNull {
val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull {
when (it.msgHead.msgType) {
166 -> {
FriendMessage(
@ -134,7 +135,7 @@ internal class MessageSvc {
}
}.toMutableList()
if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
return GetMsgSuccess(messages)
return GetMsgSuccess(mutableListOf(messages.last()))
}
return Response(resp.syncFlag, messages)
}
@ -146,7 +147,7 @@ internal class MessageSvc {
MsgSvc.SyncFlag.CONTINUE -> {
network.run {
PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds)
PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendWithoutExpect()
}
return
}
@ -199,8 +200,7 @@ internal class MessageSvc {
),
msgSeq = client.atomicNextMessageSequenceId(),
msgRand = Random.nextInt().absoluteValue,
syncCookie = client.c2cMessageSync.syncCookie?.takeIf { it.isNotEmpty() }
?: SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
// msgVia = 1
)
)
@ -217,21 +217,22 @@ internal class MessageSvc {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
val seq = client.atomicNextMessageSequenceId()
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupId)), // TODO: 2020/1/30 确认这里是 id 还是 internalId
contentHead = MsgComm.ContentHead(pkgNum = 1),
contentHead = MsgComm.ContentHead(pkgNum = 1, divSeq = seq),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems()
)
),
msgSeq = client.atomicNextMessageSequenceId(),
//msgRand = Random.nextInt() and 0x7FFF,
syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
//SyncCookie(currentTimeSeconds, Random.nextLong().absoluteValue, Random.nextLong().absoluteValue).toByteArray(SyncCookie.serializer())
// msgVia = 1
msgSeq = seq,
msgRand = Random.nextInt().absoluteValue,
syncCookie = "08 A0 C2 C4 F1 05 10 A0 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 E4 C2 B1 95 03 48 A1 9F E0 C7 08 58 D3 C2 8F A0 09 60 1D 68 A0 C2 C4 F1 05 70 00".hexToBytes()
?: SyncCookie(time = currentTimeSeconds + client.timeDifference).toByteArray(SyncCookie.serializer()),
msgVia = 0
)
)
}

View File

@ -3,11 +3,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
@ -22,7 +21,7 @@ internal class OnlinePush {
@UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): GroupMessage {
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
val pbPushMsg = ProtoBufWithNullableSupport.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo

View File

@ -5,62 +5,123 @@ import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.utils.io.hexToBytes
internal fun NotOnlineImageFromFile.toJceData(): ImMsgBody.NotOnlineImage {
return ImMsgBody.NotOnlineImage(
filePath = this.filepath,
resId = this.resourceId,
oldPicMd5 = false,
picMd5 = this.md5,
fileLen = this.fileLength,
picHeight = this.height,
picWidth = this.width,
bizType = this.bizType,
imgType = this.imageType,
downloadPath = this.downloadPath
)
}
/*
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
}
*/
internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
val elems = mutableListOf<ImMsgBody.Elem>()
val elements = mutableListOf<ImMsgBody.Elem>()
this.forEach {
when (it) {
is PlainText -> {
elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
}
is At -> {
}
is Image -> {
elems.add(
ImMsgBody.Elem(
notOnlineImage = ImMsgBody.NotOnlineImage(
filePath = it.id.value, // 错了, 应该是 2B23D705CAD1F2CF3710FE582692FCC4.jpg
fileLen = 1149, // 假的
downloadPath = it.id.value,
imgType = 1000, // 不确定
picMd5 = "2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4".hexToBytes(),
picHeight = 66,
picWidth = 66,
resId = it.id.value,
bizType = 5,
pbReserve = ImMsgBody.PbReserve.DEFAULT // 可能还可以改变 `[动画表情]`
)
)
)
is NotOnlineImageFromServer -> {
elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes())))
}
is NotOnlineImageFromFile -> {
elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes())))
}
}
}
return elems
return elements
}
internal class NotOnlineImageFromServer(
internal val delegate: ImMsgBody.NotOnlineImage
) : NotOnlineImage() {
override val resourceId: String
get() = delegate.resId
override val md5: ByteArray
get() = delegate.picMd5
override val filepath: String
get() = delegate.filePath
override val fileLength: Int
get() = delegate.fileLen
override val height: Int
get() = delegate.picHeight
override val width: Int
get() = delegate.picWidth
override val bizType: Int
get() = delegate.bizType
override val imageType: Int
get() = delegate.imgType
override val downloadPath: String
get() = delegate.downloadPath
}
internal fun ImMsgBody.RichText.toMessageChain(): MessageChain {
val message = MessageChain(initialCapacity = elems.size)
elems.forEach {
when {
it.notOnlineImage != null -> message.add(
Image(
ImageIdQQA(
it.notOnlineImage.resId,
it.notOnlineImage.origUrl
)
)
NotOnlineImageFromServer(it.notOnlineImage)
)
it.customFace != null -> message.add(
Image(
ImageIdQQA(
it.customFace.filePath,
it.customFace.origUrl
)
NotOnlineImageFromFile(
it.customFace.filePath,
it.customFace.md5,
it.customFace.origUrl,
it.customFace.downloadLen,
it.customFace.height,
it.customFace.width,
it.customFace.bizType,
it.customFace.imageType,
it.customFace.filePath
)
)
it.text != null -> message.add(it.text.str.toMessage())
@ -70,12 +131,5 @@ internal fun ImMsgBody.RichText.toMessageChain(): MessageChain {
return message
}
internal class ImageIdQQA(
override val value: String,
originalLink: String
) : ImageId {
val link: ImageLink =
ImageLinkQQA("http://gchat.qpic.cn$originalLink")
}
internal inline class ImageLinkQQA(override val original: String) : ImageLink

View File

@ -8,7 +8,6 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.use
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.GroupNotFoundException
@ -104,11 +103,9 @@ abstract class Bot : CoroutineScope {
// region actions
abstract suspend fun Image.getLink(): ImageLink
abstract suspend fun Image.downloadAsByteArray(): ByteArray
suspend fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
suspend fun Image.download(): ByteReadPacket = getLink().download()
abstract suspend fun Image.download(): ByteReadPacket
/**
* 添加一个好友

View File

@ -36,7 +36,7 @@ interface Contact : CoroutineScope {
*/
suspend fun sendMessage(message: MessageChain)
suspend fun uploadImage(image: ExternalImage): ImageId
suspend fun uploadImage(image: ExternalImage): Image
}
suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain())

View File

@ -37,7 +37,7 @@ class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedLis
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
forEach { if (it.id == id) return it }
throw NoSuchElementException()
throw NoSuchElementException("No such contact with id $id")
}
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {

View File

@ -6,7 +6,6 @@ import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.EventPacket
import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.*
@ -65,17 +64,14 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
suspend inline fun Image.send() = this.sendTo(subject)
suspend inline fun ImageId.send() = this.sendTo(subject)
suspend inline fun Message.send() = this.sendTo(subject)
suspend inline fun String.send() = this.toMessage().sendTo(subject)
// endregion
// region Image download
suspend inline fun Image.getLink(): ImageLink = with(bot) { getLink() }
suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
suspend inline fun Image.download(): ByteReadPacket = getLink().download()
suspend inline fun Image.downloadAsByteArray(): ByteArray = bot.run { downloadAsByteArray() }
suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
// endregion
fun At.qq(): QQ = bot.getQQ(this.target)