Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-02-03 22:00:27 +08:00
commit a776b25b27
16 changed files with 1161 additions and 85 deletions

View File

@ -1,15 +1,26 @@
package net.mamoe.mirai.qqandroid
import kotlinx.io.core.readBytes
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.FriendNameRemark
import net.mamoe.mirai.data.PreviousNameList
import net.mamoe.mirai.data.Profile
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.NotOnlineImageFromFile
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.network.highway.Highway
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.unsafeWeakRef
import kotlin.coroutines.CoroutineContext
@ -116,7 +127,73 @@ internal class GroupImpl(
}
override suspend fun uploadImage(image: ExternalImage): Image {
TODO("not implemented")
}
bot.network.run {
val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
bot.client,
uin = bot.uin,
groupCode = id,
md5 = image.md5,
size = image.inputSize,
picWidth = image.width,
picHeight = image.height,
picType = image.imageType,
filename = image.filename
).sendAndExpect()
when (response) {
is ImgStore.GroupPicUp.Response.Failed -> error("upload group image failed with reason ${response.message}")
is ImgStore.GroupPicUp.Response.FileExists -> {
val resourceId = image.calculateImageResourceId()
return NotOnlineImageFromFile(
resourceId = resourceId,
md5 = response.fileInfo.fileMd5,
filepath = resourceId,
fileLength = response.fileInfo.fileSize.toInt(),
height = response.fileInfo.fileHeight,
width = response.fileInfo.fileWidth,
imageType = response.fileInfo.fileType
)
}
is ImgStore.GroupPicUp.Response.RequireUpload -> {
val socket = PlatformSocket()
socket.connect(response.uploadIpList.first().toIpV4AddressString().also { println("serverIp=$it") }, response.uploadPortList.first())
// socket.use {
socket.send(
Highway.RequestDataTrans(
uin = bot.uin,
command = "PicUp.DataUp",
buildVer = bot.client.buildVer,
uKey = response.uKey,
data = image.input,
dataSize = image.inputSize.toInt(),
md5 = image.md5,
sequenceId = bot.client.nextHighwayDataTransSequenceId()
)
)
// }
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse {
readByte()
val headLength = readInt()
val bodyLength = readInt()
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
println(proto.contentToString())
println(readBytes(bodyLength).toUHexString())
}
val resourceId = image.calculateImageResourceId()
return NotOnlineImageFromFile(
resourceId = resourceId,
md5 = image.md5,
filepath = resourceId,
fileLength = image.inputSize.toInt(),
height = image.height,
width = image.width,
imageType = image.imageType
)
}
}
}
}
}

View File

@ -95,7 +95,8 @@ internal open class QQAndroidClient(
var openAppId: Long = 715019303L
val apkVersionName: ByteArray = "8.2.0".toByteArray()
val apkVersionName: ByteArray get() = "8.2.0".toByteArray()
val buildVer: String get() = "8.2.0.1296"
private val messageSequenceId: AtomicInt = atomic(0)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
@ -103,6 +104,9 @@ internal open class QQAndroidClient(
private val requestPacketRequestId: AtomicInt = atomic(1921334513)
internal fun nextRequestPacketRequestId(): Int = requestPacketRequestId.getAndAdd(2)
private val highwayDataTransSequenceId: AtomicInt = atomic(87017)
internal fun nextHighwayDataTransSequenceId(): Int = highwayDataTransSequenceId.getAndAdd(2)
val appClientVersion: Int = 0
var networkType: NetworkType = NetworkType.WIFI

View File

@ -0,0 +1,129 @@
package net.mamoe.mirai.qqandroid.network.highway
import io.ktor.client.HttpClient
import io.ktor.client.request.post
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.content.OutgoingContent
import io.ktor.http.userAgent
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.io.ByteArrayPool
@Suppress("SpellCheckingInspection")
internal suspend inline fun HttpClient.postImage(
htcmd: String,
uin: Long,
groupcode: Long?,
imageInput: Input,
inputSize: Long,
uKeyHex: String
): Boolean = try {
post<HttpStatusCode> {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toString()
if (groupcode != null) parameters["groupcode"] = groupcode.toString()
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filesize"] = inputSize.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
userAgent("QQClient")
}
body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize
override suspend fun writeTo(channel: ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray ->
var size: Int
while (imageInput.readAvailable(buffer).also { size = it } != 0) {
channel.writeFully(buffer, 0, size)
}
}
}
}
} == HttpStatusCode.OK
} finally {
imageInput.close()
}
object Highway {
fun RequestDataTrans(
uin: Long,
command: String,
sequenceId: Int,
buildVer: String,
appId: Int = 537062845,
dataFlag: Int = 4096,
commandId: Int = 2,
localId: Int = 2052,
uKey: ByteArray,
data: Input,
dataSize: Int,
md5: ByteArray
): ByteReadPacket {
val dataHighwayHead = CSDataHighwayHead.DataHighwayHead(
version = 1,
uin = uin.toString(),
command = command,
seq = sequenceId,
retryTimes = 0,
appid = appId,
dataflag = dataFlag,
commandId = commandId,
buildVer = buildVer,
localeId = localId
)
val segHead = CSDataHighwayHead.SegHead(
datalength = dataSize,
filesize = dataSize.toLong() and 0xFFffFFff,
serviceticket = uKey,
md5 = md5,
fileMd5 = md5
)
return Codec.buildC2SData(dataHighwayHead, segHead, EMPTY_BYTE_ARRAY, null, data, dataSize)
}
private object Codec {
fun buildC2SData(
dataHighwayHead: CSDataHighwayHead.DataHighwayHead,
segHead: CSDataHighwayHead.SegHead,
extendInfo: ByteArray,
loginSigHead: CSDataHighwayHead.LoginSigHead?,
body: Input,
bodySize: Int
): ByteReadPacket {
val head = CSDataHighwayHead.ReqDataHighwayHead(
msgBasehead = dataHighwayHead,
msgSeghead = segHead,
reqExtendinfo = extendInfo,
msgLoginSigHead = loginSigHead
).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer())
return buildPacket {
writeByte(40)
writeInt(head.size)
writeInt(bodySize)
writeFully(head)
body.copyTo(this)
writeByte(41)
}
}
}
}

View File

@ -0,0 +1,279 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Serializable
class Cmd0x388 : ProtoBuf {
@Serializable
class DelImgReq(
@SerialId(1) val srcUin: Long = 0L,
@SerialId(2) val dstUin: Long = 0L,
@SerialId(3) val reqTerm: Int = 0,
@SerialId(4) val reqPlatformType: Int = 0,
@SerialId(5) val buType: Int = 0,
@SerialId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val picWidth: Int = 0,
@SerialId(9) val picHeight: Int = 0
) : ProtoBuf
@Serializable
class DelImgRsp(
@SerialId(1) val result: Int = 0,
@SerialId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ExpRoamExtendInfo(
@SerialId(1) val resid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ExpRoamPicInfo(
@SerialId(1) val shopFlag: Int = 0,
@SerialId(2) val pkgId: Int = 0,
@SerialId(3) val picId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ExtensionCommPicTryUp(
@SerialId(1) val bytesExtinfo: List<ByteArray>? = null
) : ProtoBuf
@Serializable
class ExtensionExpRoamTryUp(
@SerialId(1) val msgExproamPicInfo: List<ExpRoamPicInfo>? = null
) : ProtoBuf
@Serializable
class GetImgUrlReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val dstUin: Long = 0L,
@SerialId(3) val fileid: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val urlFlag: Int = 0,
@SerialId(6) val urlType: Int = 0,
@SerialId(7) val reqTerm: Int = 0,
@SerialId(8) val reqPlatformType: Int = 0,
@SerialId(9) val innerIp: Int = 0,
@SerialId(10) val buType: Int = 0,
@SerialId(11) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val fileId: Long = 0L,
@SerialId(13) val fileSize: Long = 0L,
@SerialId(14) val originalPic: Int = 0,
@SerialId(15) val retryReq: Int = 0,
@SerialId(16) val fileHeight: Int = 0,
@SerialId(17) val fileWidth: Int = 0,
@SerialId(18) val picType: Int = 0,
@SerialId(19) val picUpTimestamp: Int = 0,
@SerialId(20) val reqTransferType: Int = 0
) : ProtoBuf
@Serializable
class GetImgUrlRsp(
@SerialId(1) val fileid: Long = 0L,
@SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val msgImgInfo: ImgInfo? = null,
@SerialId(6) val bytesThumbDownUrl: List<ByteArray>? = null,
@SerialId(7) val bytesOriginalDownUrl: List<ByteArray>? = null,
@SerialId(8) val bytesBigDownUrl: List<ByteArray>? = null,
@SerialId(9) val uint32DownIp: List<Int>? = null,
@SerialId(10) val uint32DownPort: List<Int>? = null,
@SerialId(11) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(13) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(15) val fileId: Long = 0L,
@SerialId(16) val autoDownType: Int = 0,
@SerialId(17) val uint32OrderDownType: List<Int>? = null,
@SerialId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(20) val httpsUrlFlag: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class GetPttUrlReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val dstUin: Long = 0L,
@SerialId(3) val fileid: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val reqTerm: Int = 0,
@SerialId(6) val reqPlatformType: Int = 0,
@SerialId(7) val innerIp: Int = 0,
@SerialId(8) val buType: Int = 0,
@SerialId(9) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val fileId: Long = 0L,
@SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val codec: Int = 0,
@SerialId(13) val buId: Int = 0,
@SerialId(14) val reqTransferType: Int = 0,
@SerialId(15) val isAuto: Int = 0
) : ProtoBuf
@Serializable
class GetPttUrlRsp(
@SerialId(1) val fileid: Long = 0L,
@SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val bytesDownUrl: List<ByteArray>? = null,
@SerialId(6) val uint32DownIp: List<Int>? = null,
@SerialId(7) val uint32DownPort: List<Int>? = null,
@SerialId(8) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val downPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val fileId: Long = 0L,
@SerialId(11) val transferType: Int = 0,
@SerialId(12) val allowRetry: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(28) val strDomain: String = ""
) : ProtoBuf
@Suppress("ArrayInDataClass")
@Serializable
data class ImgInfo(
@SerialId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val fileType: Int = 0,
@SerialId(3) val fileSize: Long = 0L,
@SerialId(4) val fileWidth: Int = 0,
@SerialId(5) val fileHeight: Int = 0
) : ProtoBuf {
override fun toString(): String {
return "ImgInfo(fileMd5=${fileMd5.contentToString()}, fileType=$fileType, fileSize=$fileSize, fileWidth=$fileWidth, fileHeight=$fileHeight)"
}
}
@Serializable
class IPv6Info(
@SerialId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val port: Int = 0
) : ProtoBuf
@Serializable
class PicSize(
@SerialId(1) val original: Int = 0,
@SerialId(2) val thumb: Int = 0,
@SerialId(3) val high: Int = 0
) : ProtoBuf
@Serializable
class ReqBody(
@SerialId(1) val netType: Int = 0,
@SerialId(2) val subcmd: Int = 0,
@SerialId(3) val msgTryupImgReq: List<TryUpImgReq>? = null,
@SerialId(4) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,
@SerialId(5) val msgTryupPttReq: List<TryUpPttReq>? = null,
@SerialId(6) val msgGetpttUrlReq: List<GetPttUrlReq>? = null,
@SerialId(7) val commandId: Int = 0,
@SerialId(8) val msgDelImgReq: List<DelImgReq>? = null,
@SerialId(1001) val extension: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class RspBody(
@SerialId(1) val clientIp: Int = 0,
@SerialId(2) val subcmd: Int = 0,
@SerialId(3) val msgTryupImgRsp: List<TryUpImgRsp>? = null,
@SerialId(4) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null,
@SerialId(5) val msgTryupPttRsp: List<TryUpPttRsp>? = null,
@SerialId(6) val msgGetpttUrlRsp: List<GetPttUrlRsp>? = null,
@SerialId(7) val msgDelImgRsp: List<DelImgRsp>? = null
) : ProtoBuf
@Serializable
class TryUpImgReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val srcUin: Long = 0L,
@SerialId(3) val fileId: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileSize: Long = 0L,
@SerialId(6) val fileName: String ="",
@SerialId(7) val srcTerm: Int = 0,
@SerialId(8) val platformType: Int = 0,
@SerialId(9) val buType: Int = 0,
@SerialId(10) val picWidth: Int = 0,
@SerialId(11) val picHeight: Int = 0,
@SerialId(12) val picType: Int = 0,
@SerialId(13) val buildVer: String = "",
@SerialId(14) val innerIp: Int = 0,
@SerialId(15) val appPicType: Int = 0,
@SerialId(16) val originalPic: Int = 0,
@SerialId(17) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(18) val dstUin: Long = 0L,
@SerialId(19) val srvUpload: Int = 0,
@SerialId(20) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class TryUpImgRsp(
@SerialId(1) val fileId: Long = 0L,
@SerialId(2) val result: Int = 0,
@SerialId(3) val failMsg: String = "",
@SerialId(4) val boolFileExit: Boolean = false,
@SerialId(5) val msgImgInfo: ImgInfo? = null,
@SerialId(6) val uint32UpIp: List<Int>? = null,
@SerialId(7) val uint32UpPort: List<Int>? = null,
@SerialId(8) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val fileid: Long = 0L,
@SerialId(10) val upOffset: Long = 0L,
@SerialId(11) val blockSize: Long = 0L,
@SerialId(12) val boolNewBigChan: Boolean = false,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(1001) val msgInfo4busi: TryUpInfo4Busi? = null
) : ProtoBuf
@Serializable
class TryUpInfo4Busi(
@SerialId(1) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class TryUpPttReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val srcUin: Long = 0L,
@SerialId(3) val fileId: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileSize: Long = 0L,
@SerialId(6) val fileName: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val srcTerm: Int = 0,
@SerialId(8) val platformType: Int = 0,
@SerialId(9) val buType: Int = 0,
@SerialId(10) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val innerIp: Int = 0,
@SerialId(12) val voiceLength: Int = 0,
@SerialId(13) val boolNewUpChan: Boolean = false,
@SerialId(14) val codec: Int = 0,
@SerialId(15) val voiceType: Int = 0,
@SerialId(16) val buId: Int = 0
) : ProtoBuf
@Serializable
class TryUpPttRsp(
@SerialId(1) val fileId: Long = 0L,
@SerialId(2) val result: Int = 0,
@SerialId(3) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val boolFileExit: Boolean = false,
@SerialId(5) val uint32UpIp: List<Int>? = null,
@SerialId(6) val uint32UpPort: List<Int>? = null,
@SerialId(7) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val fileid: Long = 0L,
@SerialId(9) val upOffset: Long = 0L,
@SerialId(10) val blockSize: Long = 0L,
@SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val channelType: Int = 0,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
}

View File

@ -0,0 +1,454 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Serializable
class BdhExtinfo : ProtoBuf {
@Serializable
class CommFileExtReq(
@SerialId(1) val actionType: Int = 0,
@SerialId(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class CommFileExtRsp(
@SerialId(1) val int32Retcode: Int = 0,
@SerialId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class PicInfo(
@SerialId(1) val idx: Int = 0,
@SerialId(2) val size: Int = 0,
@SerialId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val type: Int = 0
) : ProtoBuf
@Serializable
class QQVoiceExtReq(
@SerialId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val fmt: Int = 0,
@SerialId(3) val rate: Int = 0,
@SerialId(4) val bits: Int = 0,
@SerialId(5) val channel: Int = 0,
@SerialId(6) val pinyin: Int = 0
) : ProtoBuf
@Serializable
class QQVoiceExtRsp(
@SerialId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val int32Retcode: Int = 0,
@SerialId(3) val msgResult: List<QQVoiceResult>? = null
) : ProtoBuf
@Serializable
class QQVoiceResult(
@SerialId(1) val text: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val source: Int = 0
) : ProtoBuf
@Serializable
class ShortVideoReqExtInfo(
@SerialId(1) val cmd: Int = 0,
@SerialId(2) val sessionId: Long = 0L,
@SerialId(3) val msgThumbinfo: PicInfo? = null,
@SerialId(4) val msgVideoinfo: VideoInfo? = null,
@SerialId(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null,
@SerialId(6) val boolIsMergeCmdBeforeData: Boolean = false
) : ProtoBuf
@Serializable
class ShortVideoRspExtInfo(
@SerialId(1) val cmd: Int = 0,
@SerialId(2) val sessionId: Long = 0L,
@SerialId(3) val int32Retcode: Int = 0,
@SerialId(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val msgThumbinfo: PicInfo? = null,
@SerialId(6) val msgVideoinfo: VideoInfo? = null,
@SerialId(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null,
@SerialId(8) val retryFlag: Int = 0
) : ProtoBuf
@Serializable
class ShortVideoSureReqInfo(
@SerialId(1) val fromuin: Long = 0L,
@SerialId(2) val chatType: Int = 0,
@SerialId(3) val touin: Long = 0L,
@SerialId(4) val groupCode: Long = 0L,
@SerialId(5) val clientType: Int = 0,
@SerialId(6) val msgThumbinfo: PicInfo? = null,
@SerialId(7) val msgMergeVideoinfo: List<VideoInfo>? = null,
@SerialId(8) val msgDropVideoinfo: List<VideoInfo>? = null,
@SerialId(9) val businessType: Int = 0,
@SerialId(10) val subBusinessType: Int = 0
) : ProtoBuf
@Serializable
class ShortVideoSureRspInfo(
@SerialId(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val msgVideoinfo: VideoInfo? = null,
@SerialId(4) val mergeCost: Int = 0
) : ProtoBuf
@Serializable
class StoryVideoExtReq : ProtoBuf
@Serializable
class StoryVideoExtRsp(
@SerialId(1) val int32Retcode: Int = 0,
@SerialId(2) val msg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class UploadPicExtInfo(
@SerialId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class VideoInfo(
@SerialId(1) val idx: Int = 0,
@SerialId(2) val size: Int = 0,
@SerialId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val format: Int = 0,
@SerialId(5) val resLen: Int = 0,
@SerialId(6) val resWidth: Int = 0,
@SerialId(7) val time: Int = 0,
@SerialId(8) val starttime: Long = 0L,
@SerialId(9) val isAudio: Int = 0
) : ProtoBuf
}
@Serializable
class CSDataHighwayHead : ProtoBuf {
@Serializable
class C2CCommonExtendinfo(
@SerialId(1) val infoId: Int = 0,
@SerialId(2) val msgFilterExtendinfo: FilterExtendinfo? = null
) : ProtoBuf
@Serializable
class DataHighwayHead(
@SerialId(1) val version: Int = 0,
@SerialId(2) val uin: String = "",
@SerialId(3) val command: String = "",
@SerialId(4) val seq: Int = 0,
@SerialId(5) val retryTimes: Int = 0,
@SerialId(6) val appid: Int = 0,
@SerialId(7) val dataflag: Int = 0,
@SerialId(8) val commandId: Int = 0,
@SerialId(9) val buildVer: String = "",
@SerialId(10) val localeId: Int = 0
) : ProtoBuf
@Serializable
class DataHole(
@SerialId(1) val begin: Long = 0L,
@SerialId(2) val end: Long = 0L
) : ProtoBuf
@Serializable
class FilterExtendinfo(
@SerialId(1) val filterFlag: Int = 0,
@SerialId(2) val msgImageFilterRequest: ImageFilterRequest? = null
) : ProtoBuf
@Serializable
class FilterStyle(
@SerialId(1) val styleId: Int = 0,
@SerialId(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ImageFilterRequest(
@SerialId(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val clientIp: Int = 0,
@SerialId(3) val uin: Long = 0L,
@SerialId(4) val style: FilterStyle? = null,
@SerialId(5) val width: Int = 0,
@SerialId(6) val height: Int = 0,
@SerialId(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ImageFilterResponse(
@SerialId(1) val retCode: Int = 0,
@SerialId(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val costTime: Int = 0
) : ProtoBuf
@Serializable
class LoginSigHead(
@SerialId(1) val loginsigType: Int = 0,
@SerialId(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class NewServiceTicket(
@SerialId(1) val signature: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class PicInfoExt(
@SerialId(1) val picWidth: Int = 0,
@SerialId(2) val picHeight: Int = 0,
@SerialId(3) val picFlag: Int = 0,
@SerialId(4) val busiType: Int = 0,
@SerialId(5) val srcTerm: Int = 0,
@SerialId(6) val platType: Int = 0,
@SerialId(7) val netType: Int = 0,
@SerialId(8) val imgType: Int = 0,
@SerialId(9) val appPicType: Int = 0
) : ProtoBuf
@Serializable
class PicRspExtInfo(
@SerialId(1) val skey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val clientIp: Int = 0,
@SerialId(3) val upOffset: Long = 0L,
@SerialId(4) val blockSize: Long = 0L
) : ProtoBuf
@Serializable
class QueryHoleRsp(
@SerialId(1) val result: Int = 0,
@SerialId(2) val dataHole: List<DataHole>? = null,
@SerialId(3) val boolCompFlag: Boolean = false
) : ProtoBuf
@Serializable
class ReqDataHighwayHead(
@SerialId(1) val msgBasehead: DataHighwayHead? = null,
@SerialId(2) val msgSeghead: SegHead? = null,
@SerialId(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val timestamp: Long = 0L,
@SerialId(5) val msgLoginSigHead: LoginSigHead? = null
) : ProtoBuf
@Serializable
class RspBody(
@SerialId(1) val msgQueryHoleRsp: QueryHoleRsp? = null
) : ProtoBuf
@Serializable
class RspDataHighwayHead(
@SerialId(1) val msgBasehead: DataHighwayHead? = null,
@SerialId(2) val msgSeghead: SegHead? = null,
@SerialId(3) val errorCode: Int = 0,
@SerialId(4) val allowRetry: Int = 0,
@SerialId(5) val cachecost: Int = 0,
@SerialId(6) val htcost: Int = 0,
@SerialId(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val timestamp: Long = 0L,
@SerialId(9) val range: Long = 0L,
@SerialId(10) val isReset: Int = 0
) : ProtoBuf
@Serializable
class SegHead(
@SerialId(1) val serviceid: Int = 0,
@SerialId(2) val filesize: Long = 0L,
@SerialId(3) val dataoffset: Long = 0L,
@SerialId(4) val datalength: Int = 0,
@SerialId(5) val rtcode: Int = 0,
@SerialId(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val flag: Int = 0,
@SerialId(8) val md5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val cacheAddr: Int = 0,
@SerialId(11) val queryTimes: Int = 0,
@SerialId(12) val updateCacheip: Int = 0
) : ProtoBuf
}
@Serializable
class HwConfigPersistentPB : ProtoBuf {
@Serializable
class HwConfigItemPB(
@SerialId(1) val ingKey: String = "",
@SerialId(2) val endPointList: List<HwEndPointPB>? = null
) : ProtoBuf
@Serializable
class HwConfigPB(
@SerialId(1) val configItemList: List<HwConfigItemPB>? = null,
@SerialId(2) val netSegConfList: List<HwNetSegConfPB>? = null,
@SerialId(3) val shortVideoNetConf: List<HwNetSegConfPB>? = null,
@SerialId(4) val configItemListIp6: List<HwConfigItemPB>? = null
) : ProtoBuf
@Serializable
class HwEndPointPB(
@SerialId(1) val ingHost: String = "",
@SerialId(2) val int32Port: Int = 0,
@SerialId(3) val int64Timestampe: Long = 0L
) : ProtoBuf
@Serializable
class HwNetSegConfPB(
@SerialId(1) val int64NetType: Long = 0L,
@SerialId(2) val int64SegSize: Long = 0L,
@SerialId(3) val int64SegNum: Long = 0L,
@SerialId(4) val int64CurConnNum: Long = 0L
) : ProtoBuf
}
@Serializable
class HwSessionInfoPersistentPB : ProtoBuf {
@Serializable
class HwSessionInfoPB(
@SerialId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
}
@Serializable
class Subcmd0x501 : ProtoBuf {
@Serializable
class ReqBody(
@SerialId(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null
) : ProtoBuf
@Serializable
class RspBody(
@SerialId(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null
) : ProtoBuf
@Serializable
class SubCmd0x501ReqBody(
@SerialId(1) val uin: Long = 0L,
@SerialId(2) val idcId: Int = 0,
@SerialId(3) val appid: Int = 0,
@SerialId(4) val loginSigType: Int = 0,
@SerialId(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val requestFlag: Int = 0,
@SerialId(7) val uint32ServiceTypes: List<Int>? = null,
@SerialId(8) val bid: Int = 0,
@SerialId(9) val term: Int = 0,
@SerialId(10) val plat: Int = 0,
@SerialId(11) val net: Int = 0,
@SerialId(12) val caller: Int = 0
) : ProtoBuf
@Serializable
class SubCmd0x501Rspbody(
@SerialId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val msgHttpconnAddrs: List<SrvAddrs>? = null,
@SerialId(4) val preConnection: Int = 0,
@SerialId(5) val csConn: Int = 0,
@SerialId(6) val msgIpLearnConf: IpLearnConf? = null,
@SerialId(7) val msgDynTimeoutConf: DynTimeOutConf? = null,
@SerialId(8) val msgOpenUpConf: OpenUpConf? = null,
@SerialId(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null,
@SerialId(10) val msgShortVideoConf: ShortVideoConf? = null,
@SerialId(11) val msgPtvConf: PTVConf? = null
) : ProtoBuf {
@Serializable
class DownloadEncryptConf(
@SerialId(1) val boolEnableEncryptRequest: Boolean = false,
@SerialId(2) val boolEnableEncryptedPic: Boolean = false,
@SerialId(3) val ctrlFlag: Int = 0
) : ProtoBuf
@Serializable
class DynTimeOutConf(
@SerialId(1) val tbase2g: Int = 0,
@SerialId(2) val tbase3g: Int = 0,
@SerialId(3) val tbase4g: Int = 0,
@SerialId(4) val tbaseWifi: Int = 0,
@SerialId(5) val torg2g: Int = 0,
@SerialId(6) val torg3g: Int = 0,
@SerialId(7) val torg4g: Int = 0,
@SerialId(8) val torgWifi: Int = 0,
@SerialId(9) val maxTimeout: Int = 0,
@SerialId(10) val enableDynTimeout: Int = 0,
@SerialId(11) val maxTimeout2g: Int = 0,
@SerialId(12) val maxTimeout3g: Int = 0,
@SerialId(13) val maxTimeout4g: Int = 0,
@SerialId(14) val maxTimeoutWifi: Int = 0,
@SerialId(15) val hbTimeout2g: Int = 0,
@SerialId(16) val hbTimeout3g: Int = 0,
@SerialId(17) val hbTimeout4g: Int = 0,
@SerialId(18) val hbTimeoutWifi: Int = 0,
@SerialId(19) val hbTimeoutDefault: Int = 0
) : ProtoBuf
@Serializable
class Ip6Addr(
@SerialId(1) val type: Int = 0,
@SerialId(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val port: Int = 0,
@SerialId(4) val area: Int = 0,
@SerialId(5) val sameIsp: Int = 0
) : ProtoBuf
@Serializable
class IpAddr(
@SerialId(1) val type: Int = 0,
@ProtoType(ProtoNumberType.FIXED) @SerialId(2) val ip: Int = 0,
@SerialId(3) val port: Int = 0,
@SerialId(4) val area: Int = 0,
@SerialId(5) val sameIsp: Int = 0
) : ProtoBuf
@Serializable
class IpLearnConf(
@SerialId(1) val refreshCachedIp: Int = 0,
@SerialId(2) val enableIpLearn: Int = 0
) : ProtoBuf
@Serializable
class NetSegConf(
@SerialId(1) val netType: Int = 0,
@SerialId(2) val segsize: Int = 0,
@SerialId(3) val segnum: Int = 0,
@SerialId(4) val curconnnum: Int = 0
) : ProtoBuf
@Serializable
class OpenUpConf(
@SerialId(1) val boolEnableOpenup: Boolean = false,
@SerialId(2) val preSendSegnum: Int = 0,
@SerialId(3) val preSendSegnum3g: Int = 0,
@SerialId(4) val preSendSegnum4g: Int = 0,
@SerialId(5) val preSendSegnumWifi: Int = 0
) : ProtoBuf
@Serializable
class PTVConf(
@SerialId(1) val channelType: Int = 0,
@SerialId(2) val msgNetsegconf: List<NetSegConf>? = null,
@SerialId(3) val boolOpenHardwareCodec: Boolean = false
) : ProtoBuf
@Serializable
class ShortVideoConf(
@SerialId(1) val channelType: Int = 0,
@SerialId(2) val msgNetsegconf: List<NetSegConf>? = null,
@SerialId(3) val boolOpenHardwareCodec: Boolean = false,
@SerialId(4) val boolSendAheadSignal: Boolean = false
) : ProtoBuf
@Serializable
class SrvAddrs(
@SerialId(1) val serviceType: Int = 0,
@SerialId(2) val msgAddrs: List<IpAddr>? = null,
@SerialId(3) val fragmentSize: Int = 0,
@SerialId(4) val msgNetsegconf: List<NetSegConf>? = null,
@SerialId(5) val msgAddrsV6: List<Ip6Addr>? = null
) : ProtoBuf
}
}

View File

@ -5,7 +5,9 @@ import kotlinx.io.pool.useInstance
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImageUpPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
@ -119,6 +121,9 @@ internal object KnownPacketFactories {
FriendList.GetFriendGroupList,
FriendList.GetTroopListSimplify,
FriendList.GetTroopMemberList,
ImgStore.GroupPicUp,
ImageUpPacket,
LongConn.OffPicDown,
TroopManagement.EditNametag,
TroopManagement.Mute,
TroopManagement.MuteAll

View File

@ -1,42 +0,0 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.GetImgUrlReq
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
internal object ImageDownPacket : OutgoingPacketFactory<ImageDownPacket.ImageDownPacketResponse>("LongConn.OffPicDown") {
operator fun invoke(client: QQAndroidClient, req: GetImgUrlReq): OutgoingPacket {
// TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) {
val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(),
Cmd0x352Packet.createByImageRequest(req)
)
writeInt(data.size + 4)
writeFully(data)
}
}
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ImageDownPacketResponse {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
sealed class ImageDownPacketResponse : Packet {
object Success : ImageDownPacketResponse()
}
}

View File

@ -10,15 +10,13 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.UploadImgReq
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
internal object ImageUpPacket : OutgoingPacketFactory<ImageUpPacket.ImageUpPacketResponse>("LongConn.OffPicUp") {
operator fun invoke(client: QQAndroidClient, req: UploadImgReq): OutgoingPacket {
// TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) {
return buildOutgoingUniPacket(client) {
val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(),
Cmd0x352Packet.createByImageRequest(req)
@ -27,7 +25,6 @@ internal object ImageUpPacket : OutgoingPacketFactory<ImageUpPacket.ImageUpPacke
writeFully(data)
}
}
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ImageUpPacketResponse {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.

View File

@ -0,0 +1,101 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import io.ktor.client.HttpClient
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
internal class ImgStore {
object GroupPicUp : OutgoingPacketFactory<GroupPicUp.Response>("ImgStore.GroupPicUp") {
operator fun invoke(
client: QQAndroidClient,
uin: Long,
groupCode: Long,
md5: ByteArray,
size: Long,
picWidth: Int,
picHeight: Int,
picType: Int = 1000,
fileId: Long = 0,
filename: String,
srcTerm: Int = 5,
platformType: Int = 9,
buType: Int = 1,
appPicType: Int = 1006,
originalPic: Int = 0
): OutgoingPacket = buildOutgoingUniPacket(client) {
writeProtoBuf(
Cmd0x388.ReqBody.serializer(),
Cmd0x388.ReqBody(
netType = 3, // wifi
subcmd = 1,
msgTryupImgReq = listOf(
Cmd0x388.TryUpImgReq(
groupCode = groupCode,
srcUin = uin,
fileMd5 = md5,
fileSize = size,
fileId = fileId,
fileName = filename,
picWidth = picWidth,
picHeight = picHeight,
picType = picType,
appPicType = appPicType,
buildVer = client.buildVer,
srcTerm = srcTerm,
platformType = platformType,
originalPic = originalPic,
buType = buType
)
)
)
)
}
sealed class Response : Packet {
class FileExists(
val fileId: Long,
val fileInfo: Cmd0x388.ImgInfo
) : Response() {
override fun toString(): String {
return "FileExists(fileId=$fileId, fileInfo=$fileInfo)"
}
}
class RequireUpload(
val fileId: Long,
val uKey: ByteArray,
val uploadIpList: List<Int>,
val uploadPortList: List<Int>
) : Response() {
override fun toString(): String {
return "RequireUpload(fileId=$fileId, uKey=${uKey.contentToString()})"
}
}
data class Failed(
val resultCode: Int,
val message: String
) : Response()
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val resp0 = readProtoBuf(Cmd0x388.RspBody.serializer())
resp0.msgTryupImgRsp ?: error("cannot find `msgTryupImgRsp` from `Cmd0x388.RspBody`")
val resp = resp0.msgTryupImgRsp.first()
return when {
resp.result != 0 -> Response.Failed(resultCode = resp.result, message = resp.failMsg)
resp.boolFileExit -> Response.FileExists(fileId = resp.fileid, fileInfo = resp.msgImgInfo!!)
else -> Response.RequireUpload(fileId = resp.fileid, uKey = resp.upUkey, uploadIpList = resp.uint32UpIp!!, uploadPortList = resp.uint32UpPort!!)
}
}
}
}

View File

@ -0,0 +1,42 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.GetImgUrlReq
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
internal class LongConn {
object OffPicDown : OutgoingPacketFactory<OffPicDown.ImageDownPacketResponse>("LongConn.OffPicDown"){
operator fun invoke(client: QQAndroidClient, req: GetImgUrlReq): OutgoingPacket {
// TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) {
val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(),
Cmd0x352Packet.createByImageRequest(req)
)
writeInt(data.size + 4)
writeFully(data)
}
}
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ImageDownPacketResponse {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
sealed class ImageDownPacketResponse : Packet {
object Success : ImageDownPacketResponse()
}
}
}

View File

@ -6,7 +6,7 @@ import java.io.File
fun main() {
println(
File("""/Users/jiahua.liu/Desktop/QQAndroid-F/app/src/main/java/tencent/im/group/group_label/""")
File("""/Users/jiahua.liu/Desktop/QQAndroid-F/app/src/main/java/tencent/im/s2c/msgtype0x210/submsgtype0xc7/bussinfo/mutualmark""")
.generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n")
)
}

View File

@ -31,8 +31,6 @@ interface Group : Contact, CoroutineScope {
/**
* [Group] 实例创建的时候查询一次. 并与事件同步事件更新
*
* **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新
*/
val members: ContactList<Member>

View File

@ -5,6 +5,7 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.Serializable
sealed class Image : Message {
abstract val filepath: String
abstract val md5: ByteArray
abstract override fun toString(): String
@ -15,7 +16,7 @@ sealed class Image : Message {
}
abstract class CustomFace : Image() {
abstract val filepath: String
abstract override val filepath: String
abstract val fileId: Int
abstract val serverIp: Int
abstract val serverPort: Int
@ -106,7 +107,7 @@ data class CustomFaceFromFile(
abstract class NotOnlineImage : Image() {
abstract val resourceId: String
abstract override val md5: ByteArray
abstract val filepath: String
abstract override val filepath: String
abstract val fileLength: Int
abstract val height: Int
abstract val width: Int

View File

@ -7,18 +7,10 @@ import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.sendTo
import net.mamoe.mirai.utils.io.toUHexString
@Suppress("FunctionName")
fun ExternalImage(
width: Int,
height: Int,
md5: ByteArray,
format: String,
data: ByteReadPacket
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining)
/**
* 外部图片. 图片数据还没有读取到内存.
*
@ -33,19 +25,53 @@ class ExternalImage(
val md5: ByteArray,
imageFormat: String,
val input: Input,
val inputSize: Long
val inputSize: Long,
val filename: String
) {
private val format: String
init {
if (imageFormat == "JPEG" || imageFormat == "jpeg") {//必须转换
this.format = "jpg"
} else {
this.format = imageFormat
companion object {
operator fun invoke(
width: Int,
height: Int,
md5: ByteArray,
format: String,
data: ByteReadPacket,
filename: String
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename)
}
private val format: String = when (val it =imageFormat.toLowerCase()) {
"jpeg" -> "jpg" //必须转换
else -> it
}
/**
*
* ImgType:
* JPG: 1000
* PNG: 1001
* WEBP: 1002
* BMP: 1005
* GIG: 2000
* APNG: 2001
* SHARPP: 1004
*/
val imageType: Int
get() = when (format){
"jpg" -> 1000
"png" -> 1001
"webp" -> 1002
"bmp" -> 1005
"gig" -> 2000
"apng" -> 2001
"sharpp" -> 1004
else -> 1000 // unsupported, just make it jpg
}
override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
fun calculateImageResourceId(): String {
return "{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format"
}
}
/**

View File

@ -12,6 +12,7 @@ import kotlinx.io.core.copyTo
import kotlinx.io.errors.IOException
import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput
import net.mamoe.mirai.utils.io.getRandomString
import java.awt.image.BufferedImage
import java.io.File
import java.io.InputStream
@ -44,7 +45,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage {
})
}
return ExternalImage(width, height, digest.digest(), formatName, buffer)
return ExternalImage(width, height, digest.digest(), formatName, buffer, getRandomString(10) + "." + formatName)
}
suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
@ -66,7 +67,8 @@ fun File.toExternalImage(): ExternalImage {
md5 = this.inputStream().use { it.md5() },
imageFormat = image.formatName,
input = this.inputStream().asInput(IoBuffer.Pool),
inputSize = this.length()
inputSize = this.length(),
filename = this.name
)
}

View File

@ -15,7 +15,10 @@ dependencies {
implementation 'org.jsoup:jsoup:1.12.1'
}
mainClassName = "demo.gentleman.MainKt"
run{
standardInput = System.in
mainClassName = "demo.gentleman.MainKt"
}
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-XXLanguage:+InlineClasses"]