diff --git a/README.md b/README.md index d1a63355d..e76c8d86e 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ subscribeAlways{ - 成员权限, 昵称(10/18) - 好友在线状态改变(10/14) - Android客户端上线/下线(10/18) -- 上传并发送图片(10/21) +- 上传并发送好友/群图片(10/26) ## 使用方法 ### 要求 diff --git a/mirai-console/src/main/java/net/mamoe/mirai/MiraiServer.kt b/mirai-console/src/main/java/net/mamoe/mirai/MiraiServer.kt index 0ed8a77e2..9e7d1165e 100644 --- a/mirai-console/src/main/java/net/mamoe/mirai/MiraiServer.kt +++ b/mirai-console/src/main/java/net/mamoe/mirai/MiraiServer.kt @@ -147,7 +147,7 @@ object MiraiServer { Bot bot = new Bot(section); var state = bot.network.login$mirai_core().of(); //bot.network.login$mirai_core().whenComplete((state, e) -> { - if (state == LoginState.SUCCESS) { + if (state == LoginState.REQUIRE_UPLOAD) { Bot.instances.add(bot); getLogger().logGreen(" Login Succeed"); } else { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt index be2175e7a..076beffe5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt @@ -3,8 +3,10 @@ package net.mamoe.mirai import net.mamoe.mirai.contact.* +import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult +import net.mamoe.mirai.network.session import net.mamoe.mirai.utils.BotNetworkConfiguration /* @@ -22,6 +24,8 @@ suspend fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.get val Bot.groups: ContactList get() = this.contacts.groups val Bot.qqs: ContactList get() = this.contacts.qqs +inline fun Bot.withSession(block: BotSession.() -> T): T = with(this.network.session) { block() } + //NetworkHandler suspend fun Bot.sendPacket(packet: OutgoingPacket) = this.network.sendPacket(packet) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt index 61f7c0731..981f0e02f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt @@ -4,7 +4,7 @@ package net.mamoe.mirai.message import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.network.protocol.tim.packet.FriendImageIdRequestPacket +import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket import net.mamoe.mirai.utils.ExternalImage /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt index 08f8652b3..40a9a83b9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt @@ -97,7 +97,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : override suspend fun close(cause: Throwable?) { super.close(cause) - this.heartbeatJob?.cancel(CancellationException("handler closed")) + this.heartbeatJob?.cancelChildren(CancellationException("handler closed")) this.heartbeatJob?.join()//等待 cancel 完成 this.heartbeatJob = null diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMProtocol.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMProtocol.kt index e8df9dc90..fe06e2dca 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMProtocol.kt @@ -20,7 +20,7 @@ object TIMProtocol { ).forEach { list.add(solveIpAddress(it)) } list.toList() - }()//不使用lazy是为了在启动时就加载. + }()//不使用lazy, 在初始化时就加载. const val head = "02" const val ver = "37 13" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UploadFriendImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt similarity index 72% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UploadFriendImage.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt index fae0d9c42..9d136f531 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UploadFriendImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt @@ -1,16 +1,23 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused") -package net.mamoe.mirai.network.protocol.tim.packet +package net.mamoe.mirai.network.protocol.tim.packet.action import kotlinx.io.core.* import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.network.protocol.tim.TIMProtocol +import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket +import net.mamoe.mirai.network.protocol.tim.packet.PacketId +import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion +import net.mamoe.mirai.network.protocol.tim.packet.ResponsePacket import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.network.session import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.httpPostFriendImage import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.utils.readUnsignedVarInt +import net.mamoe.mirai.utils.writeUVarInt /** * 上传图片 @@ -18,14 +25,16 @@ import net.mamoe.mirai.utils.io.* suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.session) { //SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect().join() DebugLogger.logPurple("正在上传好友图片, md5=${image.md5.toUHexString()}") - return FriendImageIdRequestPacket(this.qqAccount, sessionKey, this.qqAccount, image).sendAndExpect { + return FriendImageIdRequestPacket(this.qqAccount, sessionKey, id, image).sendAndExpect { if (it.uKey != null) - require(httpPostFriendImage( - botAccount = bot.qqAccount, + require( + httpPostFriendImage( + botAccount = bot.qqAccount, uKeyHex = it.uKey!!.toUHexString(""), - imageInput = image.input, - inputSize = image.inputSize - )) + imageInput = image.input, + inputSize = image.inputSize + ) + ) it.imageId!! }.await() } @@ -43,12 +52,12 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.ses * 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00 */ @PacketId(0X01_BDu) -@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") +@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") class SubmitImageFilenamePacket( - private val bot: UInt, - private val target: UInt, - private val filename: String, - private val sessionKey: ByteArray + private val bot: UInt, + private val target: UInt, + private val filename: String, + private val sessionKey: ByteArray ) : OutgoingPacket() { override fun encode(builder: BytePacketBuilder) = with(builder) { writeQQ(bot) @@ -99,7 +108,7 @@ class SubmitImageFilenamePacket( * - 服务器未存有, 返回一个 key 用于客户端上传 */ @PacketId(0x03_52u) -@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173") +@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") class FriendImageIdRequestPacket( private val botNumber: UInt, private val sessionKey: ByteArray, @@ -187,13 +196,10 @@ class FriendImageIdRequestPacket( * 70 [80 14] * 78 [A0 0B]//84 */ - - writeZero(3) - writeUShort(0x07_00u) - writeZero(1) + writeHex("00 00 00 07 00 00 00") //proto - val packet = buildPacket { + writeUVarintLVPacket(lengthOffset = { it - 7 }) { writeUByte(0x08u) writeUShort(0x01_12u) writeUShort(0x03_98u) @@ -201,62 +207,85 @@ class FriendImageIdRequestPacket( writeUShort(0x08_01u) - writeUShort(0x12_47u)//?似乎会变 + writeUVarintLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) { + writeUByte(0x08u) + writeUVarInt(botNumber) - writeUByte(0x08u) - writeUVarInt(botNumber) + writeUByte(0x10u) + writeUVarInt(target) - writeUByte(0x10u) - writeUVarInt(target) + writeUShort(0x18_00u) - writeUShort(0x18_00u) + writeUByte(0x22u) + writeUByte(0x10u) + writeFully(image.md5) - writeUByte(0x22u) - writeUByte(0x10u) - writeFully(image.md5) - - writeUByte(0x28u) - writeUVarInt(image.inputSize.toUInt()) + writeUByte(0x28u) + writeUVarInt(image.inputSize.toUInt()) - writeUByte(0x32u) - //长度应为1A - writeUVarintLVPacket { - writeUShort(0x28_00u) - writeUShort(0x46_00u) - writeUShort(0x51_00u) - writeUShort(0x56_00u) - writeUShort(0x4B_00u) - writeUShort(0x41_00u) - writeUShort(0x49_00u) - writeUShort(0x25_00u) - writeUShort(0x4B_00u) - writeUShort(0x24_00u) - writeUShort(0x55_00u) - writeUShort(0x30_00u) - writeUShort(0x24_00u) + writeUByte(0x32u) + //长度应为1A + writeUVarintLVPacket { + writeUShort(0x28_00u) + writeUShort(0x46_00u) + writeUShort(0x51_00u) + writeUShort(0x56_00u) + writeUShort(0x4B_00u) + writeUShort(0x41_00u) + writeUShort(0x49_00u) + writeUShort(0x25_00u) + writeUShort(0x4B_00u) + writeUShort(0x24_00u) + writeUShort(0x55_00u) + writeUShort(0x30_00u) + writeUShort(0x24_00u) + } + + writeUShort(0x38_01u) + writeUShort(0x48_00u) + + writeUByte(0x70u) + writeUVarInt(image.width.toUInt()) + writeUByte(0x78u) + writeUVarInt(image.height.toUInt()) } - - writeUShort(0x38_01u) - writeUShort(0x48_00u) - - writeUByte(0x70u) - writeUVarInt(image.width.toUInt()) - writeUByte(0x78u) - writeUVarInt(image.height.toUInt()) } - writeShort((packet.remaining - 7).toShort())//why? - writePacket(packet) + //println(this.build().readBytes().toUHexString()) } } @PacketId(0x0352u) - @PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173") + @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") class Response(input: ByteReadPacket) : ResponsePacket(input) { - var uKey: ByteArray? = null//最终可能为null - var imageId: ImageId? = null//最终不会为null + /** + * 访问 HTTP API 时需要使用的一个 key. 128 位 + */ + var uKey: ByteArray? = null + + /** + * 发送消息时使用的 id + */ + var imageId: ImageId? = null + + lateinit var state: State + + enum class State { + /** + * 需要上传. 此时 [uKey], [imageId] 均不为 `null` + */ + REQUIRE_UPLOAD, + /** + * 服务器已有这个图片. 此时 [uKey] 为 `null`, [imageId] 不为 `null` + */ + ALREADY_EXISTS, + /** + * 图片过大. 此时 [uKey], [imageId] 均为 `null` + */ + OVER_FILE_SIZE_MAX, + } override fun decode() = with(input) { //00 00 00 08 00 00 @@ -278,41 +307,27 @@ class FriendImageIdRequestPacket( discardExact(1)//52, id imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 + state = State.REQUIRE_UPLOAD //DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}") //DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}") } else { //服务器已经有这个图片了 //DebugLogger.logPurple("服务器已有好友图片 ") - //89 12 06 98 01 01 A0 01 00 08 01 12 82 01 08 00 10 AB A7 89 D8 02 18 00 28 01 32 20 0A 10 5A 39 37 10 EA D5 B5 57 A8 04 14 70 CE 90 67 14 10 67 18 8A 94 17 20 ED 03 28 97 04 30 0A 52 25 2F 39 38 31 65 61 31 64 65 2D 62 32 31 33 2D 34 31 61 39 2D 38 38 37 65 2D 32 38 37 39 39 66 31 39 36 37 35 65 5A 25 2F 39 38 31 65 61 31 64 65 2D 62 32 31 33 2D 34 31 61 39 2D 38 38 37 65 2D 32 38 37 39 39 66 31 39 36 37 35 65 60 00 68 80 80 08 20 01 + // 89 + // 12 06 98 01 01 A0 01 00 08 01 12 82 01 08 00 10 AB A7 89 D8 02 18 00 28 01 32 20 0A 10 5A 39 37 10 EA D5 B5 57 A8 04 14 70 CE 90 67 14 10 67 18 8A 94 17 20 ED 03 28 97 04 30 0A 52 25 2F 39 38 31 65 61 31 64 65 2D 62 32 31 33 2D 34 31 61 39 2D 38 38 37 65 2D 32 38 37 39 39 66 31 39 36 37 35 65 5A 25 2F 39 38 31 65 61 31 64 65 2D 62 32 31 33 2D 34 31 61 39 2D 38 38 37 65 2D 32 38 37 39 39 66 31 39 36 37 35 65 60 00 68 80 80 08 20 01 - discardExact(60) - discardExact(1)//52, id - imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 + //83 12 06 98 01 01 A0 01 00 08 01 12 7D 08 00 10 9B A4 DC 92 06 18 00 28 01 32 1B 0A 10 8E C4 9D 72 26 AE 20 C0 5D A2 B6 78 4D 12 B7 3A 10 00 18 86 1F 20 30 28 30 52 25 2F 30 31 62 + val toDiscard = readUByte().toInt() - 37 + if (toDiscard < 0) { + state = State.OVER_FILE_SIZE_MAX + } else { + discardExact(toDiscard) + imageId = ImageId(readString(37)) + state = State.ALREADY_EXISTS + } } } } -} - -fun main() { - //GlobalSysTemp:II%E]PA}OVFK]61EGGF$356.jpg - //实际文件名为 II%E]PA}OVFK]61EGGF$356.jpg - - println(SubmitImageFilenamePacket( - 1994701021u, - 1040400290u, - "testfilename.png", - "99 82 67 D4 62 20 CA 5D 81 F8 6F 83 EE 8A F7 68".hexToBytes() - - ).packet.readBytes().toUHexString()) - - println("01ee6426-5ff1-4cf0-8278-e8634d2909e".toByteArray().toUHexString()) - - "5A 25 2F 36 61 38 35 32 66 64 65 2D 38 32 38 35 2D 34 33 35 31 2D 61 65 65 38 2D 35 34 65 37 35 65 65 32 65 61 37 63 60 00 68 80 80 08 20 01" - .printStringFromHex() - - "25 2F ".hexToBytes().read { - println(readUnsignedVarInt()) - } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UploadGroupImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt similarity index 71% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UploadGroupImage.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt index 24e4a3f37..e9526566a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UploadGroupImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt @@ -1,41 +1,62 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.tim.packet +package net.mamoe.mirai.network.protocol.tim.packet.action import kotlinx.io.core.* import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupInternalId -import net.mamoe.mirai.network.session +import net.mamoe.mirai.contact.withSession +import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket +import net.mamoe.mirai.network.protocol.tim.packet.PacketId +import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion +import net.mamoe.mirai.network.protocol.tim.packet.ResponsePacket import net.mamoe.mirai.qqAccount import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.httpPostGroupImage import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.readUnsignedVarInt +/** + * 图片文件过大 + */ +class OverFileSizeMaxException : IllegalStateException() + +/** + * 上传群图片 + * 挂起直到上传完成或失败 + * 失败后抛出 [OverFileSizeMaxException] + */ suspend fun Group.uploadImage( image: ExternalImage -) = with(bot.network.session) { +) = withSession { GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey) .sendAndExpect { - if (it.uKey != null) { - httpPostGroupImage( - botAccount = bot.qqAccount, - groupInternalId = internalId, - imageInput = image.input, - inputSize = image.inputSize, - uKeyHex = it.uKey!!.toUHexString("") - ) + when (it.state) { + GroupImageIdRequestPacket.Response.State.REQUIRE_UPLOAD -> { + httpPostGroupImage( + botAccount = bot.qqAccount, + groupId = GroupId(id), + imageInput = image.input, + inputSize = image.inputSize, + uKeyHex = it.uKey!!.toUHexString("") + ) + } + + GroupImageIdRequestPacket.Response.State.ALREADY_EXISTS -> { + + } + + GroupImageIdRequestPacket.Response.State.OVER_FILE_SIZE_MAX -> throw OverFileSizeMaxException() } - }.await() + }.join() } /** * 获取 Image Id 和上传用的一个 uKey */ @PacketId(0x0388u) -@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173") +@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") class GroupImageIdRequestPacket( private val bot: UInt, private val groupInternalId: GroupInternalId, @@ -51,8 +72,8 @@ class GroupImageIdRequestPacket( //小图B // 00 00 00 07 00 00 00 - // 5B 08 =后文长度-6 - // 01 12 03 98 01 01 10 01 1A + // 5B =后文长度-7 + // 08 01 12 03 98 01 01 10 01 1A // 57长度 // 08 FB D2 D8 94 02 // 10 A2 FF 8C F0 03 @@ -83,6 +104,28 @@ class GroupImageIdRequestPacket( // 78 03 // 80 01 00 + //450*298 + //00 00 00 07 00 00 00 + // 5D=后文-7 varint + // 08 01 12 03 98 01 01 10 01 1A + // 59 =后文长度 varint + // 08 A0 89 F7 B6 03 + // 10 A2 FF 8C F0 03 + // 18 00 + // 22 10 01 FC 9D 6B E9 B2 D9 CD AC 25 66 73 F9 AF 6A 67 + // 28 [C9 10] varint size + // 32 1A + // 58 00 51 00 56 00 51 00 58 00 47 00 55 00 47 00 38 00 57 00 5F 00 4A 00 43 00 + // 38 01 48 01 + // 50 [C2 03] + // 58 [AA 02] + // 60 02 + // 6A 05 32 36 39 33 33 + // 70 00 + // 78 03 + // 80 01 + // 00 + //大图C // 00 00 00 07 00 00 00 // 5E 08 =后文长度-6 @@ -144,11 +187,11 @@ class GroupImageIdRequestPacket( encryptAndWrite(sessionKey) { writeHex("00 00 00 07 00 00 00") - writeUVarintLVPacket(lengthOffset = { it - 6 }) { + writeUVarintLVPacket(lengthOffset = { it - 7 }) { writeByte(0x08) writeHex("01 12 03 98 01 01 10 01 1A") - writeUVarintLVPacket(lengthOffset = { it + 1 }) { + writeUVarintLVPacket(lengthOffset = { it }) { writeTUVarint(0x08u, groupInternalId.value) writeTUVarint(0x10u, bot) writeTV(0x1800u) @@ -207,31 +250,6 @@ class GroupImageIdRequestPacket( }.readBytes().toUHexString()) */ } - - - //以下仅支持中等大小图片 -/* - writeQQ(bot) - writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") - - encryptAndWrite(sessionKey) { - writeHex("00 00 00 07 00 00 00 5E 08 01 12 03 98 01 01 10 01 1A") - writeHex("5A 08") - writeUVarInt(groupId) - writeUByte(0x10u) - writeUVarInt(bot) - writeHex("18 00 22 10") - writeFully(image.md5) - writeUByte(0x28u) - writeUVarInt(image.fileSize.toUInt()) - writeHex("32 1A 37 00 4D 00 32 00 25 00 4C 00 31 00 56 00 32 00 7B 00 39 00 30 00 29 00 52 00") - writeHex("38 01 48 01 50") - writeUVarInt(image.width.toUInt()) - writeUByte(0x58u) - writeUVarInt(image.height.toUInt()) - writeHex("60 04 6A 05 32 36 36 35 36 70 00 78 03 80 01 00") - } - */ } companion object { @@ -239,21 +257,51 @@ class GroupImageIdRequestPacket( } @PacketId(0x0388u) - @PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173") + @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") class Response(input: ByteReadPacket) : ResponsePacket(input) { + lateinit var state: State + + /** + * 访问 HTTP API 时需要使用的一个 key. 128 位 + */ var uKey: ByteArray? = null + enum class State { + /** + * 需要上传. 此时 [uKey] 不为 `null` + */ + REQUIRE_UPLOAD, + /** + * 服务器已有这个图片. 此时 [uKey] 为 `null` + */ + ALREADY_EXISTS, + /** + * 图片过大. 此时 [uKey] 为 `null` + */ + OVER_FILE_SIZE_MAX, + } + override fun decode(): Unit = with(input) { discardExact(6)//00 00 00 05 00 00 val length = remaining - 128 - 14 if (length < 0) { - //服务器已经有这个图片了 + state = if (readUShort().toUInt() == 0x0025u) { + State.OVER_FILE_SIZE_MAX + } else { + State.ALREADY_EXISTS + } + + //图片过大 00 25 12 03 98 01 01 08 9B A4 DC 92 06 10 01 1A 1B 08 00 10 C5 01 1A 12 6F 76 65 72 20 66 69 6C 65 20 73 69 7A 65 20 6D 61 78 20 00 + //图片过大 00 25 12 03 98 01 01 08 9B A4 DC 92 06 10 01 1A 1B 08 00 10 C5 01 1A 12 6F 76 65 72 20 66 69 6C 65 20 73 69 7A 65 20 6D 61 78 20 00 + //图片已有 00 3F 12 03 98 01 01 08 9B A4 DC 92 06 10 01 1A 35 08 00 10 00 20 01 2A 1F 0A 10 24 66 B9 6B E8 58 FE C0 12 BD 1E EC CB 74 A8 8E 10 04 18 83 E2 AF 01 20 80 3C 28 E0 21 30 EF 9A 88 B9 0B 38 50 48 90 D7 DA B0 08 + //debugPrint("后文") return@with } discardExact(length) uKey = readBytes(128) + state = State.REQUIRE_UPLOAD //} else { // println("服务器已经有了这个图片") //println("后文 = ${readRemainingBytes().toUHexString()}") @@ -272,12 +320,4 @@ class GroupImageIdRequestPacket( // [80 01] 04 9A 01 79 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 33 39 36 37 39 34 39 34 32 37 2F 33 39 36 37 39 34 39 34 32 37 2D 32 36 36 32 36 30 30 34 34 30 2D 32 46 43 41 36 42 45 37 42 37 39 35 42 37 32 37 30 36 33 35 32 37 35 34 30 45 34 33 42 34 33 30 2F 34 30 30 3F 76 75 69 6E 3D 31 30 34 30 34 30 30 32 39 30 26 74 65 72 6D 3D 32 35 35 26 73 72 76 76 65 72 3D 32 36 39 33 33 A0 01 00 } } -} - -fun main() { - ("A2 FF 8C F0 03").hexToBytes().read { - println(readUnsignedVarInt()) - } - - println(0x40) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt index 63eceb2a5..038525bb5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt @@ -20,7 +20,7 @@ data class EventPacketIdentity( val to: UInt,//对于好友消息, 这个是bot internal val uniqueId: IoBuffer//8 ) { - override fun toString(): String = "(from=$from, to=$to)" + override fun toString(): String = "($from->$to)" } fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index 2fda0a9ea..2998b63cd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -18,17 +18,26 @@ class ExternalImage( val width: Int, val height: Int, val md5: ByteArray, - val format: String, + imageFormat: String, val input: Input, val inputSize: Long ) { + private val format: String + + init { + if (imageFormat == "JPEG" || imageFormat == "jpeg") {//必须转换 + this.format = "jpg" + } else { + this.format = imageFormat + } + } /** * 用于发送消息的 [ImageId] */ val groupImageId: ImageId by lazy { ImageId("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format") } - override fun toString(): String = "[ExternalImage(${width}x${height} $format)]" + override fun toString(): String = "[ExternalImage(${width}x$height $format)]" } private operator fun ByteArray.get(range: IntRange): String = buildString { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt index f8efe863f..0b3b59f96 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt @@ -10,7 +10,7 @@ import io.ktor.http.HttpStatusCode import io.ktor.http.URLProtocol import io.ktor.http.userAgent import kotlinx.io.core.Input -import net.mamoe.mirai.contact.GroupInternalId +import net.mamoe.mirai.contact.GroupId /** * 时间戳 @@ -59,52 +59,95 @@ suspend fun httpPostFriendImage( uKeyHex: String, imageInput: Input, inputSize: Long -): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) { - url { - parameters["htcmd"] = "0x6ff0070" - parameters["uin"] = botAccount.toLong().toString() - } +): Boolean = (httpClient.postImage( + htcmd = "0x6ff0070", + uin = botAccount, + groupcode = null, + imageInput = imageInput, + inputSize = inputSize, + uKeyHex = uKeyHex +) as HttpStatusCode).value.also { println(it) } == 200 -} as HttpStatusCode).value.also { println(it) } == 200 +/* + httpPostFriendImageOld(uKeyHex, botAccount, imageInput.readBytes().toReadPacket()) +expect suspend fun httpPostFriendImageOld( + uKeyHex: String, + botNumber: UInt, + imageData: ByteReadPacket +): Boolean +*/ /** * 上传群图片 */ @Suppress("DuplicatedCode") suspend fun httpPostGroupImage( botAccount: UInt, - groupInternalId: GroupInternalId, + groupId: GroupId, uKeyHex: String, imageInput: Input, inputSize: Long -): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) { - url { - parameters["htcmd"] = "0x6ff0071" - parameters["uin"] = botAccount.toLong().toString() - parameters["groupcode"] = groupInternalId.value.toLong().toString() - } -} as HttpStatusCode).value.also { println(it) } == 200 +): Boolean = (httpClient.postImage( + htcmd = "0x6ff0071", + uin = botAccount, + groupcode = groupId, + imageInput = imageInput, + inputSize = inputSize, + uKeyHex = uKeyHex +) as HttpStatusCode).value.also { println(it) } == 200 +/* = (httpClient.post { +url { + protocol = URLProtocol.HTTP + host = "htdata2.qq.com" + path("cgi-bin/httpconn") + parameters["htcmd"] = "0x6ff0071" + parameters["ver"] = "5603" + parameters["term"] = "pc" + parameters["ukey"] = uKeyHex + parameters["filesize"] = inputSize.toString() + parameters["range"] = 0.toString() + parameters["uin"] = botAccount.toLong().toString() + parameters["groupcode"] = groupId.value.toLong().toString() + // userAgent("QQClient") +} +println(url.buildString()) +body = ByteArrayContent(imageInput.readBytes()) +//configureBody(inputSize, imageInput) +} as HttpStatusCode).value.also { println(it) } == 200*/ + +@Suppress("SpellCheckingInspection") private suspend inline fun HttpClient.postImage( + htcmd: String, + uin: UInt, + groupcode: GroupId?, imageInput: Input, inputSize: Long, - uKeyHex: String, - block: HttpRequestBuilder.() -> Unit = {} + uKeyHex: String ): T = post { url { protocol = URLProtocol.HTTP host = "htdata2.qq.com" path("cgi-bin/httpconn") + parameters["htcmd"] = htcmd + parameters["uin"] = uin.toLong().toString() + + if (groupcode != null) { + parameters["groupcode"] = groupcode.value.toLong().toString() + } + + parameters["term"] = "pc" parameters["ver"] = "5603" - parameters["filezise"] = inputSize.toString() + parameters["filesize"] = inputSize.toString() parameters["range"] = 0.toString() parameters["ukey"] = uKeyHex userAgent("QQClient") } - block() + + println(url.buildString()) configureBody(inputSize, imageInput) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt index 352a32bc5..4bea97013 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt @@ -6,6 +6,7 @@ import kotlinx.io.charsets.Charset import kotlinx.io.charsets.Charsets import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.String +import kotlinx.io.core.use import kotlin.jvm.JvmOverloads @JvmOverloads @@ -34,6 +35,6 @@ fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString fun ByteArray.toReadPacket() = ByteReadPacket(this) -fun ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().run(t) +fun ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t) fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt index 60180c5a9..e6a12c223 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt @@ -5,9 +5,7 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.* import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.packet.* -import net.mamoe.mirai.network.protocol.tim.packet.action.CanAddFriendPacket -import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket -import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket +import net.mamoe.mirai.network.protocol.tim.packet.action.* import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket import net.mamoe.mirai.network.protocol.tim.packet.login.* import net.mamoe.mirai.utils.MiraiLogger diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt index b3441d857..39ba9ae54 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt @@ -6,6 +6,8 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.IoBuffer import net.mamoe.mirai.utils.io.toUHexString import java.lang.reflect.Field +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.jvm.kotlinProperty internal object PacketNameFormatter { @JvmStatic @@ -22,7 +24,7 @@ internal object PacketNameFormatter { } } -private object IgnoreIdList : List by listOf( +private object IgnoreIdListEquals : List by listOf( "idHex", "id", "packetId", @@ -32,18 +34,32 @@ private object IgnoreIdList : List by listOf( "idByteArray", "encoded", "packet", - "Companion", "EMPTY_ID_HEX", "input", + "sequenceId", "output", - "this\$0", - "\$\$delegatedProperties", + "bot", "UninitializedByteReadPacket", "sessionKey" ) +private object IgnoreIdListInclude : List by listOf( + "Companion", + "EMPTY_ID_HEX", + "input", + "output", + "this\$", + "\$\$delegatedProperties", + "UninitializedByteReadPacket", + "\$FU", + "RefVolatile" +) + +@Suppress("UNCHECKED_CAST") internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields - .filterNot { it.name in IgnoreIdList /*|| "delegate" in it.name|| "$" in it.name */ } + .filterNot { field -> + IgnoreIdListEquals.any { field.name.replace("\$delegate", "") == it } || IgnoreIdListInclude.any { it in field.name } + } .joinToString(", ", "{", "}") { it.isAccessible = true it.name.replace("\$delegate", "") + "=" + it.get(this).let { value -> @@ -55,6 +71,7 @@ internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjust //is ByteReadPacket -> value.copy().readBytes().toUHexString() is IoBuffer -> "[IoBuffer(${value.readRemaining})]" is Lazy<*> -> "[Lazy]" + is ReadWriteProperty<*, *> -> (value as ReadWriteProperty).getValue(this, it.kotlinProperty!!) else -> value.toString() } } diff --git a/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt b/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt index 4d3eef1da..1415cc18b 100644 --- a/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt +++ b/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt @@ -2,18 +2,18 @@ import net.mamoe.mirai.contact.groupId import net.mamoe.mirai.contact.toInternalId -import net.mamoe.mirai.network.protocol.tim.packet.GroupImageIdRequestPacket +import net.mamoe.mirai.network.protocol.tim.packet.action.GroupImageIdRequestPacket import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.io.readRemainingBytes import net.mamoe.mirai.utils.io.toUHexString -import net.mamoe.mirai.utils.toMiraiImage +import net.mamoe.mirai.utils.toExternalImage import java.io.File import javax.imageio.ImageIO val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes() fun main() = println({ - val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.gif").readBytes().inputStream()).toMiraiImage("png") + val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.gif").readBytes().inputStream()).toExternalImage("png") // File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes()) GroupImageIdRequestPacket( diff --git a/mirai-core/src/jvmTest/kotlin/TestImageFile.kt b/mirai-core/src/jvmTest/kotlin/TestImageFile.kt new file mode 100644 index 000000000..ad9ff6baa --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/TestImageFile.kt @@ -0,0 +1,9 @@ +import java.io.File + +fun main() { + + + val file = File("C:\\Users\\Him18\\Desktop\\lemon.png") + println(file.inputStream().readAllBytes().size) + println(file.length()) +} \ No newline at end of file diff --git a/mirai-debug/build.gradle b/mirai-debug/build.gradle index 5b3bdc6d5..63e977a22 100644 --- a/mirai-debug/build.gradle +++ b/mirai-debug/build.gradle @@ -1,16 +1,36 @@ +plugins { + id 'application' + id 'org.openjfx.javafxplugin' version '0.0.8' +} + apply plugin: "kotlin" apply plugin: "java" +javafx { + version = "11" + modules = [ 'javafx.controls' ] +} + dependencies { implementation project(':mirai-core') - compile files('./lib/jpcap.jar') + implementation files('./lib/jpcap.jar') api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version + +// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-javafx + compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-javafx', version: '1.3.2' + + implementation 'org.pcap4j:pcap4j-distribution:1.8.2' + + implementation 'no.tornado:tornadofx:1.7.17' + } +mainClassName = 'Application' + tasks.withType(JavaCompile) { options.encoding = "UTF-8" } diff --git a/mirai-debug/src/main/java/HexComparator.kt b/mirai-debug/src/main/java/HexComparator.kt index d9ffd5ad1..14efb288d 100644 --- a/mirai-debug/src/main/java/HexComparator.kt +++ b/mirai-debug/src/main/java/HexComparator.kt @@ -1,17 +1,15 @@ @file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant") import net.mamoe.mirai.utils.io.printCompareHex -import java.util.* fun main() { // println(HexComparator.printColorize("00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00")) - val scanner = Scanner(System.`in`) while (true) { println("Hex1: ") - val hex1 = scanner.nextLine() + val hex1 = readLine()!! println("Hex2: ") - val hex2 = scanner.nextLine() + val hex2 = readLine()!! println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") println(printCompareHex(hex1, hex2)) println() diff --git a/mirai-debug/src/main/java/PacketDebuger.kt b/mirai-debug/src/main/java/PacketDebuger.kt index 858984350..02c1fded7 100644 --- a/mirai-debug/src/main/java/PacketDebuger.kt +++ b/mirai-debug/src/main/java/PacketDebuger.kt @@ -3,9 +3,7 @@ import Main.localIp import Main.qq import Main.sessionKey -import jpcap.JpcapCaptor -import jpcap.packet.IPPacket -import jpcap.packet.UDPPacket +import com.sun.jna.Platform import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import kotlinx.io.core.readUInt @@ -20,6 +18,12 @@ import net.mamoe.mirai.utils.decryptBy import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.toUHexString +import org.pcap4j.core.BpfProgram.BpfCompileMode +import org.pcap4j.core.PacketListener +import org.pcap4j.core.PcapNetworkInterface +import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode +import org.pcap4j.core.Pcaps + /** * 抓包分析器. @@ -30,11 +34,31 @@ import net.mamoe.mirai.utils.toUHexString object Main { @JvmStatic fun main(args: Array) { - val devices = JpcapCaptor.getDeviceList() - val jpcap: JpcapCaptor? - val caplen = 4096 - val promiscCheck = true - jpcap = JpcapCaptor.openDevice(devices[0], caplen, promiscCheck, 50) + val nif: PcapNetworkInterface = Pcaps.findAllDevs()[0] + println(nif.name + "(" + nif.description + ")") + + val handle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 3000) + + handle.setFilter("src $localIp && udp port 8000", BpfCompileMode.OPTIMIZE) + + val listener = PacketListener { + println(it.rawData.toUHexString()) + println() + } + + handle.loop(Int.MAX_VALUE, listener) + + val ps = handle.stats + println("ps_recv: " + ps.numPacketsReceived) + println("ps_drop: " + ps.numPacketsDropped) + println("ps_ifdrop: " + ps.numPacketsDroppedByIf) + if (Platform.isWindows()) { + println("bs_capt: " + ps.numPacketsCaptured) + } + + handle.close() + +/* while (true) { assert(jpcap != null) val pk = jpcap!!.packet ?: continue @@ -65,6 +89,8 @@ object Main { //pk.dst_ip } } +*/ + } /** @@ -79,9 +105,9 @@ object Main { * 6. 运行到 `mov eax,dword ptr ss:[ebp+10]` * 7. 查看内存, 从 `eax` 开始的 16 bytes 便是 `sessionKey` */ - val sessionKey: ByteArray = "1D 1E 71 68 B9 41 FD 5B F3 5A 3F 71 87 B5 86 CB".hexToBytes() + val sessionKey: ByteArray = "0D D7 C8 06 C6 C1 40 FE A8 3B CF 81 EE DF 69 83".hexToBytes() const val qq: UInt = 1040400290u - const val localIp = "192.168.3." + const val localIp = "192.168.3.10" fun dataReceived(data: ByteArray) { //println("raw = " + data.toUHexString()) @@ -159,12 +185,14 @@ object Main { return@read } - println("fixVer2=" + when (val flag = readByte().toInt()) { - 2 -> byteArrayOf(2) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1) - 4 -> byteArrayOf(4) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1 + 8)//8个0 - 0 -> byteArrayOf(0) + readBytes(2) - else -> error("unknown fixVer2 flag=$flag. Remaining =${readBytes().toUHexString()}") - }.toUHexString()) + println( + "fixVer2=" + when (val flag = readByte().toInt()) { + 2 -> byteArrayOf(2) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1) + 4 -> byteArrayOf(4) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1 + 8)//8个0 + 0 -> byteArrayOf(0) + readBytes(2) + else -> error("unknown fixVer2 flag=$flag. Remaining =${readBytes().toUHexString()}") + }.toUHexString() + ) //39 27 DC E2 04 00 00 00 00 00 00 00 1E 0E 89 00 00 01 05 0F 05 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 3E 03 3F A2 00 00 00 00 00 00 00 00 00 00 00 @@ -191,8 +219,8 @@ object Main { try { messageData.read { discardExact( - 4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2 - + 1 + 4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2 + + 1 ) val chain = readMessageChain() println(chain) diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo1/Main.kt b/mirai-demos/mirai-demo-1/src/main/java/demo1/Main.kt index 2fdc65b1f..129cc4307 100644 --- a/mirai-demos/mirai-demo-1/src/main/java/demo1/Main.kt +++ b/mirai-demos/mirai-demo-1/src/main/java/demo1/Main.kt @@ -17,8 +17,8 @@ import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.PlainText import net.mamoe.mirai.message.firstOrNull import net.mamoe.mirai.network.protocol.tim.packet.OutgoingRawPacket +import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult -import net.mamoe.mirai.network.protocol.tim.packet.uploadImage import net.mamoe.mirai.network.session import net.mamoe.mirai.qqAccount import net.mamoe.mirai.utils.* @@ -99,25 +99,27 @@ suspend fun main() { } "上传好友图片" in it.message -> withTimeoutOrNull(5000) { + val filename = it.message.toString().substringAfter("上传好友图片") val id = 1040400290u.qq() - .uploadImage(File("C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传好友图片")}").toMiraiImage()) + .uploadImage(File("C:\\Users\\Him18\\Desktop\\$filename").toExternalImage()) it.reply(id.value) - delay(1000) + delay(100) it.reply(Image(id)) } "上传群图片" in it.message -> withTimeoutOrNull(5000) { + val filename = it.message.toString().substringAfter("上传群图片") val image = File( - "C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传群图片")}" - ).toMiraiImage() - 580266363u.group().uploadImage(image) + "C:\\Users\\Him18\\Desktop\\$filename" + ).toExternalImage() + 920503456u.group().uploadImage(image) it.reply(image.groupImageId.value) - delay(1000) - 580266363u.group().sendMessage(Image(image.groupImageId)) + delay(100) + 920503456u.group().sendMessage(Image(image.groupImageId)) } "发群图片" in it.message -> { - 580266363u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片")))) + 920503456u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片")))) } "发好友图片" in it.message -> {