Commit d06197d3 authored by Him188's avatar Him188

Image uploading

parent 3474c87f
......@@ -6,8 +6,8 @@ import kotlinx.coroutines.cancelChildren
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientHeartbeatPacket
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.HeartbeatPacket
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
......@@ -39,7 +39,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
*
* [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [PlatformDatagramChannel.read]
* - 心跳 Job [ClientHeartbeatPacket]
* - 心跳 Job [HeartbeatPacket]
* - SKey 刷新 [ClientSKeyRefreshmentRequestPacket]
* - 所有数据包处理和发送
*
......
......@@ -48,29 +48,6 @@ class BotSession(
var gtk: Int = 0
private set
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
*
* 实现方法:
* ```kotlin
* session.sendAndExpect<ServerPacketXXX> {
* toSend { ClientPacketXXX(...) }
* onExpect {//it: ServerPacketXXX
*
* }
* }
* ```
*
* @param P 期待的包
* @param handlerTemporary 处理器.
*/
//@JvmSynthetic
suspend inline fun <reified P : ServerPacket, R> sendAndExpect(handlerTemporary: TemporaryPacketHandler<P, R>.() -> Unit): CompletableDeferred<R> {
val deferred: CompletableDeferred<R> = coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
return deferred
}
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
......@@ -78,9 +55,11 @@ class BotSession(
*
* 实现方法:
* ```kotlin
* with(session){
* ClientPacketXXX(...).sendAndExpect<ServerPacketXXX> {
* //it: ServerPacketXXX
* }
* }
* ```
*
* @param P 期待的包
......@@ -95,6 +74,8 @@ class BotSession(
return deferred
}
suspend inline fun <reified P : ServerPacket> ClientPacket.sendAndExpect(): CompletableDeferred<Unit> = sendAndExpect<P, Unit> {}
suspend inline fun ClientPacket.send() = socket.sendPacket(this)
}
......
......@@ -16,7 +16,10 @@ import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.HeartbeatPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.UnknownServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.network.session
......@@ -408,7 +411,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) {
ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket, Unit> {}
HeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<HeartbeatPacket.Response>().join()
} == null) {
bot.logPurple("Heartbeat timed out")
bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException())
......@@ -438,11 +441,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerHeartbeatResponsePacket -> {
}
is UnknownServerPacket.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey))
else -> {
......
......@@ -8,7 +8,10 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.isOpen
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientAccountInfoRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerAccountInfoResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerSessionPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.AddFriendResult
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket
......@@ -42,20 +45,6 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
it.onPacketReceived(packet)
}
}
is ServerTryGetImageIDSuccessPacket -> {
// ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
}
is ServerTryGetImageIDFailedPacket -> {
}
is ServerSubmitImageFilenameResponsePacket -> {
}
is ServerTryGetImageIDResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerSubmitImageFilenameResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket -> {
......@@ -82,6 +71,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute())
is ServerSessionPacket.Encrypted<*> -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
else -> {
}
......
......@@ -26,25 +26,21 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
) {
private lateinit var toSend: ClientPacket
private lateinit var expect: suspend (P) -> R
private lateinit var handler: suspend (P) -> R
lateinit var session: BotSession//无需覆盖
fun toSend(packet: () -> ClientPacket) {
this.toSend = packet()
}
fun toSend(packet: ClientPacket) {
this.toSend = packet
}
fun onExpect(handler: suspend (P) -> R) {
this.expect = handler
this.handler = handler
}
suspend fun send(session: BotSession) {
require(::handler.isInitialized) { "handler is not initialized" }
this.session = session
session.socket.sendPacket(toSend)
}
......@@ -54,7 +50,7 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
@Suppress("UNCHECKED_CAST")
val ret = try {
expect(packet as P)
handler(packet as P)
} catch (e: Exception) {
deferred.completeExceptionally(e)
return true
......
......@@ -10,7 +10,7 @@ import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
@PacketId(0x00_58u)
class ClientHeartbeatPacket(
class HeartbeatPacket(
private val bot: UInt,
private val sessionKey: ByteArray
) : ClientPacket() {
......@@ -21,7 +21,7 @@ class ClientHeartbeatPacket(
writeHex("00 01 00 01")
}
}
}
@PacketId(0x00_58u)
class ServerHeartbeatResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
@PacketId(0x00_58u)
class Response(input: ByteReadPacket) : ServerSessionPacket(input)
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
/**
* 登录完成之后的所有 packet.
* 它们都使用 sessionKey 解密.
* 它们都必须有一个公开的仅有一个 [ByteReadPacket] 参数的构造器.
*
* 注意: 需要为指定 ID, 通过 [PacketId].
*/
abstract class ServerSessionPacket(input: ByteReadPacket) : ServerPacket(input) {
/**
* 加密过的 [ServerSessionPacket]. 将会在处理时解密为对应的 [ServerSessionPacket]
*/
class Encrypted<P : ServerSessionPacket>(input: ByteReadPacket, val constructor: (ByteReadPacket) -> P) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): P = constructor(decryptBy(sessionKey)).applySequence(sequenceId)
}
companion object {
@Suppress("FunctionName")
inline fun <reified P : ServerSessionPacket> Encrypted(input: ByteReadPacket): Encrypted<P> = Encrypted(input) { P::class.constructors.first().call(it) }
}
}
......@@ -3,16 +3,33 @@
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.*
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.account
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
/**
* 上传图片
*/
suspend fun QQ.uploadImage(image: PlatformImage): ImageId = with(bot.network.session) {
//SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join()
return FriendImageIdRequestPacket(account, sessionKey, account, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
if (it.uKey != null) {
require(httpPostFriendImage(
uKeyHex = it.uKey!!.toUHexString(""),
botNumber = bot.qqAccount,
imageData = image.fileData,
fileSize = image.fileSize,
qq = account
))
it.imageId!!
} else image.id //todo 是这个么
}.await()
}
//fixVer2=00 00 00 01 2E 01 00 00 69 35
//01 [3E 03 3F A2] [76 E4 B8 DD] 00 00 50 7A 00 0A 00 01 00 01 00 2D 55 73 65 72 44 61 74 61 49 6D 61 67 65 3A 43 32 43 5C 48 31 30 50 60 35 29 24 52 7D 57 45 56 54 41 4B 52 24 45 4E 54 45 58 2E 70 6E 67
......@@ -24,7 +41,8 @@ import kotlin.properties.Delegates
//01 3E 03 3F A2 76 E4 B8 DD 00 00 50 7C 00 0A 00 01 00 01 00 2D 55 73 65 72 44 61 74 61 49 6D 61 67 65 3A 43 32 43 5C 40 53 51 25 4F 46 43 50 36 4C 48 30 47 34 43 47 57 53 49 52 46 37 32 2E 70 6E 67
// 00 01 61 A7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01
@PacketId(0X01_BDu)
class ClientSubmitImageFilenamePacket(
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
class SubmitImageFilenamePacket(
private val bot: UInt,
private val target: UInt,
private val filename: String,
......@@ -32,8 +50,8 @@ class ClientSubmitImageFilenamePacket(
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(bot)
//writeHex(TIMProtocol.fixVer2)
writeHex("04 00 00 00 01 2E 01 00 00 69 35")
writeHex(TIMProtocol.fixVer2)//?
//writeHex("04 00 00 00 01 2E 01 00 00 69 35")
encryptAndWrite(sessionKey) {
writeByte(0x01)
......@@ -50,17 +68,17 @@ class ClientSubmitImageFilenamePacket(
writeRandom(2)//这个也与是哪个好友有关?
writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01")//35 02? 最后这个值是与是哪个好友有关
//this.debugPrintThis("ClientSubmitImageFilenamePacket")
//this.debugPrintThis("SubmitImageFilenamePacket")
}
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1A 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1B 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1C 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 29 37 42 53 4B 48 32 44 35 54 51 28 5A 35 7D 35 24 56 5D 32 35 49 4E 2E 6A 70 67 00 00 03 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
}
}
@PacketId(0x01_BDu)
class ServerSubmitImageFilenameResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId(0x01_BDu)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ServerSessionPacket(input) {
override fun decode() = with(input) {
require(readBytes().contentEquals(expecting))
}
......@@ -68,10 +86,6 @@ class ServerSubmitImageFilenameResponsePacket(input: ByteReadPacket) : ServerPac
companion object {
private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00)
}
@PacketId(0x01_BDu)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerSubmitImageFilenameResponsePacket = ServerSubmitImageFilenameResponsePacket(this.decryptBy(sessionKey)).applySequence()
}
}
......@@ -79,13 +93,14 @@ class ServerSubmitImageFilenameResponsePacket(input: ByteReadPacket) : ServerPac
/**
* 请求上传图片. 将发送图片的 md5, size, width, height.
* 服务器返回以下之一:
* - 服务器已经存有这个图片 [ServerTryGetImageIDFailedPacket]
* - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryGetImageIDSuccessPacket]
* - 服务器已经存有这个图片
* - 服务器未存有, 返回一个 key 用于客户端上传
*
* @author Him188moe
*/
@PacketId(0x03_52u)
class ClientTryGetImageIDPacket(
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
class FriendImageIdRequestPacket(
private val botNumber: UInt,
private val sessionKey: ByteArray,
private val target: UInt,
......@@ -94,13 +109,11 @@ class ClientTryGetImageIDPacket(
//00 00 00 07 00 00 00 4B 08 01 12 03 98 01 01 08 01 12 47 08 A2 FF 8C F0 03 10 89 FC A6 8C 0B 18 00 22 10 2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4 28 FD 08 32 1A 7B 00 47 00 47 00 42 00 7E 00 49 00 31 00 5A 00 4D 00 43 00 28 00 25 00 49 00 38 01 48 00 70 42 78 42
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(botNumber)
//04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00
writeHex("04 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 00 00 00")
val imageData = image.toByteArray()
encryptAndWrite(sessionKey) {
//好友图片
// 00 00 00
......@@ -200,10 +213,10 @@ class ClientTryGetImageIDPacket(
writeUByte(0x22u)
writeUByte(0x10u)
writeFully(md5(imageData))
writeFully(image.md5)
writeUByte(0x28u)
writeUVarInt(imageData.size.toUInt())
writeUVarInt(image.fileSize.toUInt())
writeUByte(0x32u)
//长度应为1A
......@@ -237,24 +250,42 @@ class ClientTryGetImageIDPacket(
//println(this.build().readBytes().toUHexString())
}
}
}
@PacketId(0x03_52u)
sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId(0x03_52u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
@PacketId(0x0352u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ServerSessionPacket(input) {
var uKey: ByteArray? = null
var imageId: ImageId? = null
override fun decode() = with(input) {
//00 00 00 08 00 00
//01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B3 90 73 32 C0 5D 72 4B 18 AC 16 8A 23 92 21 3C E6 FD 51 33 CE C4 84 1C 4C 7B A2 E5 27 65 1C 99 EE D6 D4 D2 0D B8 10 2D 88 7E 13 71 75 09 36 46 0F BA 87 B3 EA 54 B2 2B 18 8F F3 5A 9D 55 C6 3B E4 CB 9E B3 69 79 E2 51 61 98 1B 04 49 76 58 29 75 E3 73 56 4B 89 A4 54 A2 E1 0C 17 72 8D 77 EA CD CF 9E 68 B7 01 65 7B F1 E3 B7 FC 04 0C F4 D8 8D B3 51 1B B2 4C 14 59 DE FA 0D 64 BD 50 2E ED 52 25 2F 62 63 66 38 63 39 39 65 2D 30 65 63 35 2D 34 33 33 31 2D 62 37 30 61 2D 31 36 33 35 32 66 66 64 38 33 33 33 5A 25 2F 62 63 66 38 63 39 39 65 2D 30 65 63 35 2D 34 33 33 31 2D 62 37 30 61 2D 31 36 33 35 32 66 66 64 38 33 33 33 60 00 68 80 80 08 20 01
println("ServerTryGetImageIDResponsePacket.size=" + data.size)
//00 00 00 08 00 00 01 0C 12 06 98 01 01 A0 01 00 08 01 12 85 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 B7 87 AC E7 0B 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 50 40 90 3F 40 BB 03 40 50 4A 80 01 F2 65 BC F3 E8 C6 F3 30 B1 85 72 86 C0 95 C0 A7 09 E3 84 AC A6 68 C3 AF BB A8 96 64 AA 18 92 96 F7 3C 7B F8 EA 03 C6 6A AD B7 94 BC 76 D4 36 84 25 76 CB DF 5B 7C E7 40 DF 5D FD DF 3D 93 23 96 5D 23 A8 B2 93 FA 21 BF 68 3E 0B 71 D2 9C FF F2 55 45 11 E2 23 2E D0 49 6E 4F 1F DB 18 28 22 68 45 C9 9E A7 F4 AD EF 20 93 55 EB 0E A3 33 7B 18 E8 7C 15 6F 19 26 2C 41 E9 E4 51 61 48 AA 2F EE 52 25 2F 65 39 61 63 62 63 65 39 2D 61 62 39 36 2D 34 30 30 66 2D 38 61 66 30 2D 32 63 34 64 39 37 31 31 32 33 36 62 5A 25 2F 65 39 61 63 62 63 65 39 2D 61 62 39 36 2D 34 30 30 66 2D 38 61 66 30 2D 32 63 34 64 39 37 31 31 32 33 36 62 60 00 68 80 80 08 20 01
//00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 BB C8 E4 E2 0F 38 FB AE FA 9D 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 0E 26 8D 39 E7 88 22 74 EC 88 2B 04 C5 D1 3D D2 09 A4 2E 48 22 F5 91 51 D5 82 7A 43 9F 45 70 77 79 83 21 87 4E AA 63 6E 73 D5 D3 DA 5F FC 36 BA 97 31 74 49 D9 97 83 58 74 06 BE F2 00 83 CC B9 50 D0 C4 D1 63 33 5F AE EA 1C 99 2D 0D E7 A2 94 97 6E 18 92 86 2C C0 36 E9 D9 E3 82 01 A3 B9 AC F1 90 67 73 F3 3C 0B 26 4C C4 DE 20 AF 3D B3 20 F8 50 B4 0E 78 0E 0E 1E 8C 56 02 21 10 5B 61 39 52 25 2F 31 38 37 31 34 66 66 39 2D 61 30 39 39 2D 34 61 38 64 2D 38 34 39 62 2D 38 37 35 65 65 30 36 65 34 64 32 36 5A 25 2F 31 38 37 31 34 66 66 39 2D 61 30 39 39 2D 34 61 38 64 2D 38 34 39 62 2D 38 37 35 65 65 30 36 65 34 64 32 36 60 00 68 80 80 08 20 01
if (data.size == 285) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(sequenceId)
discardExact(6)
if (readUByte() != UByte.MIN_VALUE) {
//服务器还没有这个图片
//00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03
// 4A [80 01] B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 // 52 [25] 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01
discardExact(60)
discardExact(1)//4A, id
uKey = readBytes(readUnsignedVarInt().toInt())//128
discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}")
DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}")
} else {
//服务器已经有这个图片了
DebugLogger.logPurple("服务器已有好友图片 ")
}
return ServerTryGetImageIDFailedPacket(data.toReadPacket()).applySequence(sequenceId)
println("获取图片 repsonse 后文=" + readRemainingBytes().toUHexString())
}
}
}
......@@ -263,7 +294,7 @@ fun main() {
//GlobalSysTemp:II%E]PA}OVFK]61EGGF$356.jpg
//实际文件名为 II%E]PA}OVFK]61EGGF$356.jpg
println(ClientSubmitImageFilenamePacket(
println(SubmitImageFilenamePacket(
1994701021u,
1040400290u,
"testfilename.png",
......@@ -271,9 +302,6 @@ fun main() {
).packet.readBytes().toUHexString())
val data = "00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 52 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01".hexToBytes()
println(ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(1u).also { it.decode() })
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"
......@@ -283,77 +311,3 @@ fun main() {
println(readUnsignedVarInt())
}
}
\ No newline at end of file
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
@PacketId(0x03_52u)
class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray
var imageId: ImageId by Delegates.notNull()
override fun decode() = with(input) {
//00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03
// 4A [80 01] B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 // 52 [25] 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01
discardExact(68)
discardExact(1)//4A, id
uKey = readBytes(readUnsignedVarInt().toInt())//128
discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
DebugLogger.logPurple("获得 uKey(${uKey.size})=${uKey.toUHexString()}")
DebugLogger.logPurple("获得 imageId(${imageId.value.length})=${imageId}")
println("ServerTryGetImageIDSuccessPacket后文=" + readRemainingBytes().toUHexString())
}
}
/**
* 服务器已经存有这个图片
*/
@PacketId(0x03_52u)
class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
override fun decode(): Unit = with(input) {
readRemainingBytes().debugPrint("ServerTryGetImageIDFailedPacket的body")
}
}
suspend fun Group.uploadImage(image: PlatformImage): ImageId = this.bot.network.session.uploadGroupImage(number, image)
suspend fun QQ.uploadImage(image: PlatformImage): ImageId = this.bot.network.session.uploadFriendImage(number, image)
/**
* 需要在外 timeout 处理
*/
suspend fun BotSession.uploadFriendImage(qq: UInt, image: PlatformImage): ImageId {
ClientSubmitImageFilenamePacket(account, qq, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket, Unit> {
}.join()
return ClientTryGetImageIDPacket(account, sessionKey, qq, image).sendAndExpect<ServerTryGetImageIDResponsePacket, ImageId> {
when (it) {
is ServerTryGetImageIDFailedPacket -> {
//服务器已存有图片
ImageId("UNKNOWN")
}
is ServerTryGetImageIDSuccessPacket -> {
val data = image.toByteArray()
require(httpPostFriendImage(
uKeyHex = it.uKey.toUHexString(""),
botNumber = bot.qqAccount,
fileSize = data.size,
imageData = data,
qq = qq
))
it.imageId
}
}
}.await()
}
suspend fun BotSession.uploadGroupImage(groupNumberOrAccount: UInt, image: PlatformImage): ImageId {
TODO()
}
\ No newline at end of file
......@@ -2,30 +2,40 @@
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeUByte
import kotlinx.io.core.*
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
fun main() {
"1A".hexToBytes().read {
println(readUnsignedVarInt())
suspend fun Group.uploadImage(
image: PlatformImage
) = with(bot.network.session) {
GroupImageIdRequestPacket(bot.qqAccount, groupId, image, sessionKey)
.sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
if (it.uKey != null) {
httpPostGroupImage(
imageData = image.fileData,
fileSize = image.fileSize,
uKeyHex = it.uKey!!.toUHexString()
)
}
}.await()
}
/**
* 获取 Image Id 和上传用的一个 uKey
*/
@PacketId(0x0388u)
class ClientGroupImageIdRequestPacket(
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
class GroupImageIdRequestPacket(
private val bot: UInt,
private val group: UInt,
private val groupId: UInt,
private val image: PlatformImage,
private val imageData: ByteArray,
private val sessionKey: ByteArray
) : ClientPacket() {
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
override fun encode(builder: BytePacketBuilder) = with(builder) {
//未知图片A
// 00 00 00 07 00 00 00
......@@ -110,12 +120,12 @@ class ClientGroupImageIdRequestPacket(
writeHex("01 12 03 98 01 01 10 01 1A")
writeUVarintLVPacket(lengthOffset = { it + 1 }) {
writeUVarInt(group)
writeUVarInt(groupId)
writeUVarInt(bot)
writeTV(0x1800u)
writeTLV(0x22u, md5(imageData))
writeTUVarint(0x28u, imageData.size.toUInt())
writeTLV(0x22u, image.md5)
writeTUVarint(0x28u, image.fileSize.toUInt())
writeUVarintLVPacket(tag = 0x32u) {
writeTV(0x31_00u)
writeTV(0x35_00u)
......@@ -149,5 +159,38 @@ class ClientGroupImageIdRequestPacket(
companion object {
private val value0x6A: UByteArray = ubyteArrayOf(32u, 36u, 39u, 33u, 33u)
}
@PacketId(0x0388u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ServerSessionPacket(input) {
var uKey: ByteArray? = null
override fun decode(): Unit = with(input) {
discardExact(6)//00 00 00 05 00 00
if (readUByte() != UByte.MIN_VALUE) {
//服务器还没有
discardExact(remaining - 128)
uKey = readBytes(128)
}
// 已经有了的一张图片
// 00 3B 12 03 98 01 01
// 08 AB A7 89 D8 02 //群ID
// 10 01 1A 31 08 00 10 00 20 01 2A 1B 0A 10 7A A4 B3 AA 8C 3C 0F 45 2D 9B 7F 30 2A 0A CE AA 10 04 18 F3 06 20 41 28 34 30 DF CF A2 93 02 38 50 48 D0 A9 E5 C8 0B
// 服务器还没有的一张图片
// 02 4E 12 03 98 01 02
// 08 AB A7 89 D8 02 //群ID
// 10 02 22 C3 04 08 F8 9D D0 F5 09 12 10 2F CA 6B E7 B7 95 B7 27 06 35 27 54 0E 43 B4 30 18 00 48 BD EE 92 8D 05 48 BD EE 92 E5 01 48 BB CA 80 A3 02 48 BA F6 D7 5C 48 EF BC 90 F5 0A 50 50 50 50 50 50 50 50 50 50 5A 0D 67 63 68 61 74 2E 71 70 69 63 2E 63 6E 62 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 31 39 38 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 6A 77 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 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 72 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 37 32 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 78 00
// [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() {
("12 03 98 01 01").hexToBytes().read {
println(readUnsignedVarInt())
}
}
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlin.jvm.JvmOverloads
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.message.ImageId
data class PlatformImage(
val width: Int,
val height: Int,
val md5: ByteArray,
val format: String,
val fileData: ByteReadPacket
) {
val fileSize: Long = fileData.remaining
expect class PlatformImage
val id: ImageId by lazy { ImageId("{${md5[0..4]}-${md5[0..2]}-${md5[0..2]}-${md5[0..2]}-${md5[0..6]}}.$format") }
@JvmOverloads
expect fun PlatformImage.toByteArray(formatName: String = "JPG"): ByteArray
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PlatformImage) return false
if (width != other.width) return false
if (height != other.height) return false
if (!md5.contentEquals(other.md5)) return false
if (format != other.format) return false
if (fileData != other.fileData) return false
if (fileSize != other.fileSize) return false
return true
}
override fun hashCode(): Int {
var result = width
result = 31 * result + height
result = 31 * result + md5.contentHashCode()
result = 31 * result + format.hashCode()
result = 31 * result + fileData.hashCode()
result = 31 * result + fileSize.hashCode()
return result
}
}
private operator fun ByteArray.get(range: IntRange): String = buildString {
range.forEach {
append(this@get[it].toUHexString())
}
}
expect val PlatformImage.imageWidth: Int
......
......@@ -3,6 +3,7 @@
package net.mamoe.mirai.utils
import com.soywiz.klock.DateTime
import kotlinx.io.core.ByteReadPacket
/**
* 时间戳
......@@ -38,14 +39,23 @@ expect fun solveIpAddress(hostname: String): String
expect fun localIpAddress(): String
/**
* 上传图片
* 上传好友图片
*/
expect suspend fun httpPostFriendImage(
uKeyHex: String,
fileSize: Int,
fileSize: Long,
botNumber: UInt,
qq: UInt,
imageData: ByteArray
imageData: ByteReadPacket
): Boolean
/**
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
uKeyHex: String,
fileSize: Long,
imageData: ByteReadPacket
): Boolean
fun main() {
......
package net.mamoe.mirai.utils
import kotlin.properties.Delegates
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun <T : Any> Delegates.notNullBy(initializer: () -> T): ReadWriteProperty<Any?, T> = NotNullVarWithDefault(lazy(initializer = initializer))
class NotNullVarWithDefault<T : Any>(
private val initializer: Lazy<T>
) : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: initializer.value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
......@@ -70,18 +70,20 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
}
0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this)
0x00_EC_u -> ServerLoginSuccessPacket(this)
0x00_1D_u -> ServerSKeyResponsePacket.Encrypted(this)
0x00_5C_u -> ServerAccountInfoResponsePacket.Encrypted(this)
0x00_58_u -> ServerHeartbeatResponsePacket(this)
0x00_BA_u -> ServerCaptchaPacket.Encrypted(this)
0x00_CE_u, 0x00_17_u -> ServerEventPacket.Raw.Encrypted(this, id, sequenceId)
0x00_81_u -> ServerFriendOnlineStatusChangedPacket.Encrypted(this)
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(this)
0x00_A7_u -> ServerCanAddFriendResponsePacket(this)
0x03_52_u -> ServerTryGetImageIDResponsePacket.Encrypted(this)
0x01_BDu -> ServerSubmitImageFilenameResponsePacket.Encrypted(this)
0x00_ECu -> ServerLoginSuccessPacket(this)
0x00_1Du -> ServerSKeyResponsePacket.Encrypted(this)
0x00_5Cu -> ServerAccountInfoResponsePacket.Encrypted(this)
0x00_BAu -> ServerCaptchaPacket.Encrypted(this)
0x00_CEu, 0x00_17u -> ServerEventPacket.Raw.Encrypted(this, id, sequenceId)
0x00_81u -> ServerFriendOnlineStatusChangedPacket.Encrypted(this)
0x00_CDu -> ServerSendFriendMessageResponsePacket(this)
0x00_02u -> ServerSendGroupMessageResponsePacket(this)
0x00_A7u -> ServerCanAddFriendResponsePacket(this)
0x00_58u -> ServerSessionPacket.Encrypted<HeartbeatPacket.Response>(this)
0x03_88u -> ServerSessionPacket.Encrypted<GroupImageIdRequestPacket.Response>(this)
0x03_52u -> ServerSessionPacket.Encrypted<FriendImageIdRequestPacket.Response>(this)
0x01_BDu -> ServerSessionPacket.Encrypted<SubmitImageFilenamePacket.Response>(this)
else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applySequence(sequenceId)
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.buildPacket
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.security.MessageDigest
import javax.imageio.ImageIO
actual typealias PlatformImage = BufferedImage
fun BufferedImage.toPlatformImage(formatName: String = "PNG"): PlatformImage {
val digest = MessageDigest.getInstance("md5")
digest.reset()
val buffer = buildPacket {
ImageIO.write(this@toPlatformImage, formatName, object : OutputStream() {
override fun write(b: Int) {
b.toByte().let {
this@buildPacket.writeByte(it)
digest.update(it)
}
}
})
}
@JvmOverloads
actual fun BufferedImage.toByteArray(formatName: String): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "PNG", it); it.toByteArray() }
return PlatformImage(this.width, this.height, digest.digest(), formatName, buffer)
}
actual val PlatformImage.imageWidth: Int get() = this.width
......
......@@ -2,6 +2,11 @@
package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import org.jsoup.Connection
import java.io.OutputStream
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.URL
......@@ -28,10 +33,10 @@ fun main() {
actual suspend fun httpPostFriendImage(
uKeyHex: String,
fileSize: Int,
fileSize: Long,
botNumber: UInt,
qq: UInt,
imageData: ByteArray
imageData: ByteReadPacket
): Boolean {/*Jsoup
//htdata2.qq.com
......@@ -60,7 +65,7 @@ actual suspend fun httpPostFriendImage(
}
};*/
val conn = URL("http://101.227.143.109/cgi-bin/httpconn" +
val conn = URL("http://htdata2.qq.com/cgi-bin/httpconn" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "") +
......@@ -69,18 +74,55 @@ actual suspend fun httpPostFriendImage(
"&uin=" + botNumber.toLong()).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", "" + fileSize)
conn.setRequestProperty("connection", "Keep-Alive")
conn.setRequestProperty("Content-type", "image/png")
conn.setRequestProperty("Connection", "Keep-Alive")
conn.requestMethod = "POST"
conn.doOutput = true
conn.doInput = true
withContext(Dispatchers.IO) {
conn.connect()
}
val buffered = conn.outputStream.buffered()
buffered.write(imageData)
buffered.flush()
conn.outputStream.writePacket(imageData)
println(conn.responseMessage)
println(conn.responseCode)
return conn.responseCode == 200
}
/**
* 上传群图片
*/
actual suspend fun httpPostGroupImage(uKeyHex: String, fileSize: Long, imageData: ByteReadPacket): Boolean {
val conn = URL("http://htdata2.qq.com/cgi-bin/httpconn" +
"?htcmd=0x6ff0071" +
"&term=pc" +
"&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "")).openConnection() as HttpURLConnection
conn.setRequestProperty("Content-Length", fileSize.toString())
conn.setRequestProperty("Connection", "Keep-Alive")
conn.requestMethod = "POST"
conn.doOutput = true
conn.doInput = true
withContext(Dispatchers.IO) {
conn.connect()
}
val stream = conn.outputStream
stream.writePacket(imageData)
println(conn.responseMessage)
println(conn.responseCode)
return conn.responseCode == 200
}
private suspend fun Connection.suspendExecute(): Connection.Response = withContext(Dispatchers.IO) {
execute()
}
private fun OutputStream.writePacket(packet: ByteReadPacket) {
val byteArray = ByteArray(1)
repeat(packet.remaining.toInt()) {
packet.readAvailable(byteArray)
this.write(byteArray)
}
}
\ No newline at end of file
......@@ -2,7 +2,9 @@
package demo1
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
......@@ -78,12 +80,21 @@ suspend fun main() {
}
"上传好友图片" in it.message -> withTimeoutOrNull(3000) {
val id = QQ(bot, 1040400290u).uploadImage(ImageIO.read(File("C:\\Users\\Him18\\Desktop\\lemon.png").readBytes().inputStream()))
val id = QQ(bot, 1040400290u)
.uploadImage(withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\lemon.png").readBytes().inputStream()) }.toPlatformImage("PNG"))
it.reply(id.value)
delay(1000)
it.reply(Image(id))
}
"上传群图片" in it.message -> withTimeoutOrNull(3000) {
val image = withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\lemon.png").readBytes().inputStream()) }.toPlatformImage("PNG")
Group(bot, 580266363u).uploadImage(image)
it.reply(image.id.value)
delay(1000)
it.reply(Image(image.id))
}
/*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, Group(session.bot, 580266363)).of()
})*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment