Commit d06197d3 authored by Him188's avatar Him188

Image uploading

parent 3474c87f
...@@ -6,8 +6,8 @@ import kotlinx.coroutines.cancelChildren ...@@ -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.BotSocketAdapter
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.* 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.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.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
...@@ -39,7 +39,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> { ...@@ -39,7 +39,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
* *
* [BotNetworkHandler] 的协程包含: * [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [PlatformDatagramChannel.read] * - UDP 包接收: [PlatformDatagramChannel.read]
* - 心跳 Job [ClientHeartbeatPacket] * - 心跳 Job [HeartbeatPacket]
* - SKey 刷新 [ClientSKeyRefreshmentRequestPacket] * - SKey 刷新 [ClientSKeyRefreshmentRequestPacket]
* - 所有数据包处理和发送 * - 所有数据包处理和发送
* *
......
...@@ -48,29 +48,6 @@ class BotSession( ...@@ -48,29 +48,6 @@ class BotSession(
var gtk: Int = 0 var gtk: Int = 0
private set 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].
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时. * 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
...@@ -78,9 +55,11 @@ class BotSession( ...@@ -78,9 +55,11 @@ class BotSession(
* *
* 实现方法: * 实现方法:
* ```kotlin * ```kotlin
* with(session){
* ClientPacketXXX(...).sendAndExpect<ServerPacketXXX> { * ClientPacketXXX(...).sendAndExpect<ServerPacketXXX> {
* //it: ServerPacketXXX * //it: ServerPacketXXX
* } * }
* }
* ``` * ```
* *
* @param P 期待的包 * @param P 期待的包
...@@ -95,6 +74,8 @@ class BotSession( ...@@ -95,6 +74,8 @@ class BotSession(
return deferred return deferred
} }
suspend inline fun <reified P : ServerPacket> ClientPacket.sendAndExpect(): CompletableDeferred<Unit> = sendAndExpect<P, Unit> {}
suspend inline fun ClientPacket.send() = socket.sendPacket(this) suspend inline fun ClientPacket.send() = socket.sendPacket(this)
} }
......
...@@ -16,7 +16,10 @@ import net.mamoe.mirai.event.subscribe ...@@ -16,7 +16,10 @@ import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.handler.* 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.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.* import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.network.session import net.mamoe.mirai.network.session
...@@ -408,7 +411,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -408,7 +411,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
class HeartbeatTimeoutException : CancellationException("heartbeat timeout") class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) { if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) {
ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket, Unit> {} HeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<HeartbeatPacket.Response>().join()
} == null) { } == null) {
bot.logPurple("Heartbeat timed out") bot.logPurple("Heartbeat timed out")
bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException()) bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException())
...@@ -438,11 +441,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -438,11 +441,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey)) is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt()) is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerHeartbeatResponsePacket -> {
}
is UnknownServerPacket.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey)) is UnknownServerPacket.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey))
else -> { else -> {
......
...@@ -8,7 +8,10 @@ import kotlinx.coroutines.delay ...@@ -8,7 +8,10 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.isOpen 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.AddFriendResult
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientAddFriendPacket import net.mamoe.mirai.network.protocol.tim.packet.action.ClientAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket
...@@ -42,20 +45,6 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -42,20 +45,6 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
it.onPacketReceived(packet) 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.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket -> { is ServerAccountInfoResponsePacket -> {
...@@ -82,6 +71,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -82,6 +71,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute()) is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute())
is ServerSessionPacket.Encrypted<*> -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
else -> { else -> {
} }
......
...@@ -26,25 +26,21 @@ class TemporaryPacketHandler<P : ServerPacket, R>( ...@@ -26,25 +26,21 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
) { ) {
private lateinit var toSend: ClientPacket private lateinit var toSend: ClientPacket
private lateinit var expect: suspend (P) -> R private lateinit var handler: suspend (P) -> R
lateinit var session: BotSession//无需覆盖 lateinit var session: BotSession//无需覆盖
fun toSend(packet: () -> ClientPacket) {
this.toSend = packet()
}
fun toSend(packet: ClientPacket) { fun toSend(packet: ClientPacket) {
this.toSend = packet this.toSend = packet
} }
fun onExpect(handler: suspend (P) -> R) { fun onExpect(handler: suspend (P) -> R) {
this.expect = handler this.handler = handler
} }
suspend fun send(session: BotSession) { suspend fun send(session: BotSession) {
require(::handler.isInitialized) { "handler is not initialized" }
this.session = session this.session = session
session.socket.sendPacket(toSend) session.socket.sendPacket(toSend)
} }
...@@ -54,7 +50,7 @@ class TemporaryPacketHandler<P : ServerPacket, R>( ...@@ -54,7 +50,7 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val ret = try { val ret = try {
expect(packet as P) handler(packet as P)
} catch (e: Exception) { } catch (e: Exception) {
deferred.completeExceptionally(e) deferred.completeExceptionally(e)
return true return true
......
...@@ -10,7 +10,7 @@ import net.mamoe.mirai.utils.writeHex ...@@ -10,7 +10,7 @@ import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ import net.mamoe.mirai.utils.writeQQ
@PacketId(0x00_58u) @PacketId(0x00_58u)
class ClientHeartbeatPacket( class HeartbeatPacket(
private val bot: UInt, private val bot: UInt,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
...@@ -21,7 +21,7 @@ class ClientHeartbeatPacket( ...@@ -21,7 +21,7 @@ class ClientHeartbeatPacket(
writeHex("00 01 00 01") writeHex("00 01 00 01")
} }
} }
}
@PacketId(0x00_58u) @PacketId(0x00_58u)
class ServerHeartbeatResponsePacket(input: ByteReadPacket) : ServerPacket(input) class Response(input: ByteReadPacket) : ServerSessionPacket(input)
\ No newline at end of file }
\ 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) }
}
}
...@@ -2,30 +2,40 @@ ...@@ -2,30 +2,40 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.*
import kotlinx.io.core.writeUByte import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.network.protocol.tim.TIMProtocol 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 net.mamoe.mirai.utils.*
fun main() { suspend fun Group.uploadImage(
"1A".hexToBytes().read { image: PlatformImage
println(readUnsignedVarInt()) ) = 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 * 获取 Image Id 和上传用的一个 uKey
*/ */
@PacketId(0x0388u) @PacketId(0x0388u)
class ClientGroupImageIdRequestPacket( @PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
class GroupImageIdRequestPacket(
private val bot: UInt, private val bot: UInt,
private val group: UInt, private val groupId: UInt,
private val image: PlatformImage, private val image: PlatformImage,
private val imageData: ByteArray,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
//未知图片A //未知图片A
// 00 00 00 07 00 00 00 // 00 00 00 07 00 00 00
...@@ -110,12 +120,12 @@ class ClientGroupImageIdRequestPacket( ...@@ -110,12 +120,12 @@ class ClientGroupImageIdRequestPacket(
writeHex("01 12 03 98 01 01 10 01 1A") writeHex("01 12 03 98 01 01 10 01 1A")
writeUVarintLVPacket(lengthOffset = { it + 1 }) { writeUVarintLVPacket(lengthOffset = { it + 1 }) {
writeUVarInt(group) writeUVarInt(groupId)
writeUVarInt(bot) writeUVarInt(bot)
writeTV(0x1800u) writeTV(0x1800u)
writeTLV(0x22u, md5(imageData)) writeTLV(0x22u, image.md5)
writeTUVarint(0x28u, imageData.size.toUInt()) writeTUVarint(0x28u, image.fileSize.toUInt())
writeUVarintLVPacket(tag = 0x32u) { writeUVarintLVPacket(tag = 0x32u) {
writeTV(0x31_00u) writeTV(0x31_00u)
writeTV(0x35_00u) writeTV(0x35_00u)
...@@ -149,5 +159,38 @@ class ClientGroupImageIdRequestPacket( ...@@ -149,5 +159,38 @@ class ClientGroupImageIdRequestPacket(
companion object { companion object {
private val value0x6A: UByteArray = ubyteArrayOf(32u, 36u, 39u, 33u, 33u) 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 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 expect val PlatformImage.imageWidth: Int
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import kotlinx.io.core.ByteReadPacket
/** /**
* 时间戳 * 时间戳
...@@ -38,14 +39,23 @@ expect fun solveIpAddress(hostname: String): String ...@@ -38,14 +39,23 @@ expect fun solveIpAddress(hostname: String): String
expect fun localIpAddress(): String expect fun localIpAddress(): String
/** /**
* 上传图片 * 上传好友图片
*/ */
expect suspend fun httpPostFriendImage( expect suspend fun httpPostFriendImage(
uKeyHex: String, uKeyHex: String,
fileSize: Int, fileSize: Long,
botNumber: UInt, botNumber: UInt,
qq: UInt, qq: UInt,
imageData: ByteArray imageData: ByteReadPacket
): Boolean
/**
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
uKeyHex: String,
fileSize: Long,
imageData: ByteReadPacket
): Boolean ): Boolean
fun main() { 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 { ...@@ -70,18 +70,20 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
} }
0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this) 0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this)
0x00_EC_u -> ServerLoginSuccessPacket(this) 0x00_ECu -> ServerLoginSuccessPacket(this)
0x00_1D_u -> ServerSKeyResponsePacket.Encrypted(this) 0x00_1Du -> ServerSKeyResponsePacket.Encrypted(this)
0x00_5C_u -> ServerAccountInfoResponsePacket.Encrypted(this) 0x00_5Cu -> ServerAccountInfoResponsePacket.Encrypted(this)
0x00_58_u -> ServerHeartbeatResponsePacket(this) 0x00_BAu -> ServerCaptchaPacket.Encrypted(this)
0x00_BA_u -> ServerCaptchaPacket.Encrypted(this) 0x00_CEu, 0x00_17u -> ServerEventPacket.Raw.Encrypted(this, id, sequenceId)
0x00_CE_u, 0x00_17_u -> ServerEventPacket.Raw.Encrypted(this, id, sequenceId) 0x00_81u -> ServerFriendOnlineStatusChangedPacket.Encrypted(this)
0x00_81_u -> ServerFriendOnlineStatusChangedPacket.Encrypted(this) 0x00_CDu -> ServerSendFriendMessageResponsePacket(this)
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this) 0x00_02u -> ServerSendGroupMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(this) 0x00_A7u -> ServerCanAddFriendResponsePacket(this)
0x00_A7_u -> ServerCanAddFriendResponsePacket(this)
0x03_52_u -> ServerTryGetImageIDResponsePacket.Encrypted(this) 0x00_58u -> ServerSessionPacket.Encrypted<HeartbeatPacket.Response>(this)
0x01_BDu -> ServerSubmitImageFilenameResponsePacket.Encrypted(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) else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applySequence(sequenceId) }.applySequence(sequenceId)
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.core.buildPacket
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream import java.io.OutputStream
import java.security.MessageDigest
import javax.imageio.ImageIO 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 return PlatformImage(this.width, this.height, digest.digest(), formatName, buffer)
actual fun BufferedImage.toByteArray(formatName: String): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "PNG", it); it.toByteArray() } }
actual val PlatformImage.imageWidth: Int get() = this.width actual val PlatformImage.imageWidth: Int get() = this.width
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
package net.mamoe.mirai.utils 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.HttpURLConnection
import java.net.InetAddress import java.net.InetAddress
import java.net.URL import java.net.URL
...@@ -28,10 +33,10 @@ fun main() { ...@@ -28,10 +33,10 @@ fun main() {
actual suspend fun httpPostFriendImage( actual suspend fun httpPostFriendImage(
uKeyHex: String, uKeyHex: String,
fileSize: Int, fileSize: Long,
botNumber: UInt, botNumber: UInt,
qq: UInt, qq: UInt,
imageData: ByteArray imageData: ByteReadPacket
): Boolean {/*Jsoup ): Boolean {/*Jsoup
//htdata2.qq.com //htdata2.qq.com
...@@ -60,7 +65,7 @@ actual suspend fun httpPostFriendImage( ...@@ -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" + "?htcmd=0x6ff0070" +
"&ver=5603" + "&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "") + "&ukey=" + uKeyHex.replace(" ", "") +
...@@ -69,18 +74,55 @@ actual suspend fun httpPostFriendImage( ...@@ -69,18 +74,55 @@ actual suspend fun httpPostFriendImage(
"&uin=" + botNumber.toLong()).openConnection() as HttpURLConnection "&uin=" + botNumber.toLong()).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient") conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", "" + fileSize) conn.setRequestProperty("Content-Length", "" + fileSize)
conn.setRequestProperty("connection", "Keep-Alive") conn.setRequestProperty("Connection", "Keep-Alive")
conn.setRequestProperty("Content-type", "image/png")
conn.requestMethod = "POST" conn.requestMethod = "POST"
conn.doOutput = true conn.doOutput = true
conn.doInput = true conn.doInput = true
withContext(Dispatchers.IO) {
conn.connect() conn.connect()
}
val buffered = conn.outputStream.buffered() conn.outputStream.writePacket(imageData)
buffered.write(imageData)
buffered.flush()
println(conn.responseMessage) println(conn.responseMessage)
println(conn.responseCode) println(conn.responseCode)
return conn.responseCode == 200 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 @@ ...@@ -2,7 +2,9 @@
package demo1 package demo1
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
...@@ -78,12 +80,21 @@ suspend fun main() { ...@@ -78,12 +80,21 @@ suspend fun main() {
} }
"上传好友图片" in it.message -> withTimeoutOrNull(3000) { "上传好友图片" 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) it.reply(id.value)
delay(1000) delay(1000)
it.reply(Image(id)) 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 -> /*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() 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