mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Shamrock
: 支持添加QQ收藏(文本) /fav/add_rich_media_msg
This commit is contained in:
parent
d07eea7766
commit
df25b0bc76
@ -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<NetResp> {
|
||||
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<NetResp> {
|
||||
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 {
|
||||
|
@ -64,6 +64,7 @@ internal object HTTPServer {
|
||||
guildAction()
|
||||
testAction()
|
||||
requestRouter()
|
||||
fav()
|
||||
if (ShamrockConfig.isDev()) {
|
||||
qsign()
|
||||
obtainProtocolData()
|
||||
|
@ -51,6 +51,9 @@ internal object ActionManager {
|
||||
// WEATHER
|
||||
GetWeatherCityCode, GetWeather,
|
||||
|
||||
// FAV
|
||||
FavAddRichMediaMsg,
|
||||
|
||||
// OTHER
|
||||
GetDeviceBattery, DownloadFile
|
||||
).forEach {
|
||||
|
@ -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<String> = arrayOf("user_id", "nick", "content")
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user