From df25b0bc76a7e07c8a5c2b242c33904d3b2ee665 Mon Sep 17 00:00:00 2001 From: WhiteChi Date: Fri, 22 Dec 2023 03:08:46 +0800 Subject: [PATCH] =?UTF-8?q?`Shamrock`:=20=E6=94=AF=E6=8C=81=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0QQ=E6=94=B6=E8=97=8F=EF=BC=88=E6=96=87=E6=9C=AC?= =?UTF-8?q?=EF=BC=89=20`/fav/add=5Frich=5Fmedia=5Fmsg`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fuqiuluo/qqinterface/servlet/QFavSvc.kt | 191 ++++++++++++++---- .../fuqiuluo/shamrock/remote/HTTPServer.kt | 1 + .../shamrock/remote/action/ActionManager.kt | 3 + .../action/handlers/FavAddRichMediaMsg.kt | 47 +++++ .../shamrock/remote/api/FavouriteWeiyun.kt | 28 +++ .../shamrock/xposed/actions/HookForDebug.kt | 5 +- 6 files changed, 240 insertions(+), 35 deletions(-) create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddRichMediaMsg.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.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 010cded..5456c70 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt @@ -1,6 +1,5 @@ package moe.fuqiuluo.qqinterface.servlet -import android.text.TextUtils import com.tencent.mobileqq.app.QQAppInterface import com.tencent.mobileqq.transfile.HttpNetReq import com.tencent.mobileqq.transfile.INetEngineListener @@ -8,9 +7,13 @@ import com.tencent.mobileqq.transfile.NetReq import com.tencent.mobileqq.transfile.NetResp import com.tencent.mobileqq.transfile.ServerAddr import com.tencent.mobileqq.transfile.api.IHttpEngineService -import io.netty.buffer.ByteBuf +import kotlinx.coroutines.suspendCancellableCoroutine +import moe.fuqiuluo.proto.protobufMapOf import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY +import moe.fuqiuluo.shamrock.tools.toInnerValuesString +import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher import mqq.manager.TicketManager import oicq.wlogin_sdk.request.Ticket @@ -18,59 +21,179 @@ import oicq.wlogin_sdk.request.WtTicketPromise import oicq.wlogin_sdk.tools.ErrMsg import java.io.ByteArrayOutputStream import java.nio.ByteBuffer +import kotlin.coroutines.resume /** * QQ收藏相关接口 */ internal object QFavSvc: BaseSvc() { - private const val VI = 27 + private val SERVER_LIST = listOf(ServerAddr().also { + it.isIpv6 = false + it.mIp = "collector.weiyun.com" + it.port = 80 + }) + private const val VT = 27 + private const val VERSION = 12820 private const val APPID = 30244 private const val SUB_APPID = 538116905 + private const val MAJOR_VERSION = 8 + private const val MINOR_VERSION = 9 + private var seq = 1 - fun sendWeiyunReq() { - val httpNetReq = HttpNetReq() - httpNetReq.userData = null - (httpNetReq as NetReq).mCallback = object: INetEngineListener { - override fun onResp(netResp: NetResp) { - - } - - override fun onUpdateProgeress(netReq: NetReq, j2: Long, j3: Long) { + suspend fun addRichMediaMsg( + uin: Long, + name: String, + groupId: Long = 0, + groupName: String = "", + time: Long = System.currentTimeMillis(), + content: String + ): Result { + val data = protobufMapOf { + it[1] = mapOf( + 20009 to mapOf( + 1 to mapOf( + /** + * 1 => bid + * 2 => category + * 3 => author + * 4 => create_time + * 5 => sequence + * 6 => biz_key + * 7 => biz_data_list + * 8 => share_url + * 9 => original_app_id + * 10 => custom_group_id + * 506 => modify_time + * 507 => qzone_ugc_key + */ + 1 to 1, // bid + 2 to 1, // category + 3 to mapOf( // author + 1 to if (groupId == 0L) 1 else 2, // type + 2 to uin, // num_id + 3 to name, // str_id + 4 to groupId, // group_id + 5 to groupName // group_name + ), + 4 to time - 2000, // create_time + 5 to time - 1000, // sequence + 9 to 0, // original_app_id + 10 to 0 // custom_group_id + ), + 2 to mapOf( + /** + * 1 => title + * 2 => sub_title + * 3 => brief + * 4 => pic_list + * 5 => content_type + * 6 => original_uri + * 7 => publisher + * 8 => rich_media_version + */ + 3 to content, + 5 to 1 + ), + 3 to mapOf( + /** + * 1 => rich_media + * 2 => raw_data + * 3 => biz_data_list + * 4 => pic_list + * 5 => file_list + */ + 2 to content + ) + ) + ) + }.toByteArray() + return sendWeiyunReq(20009, data) + } + suspend fun sendWeiyunReq(cmd: Int, body: ByteArray): Result { + return suspendCancellableCoroutine { + val httpNetReq = HttpNetReq() + httpNetReq.userData = null + httpNetReq.mCallback = object: INetEngineListener { + override fun onResp(netResp: NetResp) { + if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) { + netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"] + } + it.resume(Result.success(netResp)) + } + + 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 = ByteArrayOutputStream() + httpNetReq.mStartDownOffset = 0L + httpNetReq.mReqProperties["Shamrock"] = "true" + httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.currentAccountUin, VT, pSKey, APPID) + httpNetReq.mReqProperties["host"] = "collector.weiyun.com" + httpNetReq.mReqProperties["Range"] = "bytes=0-" + httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString() + httpNetReq.mReqProperties["Accept-Encoding"] = "gzip" + httpNetReq.mReqProperties["Content-Encoding"] = "gzip" + httpNetReq.mPrioty = 1 + httpNetReq.mReqUrl = "https://collector.weiyun.com/collector.fcg" + httpNetReq.mServerList = SERVER_LIST + val service = AppRuntimeFetcher.appRuntime + .getRuntimeService(IHttpEngineService::class.java, "qqfav") + service.sendReq(httpNetReq) } - val pSKey = getWeiYunPSKey() - httpNetReq.mHttpMethod = 1 - httpNetReq.mSendData = byteArrayOf() - val byteArrayOutputStream = ByteArrayOutputStream() - httpNetReq.mOutStream = byteArrayOutputStream - httpNetReq.mStartDownOffset = 0L - httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.longAccountUin, VI, APPID,) - httpNetReq.mReqProperties["host"] = "collector.weiyun.com" - httpNetReq.mReqProperties["Range"] = "bytes=" + httpNetReq.mStartDownOffset + "-" - httpNetReq.mReqProperties["Accept-Encoding"] = "gzip" - httpNetReq.mReqProperties["Content-Encoding"] = "gzip" - httpNetReq.mPrioty = 1 - val service = AppRuntimeFetcher.appRuntime - .getRuntimeService(IHttpEngineService::class.java, "qqfav") - httpNetReq.mReqUrl = "https://collector.weiyun.com" - httpNetReq.mServerList = emptyList() - service.sendReq(httpNetReq) + } + + private fun packHead(cmd: Int, pskey: String): ByteArray { + /** + * 1 => uin + * 2 => seq + * 3 => type + * 4 => cmd + * 5 => appid + * 6 => version + * 7 => nettype + * 8 => clientip + * 9 => encrypt + * 10 => keytype + * 11 => encryptkey + * 14 => major_version + * 15 => minor_version + * 101 => retcode + * 102 => retmsg + * 103 => promptmsg + * 111 => total_space + * 112 => used_space + */ + return protobufMapOf { + it[1] = app.longAccountUin + it[2] = seq++ // seq + it[3] = 1 // type + it[4] = cmd + it[5] = APPID + it[6] = VERSION // VERSION + it[7] = 3 // nettype + it[10] = 27 // keytype + it[11] = pskey + it[14] = MAJOR_VERSION + it[15] = MINOR_VERSION + }.toByteArray() } private fun packData(head: ByteArray, body: ByteArray): ByteArray { - val buf = ByteBuffer.allocateDirect(100) + val len = 16 + head.size + body.size + val buf = ByteBuffer.allocate(len) buf.putInt(SUB_APPID) buf.putShort(1) - buf.putInt(16 + head.size + body.size) + buf.putInt(len) buf.putInt(body.size) buf.putShort(0) buf.put(head) buf.put(body) - val ret = buf.array() - buf.clear() - return ret + return buf.array() } private fun getWeiYunPSKey(): String { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt index 979621e..59ff336 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt @@ -64,6 +64,7 @@ internal object HTTPServer { guildAction() testAction() requestRouter() + fav() if (ShamrockConfig.isDev()) { qsign() obtainProtocolData() 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 e57fd71..f44a513 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 @@ -51,6 +51,9 @@ internal object ActionManager { // WEATHER GetWeatherCityCode, GetWeather, + // FAV + FavAddRichMediaMsg, + // OTHER GetDeviceBattery, DownloadFile ).forEach { 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/FavAddRichMediaMsg.kt new file mode 100644 index 0000000..a927e7e --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddRichMediaMsg.kt @@ -0,0 +1,47 @@ +package moe.fuqiuluo.shamrock.remote.action.handlers + +import kotlinx.serialization.json.JsonElement +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 + +internal object FavAddRichMediaMsg: IActionHandler() { + override suspend fun internalHandle(session: ActionSession): String { + val uin = session.getLong("user_id") + val nickName = session.getString("nick") + val groupName = session.getStringOrNull("groupName") ?: "" + val groupId = session.getLongOrNull("group_id") ?: 0L + val time = session.getLongOrNull("time") ?: System.currentTimeMillis() + val content = session.getString("content") + return invoke(uin, nickName, time, content, groupName, groupId, session.echo) + } + + suspend operator fun invoke( + uin: Long, + nickName: String, + time: Long = System.currentTimeMillis(), + content: String, + groupName: String = "", + groupId: Long = 0L, + echo: JsonElement = EmptyJsonString + ): String { + QFavSvc.addRichMediaMsg(uin, nickName, + time = time, + content = content, + groupName = groupName, + groupId = groupId + ).onSuccess { + return if (it.mHttpCode == 200 && it.mResult == 0) { + ok("成功", echo) + } else { + logic(it.mErrDesc, echo) + } + } + return ok("请求已提交", echo) + } + + override fun path(): String = "fav.add_rich_media_msg" + + override val requiredParams: Array = arrayOf("user_id", "nick", "content") +} \ 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 new file mode 100644 index 0000000..a1b8c93 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt @@ -0,0 +1,28 @@ +package moe.fuqiuluo.shamrock.remote.api + +import io.ktor.http.ContentType +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.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.tools.fetchOrNull +import moe.fuqiuluo.shamrock.tools.fetchOrThrow +import moe.fuqiuluo.shamrock.tools.getOrPost + +// fav.add_rich_media_msg + +fun Routing.fav() { + getOrPost("/fav/add_rich_media_msg") { + val uin = call.fetchOrThrow("user_id").toLong() + val nickName = call.fetchOrThrow("nick") + val time = call.fetchOrNull("time")?.toLong() ?: System.currentTimeMillis() + val content = call.fetchOrThrow("content") + val groupName = call.fetchOrNull("groupName") ?: "" + val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L + call.respondText(FavAddRichMediaMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/HookForDebug.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/HookForDebug.kt index 1124e6f..a729b85 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/HookForDebug.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/HookForDebug.kt @@ -31,8 +31,11 @@ internal class HookForDebug: IAction { .getRuntimeService(IHttpEngineService::class.java, "all") httpEngineService.javaClass.hookMethod("sendReq").before { if (it.args[0] is HttpNetReq) { - LogCenter.log("已记录一个IHttpEngineService请求") val req = it.args[0] as HttpNetReq + if (req.mReqProperties["Shamrock"] == "true") { + return@before + } + LogCenter.log("已记录一个IHttpEngineService请求") if (req.mReqUrl == null || req.mReqUrl.isBlank()) { val host = req.mReqProperties["host"] ?: "collector.weiyun.com" req.mReqUrl = "http://$host"