From 14904501781174a0633388cec40e22b095f1acf2 Mon Sep 17 00:00:00 2001 From: WhiteChi Date: Sat, 23 Dec 2023 04:16:11 +0800 Subject: [PATCH] =?UTF-8?q?`Shamrock`:=20=E6=94=AF=E6=8C=81=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=8C=87=E5=AE=9AQQ=E6=94=B6=E8=97=8F=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9=20`/fav/get=5Fitem=5Fcontent`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fuqiuluo/qqinterface/servlet/QFavSvc.kt | 20 ++- .../shamrock/remote/action/ActionManager.kt | 2 +- .../remote/action/handlers/FavAddImageMsg.kt | 121 +++++++++++------- ...FavAddRichMediaMsg.kt => FavAddTextMsg.kt} | 36 +++++- .../action/handlers/FavGetItemContent.kt | 55 ++++++++ .../shamrock/remote/api/FavouriteWeiyun.kt | 14 +- 6 files changed, 192 insertions(+), 56 deletions(-) rename xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/{FavAddRichMediaMsg.kt => FavAddTextMsg.kt} (54%) create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt index fed2c83..3f1a25b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt @@ -51,6 +51,19 @@ internal object QFavSvc: BaseSvc() { private const val MINOR_VERSION = 9 private var seq = 1 + suspend fun getItemContent( + id: String + ): Result { + val data = protobufMapOf { + it[1] = mapOf( + 20001 to mapOf( + 1 to id + ) + ) + }.toByteArray() + return sendWeiyunReq(20001, data) + } + suspend fun addImageMsg( uin: Long, name: String, @@ -215,7 +228,7 @@ internal object QFavSvc: BaseSvc() { * 4 => pic_list * 5 => file_list */ - 2 to content + 2 to content.textToHtml() ) ) ) @@ -223,6 +236,10 @@ internal object QFavSvc: BaseSvc() { return sendWeiyunReq(20009, data) } + private fun String.textToHtml(): String { + return replace("\n", "

") + } + suspend fun sendPicUpBlock( fileSize: Long, offset: Long, @@ -300,7 +317,6 @@ internal object QFavSvc: BaseSvc() { override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {} } val pSKey = getWeiYunPSKey() - //LogCenter.log(pSKey) httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), body)) httpNetReq.mOutStream = outputStream diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt index 15e6766..5d7dd9a 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt @@ -52,7 +52,7 @@ internal object ActionManager { GetWeatherCityCode, GetWeather, // FAV - FavAddRichMediaMsg, FavAddImageMsg, + FavAddTextMsg, FavAddImageMsg, FavGetItemContent, // OTHER GetDeviceBattery, DownloadFile diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt index 6b1cf23..14b768b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt @@ -3,6 +3,8 @@ package moe.fuqiuluo.shamrock.remote.action.handlers import android.graphics.BitmapFactory import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import moe.fuqiuluo.proto.ProtoUtils import moe.fuqiuluo.proto.asUtf8String @@ -40,8 +42,14 @@ internal object FavAddImageMsg: IActionHandler() { FileUtils.parseAndSave(it) } } + val options = BitmapFactory.Options() BitmapFactory.decodeFile(image.absolutePath, options) + lateinit var picUrl: String + lateinit var picId: String + lateinit var itemId: String + lateinit var md5: String + QFavSvc.applyUpImageMsg(uin, nickName, image = image, groupName = groupName, @@ -49,14 +57,12 @@ internal object FavAddImageMsg: IActionHandler() { width = options.outWidth, height = options.outHeight ).onSuccess { - return if (it.mHttpCode == 200 && it.mResult == 0) { + if (it.mHttpCode == 200 && it.mResult == 0) { val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) readPacket.discardExact(6) val allLength = readPacket.readInt() val dataLength = readPacket.readInt() val headLength = allLength - dataLength - 16 - //LogCenter.log("上传图片请求成功: ${DeflateTools.ungzip(it.mRespData).toHexString()}") - //LogCenter.log("图片上传响应: allLength=$allLength, dataLength=$dataLength, headLength=$headLength") readPacket.discardExact(2) ByteArray(headLength).also { readPacket.readFully(it, 0, it.size) @@ -66,55 +72,82 @@ internal object FavAddImageMsg: IActionHandler() { } val pb = ProtoUtils.decodeFromByteArray(data) val resp = pb[2, 20010, 1, 2] - val picUrl = resp[1].asUtf8String - val picId = resp[11].asUtf8String - val md5 = resp[4].asUtf8String - - val sha = CryptTools - .getSHA1("/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQ_Collection/pic/" + md5.uppercase() + "_0") - - image.inputStream().use { - var offset = 0L - val block = ByteArray(131072) - var rest = image.length() - do { - val length = if (rest <= 131072) rest else 131072L - if(it.read(block, 0, length.toInt()) != -1) { - QFavSvc.sendPicUpBlock( - fileSize = image.length(), - offset = offset, - block = block, - blockSize = length, - pid = picId, - sha = sha - ).onFailure { - return error(it.message ?: it.toString(), echo) - } - offset += length - rest -= length - } else { - rest = -1 - } - } while (rest > 0) - } - - QFavSvc.addImageMsg( - uin, nickName, groupId, groupName, picUrl, picId, options.outWidth, options.outHeight, image.length(), md5.uppercase() - ).onFailure { - return error(it.message ?: it.toString(), echo) - } - - ok(picUrl, echo) + picUrl = resp[1].asUtf8String + picId = resp[11].asUtf8String + md5 = resp[4].asUtf8String } else { - logic(it.mErrDesc, echo) + return logic(it.mErrDesc, echo) } }.onFailure { return error(it.message ?: it.toString(), echo) } - return ok("请求已提交", echo) + + val sha = CryptTools + .getSHA1("/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQ_Collection/pic/" + md5.uppercase() + "_0") + + image.inputStream().use { + var offset = 0L + val block = ByteArray(131072) + var rest = image.length() + do { + val length = if (rest <= 131072) rest else 131072L + if(it.read(block, 0, length.toInt()) != -1) { + QFavSvc.sendPicUpBlock( + fileSize = image.length(), + offset = offset, + block = block, + blockSize = length, + pid = picId, + sha = sha + ).onFailure { + return error(it.message ?: it.toString(), echo) + } + offset += length + rest -= length + } else { + rest = -1 + } + } while (rest > 0) + } + + QFavSvc.addImageMsg( + uin, nickName, groupId, groupName, picUrl, picId, options.outWidth, options.outHeight, image.length(), md5.uppercase() + ).onFailure { + return error(it.message ?: it.toString(), echo) + }.onSuccess { + if (it.mHttpCode == 200 && it.mResult == 0) { + val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) + readPacket.discardExact(6) + val allLength = readPacket.readInt() + val dataLength = readPacket.readInt() + val headLength = allLength - dataLength - 16 + readPacket.discardExact(2) + ByteArray(headLength).also { + readPacket.readFully(it, 0, it.size) + } + val data = ByteArray(dataLength).also { + readPacket.readFully(it, 0, it.size) + } + val pb = ProtoUtils.decodeFromByteArray(data) + itemId = pb[2, 20009, 1].asUtf8String + } + } + + return ok(PicInfo( + picUrl = picUrl, + picId = picId, + id = itemId + ), echo) } override fun path(): String = "fav.add_image_msg" override val requiredParams: Array = arrayOf("user_id", "nick", "file") + + @Serializable + private data class PicInfo( + @SerialName("pic_url") val picUrl: String, + @SerialName("pic_id") val picId: String, + @SerialName("id") val id: String + ) } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddRichMediaMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddTextMsg.kt similarity index 54% rename from xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddRichMediaMsg.kt rename to xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddTextMsg.kt index 216ab79..e54ee41 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddRichMediaMsg.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddTextMsg.kt @@ -1,12 +1,21 @@ package moe.fuqiuluo.shamrock.remote.action.handlers +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.discardExact +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement +import moe.fuqiuluo.proto.ProtoUtils +import moe.fuqiuluo.proto.asUtf8String import moe.fuqiuluo.qqinterface.servlet.QFavSvc +import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.remote.action.ActionSession import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.tools.EmptyJsonString +import moe.fuqiuluo.shamrock.tools.toHexString +import moe.fuqiuluo.shamrock.utils.DeflateTools -internal object FavAddRichMediaMsg: IActionHandler() { +internal object FavAddTextMsg: IActionHandler() { override suspend fun internalHandle(session: ActionSession): String { val uin = session.getLong("user_id") val nickName = session.getString("nick") @@ -33,7 +42,23 @@ internal object FavAddRichMediaMsg: IActionHandler() { groupId = groupId ).onSuccess { return if (it.mHttpCode == 200 && it.mResult == 0) { - ok("成功", echo) + val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) + readPacket.discardExact(6) + val allLength = readPacket.readInt() + val dataLength = readPacket.readInt() + val headLength = allLength - dataLength - 16 + readPacket.discardExact(2) + ByteArray(headLength).also { + readPacket.readFully(it, 0, it.size) + } + val data = ByteArray(dataLength).also { + readPacket.readFully(it, 0, it.size) + } + val pb = ProtoUtils.decodeFromByteArray(data) + + ok(data = QFavItem( + pb[2, 20009, 1].asUtf8String + ), echo) } else { logic(it.mErrDesc, echo) } @@ -41,7 +66,12 @@ internal object FavAddRichMediaMsg: IActionHandler() { return ok("请求已提交", echo) } - override fun path(): String = "fav.add_rich_media_msg" + override fun path(): String = "fav.add_text_msg" override val requiredParams: Array = arrayOf("user_id", "nick", "content") + + @Serializable + private data class QFavItem( + @SerialName("id") val id: String + ) } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt new file mode 100644 index 0000000..3d59b4a --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt @@ -0,0 +1,55 @@ +package moe.fuqiuluo.shamrock.remote.action.handlers + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.discardExact +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import moe.fuqiuluo.proto.ProtoUtils +import moe.fuqiuluo.proto.asUtf8String +import moe.fuqiuluo.qqinterface.servlet.QFavSvc +import moe.fuqiuluo.shamrock.remote.action.ActionSession +import moe.fuqiuluo.shamrock.remote.action.IActionHandler +import moe.fuqiuluo.shamrock.tools.EmptyJsonString +import moe.fuqiuluo.shamrock.tools.toHexString +import moe.fuqiuluo.shamrock.utils.DeflateTools + +internal object FavGetItemContent: IActionHandler() { + override suspend fun internalHandle(session: ActionSession): String { + val id = session.getString("id") + return invoke(id, session.echo) + } + + suspend operator fun invoke( + id: String, + echo: JsonElement = EmptyJsonString + ): String { + val respData = DeflateTools.ungzip(QFavSvc.getItemContent(id).onSuccess { + if (it.mHttpCode != 200 || it.mResult != 0) { + return logic(it.mErrDesc, echo) + } + }.getOrThrow().mRespData) + val readPacket = ByteReadPacket(respData) + readPacket.discardExact(6) + val allLength = readPacket.readInt() + val dataLength = readPacket.readInt() + val headLength = allLength - dataLength - 16 + readPacket.discardExact(2) + ByteArray(headLength).also { + readPacket.readFully(it, 0, it.size) + } + val data = ByteArray(dataLength).also { + readPacket.readFully(it, 0, it.size) + } + val pb = ProtoUtils.decodeFromByteArray(data) + + return ok(ItemContent(pb[2, 20001, 1, 8, 2].asUtf8String)) + } + + override fun path(): String = "fav.get_item_content" + + @Serializable + private data class ItemContent( + @SerialName("content") val content: String + ) +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt index 087a0bc..d8752f2 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt @@ -5,11 +5,8 @@ import io.ktor.server.application.call import io.ktor.server.response.respondText import io.ktor.server.routing.Routing import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddImageMsg -import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddRichMediaMsg -import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList -import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendSystemMsg -import moe.fuqiuluo.shamrock.remote.action.handlers.GetStrangerInfo -import moe.fuqiuluo.shamrock.remote.action.handlers.IsBlackListUin +import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddTextMsg +import moe.fuqiuluo.shamrock.remote.action.handlers.FavGetItemContent import moe.fuqiuluo.shamrock.tools.fetchOrNull import moe.fuqiuluo.shamrock.tools.fetchOrThrow import moe.fuqiuluo.shamrock.tools.getOrPost @@ -24,7 +21,7 @@ fun Routing.fav() { val content = call.fetchOrThrow("content") val groupName = call.fetchOrNull("group_name") ?: "" val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L - call.respondText(FavAddRichMediaMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json) + call.respondText(FavAddTextMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json) } getOrPost("/fav/add_image_msg") { @@ -35,4 +32,9 @@ fun Routing.fav() { val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L call.respondText(FavAddImageMsg(uin, nickName, file, groupName, groupId), ContentType.Application.Json) } + + getOrPost("/fav/get_item_content") { + val id = call.fetchOrThrow("id") + call.respondText(FavGetItemContent(id), ContentType.Application.Json) + } } \ No newline at end of file