Image uploading is now available

This commit is contained in:
Him188 2019-10-21 01:21:24 +08:00
parent 0e5f6d4787
commit d6201a14ba
10 changed files with 90 additions and 108 deletions

View File

@ -59,7 +59,7 @@ subscribe<FriendMessageEvent>{
![JsssF](.github/J%5DCE%29IK4BU08%28EO~UVLJ%7B%5BF.png)
![](.github/68f8fec9.png)
发送图片已经完成,但我们还在开发上传图片至服务器。
上传发送图片已经完成, 您可以在 Demo 中找到发送方式.
机器人可以转发图片消息.详情查看 [Image.kt](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt#L81)
## 现已支持
@ -70,6 +70,7 @@ subscribe<FriendMessageEvent>{
- 成员权限, 昵称(10/18)
- 好友在线状态改变(10/14)
- Android客户端上线/下线(10/18)
- 上传并发送图片(10/21)
## 使用方法
### 要求

View File

@ -20,7 +20,8 @@ class ServerFriendOnlineStatusChangedPacket(input: ByteReadPacket) : ServerPacke
override fun decode() = with(input) {
qq = readUInt()
discardExact(8)
status = OnlineStatus.ofId(readUByte())
val id = readUByte()
status = OnlineStatus.ofId(id) ?: error("Unknown online status id $id")
}
//在线 XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00

View File

@ -18,7 +18,7 @@ suspend fun QQ.uploadImage(image: BufferedImage): ImageId = with(bot.network.ses
//SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join()
DebugLogger.logPurple("正在上传好友图片, md5=${image.md5.toUHexString()}")
return FriendImageIdRequestPacket(account, sessionKey, account, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
if (it.uKey != null) {
if (it.uKey != null)
require(httpPostFriendImage(
uKeyHex = it.uKey!!.toUHexString(""),
botNumber = bot.qqAccount,
@ -26,8 +26,7 @@ suspend fun QQ.uploadImage(image: BufferedImage): ImageId = with(bot.network.ses
fileSize = image.fileSize,
qq = account
))
it.imageId!!
} else TODO("分析服务器已有图片时的 imageId")
it.imageId!!
}.await()
}
@ -255,8 +254,8 @@ class FriendImageIdRequestPacket(
@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
var uKey: ByteArray? = null//最终可能为null
var imageId: ImageId? = null//最终不会为null
override fun decode() = with(input) {
//00 00 00 08 00 00
@ -279,13 +278,17 @@ class FriendImageIdRequestPacket(
discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}")
DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}")
//DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}")
//DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}")
} else {
//服务器已经有这个图片了
DebugLogger.logPurple("服务器已有好友图片 ")
println("获取图片 repsonse 后文=" + readRemainingBytes().toUHexString())
TODO("分析后文获取 imageId")
//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
discardExact(60)
discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
}
}
}

View File

@ -16,9 +16,11 @@ suspend fun Group.uploadImage(
.sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
if (it.uKey != null) {
httpPostGroupImage(
bot = bot.qqAccount,
groupNumber = groupId,
imageData = image.data,
fileSize = image.fileSize,
uKeyHex = it.uKey!!.toUHexString()
uKeyHex = it.uKey!!.toUHexString("")
)
}
}.await()
@ -111,7 +113,7 @@ class GroupImageIdRequestPacket(
// 80 01 00
/*
/*
writeQQ(bot)
writeHex(TIMProtocol.version0x04)
@ -145,8 +147,8 @@ class GroupImageIdRequestPacket(
}
writeTV(0x38_01u)
writeTV(0x48_01u)
writeTUVarint(0x50u, image.imageWidth.toUInt())
writeTUVarint(0x58u, image.imageHeight.toUInt())
writeTUVarint(0x50u, image.width.toUInt())
writeTUVarint(0x58u, image.height.toUInt())
writeTV(0x60_02u)
writeTByteArray(0x6Au, value0x6A)
}
@ -159,6 +161,7 @@ class GroupImageIdRequestPacket(
// this.debugColorizedPrintThis(compareTo = "00 00 00 07 00 00 00 5D 08 01 12 03 98 01 01 10 01 1A 59 08 FB D2 D8 94 02 10 A2 FF 8C F0 03 18 00 22 10 1D D2 2B 9B BC F2 10 83 DC 99 D2 2E 20 39 CC 0E 28 8A 03 32 1A 5B 00 40 00 33 00 48 00 5F 00 58 00 46 00 51 00 45 00 51 00 40 00 24 00 4F 00 38 01 48 01 50 EF 01 58 C2 01 60 02 6A 05 32 36 39 33 33 70 00 78 03 80 01 00")
}*/
writeQQ(bot)
writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00")
@ -193,9 +196,13 @@ class GroupImageIdRequestPacket(
override fun decode(): Unit = with(input) {
discardExact(6)//00 00 00 05 00 00
//if (readUByte() != UByte.MIN_VALUE) {
//服务器还没有
discardExact(remaining - 128 - 14)
val length = remaining - 128 - 14
if (length < 0) {
//服务器已经有这个图片了
return@with
}
discardExact(length)
uKey = readBytes(128)
//} else {
// println("服务器已经有了这个图片")

View File

@ -7,10 +7,7 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.utils.printStringFromHex
import net.mamoe.mirai.utils.read
import net.mamoe.mirai.utils.readLVByteArray
import net.mamoe.mirai.utils.readTLVMap
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
@ -45,7 +42,7 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
val map = readTLVMap(true)
//map.printTLVMap("父map")
if (map.containsKey(18)) {
senderName = map.getValue(18).read {
map.getValue(18).read {
val tlv = readTLVMap(true)
//tlv.printTLVMap("子map")
////群主的18: 05 00 04 00 00 00 03 08 00 04 00 00 00 04 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
@ -66,10 +63,13 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
}
0x01u -> SenderPermission.MEMBER
else -> error("Could not determine member permission, unknown tlv(key=0x03,value=$value0x03)")
else -> {
tlv.printTLVMap("Child TLV map")
error("Could not determine member permission, unknown TLV(key=0x03,value=$value0x03;)")
}
}
when {
senderName = when {
tlv.containsKey(0x01) -> kotlinx.io.core.String(tlv.getValue(0x01))//这个人的qq昵称
tlv.containsKey(0x02) -> kotlinx.io.core.String(tlv.getValue(0x02))//这个人的群名片
else -> "null"

View File

@ -23,6 +23,6 @@ enum class OnlineStatus(
companion object {
fun ofId(id: UByte): OnlineStatus = values().first { it.id == id }
fun ofId(id: UByte): OnlineStatus? = values().firstOrNull { it.id == id }
}
}

View File

@ -53,6 +53,8 @@ expect suspend fun httpPostFriendImage(
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
bot: UInt,
groupNumber: UInt,
uKeyHex: String,
fileSize: Long,
imageData: ByteReadPacket

View File

@ -41,7 +41,7 @@ private object IgnoreIdList : List<String> by listOf(
)
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 { it.name in IgnoreIdList || /*"delegate" in it.name||*/ "$" in it.name }
.joinToString(", ", "{", "}") {
it.isAccessible = true
it.name + "=" + it.get(this).let { value ->

View File

@ -5,11 +5,10 @@ package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.streams.writePacket
import kotlinx.io.core.readBytes
import org.jsoup.Connection
import java.net.HttpURLConnection
import org.jsoup.Jsoup
import java.net.InetAddress
import java.net.URL
import java.security.MessageDigest
import java.util.zip.CRC32
@ -37,83 +36,46 @@ actual suspend fun httpPostFriendImage(
botNumber: UInt,
qq: UInt,
imageData: ByteReadPacket
): Boolean {/*Jsoup
//htdata2.qq.com
// 101.227.143.109/cgi-bin/httpconn
// ?htcmd=0x6ff0070
// &ver=5603
// &ukey=3B121C959B85035F12497519221FB9E09740E477D3A440D28253E96C95BD72EA1D11B25189894F0256F3E0F3D553FB92925A8834F85583C78D0D9639A3F35C730783D45C065FF9E9E74765183A11492D50750C6BB5DCAD9F171285B68F6A11061CDDA740AD2DCD28D5B2DB2D6440143FA53F1B6F14584DB49E926FDDC4F49907
// &filesize=137791
// &range=0
// &uin=1040400290
.connect("http://101.227.143.109/cgi-bin/httpconn" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + botNumber.toLong())
//.userAgent("QQClient")
.header("Content-Length", fileSize.toString())
.requestBody(String(imageData, Charset.forName("ASCII")))
.method(Connection.Method.POST)
.ignoreContentType(true)
.let {
withContext(Dispatchers.IO) {
it.execute()
}
};*/
val conn = URL("http://htdata2.qq.com/cgi-bin/httpconn" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + botNumber.toLong()).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", imageData.toString())
conn.setRequestProperty("Connection", "Keep-Alive")
conn.requestMethod = "POST"
conn.doOutput = true
conn.doInput = true
withContext(Dispatchers.IO) {
conn.connect()
}
conn.outputStream.writePacket(imageData)
println(conn.responseMessage)
println(conn.responseCode)
return conn.responseCode == 200
}
): Boolean = Jsoup.connect("http://htdata2.qq.com/cgi-bin/httpconn" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&ukey=${uKeyHex}" +
"&filezise=${imageData.remaining}" +
"&range=0" +
"&uin=$botNumber")
.postImage(imageData)
/**
* 上传群图片
*/
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", imageData.remaining.toString())
conn.setRequestProperty("Connection", "Keep-Alive")
conn.requestMethod = "POST"
conn.doOutput = true
conn.doInput = true
withContext(Dispatchers.IO) {
conn.connect()
}
actual suspend fun httpPostGroupImage(
bot: UInt,
groupNumber: UInt,
uKeyHex: String,
fileSize: Long,
imageData: ByteReadPacket
): Boolean = Jsoup.connect("http://htdata2.qq.com/cgi-bin/httpconn" +
"?htcmd=0x6ff0071" +
"&term=pc" +
"&ver=5603" +
"&filesize=${imageData.remaining}" +
"&uin=$bot" +
"&groupcode=$groupNumber" +
"&range=0" +
"&ukey=" + uKeyHex)
.postImage(imageData)
val stream = conn.outputStream
stream.writePacket(imageData)
println(conn.responseMessage)
println(conn.responseCode)
return conn.responseCode == 200
}
private suspend fun Connection.postImage(image: ByteReadPacket): Boolean = this
.userAgent("QQClient")
.header("Content-Length", image.remaining.toString())
.requestBody(String(image.readBytes(), Charsets.ISO_8859_1))
.method(Connection.Method.POST)
.postDataCharset("ISO_8859_1")
.header("Content-type", "image/png")
.ignoreContentType(true)
.suspendExecute()
.statusCode() == 200
private suspend fun Connection.suspendExecute(): Connection.Response = withContext(Dispatchers.IO) {
execute()

View File

@ -57,6 +57,12 @@ suspend fun main() {
}
}
subscribeAlways<GroupMessageEvent> {
if (it.message eq "复读" && it.group.groupId == 580266363u) {
it.reply(it.message)
}
}
//提供泛型以监听事件
subscribeAlways<FriendMessageEvent> {
//获取第一个纯文本消息, 获取不到会抛出 NoSuchElementException
@ -71,7 +77,7 @@ suspend fun main() {
"复读" in it.message -> it.sender.sendMessage(it.message)
"发群" in it.message -> Group(bot, 580266363u).sendMessage("h")
"发群消息" in it.message -> Group(bot, 580266363u).sendMessage(it.message.toString().substringAfter("发群消息"))
"直接发送包" in it.message -> {
val d = ("01 " + 1994701021u.toByteArray().toUHexString() + " 3E 03 3F A2 00 00 02 BB 00 0A 00 01 00 01 00 5E 4F 53 52 6F 6F 74 3A 43 3A 5C 55 73 65 72 73 5C 48 69 6D 31 38 5C 44 6F 63 75 6D 65 6E 74 73 5C 54 65 6E 63 65 6E 74 20 46 69 6C 65 73 5C 31 30 34 30 34 30 30 32 39 30 5C 49 6D 61 67 65 5C 43 32 43 5C 7B 47 47 42 7E 49 31 5A 4D 43 28 25 49 4D 5A 5F 47 55 51 36 35 5D 51 2E 6A 70 67 00 00 04 7D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 35 02")
@ -79,16 +85,16 @@ suspend fun main() {
it.bot.network.socket.sendPacket(ClientRawPacket(0x01_BDu, it.bot.qqAccount, "00 00 00 01 2E 01 00 00 69 35".hexToBytes(), it.bot.network.session.sessionKey, d))
}
"上传好友图片" in it.message -> withTimeoutOrNull(3000) {
"上传好友图片" in it.message -> withTimeoutOrNull(5000) {
val id = QQ(bot, 1040400290u)
.uploadImage(withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\色图.jpg").readBytes().inputStream()) }.toMiraiImage("png"))
.uploadImage(withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传好友图片")}").readBytes().inputStream()) }.toMiraiImage("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\\色图.jpg").readBytes().inputStream()) }.toMiraiImage("png")
"上传群图片" in it.message -> withTimeoutOrNull(5000) {
val image = withContext(Dispatchers.IO) { ImageIO.read(File("C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传群图片")}").readBytes().inputStream()) }.toMiraiImage("png")
Group(bot, 580266363u).uploadImage(image)
it.reply(image.groupImageId.value)
delay(1000)
@ -96,7 +102,7 @@ suspend fun main() {
}
"发群图片" in it.message -> {
Group(bot, 580266363u).sendMessage(Image(ImageId(it.message.toString().substringAfter("图片"))))
Group(bot, 580266363u).sendMessage(Image(ImageId(it.message.toString().substringAfter("图片"))))
}
"发好友图片" in it.message -> {