Commit 0494b374 authored by Him188's avatar Him188

Image sending is now available

parent bc57001e
...@@ -90,7 +90,7 @@ data class PlainText(override val stringValue: String) : Message() { ...@@ -90,7 +90,7 @@ data class PlainText(override val stringValue: String) : Message() {
* 图片消息. 在发送时将会区分群图片和好友图片发送. * 图片消息. 在发送时将会区分群图片和好友图片发送.
* 由接收消息时构建, 可直接发送 * 由接收消息时构建, 可直接发送
* *
* @param id 类似 `/01ee6426-5ff1-4cf0-8278-e8634d2909ef` 或 `{F61593B5-5B98-1798-3F47-2A91D32ED2FC}.jpg` * @param id 好友的为 `/01ee6426-5ff1-4cf0-8278-e8634d2909ef`, 群的为 `{F61593B5-5B98-1798-3F47-2A91D32ED2FC}.jpg`
* @param filename 文件名. 这将决定图片的显示 * @param filename 文件名. 这将决定图片的显示
*/ */
data class Image(val id: ImageId, val filename: String = "") : Message() { data class Image(val id: ImageId, val filename: String = "") : Message() {
......
...@@ -203,11 +203,10 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket { ...@@ -203,11 +203,10 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
* 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66 41 * 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66 41
*/ */
writeShortLVPacket { writeShortLVPacket {
//todo
writeByte(0x02) writeByte(0x02)
//"46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67".hexToBytes().stringOfWitch() //"46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67".hexToBytes().stringOfWitch()
// writeShortLVString(filename)//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg // writeShortLVString(filename)//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg
writeShortLVString(id.value.substring(1..24) + ".gif")//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg writeShortLVString(id.value.substring(1..24) + ".gif")//图片文件名. 后缀不影响. 但无后缀会导致 PC QQ 无法显示这个图片
writeHex("03 00 04 00 00 02 A2 04") writeHex("03 00 04 00 00 02 A2 04")
writeShortLVString(id.value) writeShortLVString(id.value)
writeHex("14 00 04 03 00 00 00 0B 00 00 18") writeHex("14 00 04 03 00 00 00 0B 00 00 18")
...@@ -215,11 +214,14 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket { ...@@ -215,11 +214,14 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 ") writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 ")
writeStringUtf8("674e")//补长度 writeStringUtf8("674e")//没有 "e" 服务器就不回复
writeStringUtf8(id.value.substring(1..id.value.lastIndex - 4).replace("-", "B"))//这一串文件名决定手机QQ保存的图片. 可以随意
writeStringUtf8(".gif") writeStringUtf8(id.value.substring(1..id.value.lastIndex - 4))//这一串文件名决定手机 QQ 保存的图片. 可以随意
writeStringUtf8(".gif")//后缀要有
writeUByte(0x66u) writeUByte(0x66u)
//有时候 PC QQ 看不到发这些消息, 但手机可以. 可能是 ID 过期了, 手机有缓存而电脑没有
/* /*
* writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 ") * writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 ")
......
...@@ -187,7 +187,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -187,7 +187,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
bot.logCyan("Packet received: $packet") bot.logCyan("Packet received: $packet")
} }
//coz removeIf is not inline
handlersLock.withLock { handlersLock.withLock {
temporaryPacketHandlers.removeIfInlined { temporaryPacketHandlers.removeIfInlined {
it.shouldRemove(this@TIMBotNetworkHandler[ActionPacketHandler].session, packet) it.shouldRemove(this@TIMBotNetworkHandler[ActionPacketHandler].session, packet)
......
...@@ -14,7 +14,7 @@ import net.mamoe.mirai.utils.* ...@@ -14,7 +14,7 @@ import net.mamoe.mirai.utils.*
/** /**
* 上传图片 * 上传图片
*/ */
suspend fun QQ.uploadImage(image: PlatformImage): ImageId = with(bot.network.session) { suspend fun QQ.uploadImage(image: BufferedImage): ImageId = with(bot.network.session) {
//SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join() //SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join()
DebugLogger.logPurple("正在上传好友图片, md5=${image.md5.toUHexString()}") DebugLogger.logPurple("正在上传好友图片, md5=${image.md5.toUHexString()}")
return FriendImageIdRequestPacket(account, sessionKey, account, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> { return FriendImageIdRequestPacket(account, sessionKey, account, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
...@@ -22,12 +22,12 @@ suspend fun QQ.uploadImage(image: PlatformImage): ImageId = with(bot.network.ses ...@@ -22,12 +22,12 @@ suspend fun QQ.uploadImage(image: PlatformImage): ImageId = with(bot.network.ses
require(httpPostFriendImage( require(httpPostFriendImage(
uKeyHex = it.uKey!!.toUHexString(""), uKeyHex = it.uKey!!.toUHexString(""),
botNumber = bot.qqAccount, botNumber = bot.qqAccount,
imageData = image.fileData, imageData = image.data,
fileSize = image.fileSize, fileSize = image.fileSize,
qq = account qq = account
)) ))
it.imageId!! it.imageId!!
} else image.id //todo 是这个么 } else TODO("分析服务器已有图片时的 imageId")
}.await() }.await()
} }
...@@ -40,6 +40,9 @@ suspend fun QQ.uploadImage(image: PlatformImage): ImageId = with(bot.network.ses ...@@ -40,6 +40,9 @@ suspend fun QQ.uploadImage(image: PlatformImage): ImageId = with(bot.network.ses
//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 //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 // 00 01 61 A7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01
/**
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
*/
@PacketId(0X01_BDu) @PacketId(0X01_BDu)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
class SubmitImageFilenamePacket( class SubmitImageFilenamePacket(
...@@ -95,16 +98,14 @@ class SubmitImageFilenamePacket( ...@@ -95,16 +98,14 @@ class SubmitImageFilenamePacket(
* 服务器返回以下之一: * 服务器返回以下之一:
* - 服务器已经存有这个图片 * - 服务器已经存有这个图片
* - 服务器未存有, 返回一个 key 用于客户端上传 * - 服务器未存有, 返回一个 key 用于客户端上传
*
* @author Him188moe
*/ */
@PacketId(0x03_52u) @PacketId(0x03_52u)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
class FriendImageIdRequestPacket( class FriendImageIdRequestPacket(
private val botNumber: UInt, private val botNumber: UInt,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val target: UInt, private val target: UInt,
private val image: PlatformImage private val image: BufferedImage
) : ClientPacket() { ) : ClientPacket() {
//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 //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
...@@ -240,9 +241,9 @@ class FriendImageIdRequestPacket( ...@@ -240,9 +241,9 @@ class FriendImageIdRequestPacket(
writeUShort(0x48_00u) writeUShort(0x48_00u)
writeUByte(0x70u) writeUByte(0x70u)
writeUVarInt(image.imageWidth.toUInt()) writeUVarInt(image.width.toUInt())
writeUByte(0x78u) writeUByte(0x78u)
writeUVarInt(image.imageHeight.toUInt()) writeUVarInt(image.height.toUInt())
} }
writeShort((packet.remaining - 7).toShort())//why? writeShort((packet.remaining - 7).toShort())//why?
writePacket(packet) writePacket(packet)
...@@ -283,9 +284,9 @@ class FriendImageIdRequestPacket( ...@@ -283,9 +284,9 @@ class FriendImageIdRequestPacket(
} else { } else {
//服务器已经有这个图片了 //服务器已经有这个图片了
DebugLogger.logPurple("服务器已有好友图片 ") DebugLogger.logPurple("服务器已有好友图片 ")
}
println("获取图片 repsonse 后文=" + readRemainingBytes().toUHexString()) println("获取图片 repsonse 后文=" + readRemainingBytes().toUHexString())
TODO("分析后文获取 imageId")
}
} }
} }
} }
......
...@@ -10,13 +10,13 @@ import net.mamoe.mirai.utils.* ...@@ -10,13 +10,13 @@ import net.mamoe.mirai.utils.*
suspend fun Group.uploadImage( suspend fun Group.uploadImage(
image: PlatformImage image: BufferedImage
) = with(bot.network.session) { ) = with(bot.network.session) {
GroupImageIdRequestPacket(bot.qqAccount, groupId, image, sessionKey) GroupImageIdRequestPacket(bot.qqAccount, groupId, image, sessionKey)
.sendAndExpect<GroupImageIdRequestPacket.Response, Unit> { .sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
if (it.uKey != null) { if (it.uKey != null) {
httpPostGroupImage( httpPostGroupImage(
imageData = image.fileData, imageData = image.data,
fileSize = image.fileSize, fileSize = image.fileSize,
uKeyHex = it.uKey!!.toUHexString() uKeyHex = it.uKey!!.toUHexString()
) )
...@@ -32,7 +32,7 @@ suspend fun Group.uploadImage( ...@@ -32,7 +32,7 @@ suspend fun Group.uploadImage(
class GroupImageIdRequestPacket( class GroupImageIdRequestPacket(
private val bot: UInt, private val bot: UInt,
private val groupId: UInt, private val groupId: UInt,
private val image: PlatformImage, private val image: BufferedImage,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
......
...@@ -3,18 +3,18 @@ package net.mamoe.mirai.utils ...@@ -3,18 +3,18 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.ImageId
class PlatformImage( class BufferedImage(
val width: Int, val width: Int,
val height: Int, val height: Int,
val md5: ByteArray, val md5: ByteArray,
val format: String, val format: String,
val fileData: ByteReadPacket val data: ByteReadPacket
) { ) {
val fileSize: Long = fileData.remaining val fileSize: Long = data.remaining
val id: ImageId by lazy { ImageId("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format") } 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 = "[PlatformImage(${width}x${height} $format)]" override fun toString(): String = "[BufferedImage(${width}x${height} $format)]"
} }
private operator fun ByteArray.get(range: IntRange): String = buildString { private operator fun ByteArray.get(range: IntRange): String = buildString {
...@@ -22,7 +22,3 @@ private operator fun ByteArray.get(range: IntRange): String = buildString { ...@@ -22,7 +22,3 @@ private operator fun ByteArray.get(range: IntRange): String = buildString {
append(this@get[it].toUHexString()) append(this@get[it].toUHexString())
} }
} }
\ No newline at end of file
expect val PlatformImage.imageWidth: Int
expect val PlatformImage.imageHeight: Int
\ No newline at end of file
...@@ -3,17 +3,19 @@ ...@@ -3,17 +3,19 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
import java.awt.image.BufferedImage import kotlinx.io.streams.writePacket
import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.security.MessageDigest import java.security.MessageDigest
import javax.imageio.ImageIO import javax.imageio.ImageIO
import java.awt.image.BufferedImage as JavaBufferedImage
fun BufferedImage.toPlatformImage(formatName: String = "png"): PlatformImage { fun JavaBufferedImage.toMiraiImage(formatName: String = "png"): BufferedImage {
val digest = MessageDigest.getInstance("md5") val digest = MessageDigest.getInstance("md5")
digest.reset() digest.reset()
val buffer = buildPacket { val buffer = buildPacket {
ImageIO.write(this@toPlatformImage, formatName, object : OutputStream() { ImageIO.write(this@toMiraiImage, formatName, object : OutputStream() {
override fun write(b: Int) { override fun write(b: Int) {
b.toByte().let { b.toByte().let {
this@buildPacket.writeByte(it) this@buildPacket.writeByte(it)
...@@ -23,9 +25,18 @@ fun BufferedImage.toPlatformImage(formatName: String = "png"): PlatformImage { ...@@ -23,9 +25,18 @@ fun BufferedImage.toPlatformImage(formatName: String = "png"): PlatformImage {
}) })
} }
return PlatformImage(this.width, this.height, digest.digest(), formatName, buffer) return BufferedImage(width, height, digest.digest(), formatName, buffer)
} }
actual val PlatformImage.imageWidth: Int get() = this.width fun BufferedImage.toJavaImage(): JavaBufferedImage = ImageIO.read(object : InputStream() {
override fun read(): Int = with(this@toJavaImage.data) {
if (remaining != 0L)
readByte().toInt()
else -1
}
})
actual val PlatformImage.imageHeight: Int get() = this.height /**
\ No newline at end of file * 将缓存的图片写入流. 注意, 写入后缓存将会被清空.
*/
fun OutputStream.writeImage(image: BufferedImage) = this.writePacket(image.data)
\ No newline at end of file
...@@ -5,8 +5,8 @@ package net.mamoe.mirai.utils ...@@ -5,8 +5,8 @@ package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.streams.writePacket
import org.jsoup.Connection 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
...@@ -118,11 +118,3 @@ actual suspend fun httpPostGroupImage(uKeyHex: String, fileSize: Long, imageData ...@@ -118,11 +118,3 @@ actual suspend fun httpPostGroupImage(uKeyHex: String, fileSize: Long, imageData
private suspend fun Connection.suspendExecute(): Connection.Response = withContext(Dispatchers.IO) { private suspend fun Connection.suspendExecute(): Connection.Response = withContext(Dispatchers.IO) {
execute() execute()
} }
\ No newline at end of file
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
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import net.mamoe.mirai.network.protocol.tim.packet.GroupImageIdRequestPacket import net.mamoe.mirai.network.protocol.tim.packet.GroupImageIdRequestPacket
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.readRemainingBytes import net.mamoe.mirai.utils.readRemainingBytes
import net.mamoe.mirai.utils.toPlatformImage import net.mamoe.mirai.utils.toMiraiImage
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.toUHexString
import java.io.File import java.io.File
import javax.imageio.ImageIO import javax.imageio.ImageIO
...@@ -11,7 +11,7 @@ import javax.imageio.ImageIO ...@@ -11,7 +11,7 @@ import javax.imageio.ImageIO
val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes() val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes()
fun main() = println({ fun main() = println({
val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.png").readBytes().inputStream()).toPlatformImage("png") val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.png").readBytes().inputStream()).toMiraiImage("png")
// File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes()) // File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes())
GroupImageIdRequestPacket( GroupImageIdRequestPacket(
......
...@@ -81,18 +81,18 @@ suspend fun main() { ...@@ -81,18 +81,18 @@ suspend fun main() {
"上传好友图片" in it.message -> withTimeoutOrNull(3000) { "上传好友图片" in it.message -> withTimeoutOrNull(3000) {
val id = QQ(bot, 1040400290u) val id = QQ(bot, 1040400290u)
.uploadImage(withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\色图.jpg").readBytes().inputStream()) }.toPlatformImage("png")) .uploadImage(withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\色图.jpg").readBytes().inputStream()) }.toMiraiImage("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) { "上传群图片" in it.message -> withTimeoutOrNull(3000) {
val image = withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\色图.jpg").readBytes().inputStream()) }.toPlatformImage("png") val image = withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\色图.jpg").readBytes().inputStream()) }.toMiraiImage("png")
Group(bot, 580266363u).uploadImage(image) Group(bot, 580266363u).uploadImage(image)
it.reply(image.id.value) it.reply(image.groupImageId.value)
delay(1000) delay(1000)
Group(bot, 580266363u).sendMessage(Image(image.id)) Group(bot, 580266363u).sendMessage(Image(image.groupImageId))
} }
"发群图片" in it.message -> { "发群图片" in it.message -> {
......
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