`Shamrock`: remake code struct

This commit is contained in:
FQL 2023-08-26 21:12:33 +08:00
parent 31306ef99c
commit 7baacda0ee
76 changed files with 957 additions and 842 deletions

View File

@ -8,8 +8,8 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import moe.fuqiuluo.http.entries.CommonResult
import moe.fuqiuluo.http.entries.CurrentAccount
import moe.fuqiuluo.remote.entries.CommonResult
import moe.fuqiuluo.remote.entries.CurrentAccount
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
import moe.fuqiuluo.shamrock.ui.app.Level
import moe.fuqiuluo.xposed.tools.GlobalClient

View File

@ -20,4 +20,5 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.default.buildFeatures.aidl=true

View File

@ -2,7 +2,13 @@ package com.tencent.mobileqq.app;
import com.tencent.common.app.business.BaseQQAppInterface;
import mqq.manager.Manager;
public class QQAppInterface extends BaseQQAppInterface {
public static final int ACCOUNT_MANAGER = 0;
public static final int WTLOGIN_MANAGER = 1;
public static final int TICKET_MANAGER = 2;
@Override
public String getCurrentAccountUin() {
return null;
@ -23,4 +29,8 @@ public class QQAppInterface extends BaseQQAppInterface {
public MessageHandler getMsgHandler() {
return null;
}
public Manager getManager(int id) {
return null;
}
}

View File

@ -0,0 +1,5 @@
package mqq.manager;
public interface Manager {
void onDestroy();
}

View File

@ -0,0 +1,49 @@
package mqq.manager;
public interface TicketManager extends Manager {
String getA2(String uin);
byte[] getDA2(String uin);
//Ticket getLocalTicket(String uin, int i2);
String getOpenSdkKey(String uin, int i2);
String getPskey(String uin, String str2);
//Ticket getPskey(String uin, long j2, String[] strArr, WtTicketPromise wtTicketPromise);
//Ticket getPskeyForOpen(String uin, long j2, String[] strArr, byte[] bArr, WtTicketPromise wtTicketPromise);
//void getPskeyIgnoreCache(String uin, long j2, String[] strArr, WtTicketPromise wtTicketPromise);
String getPt4Token(String uin, String str2);
String getSkey(String uin);
//Ticket getSkey(String str, long j2, WtTicketPromise wtTicketPromise);
byte[] getSt(String uin, int appid);
byte[] getStkey(String uin, int appid);
String getStweb(String uin);
String getSuperkey(String str);
//Ticket getTicket(String str, long j2, int i2, WtTicketPromise wtTicketPromise, Bundle bundle);
String getVkey(String str);
//void registTicketManagerListener(TicketManagerListener ticketManagerListener);
//void reloadCache(Context context);
int sendRPCData(long j2, String str, String str2, byte[] bArr, int i2);
//void setAlterTicket(HashMap<String, String> hashMap);
//void setPskeyManager(IPskeyManager iPskeyManager);
//void unregistTicketManagerListener(TicketManagerListener ticketManagerListener);
}

View File

@ -20,6 +20,9 @@ android {
}
}
buildFeatures {
aidl = true
}
buildTypes {
release {
isMinifyEnabled = true

View File

@ -0,0 +1,4 @@
// IQSign.aidl
package moe.fuqiuluo.xposed.ipc;
parcelable IQSign;

View File

@ -0,0 +1,8 @@
// IQSigner.aidl
package moe.fuqiuluo.xposed.ipc;
import moe.fuqiuluo.xposed.ipc.IQSign;
interface IQSigner {
IQSign sign(String cmd, int seq, String uin, in byte[] buffer);
}

View File

@ -14,6 +14,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.toSegment
import com.tencent.qqnt.protocol.GroupSvc
import com.tencent.qqnt.protocol.MsgSvc
import io.ktor.client.network.sockets.ConnectTimeoutException
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
@ -80,6 +81,8 @@ internal object HttpPusher {
))
}.bodyAsText()
handleQuicklyReply(record, msgHash, respond)
} catch (e: ConnectTimeoutException) {
LogCenter.log("消息推送失败: ${e.message}", Level.ERROR)
} catch (e: Throwable) {
LogCenter.log("消息推送失败: ${e.stackTraceToString()}", Level.ERROR)
}

View File

@ -107,7 +107,7 @@ internal object MsgConvert {
"file" to md5.json,
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> "http://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2"
MsgConstant.KCHATTYPEC2C -> "https://c2cpicdw.qpic.cn/offpic_new/0/${md5.uppercase()}/0?term=2"
MsgConstant.KCHATTYPEC2C -> "https://c2cpicdw.qpic.cn/offpic_new/0/123-0-${md5.uppercase()}/0?term=2"
else -> error("Not supported chat type: $chatType, convertMsgElementsToMsgSegment::Pic")
}.json
))

View File

@ -10,20 +10,18 @@ import tencent.im.oidb.oidb_sso
abstract class BaseSvc {
protected val currentUin: String
get() = (MobileQQ.getMobileQQ().waitAppRuntime() as QQAppInterface).currentAccountUin
get() = app.currentAccountUin
protected val app: QQAppInterface
get() = MobileQQ.getMobileQQ().waitAppRuntime() as QQAppInterface
protected fun sendExtra(cmd: String, builder: (Bundle) -> Unit) {
val app = MobileQQ.getMobileQQ().waitAppRuntime()
if (app !is QQAppInterface) {
error("app is not QQAppInterface")
}
val toServiceMsg = ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd)
builder(toServiceMsg.extraData)
app.sendToService(toServiceMsg)
}
protected fun sendPb(cmd: String, buffer: ByteArray) {
val app = MobileQQ.getMobileQQ().waitAppRuntime() as QQAppInterface
val toServiceMsg = ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd)
toServiceMsg.putWupBuffer(buffer)
toServiceMsg.addAttribute("req_pb_protocol_flag", true)
@ -31,7 +29,6 @@ abstract class BaseSvc {
}
protected fun sendOidb(cmd: String, cmdId: Int, serviceId: Int, buffer: ByteArray) {
val app = MobileQQ.getMobileQQ().waitAppRuntime() as QQAppInterface
val to = ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd)
val oidb = oidb_sso.OIDBSSOPkg()
oidb.uint32_command.set(cmdId)

View File

@ -11,7 +11,6 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.http.action.handlers.GetProfileCard
import moe.fuqiuluo.xposed.helper.PacketHandler
import moe.fuqiuluo.xposed.tools.slice
import mqq.app.MobileQQ

View File

@ -0,0 +1,36 @@
package com.tencent.qqnt.protocol
import com.tencent.mobileqq.app.QQAppInterface
import mqq.manager.TicketManager
internal object TicketSvc: BaseSvc() {
object SigType {
const val WLOGIN_A2 = 64
const val WLOGIN_A5 = 2
const val WLOGIN_AQSIG = 2097152
const val WLOGIN_D2 = 262144
const val WLOGIN_DA2 = 33554432
const val WLOGIN_LHSIG = 4194304
const val WLOGIN_LSKEY = 512
const val WLOGIN_OPENKEY = 16384
const val WLOGIN_PAYTOKEN = 8388608
const val WLOGIN_PF = 16777216
const val WLOGIN_PSKEY = 1048576
const val WLOGIN_PT4Token = 134217728
const val WLOGIN_QRPUSH = 67108864
const val WLOGIN_RESERVED = 16
const val WLOGIN_SID = 524288
const val WLOGIN_SIG64 = 8192
const val WLOGIN_SKEY = 4096
const val WLOGIN_ST = 128
const val WLOGIN_STWEB = 32 // TLV 103
const val WLOGIN_TOKEN = 32768
const val WLOGIN_VKEY = 131072
}
fun getStWeb(uin: String): String {
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getStweb(uin)
}
}

View File

@ -1,125 +0,0 @@
package moe.fuqiuluo.http
import com.tencent.mobileqq.helper.ShamrockConfig
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.install
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.request.uri
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.ktor.server.routing.routing
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import moe.fuqiuluo.http.api.index
import moe.fuqiuluo.http.entries.CommonResult
import moe.fuqiuluo.http.entries.ErrorCatch
import de.robv.android.xposed.XposedBridge.log
import com.tencent.qqnt.msg.LogicException
import com.tencent.qqnt.msg.ParamsException
import moe.fuqiuluo.http.api.banTroopMember
import moe.fuqiuluo.http.api.energy
import moe.fuqiuluo.http.api.getAccountInfo
import moe.fuqiuluo.http.api.getMsfInfo
import moe.fuqiuluo.http.api.getMsg
import moe.fuqiuluo.http.api.getStartTime
import moe.fuqiuluo.http.api.isBlackListUin
import moe.fuqiuluo.http.api.kickTroopMember
import moe.fuqiuluo.http.api.sendGroupMessage
import moe.fuqiuluo.http.api.sendLike
import moe.fuqiuluo.http.api.setProfileCard
import moe.fuqiuluo.http.api.shut
import moe.fuqiuluo.http.api.sign
import moe.fuqiuluo.http.api.uploadGroupImage
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.xposed.helper.LogCenter
import moe.fuqiuluo.xposed.helper.internal.DataRequester
import mqq.app.MobileQQ
// 接口名称-------是否需要打开专业级开关
private val API_LIST = arrayOf(
Routing::index to false,
Routing::getAccountInfo to false,
Routing::getMsfInfo to true,
Routing::getStartTime to false,
Routing::uploadGroupImage to true,
Routing::energy to true,
Routing::sign to true,
Routing::isBlackListUin to false,
Routing::setProfileCard to false,
Routing::shut to false,
Routing::sendGroupMessage to false,
Routing::getMsg to false,
Routing::sendLike to false,
Routing::kickTroopMember to false,
Routing::banTroopMember to false
)
object HTTPServer {
@JvmStatic
var isQueryServiceStarted = false
internal var startTime = 0L
private val actionMutex = Mutex()
private lateinit var server: ApplicationEngine
internal var PORT: Int = 0
suspend fun start(port: Int) {
if (isQueryServiceStarted) return
actionMutex.withLock {
server = embeddedServer(Netty, port = port) {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
install(StatusPages) {
exception<Throwable> { call, cause ->
call.respond(when (cause) {
is ParamsException -> CommonResult("failed", Status.BadParam.code, ErrorCatch(call.request.uri, cause.message ?: ""))
is LogicException -> CommonResult("failed", Status.LogicError.code, ErrorCatch(call.request.uri, cause.message ?: ""))
else -> CommonResult("failed", Status.InternalHandlerError.code, ErrorCatch(call.request.uri, cause.stackTraceToString()))
})
}
}
routing {
kotlin.runCatching {
val proApi = ShamrockConfig.isPro()
API_LIST.forEach {
if (!it.second || proApi) {
it.first.invoke(this)
}
}
}.onFailure { log(it) }
}
}
server.start(wait = false)
startTime = System.currentTimeMillis()
isQueryServiceStarted = true
this.PORT = port
LogCenter.log("Start HTTP Server: http://0.0.0.0:$PORT/")
DataRequester.request("success", mapOf("port" to PORT))
}
}
suspend fun changePort(port: Int) {
if (this.PORT == port && isQueryServiceStarted) return
stop()
start(port)
}
suspend fun stop() {
actionMutex.withLock {
server.stop()
isQueryServiceStarted = false
}
}
}

View File

@ -1,15 +0,0 @@
package moe.fuqiuluo.http.action.handlers
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
internal object GetSupportedActions: IActionHandler() {
override suspend fun handle(session: ActionSession): String {
return resultToString(true, Status.Ok, ActionManager.actionMap.keys.toList())
}
override fun path(): String = "get_supported_actions"
}

View File

@ -1,17 +0,0 @@
package moe.fuqiuluo.http.api
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.HTTPServer
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.xposed.tools.getOrPost
import moe.fuqiuluo.xposed.tools.respond
fun Routing.getStartTime() {
getOrPost("/get_start_time") {
respond(
isOk = true,
code = Status.Ok,
HTTPServer.startTime
)
}
}

View File

@ -1,24 +0,0 @@
package moe.fuqiuluo.http.api
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
import com.tencent.mobileqq.qroute.QRoute
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.xposed.tools.fetchGetOrThrow
import moe.fuqiuluo.xposed.tools.respond
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
fun Routing.isBlackListUin() {
get("/is_blacklist_uin") {
val uin = fetchGetOrThrow("uin")
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
val isBlack = suspendCoroutine { continuation ->
blacklistApi.isBlackOrBlackedUin(uin) {
continuation.resume(it)
}
}
respond(true, Status.Ok, isBlack)
}
}

View File

@ -1,35 +0,0 @@
package moe.fuqiuluo.http.api
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.xposed.tools.fetchOrNull
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
fun Routing.setProfileCard() {
getOrPost("/set_qq_profile") {
val nickName = fetchOrThrow("nickname")
val company = fetchOrThrow("company")
val email = fetchOrThrow("email")
val college = fetchOrThrow("college")
val personalNote = fetchOrThrow("personal_note")
val age = fetchOrNull("age")
val birthday = fetchOrNull("birthday")
val handler = ActionManager["set_qq_profile"]!!
call.respondText(handler.handle(ActionSession(mapOf(
"nickname" to nickName,
"company" to company,
"email" to email,
"college" to college,
"personal_note" to personalNote,
"age" to age,
"birthday" to birthday
))))
}
}

View File

@ -1,17 +0,0 @@
package moe.fuqiuluo.http.api
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import kotlinx.coroutines.delay
import moe.fuqiuluo.http.HTTPServer
import moe.fuqiuluo.xposed.helper.LogCenter
import kotlin.system.exitProcess
fun Routing.shut() {
get("/shut") {
HTTPServer.stop()
LogCenter.log("正在关闭Shamrock。", toast = true)
delay(3000)
exitProcess(0)
}
}

View File

@ -1,184 +0,0 @@
package moe.fuqiuluo.http.api
import com.tencent.mobileqq.qsec.qsecdandelionsdk.Dandelion
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import moe.fuqiuluo.xposed.tools.fetchGetOrNull
import moe.fuqiuluo.xposed.tools.fetchGetOrThrow
import moe.fuqiuluo.xposed.tools.fetchPostOrNull
import moe.fuqiuluo.xposed.tools.fetchPostOrThrow
import moe.fuqiuluo.xposed.tools.hex2ByteArray
import moe.fuqiuluo.xposed.tools.toHexString
import java.nio.ByteBuffer
fun Routing.energy() {
get("/custom_energy") {
val data = fetchGetOrThrow("data")
val salt = fetchGetOrThrow("salt").hex2ByteArray()
val sign = Dandelion.getInstance().fly(data, salt)
call.respond(OldApiResult(0, "success", sign.toHexString()))
}
post("/energy") {
val data = fetchPostOrThrow("data")
if(!(data.startsWith("810_") || data.startsWith("812_"))) {
call.respond(OldApiResult(-2, "data参数不合法", null))
return@post
}
var mode = fetchPostOrNull("mode")
if (mode == null) {
mode = when(data) {
"810_d", "810_a", "810_f", "810_9" -> "v2"
"810_2", "810_25", "810_7", "810_24" -> "v1"
"812_a" -> "v3"
"812_5" -> "v4"
else -> null
}
}
if (mode == null) {
call.respond(OldApiResult(-2, "无法自动决断mode请主动提供", null))
return@post
}
val salt = when (mode) {
"v1" -> {
val uin = fetchPostOrThrow("uin").toLong()
val version = fetchPostOrThrow("version")
val guid = fetchPostOrThrow("guid").hex2ByteArray()
val salt = ByteBuffer.allocate(8 + 2 + guid.size + 2 + 10 + 4)
val sub = data.substring(4).toInt(16)
salt.putLong(uin)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.array()
}
"v2" -> {
val version = fetchPostOrThrow("version")
val guid = fetchPostOrThrow("guid").hex2ByteArray()
val sub = data.substring(4).toInt(16)
val salt = ByteBuffer.allocate(4 + 2 + guid.size + 2 + 10 + 4 + 4)
salt.putInt(0)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.putInt(0)
salt.array()
}
"v3" -> { // 812_a
val version = fetchPostOrThrow("version")
val phone = fetchPostOrThrow("phone").toByteArray() // 86-xxx
val salt = ByteBuffer.allocate(phone.size + 2 + 2 + version.length + 2)
// 38 36 2D 31 37 33 36 30 32 32 39 31 37 32
// 00 00
// 00 06
// 38 2E 39 2E 33 38
// 00 00
// result => 0C051B17347DF3B8EFDE849FC233C88DBEA23F5277099BB313A9CD000000004B744F7A00000000
salt.put(phone)
//println(String(phone))
salt.putShort(0)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putShort(0)
salt.array()
}
"v4" -> { // 812_5
error("Not support [v4] mode.")
}
else -> ByteArray(0)
}
val sign = Dandelion.getInstance().fly(data, salt)
call.respond(OldApiResult(0, "success", sign.toHexString()))
}
get("/energy") {
val data = fetchGetOrThrow("data")
if(!(data.startsWith("810_") || data.startsWith("812_"))) {
call.respond(OldApiResult(-2, "data参数不合法", null))
return@get
}
var mode = fetchGetOrNull("mode")
if (mode == null) {
mode = when(data) {
"810_d", "810_a", "810_f", "810_9" -> "v2"
"810_2", "810_25", "810_7", "810_24" -> "v1"
"812_a" -> "v3"
"812_5" -> "v4"
else -> null
}
}
if (mode == null) {
call.respond(OldApiResult(-2, "无法自动决断mode请主动提供", null))
return@get
}
val salt = when (mode) {
"v1" -> {
val uin = fetchGetOrThrow("uin").toLong()
val version = fetchGetOrThrow("version")
val guid = fetchGetOrThrow("guid").hex2ByteArray()
val salt = ByteBuffer.allocate(8 + 2 + guid.size + 2 + 10 + 4)
val sub = data.substring(4).toInt(16)
salt.putLong(uin)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.array()
}
"v2" -> {
val version = fetchGetOrThrow("version")
val guid = fetchGetOrThrow("guid").hex2ByteArray()
val sub = data.substring(4).toInt(16)
val salt = ByteBuffer.allocate(4 + 2 + guid.size + 2 + 10 + 4 + 4)
salt.putInt(0)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.putInt(0)
salt.array()
}
"v3" -> { // 812_a
val version = fetchGetOrThrow("version")
val phone = fetchGetOrThrow("phone").toByteArray() // 86-xxx
val salt = ByteBuffer.allocate(phone.size + 2 + 2 + version.length + 2)
// 38 36 2D 31 37 33 36 30 32 32 39 31 37 32
// 00 00
// 00 06
// 38 2E 39 2E 33 38
// 00 00
// result => 0C051B17347DF3B8EFDE849FC233C88DBEA23F5277099BB313A9CD000000004B744F7A00000000
salt.put(phone)
//println(String(phone))
salt.putShort(0)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putShort(0)
salt.array()
}
"v4" -> { // 812_5
error("Not support [v4] mode.")
}
else -> ByteArray(0)
}
val sign = Dandelion.getInstance().fly(data, salt)
call.respond(OldApiResult(0, "success", sign.toHexString()))
}
}

View File

@ -1,99 +0,0 @@
package moe.fuqiuluo.http.api
import com.tencent.mobileqq.sign.QQSecuritySign.SignResult
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.util.pipeline.PipelineContext
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.Serializable
import moe.fuqiuluo.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.xposed.helper.internal.IPCRequest
import moe.fuqiuluo.xposed.tools.broadcast
import moe.fuqiuluo.xposed.tools.fetchGetOrThrow
import moe.fuqiuluo.xposed.tools.fetchPostOrThrow
import moe.fuqiuluo.xposed.tools.hex2ByteArray
import moe.fuqiuluo.xposed.tools.toHexString
import mqq.app.MobileQQ
import kotlin.coroutines.resume
fun Routing.sign() {
get("/sign") {
val uin = fetchGetOrThrow("uin")
val cmd = fetchGetOrThrow("cmd")
val seq = fetchGetOrThrow("seq").toInt()
val buffer = fetchGetOrThrow("buffer").hex2ByteArray()
requestSign(cmd, uin, seq, buffer)
}
post("/sign") {
val uin = fetchPostOrThrow("uin")
val cmd = fetchPostOrThrow("cmd")
val seq = fetchPostOrThrow("seq").toInt()
val buffer = fetchPostOrThrow("buffer").hex2ByteArray()
requestSign(cmd, uin, seq, buffer)
}
}
@Serializable
private data class Sign(
val token: String,
val extra: String,
val sign: String,
val o3did: String,
val requestCallback: List<Int>
)
private val reqLock = Mutex() // 懒得做高并发支持,写个锁,能用就行
private suspend fun PipelineContext<Unit, ApplicationCall>.requestSign(
cmd: String,
uin: String,
seq: Int,
buffer: ByteArray,
) {
val sign = reqLock.withLock {
withTimeoutOrNull(5000) {
suspendCancellableCoroutine { con ->
DynamicReceiver.register("sign_callback", IPCRequest {
con.resume(SignResult().apply {
this.sign = it.getByteArrayExtra("sign") ?: error("无法获取SIGN")
this.token = it.getByteArrayExtra("token")
this.extra = it.getByteArrayExtra("extra")
})
})
MobileQQ.getContext().broadcast("msf") {
putExtra("__cmd", "sign")
putExtra("wupCmd", cmd)
putExtra("uin", uin)
putExtra("seq", seq)
putExtra("buffer", buffer)
}
con.invokeOnCancellation {
DynamicReceiver.unregister("sign")
con.resume(SignResult())
}
}
}
} ?: SignResult()
call.respond(
OldApiResult(0, "success",
Sign(
sign.token.toHexString(),
sign.extra.toHexString(),
sign.sign.toHexString(),
"",
listOf()
)
)
)
}

View File

@ -1,19 +0,0 @@
package moe.fuqiuluo.http.api
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import com.tencent.qqnt.msg.LogicException
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
fun Routing.getMsg() {
getOrPost("/get_msg") {
val msgId = fetchOrThrow("message_id")
call.respondText(ActionManager["get_msg"]?.handle(ActionSession(mapOf(
"message_id" to msgId
))) ?: throw LogicException("Unable to obtain get_msg handler."))
}
}

View File

@ -1,60 +0,0 @@
package moe.fuqiuluo.http.api
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.request.httpVersion
import io.ktor.server.request.receiveText
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import moe.fuqiuluo.http.HTTPServer
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.http.entries.IndexData
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.xposed.tools.asJsonObject
import moe.fuqiuluo.xposed.tools.asString
import moe.fuqiuluo.xposed.tools.respond
import mqq.app.MobileQQ
@Serializable
data class OldApiResult<T>(
val code: Int,
val msg: String = "",
@Contextual
val data: T? = null
)
fun Routing.index() {
get("/") {
respond(
isOk = true,
code = Status.Ok,
data = IndexData(MobileQQ.getMobileQQ().qqProcessName, HTTPServer.startTime, call.request.httpVersion)
)
}
// Action局
post("/") {
val jsonText = call.receiveText()
val actionObject = Json.parseToJsonElement(jsonText).jsonObject
val action = actionObject["action"].asString
val params = actionObject["params"].asJsonObject
val handler = ActionManager[action]
if (handler == null) {
respond(false, Status.UnsupportedAction, EmptyObject, "不支持的Action")
} else {
call.respondText(
handler.handle(ActionSession(params)), ContentType.Application.Json
)
}
}
}

View File

@ -1,21 +0,0 @@
package moe.fuqiuluo.http.api
import com.tencent.qqnt.msg.LogicException
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.xposed.tools.fetchOrNull
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
fun Routing.kickTroopMember() {
getOrPost("/set_group_kick") {
call.respondText(ActionManager["set_group_kick"]?.handle(ActionSession(mapOf(
"user_id" to fetchOrThrow("user_id"),
"group_id" to fetchOrThrow("group_id"),
"reject_add_request" to (fetchOrNull("reject_add_request") ?: "false"),
))) ?: throw LogicException("Unable to obtain set_group_kick handler."))
}
}

View File

@ -1,21 +0,0 @@
package moe.fuqiuluo.http.api
import com.tencent.qqnt.msg.LogicException
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
fun Routing.sendLike() {
getOrPost("/send_like") {
val uin = fetchOrThrow("user_id")
val cnt = fetchOrThrow("times")
call.respondText(ActionManager["send_like"]?.handle(ActionSession(mapOf(
"user_id" to uin,
"cnt" to cnt
))) ?: throw LogicException("Unable to obtain send_like handler."))
}
}

View File

@ -0,0 +1,83 @@
package moe.fuqiuluo.remote
import com.tencent.mobileqq.helper.ShamrockConfig
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.routing.Routing
import io.ktor.server.routing.routing
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import moe.fuqiuluo.remote.api.*
import moe.fuqiuluo.remote.config.contentNegotiation
import moe.fuqiuluo.remote.config.statusPages
import moe.fuqiuluo.xposed.helper.LogCenter
import moe.fuqiuluo.xposed.helper.internal.DataRequester
// 接口名称-------是否需要打开专业级开关
private val API_LIST = arrayOf(
Routing::getAccountInfo to false,
Routing::getMsfInfo to true,
Routing::getStartTime to false,
Routing::uploadGroupImage to true,
Routing::energy to true,
Routing::isBlackListUin to false,
Routing::setProfileCard to false,
Routing::shut to false,
Routing::sendGroupMessage to false,
Routing::getMsg to false,
Routing::sendLike to false,
Routing::kickTroopMember to false,
Routing::banTroopMember to false
)
object HTTPServer {
@JvmStatic
var isQueryServiceStarted = false
internal var startTime = 0L
private val actionMutex = Mutex()
private lateinit var server: ApplicationEngine
internal var currServerPort: Int = 0
suspend fun start(port: Int) {
if (isQueryServiceStarted) return
actionMutex.withLock {
server = embeddedServer(Netty, port = port) {
contentNegotiation()
statusPages()
routing {
echoVersion()
obtainFrameworkInfo()
registerBDH()
userAction()
messageAction()
troopAction()
if (ShamrockConfig.isPro()) {
qsign()
obtainProtocolData()
}
}
}
server.start(wait = false)
}
startTime = System.currentTimeMillis()
isQueryServiceStarted = true
this.currServerPort = port
LogCenter.log("Start HTTP Server: http://0.0.0.0:$currServerPort/")
DataRequester.request("success", mapOf("port" to currServerPort))
}
suspend fun changePort(port: Int) {
if (this.currServerPort == port && isQueryServiceStarted) return
stop()
start(port)
}
suspend fun stop() {
actionMutex.withLock {
server.stop()
isQueryServiceStarted = false
}
}
}

View File

@ -1,13 +1,13 @@
package moe.fuqiuluo.http.action
package moe.fuqiuluo.remote.action
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import moe.fuqiuluo.http.action.handlers.*
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.action.handlers.*
import moe.fuqiuluo.remote.entries.EmptyObject
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
import moe.fuqiuluo.xposed.tools.*
internal object ActionManager {

View File

@ -1,8 +1,8 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.qqnt.protocol.GroupSvc
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
internal object BanTroopMember: IActionHandler() {
override suspend fun handle(session: ActionSession): String {

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.qqnt.helper.MessageHelper
import com.tencent.qqnt.protocol.MsgSvc
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.remote.entries.EmptyObject
internal object DeleteMessage: IActionHandler() {
override suspend fun handle(session: ActionSession): String {

View File

@ -1,7 +1,7 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.xposed.helper.NTServiceFetcher
internal object GetForwardMsg: IActionHandler() {

View File

@ -1,5 +1,5 @@
@file:OptIn(DelicateCoroutinesApi::class)
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.mobileqq.friend.api.IFriendDataService
import com.tencent.mobileqq.friend.api.IFriendHandlerService
@ -8,8 +8,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.FriendEntry
import com.tencent.mobileqq.data.PlatformType
import mqq.app.AppRuntime

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.entries.EmptyObject
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
// 弱智玩意,不予实现
// 请开启HTTP回调 把事件回调回去

View File

@ -1,12 +1,12 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.MessageDetail
import com.tencent.mobileqq.data.MessageSender
import com.tencent.qqnt.helper.MessageHelper

View File

@ -1,16 +1,16 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.mobileqq.data.Card
import kotlinx.coroutines.sync.Mutex
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.Location
import com.tencent.mobileqq.data.ProfileCard
import com.tencent.mobileqq.data.VipInfo
import com.tencent.mobileqq.data.VipType
import com.tencent.qqnt.protocol.CardSvc
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
internal object GetProfileCard: IActionHandler() {
private val refreshLock = Mutex() // 防止重复注册监视器导致错误

View File

@ -1,11 +1,11 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.mobileqq.app.QQAppInterface
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.UserDetail
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
import mqq.app.MobileQQ
internal object GetSelfInfo: IActionHandler() {

View File

@ -1,11 +1,11 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import com.tencent.mobileqq.data.BotStatus
import com.tencent.mobileqq.data.Self
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
import mqq.app.MobileQQ
internal object GetStatus: IActionHandler() {

View File

@ -0,0 +1,15 @@
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionManager
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
internal object GetSupportedActions: IActionHandler() {
override suspend fun handle(session: ActionSession): String {
return resultToString(true, Status.Ok, ActionManager.actionMap.keys.toList())
}
override fun path(): String = "get_supported_actions"
}

View File

@ -1,13 +1,9 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.common.app.AppInterface
import com.tencent.mobileqq.data.troop.TroopInfo
import com.tencent.mobileqq.troop.api.ITroopInfoService
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.SimpleTroopInfo
import com.tencent.qqnt.protocol.GroupSvc
import mqq.app.MobileQQ

View File

@ -1,5 +1,5 @@
@file:OptIn(DelicateCoroutinesApi::class)
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.common.app.AppInterface
import com.tencent.mobileqq.data.troop.TroopInfo
@ -9,8 +9,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.SimpleTroopInfo
import com.tencent.qqnt.protocol.GroupSvc
import mqq.app.MobileQQ

View File

@ -1,7 +1,7 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.SimpleTroopMemberInfo
import com.tencent.qqnt.protocol.GroupSvc
import moe.fuqiuluo.xposed.tools.ifNullOrEmpty

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.common.app.AppInterface
import com.tencent.mobileqq.data.troop.TroopMemberInfo
@ -7,8 +7,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.SimpleTroopMemberInfo
import com.tencent.qqnt.protocol.GroupSvc
import moe.fuqiuluo.xposed.tools.ifNullOrEmpty

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import kotlinx.coroutines.suspendCancellableCoroutine
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
import moe.fuqiuluo.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.xposed.tools.asString
import kotlin.coroutines.resume

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import kotlinx.coroutines.suspendCancellableCoroutine
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
import moe.fuqiuluo.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.xposed.tools.asString
import kotlin.coroutines.resume

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import com.tencent.mobileqq.data.VersionInfo
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
internal object GetVersion: IActionHandler() {
override suspend fun handle(session: ActionSession): String {

View File

@ -1,8 +1,8 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.qqnt.protocol.GroupSvc
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
internal object KickTroopMember: IActionHandler() {
override suspend fun handle(session: ActionSession): String {

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.common.app.AppInterface
import com.tencent.qqnt.protocol.GroupSvc
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.entries.EmptyObject
import mqq.app.MobileQQ
internal object LeaveTroop: IActionHandler() {

View File

@ -1,11 +1,11 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.mobileqq.app.QQAppInterface
import com.tencent.mobileqq.troop.api.ITroopInfoService
import com.tencent.qqnt.protocol.GroupSvc
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.entries.EmptyObject
import mqq.app.MobileQQ
internal object ModifyTroopName: IActionHandler() {

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import android.util.Base64
import com.tencent.mobileqq.qroute.QRoute
import com.tencent.mobileqq.qrscan.api.IQRCodeApi
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
// TODO
internal object ScanQRCode: IActionHandler() {

View File

@ -1,9 +1,9 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.qqnt.protocol.VisitorSvc
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.entries.EmptyObject
internal object SendLike: IActionHandler() {
override suspend fun handle(session: ActionSession): String {

View File

@ -1,10 +1,8 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.serialization.json.JsonArray
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import com.tencent.mobileqq.data.MessageResult
import com.tencent.qqnt.helper.MessageHelper
import com.tencent.qqnt.msg.InternalMessageMakerError

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import android.os.Bundle
import com.tencent.mobileqq.data.ProfileProtocolConst
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.action.IActionHandler
import mqq.app.MobileQQ
internal object SetProfileCard: IActionHandler() {

View File

@ -1,10 +1,10 @@
package moe.fuqiuluo.http.action.handlers
package moe.fuqiuluo.remote.action.handlers
import kotlinx.serialization.Serializable
import moe.fuqiuluo.http.action.IActionHandler
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.resultToString
import moe.fuqiuluo.remote.action.IActionHandler
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.resultToString
import de.robv.android.xposed.XposedBridge.log
internal object TestHandler: IActionHandler() {

View File

@ -1,12 +1,11 @@
package moe.fuqiuluo.http.api
package moe.fuqiuluo.remote.api
import android.util.Base64
import com.tencent.mobileqq.transfile.TransferRequest
import com.tencent.mobileqq.transfile.TransferRequest.PicUpExtraInfo
import com.tencent.mobileqq.transfile.api.ITransFileController
import io.ktor.server.routing.Routing
import io.ktor.server.routing.post
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.xposed.tools.fetchPost
import moe.fuqiuluo.xposed.tools.respond
import mqq.app.MobileQQ
@ -14,7 +13,7 @@ import oicq.wlogin_sdk.tools.MD5
import kotlin.random.Random
import kotlin.random.nextLong
fun Routing.uploadGroupImage() {
fun Routing.registerBDH() {
post("/upload_group_image") {
val troop = fetchPost("troop")
val picBytes = Base64.decode(fetchPost("pic"), Base64.DEFAULT)
@ -38,16 +37,13 @@ fun Routing.uploadGroupImage() {
transferRequest.mBusiType = 1030
transferRequest.mMd5 = md5Str
transferRequest.mLocalPath = file.absolutePath
val picUpExtraInfo = PicUpExtraInfo()
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = true
transferRequest.mPicSendSource = 8
transferRequest.mExtraObj = picUpExtraInfo
(runtime.getRuntimeService(ITransFileController::class.java, "all") as ITransFileController)
.transferAsync(transferRequest)
respond(
isOk = true,
Status.Ok, "$md5Str.jpg"
)
respond(isOk = true, Status.Ok, "$md5Str.jpg")
}
}

View File

@ -0,0 +1,61 @@
package moe.fuqiuluo.remote.api
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.request.httpVersion
import io.ktor.server.request.receiveText
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import moe.fuqiuluo.remote.HTTPServer
import moe.fuqiuluo.remote.action.ActionManager
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.entries.EmptyObject
import moe.fuqiuluo.remote.entries.IndexData
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.xposed.tools.asJsonObject
import moe.fuqiuluo.xposed.tools.asString
import moe.fuqiuluo.xposed.tools.respond
import mqq.app.MobileQQ
@Serializable
data class OldApiResult<T>(
val code: Int,
val msg: String = "",
@Contextual
val data: T? = null
)
fun Routing.echoVersion() {
route("/") {
get {
respond(
isOk = true,
code = Status.Ok,
data = IndexData(MobileQQ.getMobileQQ().qqProcessName, HTTPServer.startTime, call.request.httpVersion)
)
}
post {
val jsonText = call.receiveText()
val actionObject = Json.parseToJsonElement(jsonText).jsonObject
val action = actionObject["action"].asString
val params = actionObject["params"].asJsonObject
val handler = ActionManager[action]
if (handler == null) {
respond(false, Status.UnsupportedAction, EmptyObject, "不支持的Action")
} else {
call.respondText(
handler.handle(ActionSession(params)), ContentType.Application.Json
)
}
}
}
}

View File

@ -0,0 +1,240 @@
package moe.fuqiuluo.remote.api
import com.tencent.mobileqq.qsec.qsecdandelionsdk.Dandelion
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import io.ktor.util.pipeline.PipelineContext
import kotlinx.serialization.Serializable
import moe.fuqiuluo.remote.entries.EmptyObject
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.xposed.ipc.IQSigner
import moe.fuqiuluo.xposed.ipc.ShamrockIpc
import moe.fuqiuluo.xposed.tools.fetchGetOrNull
import moe.fuqiuluo.xposed.tools.fetchGetOrThrow
import moe.fuqiuluo.xposed.tools.fetchPostOrNull
import moe.fuqiuluo.xposed.tools.fetchPostOrThrow
import moe.fuqiuluo.xposed.tools.hex2ByteArray
import moe.fuqiuluo.xposed.tools.respond
import moe.fuqiuluo.xposed.tools.toHexString
import java.nio.ByteBuffer
fun Routing.qsign() {
route("/sign") {
get {
val uin = fetchGetOrThrow("uin")
val cmd = fetchGetOrThrow("cmd")
val seq = fetchGetOrThrow("seq").toInt()
val buffer = fetchGetOrThrow("buffer").hex2ByteArray()
requestSign(cmd, uin, seq, buffer)
}
post {
val uin = fetchPostOrThrow("uin")
val cmd = fetchPostOrThrow("cmd")
val seq = fetchPostOrThrow("seq").toInt()
val buffer = fetchPostOrThrow("buffer").hex2ByteArray()
requestSign(cmd, uin, seq, buffer)
}
}
get("/custom_energy") {
val data = fetchGetOrThrow("data")
val salt = fetchGetOrThrow("salt").hex2ByteArray()
val sign = Dandelion.getInstance().fly(data, salt)
call.respond(OldApiResult(0, "success", sign.toHexString()))
}
route("/energy") {
get {
val data = fetchGetOrThrow("data")
if(!(data.startsWith("810_") || data.startsWith("812_"))) {
call.respond(OldApiResult(-2, "data参数不合法", null))
return@get
}
var mode = fetchGetOrNull("mode")
if (mode == null) {
mode = when(data) {
"810_d", "810_a", "810_f", "810_9" -> "v2"
"810_2", "810_25", "810_7", "810_24" -> "v1"
"812_a" -> "v3"
"812_5" -> "v4"
else -> null
}
}
if (mode == null) {
call.respond(OldApiResult(-2, "无法自动决断mode请主动提供", null))
return@get
}
val salt = when (mode) {
"v1" -> {
val uin = fetchGetOrThrow("uin").toLong()
val version = fetchGetOrThrow("version")
val guid = fetchGetOrThrow("guid").hex2ByteArray()
val salt = ByteBuffer.allocate(8 + 2 + guid.size + 2 + 10 + 4)
val sub = data.substring(4).toInt(16)
salt.putLong(uin)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.array()
}
"v2" -> {
val version = fetchGetOrThrow("version")
val guid = fetchGetOrThrow("guid").hex2ByteArray()
val sub = data.substring(4).toInt(16)
val salt = ByteBuffer.allocate(4 + 2 + guid.size + 2 + 10 + 4 + 4)
salt.putInt(0)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.putInt(0)
salt.array()
}
"v3" -> { // 812_a
val version = fetchGetOrThrow("version")
val phone = fetchGetOrThrow("phone").toByteArray() // 86-xxx
val salt = ByteBuffer.allocate(phone.size + 2 + 2 + version.length + 2)
// 38 36 2D 31 37 33 36 30 32 32 39 31 37 32
// 00 00
// 00 06
// 38 2E 39 2E 33 38
// 00 00
// result => 0C051B17347DF3B8EFDE849FC233C88DBEA23F5277099BB313A9CD000000004B744F7A00000000
salt.put(phone)
//println(String(phone))
salt.putShort(0)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putShort(0)
salt.array()
}
"v4" -> { // 812_5
error("Not support [v4] mode.")
}
else -> ByteArray(0)
}
val sign = Dandelion.getInstance().fly(data, salt)
call.respond(OldApiResult(0, "success", sign.toHexString()))
}
post {
val data = fetchPostOrThrow("data")
if(!(data.startsWith("810_") || data.startsWith("812_"))) {
call.respond(OldApiResult(-2, "data参数不合法", null))
return@post
}
var mode = fetchPostOrNull("mode")
if (mode == null) {
mode = when(data) {
"810_d", "810_a", "810_f", "810_9" -> "v2"
"810_2", "810_25", "810_7", "810_24" -> "v1"
"812_a" -> "v3"
"812_5" -> "v4"
else -> null
}
}
if (mode == null) {
call.respond(OldApiResult(-2, "无法自动决断mode请主动提供", null))
return@post
}
val salt = when (mode) {
"v1" -> {
val uin = fetchPostOrThrow("uin").toLong()
val version = fetchPostOrThrow("version")
val guid = fetchPostOrThrow("guid").hex2ByteArray()
val salt = ByteBuffer.allocate(8 + 2 + guid.size + 2 + 10 + 4)
val sub = data.substring(4).toInt(16)
salt.putLong(uin)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.array()
}
"v2" -> {
val version = fetchPostOrThrow("version")
val guid = fetchPostOrThrow("guid").hex2ByteArray()
val sub = data.substring(4).toInt(16)
val salt = ByteBuffer.allocate(4 + 2 + guid.size + 2 + 10 + 4 + 4)
salt.putInt(0)
salt.putShort(guid.size.toShort())
salt.put(guid)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putInt(sub)
salt.putInt(0)
salt.array()
}
"v3" -> { // 812_a
val version = fetchPostOrThrow("version")
val phone = fetchPostOrThrow("phone").toByteArray() // 86-xxx
val salt = ByteBuffer.allocate(phone.size + 2 + 2 + version.length + 2)
salt.put(phone)
//println(String(phone))
salt.putShort(0)
salt.putShort(version.length.toShort())
salt.put(version.toByteArray())
salt.putShort(0)
salt.array()
}
"v4" -> { // 812_5
error("Not support [v4] mode.")
}
else -> ByteArray(0)
}
val sign = Dandelion.getInstance().fly(data, salt)
call.respond(OldApiResult(0, "success", sign.toHexString()))
}
}
}
@Serializable
private data class Sign(
val token: String,
val extra: String,
val sign: String,
val o3did: String,
val requestCallback: List<Int>
)
private lateinit var signer: IQSigner
private suspend fun PipelineContext<Unit, ApplicationCall>.requestSign(
cmd: String,
uin: String,
seq: Int,
buffer: ByteArray,
) {
if (!::signer.isInitialized) {
val binder = ShamrockIpc.get(ShamrockIpc.IPC_QSIGN)
if (binder == null) {
respond(false, Status.InternalHandlerError, EmptyObject)
return
} else {
signer = IQSigner.Stub.asInterface(binder)
}
}
val sign = signer.sign(cmd, seq, uin, buffer)
call.respond(OldApiResult(0, "success", Sign(
sign.token.toHexString(),
sign.extra.toHexString(),
sign.sign.toHexString(), "", listOf()
)))
}

View File

@ -1,18 +1,38 @@
package moe.fuqiuluo.http.api
package moe.fuqiuluo.remote.api
import com.tencent.mobileqq.app.QQAppInterface
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.entries.CommonResult
import moe.fuqiuluo.http.entries.CurrentAccount
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.http.entries.StdAccount
import io.ktor.server.routing.get
import kotlinx.coroutines.delay
import moe.fuqiuluo.remote.HTTPServer
import moe.fuqiuluo.remote.entries.CommonResult
import moe.fuqiuluo.remote.entries.CurrentAccount
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.remote.entries.StdAccount
import moe.fuqiuluo.xposed.helper.LogCenter
import moe.fuqiuluo.xposed.tools.getOrPost
import moe.fuqiuluo.xposed.tools.respond
import mqq.app.MobileQQ
import kotlin.system.exitProcess
fun Routing.obtainFrameworkInfo() {
getOrPost("/get_start_time") {
respond(
isOk = true,
code = Status.Ok,
HTTPServer.startTime
)
}
get("/shut") {
HTTPServer.stop()
LogCenter.log("正在关闭Shamrock。", toast = true)
delay(3000)
exitProcess(0)
}
fun Routing.getAccountInfo() {
getOrPost("/get_account_info") {
val accounts = MobileQQ.getMobileQQ().allAccounts
val runtime = MobileQQ.getMobileQQ().waitAppRuntime()
@ -48,4 +68,4 @@ fun Routing.getAccountInfo() {
))
}
}
}
}

View File

@ -1,10 +1,12 @@
package moe.fuqiuluo.http.api
package moe.fuqiuluo.remote.api
import com.tencent.mobileqq.dt.model.FEBound
import com.tencent.qqnt.protocol.TicketSvc
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.entries.Protocol
import moe.fuqiuluo.http.entries.QSignDtConfig
import moe.fuqiuluo.http.entries.Status
import moe.fuqiuluo.remote.entries.Protocol
import moe.fuqiuluo.remote.entries.QSignDtConfig
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
import moe.fuqiuluo.xposed.tools.respond
import moe.fuqiuluo.xposed.tools.toHexString
@ -13,9 +15,17 @@ import oicq.wlogin_sdk.tlv_type.tlv_t100
import oicq.wlogin_sdk.tlv_type.tlv_t106
import oicq.wlogin_sdk.tlv_type.tlv_t18
import oicq.wlogin_sdk.tools.util
import moe.fuqiuluo.xposed.tools.util.buf_to_string
fun Routing.getMsfInfo() {
fun Routing.obtainProtocolData() {
getOrPost("/get_ticket") {
val uin = fetchOrThrow("uin")
val ticket = when(fetchOrThrow("id").toInt()) {
32 -> TicketSvc.getStWeb(uin)
else -> error("不支持获取该Ticket")
}
respond(true, Status.Ok, "success", ticket)
}
getOrPost("/get_msf_info") {
val mqq = MobileQQ.getMobileQQ()
val ctx = MobileQQ.getContext()
@ -25,7 +35,7 @@ fun Routing.getMsfInfo() {
val t106 = tlv_t106()
val qimei = kotlin.runCatching {
buf_to_string(util.get_qimei(ctx))
moe.fuqiuluo.xposed.tools.util.buf_to_string(util.get_qimei(ctx))
}.getOrNull()
val encodeTable = FEBound::class.java.getDeclaredField("mConfigEnCode").also {
@ -44,8 +54,8 @@ fun Routing.getMsfInfo() {
mqq.msfConnectedNetType,
qimei ?: "",
util.getSvnVersion(),
buf_to_string( util.getGuidFromFile(ctx) ),
buf_to_string( util.get_ksid(ctx) ),
moe.fuqiuluo.xposed.tools.util.buf_to_string( util.getGuidFromFile(ctx) ),
moe.fuqiuluo.xposed.tools.util.buf_to_string( util.get_ksid(ctx) ),
util.get_network_type(ctx),
t18._ping_version.toByte(), t18._sso_version,
t100._sso_ver, t100._db_buf_ver,

View File

@ -1,16 +1,16 @@
package moe.fuqiuluo.http.api
package moe.fuqiuluo.remote.api
import com.tencent.qqnt.msg.LogicException
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import moe.fuqiuluo.remote.action.ActionManager
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.xposed.tools.fetchOrNull
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
fun Routing.banTroopMember() {
fun Routing.troopAction() {
getOrPost("/set_group_ban") {
val groupId = fetchOrThrow("group_id") .toLong()
val userId = fetchOrThrow("user_id") .toLong()
@ -22,4 +22,12 @@ fun Routing.banTroopMember() {
"duration" to duration,
))) ?: throw LogicException("Unable to obtain set_group_ban handler."))
}
getOrPost("/set_group_kick") {
call.respondText(ActionManager["set_group_kick"]?.handle(ActionSession(mapOf(
"user_id" to fetchOrThrow("user_id"),
"group_id" to fetchOrThrow("group_id"),
"reject_add_request" to (fetchOrNull("reject_add_request") ?: "false"),
))) ?: throw LogicException("Unable to obtain set_group_kick handler."))
}
}

View File

@ -1,22 +1,33 @@
package moe.fuqiuluo.http.api
package moe.fuqiuluo.remote.api
import com.tencent.qqnt.msg.LogicException
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import moe.fuqiuluo.http.action.ActionManager
import moe.fuqiuluo.http.action.ActionSession
import com.tencent.qqnt.msg.LogicException
import moe.fuqiuluo.remote.action.ActionManager
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.xposed.tools.fetchGetOrNull
import moe.fuqiuluo.xposed.tools.fetchGetOrThrow
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.fetchPostJsonArray
import moe.fuqiuluo.xposed.tools.fetchPostOrNull
import moe.fuqiuluo.xposed.tools.fetchPostOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
import moe.fuqiuluo.xposed.tools.isJsonData
import moe.fuqiuluo.xposed.tools.isString
fun Routing.sendGroupMessage() {
fun Routing.messageAction() {
getOrPost("/get_msg") {
val msgId = fetchOrThrow("message_id")
call.respondText(
ActionManager["get_msg"]?.handle(
ActionSession(mapOf(
"message_id" to msgId
))) ?: throw LogicException("Unable to obtain get_msg handler."))
}
get("/send_msg") {
val msgType = fetchGetOrThrow("message_type")
val message = fetchGetOrThrow("message")
@ -27,8 +38,7 @@ fun Routing.sendGroupMessage() {
peerIdKey to fetchGetOrThrow(peerIdKey),
"message" to message,
"auto_escape" to autoEscape
))) ?: throw LogicException("Unable to obtain send_message handler.")
)
))) ?: throw LogicException("Unable to obtain send_message handler."))
}
post("/send_msg") {
@ -42,8 +52,7 @@ fun Routing.sendGroupMessage() {
fetchPostJsonArray("message")
} else fetchPostOrThrow("message"),
"auto_escape" to autoEscape
))) ?: throw LogicException("Unable to obtain send_message handler.")
)
))) ?: throw LogicException("Unable to obtain send_message handler."))
}
get("/send_group_msg") {
@ -56,8 +65,7 @@ fun Routing.sendGroupMessage() {
"group_id" to groupId,
"message" to message,
"auto_escape" to autoEscape
))) ?: throw LogicException("Unable to obtain send_message handler.")
)
))) ?: throw LogicException("Unable to obtain send_message handler."))
}
post("/send_group_msg") {
@ -84,8 +92,7 @@ fun Routing.sendGroupMessage() {
"user_id" to userId,
"message" to message,
"auto_escape" to autoEscape
))) ?: throw LogicException("Unable to obtain send_message handler.")
)
))) ?: throw LogicException("Unable to obtain send_message handler."))
}
post("/send_private_msg") {
@ -98,7 +105,6 @@ fun Routing.sendGroupMessage() {
fetchPostJsonArray("message")
} else fetchPostOrThrow("message"),
"auto_escape" to autoEscape
))) ?: throw LogicException("Unable to obtain send_message handler.")
)
))) ?: throw LogicException("Unable to obtain send_message handler."))
}
}

View File

@ -0,0 +1,64 @@
package moe.fuqiuluo.remote.api
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.msg.LogicException
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import moe.fuqiuluo.remote.action.ActionManager
import moe.fuqiuluo.remote.action.ActionSession
import moe.fuqiuluo.remote.entries.Status
import moe.fuqiuluo.xposed.tools.fetchGetOrThrow
import moe.fuqiuluo.xposed.tools.fetchOrNull
import moe.fuqiuluo.xposed.tools.fetchOrThrow
import moe.fuqiuluo.xposed.tools.getOrPost
import moe.fuqiuluo.xposed.tools.respond
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
fun Routing.userAction() {
get("/is_blacklist_uin") {
val uin = fetchGetOrThrow("uin")
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
val isBlack = suspendCoroutine { continuation ->
blacklistApi.isBlackOrBlackedUin(uin) {
continuation.resume(it)
}
}
respond(true, Status.Ok, isBlack)
}
getOrPost("/set_qq_profile") {
val nickName = fetchOrThrow("nickname")
val company = fetchOrThrow("company")
val email = fetchOrThrow("email")
val college = fetchOrThrow("college")
val personalNote = fetchOrThrow("personal_note")
val age = fetchOrNull("age")
val birthday = fetchOrNull("birthday")
val handler = ActionManager["set_qq_profile"]!!
call.respondText(handler.handle(ActionSession(mapOf(
"nickname" to nickName,
"company" to company,
"email" to email,
"college" to college,
"personal_note" to personalNote,
"age" to age,
"birthday" to birthday
))))
}
getOrPost("/send_like") {
val uin = fetchOrThrow("user_id")
val cnt = fetchOrThrow("times")
call.respondText(ActionManager["send_like"]?.handle(ActionSession(mapOf(
"user_id" to uin,
"cnt" to cnt
))) ?: throw LogicException("Unable to obtain send_like handler."))
}
}

View File

@ -0,0 +1,17 @@
package moe.fuqiuluo.remote.config
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import kotlinx.serialization.json.Json
fun Application.contentNegotiation() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}

View File

@ -0,0 +1,26 @@
package moe.fuqiuluo.remote.config
import com.tencent.qqnt.msg.LogicException
import com.tencent.qqnt.msg.ParamsException
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.request.uri
import io.ktor.server.response.respond
import moe.fuqiuluo.remote.entries.CommonResult
import moe.fuqiuluo.remote.entries.ErrorCatch
import moe.fuqiuluo.remote.entries.Status
fun Application.statusPages() {
install(StatusPages) {
exception<ParamsException> { call, cause ->
call.respond(CommonResult("failed", Status.BadParam.code, ErrorCatch(call.request.uri, cause.message ?: "")))
}
exception<LogicException> { call, cause ->
call.respond(CommonResult("failed", Status.LogicError.code, ErrorCatch(call.request.uri, cause.message ?: "")))
}
exception<Throwable> { call, cause ->
call.respond(CommonResult("failed", Status.InternalHandlerError.code, ErrorCatch(call.request.uri, cause.stackTraceToString())))
}
}
}

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.http.entries
package moe.fuqiuluo.remote.entries
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.http.entries
package moe.fuqiuluo.remote.entries
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.http.entries
package moe.fuqiuluo.remote.entries
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.http.entries
package moe.fuqiuluo.remote.entries
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.http.entries
package moe.fuqiuluo.remote.entries
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@ -7,7 +7,7 @@ import de.robv.android.xposed.XposedBridge
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moe.fuqiuluo.http.HTTPServer
import moe.fuqiuluo.remote.HTTPServer
import com.tencent.qqnt.utils.PlatformUtils
internal class CreateHTTP: IAction {

View File

@ -0,0 +1,33 @@
package moe.fuqiuluo.xposed.actions
import android.content.Context
import android.os.Bundle
import com.tencent.qqnt.utils.PlatformUtils
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moe.fuqiuluo.xposed.helper.Level
import moe.fuqiuluo.xposed.helper.LogCenter
import moe.fuqiuluo.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.xposed.helper.internal.IPCRequest
import moe.fuqiuluo.xposed.ipc.ShamrockIpc
import moe.fuqiuluo.xposed.tools.broadcast
internal class IpcService: IAction {
override fun invoke(ctx: Context) {
if (!PlatformUtils.isMsfProcess()) return
DynamicReceiver.register("fetch_ipc", IPCRequest {
val name = it.getStringExtra("ipc_name")
GlobalScope.launch {
ShamrockIpc.get(name)?.also { binder ->
ctx.broadcast("xqbot") {
putExtra("__cmd", "ipc_callback")
putExtra("ipc", Bundle().also {
it.putString("name", name)
it.putBinder("binder", binder)
})
}
} ?: LogCenter.log("无法获取IPC: $name", Level.WARN)
}
})
}
}

View File

@ -1,29 +0,0 @@
package moe.fuqiuluo.xposed.actions
import android.content.Context
import com.tencent.mobileqq.fe.FEKit
import moe.fuqiuluo.xposed.actions.IAction
import com.tencent.qqnt.utils.PlatformUtils
import moe.fuqiuluo.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.xposed.helper.internal.IPCRequest
import moe.fuqiuluo.xposed.tools.broadcast
internal class MsfSignService: IAction {
override fun invoke(ctx: Context) {
if (!PlatformUtils.isMsfProcess()) return
DynamicReceiver.register("sign", IPCRequest {
val cmd = it.getStringExtra("wupCmd")
val seq = it.getIntExtra("seq", -1)
val buffer = it.getByteArrayExtra("buffer")
val uin = it.getStringExtra("uin")
val sign = FEKit.getInstance().getSign(cmd, buffer, seq, uin)
ctx.broadcast("xqbot") {
putExtra("__cmd", "sign_callback")
putExtra("sign", sign.sign)
putExtra("token", sign.token)
putExtra("extra", sign.extra)
}
})
}
}

View File

@ -4,7 +4,7 @@ import android.content.Context
import com.tencent.mobileqq.helper.ShamrockConfig
import moe.fuqiuluo.xposed.helper.internal.DataRequester
import moe.fuqiuluo.http.HTTPServer
import moe.fuqiuluo.remote.HTTPServer
import com.tencent.qqnt.utils.PlatformUtils
import moe.fuqiuluo.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.xposed.helper.internal.IPCRequest
@ -25,7 +25,7 @@ class PullConfig: IAction {
if (!PlatformUtils.isMainProcess()) return
DynamicReceiver.register("fetchPort", IPCRequest {
DataRequester.request("success", mapOf("port" to HTTPServer.PORT))
DataRequester.request("success", mapOf("port" to HTTPServer.currServerPort))
})
DataRequester.request("init", onFailure = {

View File

@ -0,0 +1,36 @@
package moe.fuqiuluo.xposed.ipc
import android.os.Parcel
import android.os.Parcelable
data class IQSign(
val token: ByteArray,
val extra: ByteArray,
val sign: ByteArray
): Parcelable {
constructor(parcel: Parcel) : this(
parcel.createByteArray()!!,
parcel.createByteArray()!!,
parcel.createByteArray()!!
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeByteArray(token)
parcel.writeByteArray(extra)
parcel.writeByteArray(sign)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<IQSign> {
override fun createFromParcel(parcel: Parcel): IQSign {
return IQSign(parcel)
}
override fun newArray(size: Int): Array<IQSign?> {
return arrayOfNulls(size)
}
}
}

View File

@ -0,0 +1,10 @@
package moe.fuqiuluo.xposed.ipc
import com.tencent.mobileqq.fe.FEKit
internal object QSignGenerator: IQSigner.Stub() {
override fun sign(cmd: String, seq: Int, uin: String, buffer: ByteArray): IQSign {
val sign = FEKit.getInstance().getSign(cmd, buffer, seq, uin)
return IQSign(sign.token, sign.extra, sign.sign,)
}
}

View File

@ -0,0 +1,44 @@
package moe.fuqiuluo.xposed.ipc
import android.os.IBinder
import com.tencent.qqnt.utils.PlatformUtils
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.xposed.helper.internal.IPCRequest
import moe.fuqiuluo.xposed.tools.broadcast
import mqq.app.MobileQQ
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
internal object ShamrockIpc {
const val IPC_QSIGN = "qsign"
private val IpcChannel = hashMapOf(
IPC_QSIGN to QSignGenerator
)
private val mLock = Mutex()
suspend fun get(name: String?): IBinder? {
return if (PlatformUtils.isMsfProcess()) {
IpcChannel[name]
} else {
mLock.withLock {
MobileQQ.getContext().broadcast("msf") {
putExtra("__cmd", "fetch_ipc")
putExtra("ipc_name", name)
}
withTimeoutOrNull(3000) {
suspendCoroutine { continuation ->
DynamicReceiver.register("ipc_callback", IPCRequest {
val bundle = it.getBundleExtra("ipc")!!
val binder = bundle.getBinder("binder")
continuation.resume(binder)
})
}
}
}
}
}
}

View File

@ -9,7 +9,7 @@ import moe.fuqiuluo.xposed.actions.ForceTablet
import moe.fuqiuluo.xposed.actions.HookForDebug
import moe.fuqiuluo.xposed.actions.HookWrapperCodec
import moe.fuqiuluo.xposed.actions.IAction
import moe.fuqiuluo.xposed.actions.MsfSignService
import moe.fuqiuluo.xposed.actions.IpcService
import moe.fuqiuluo.xposed.actions.PullConfig
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
@ -21,7 +21,7 @@ object ActionLoader {
ForceTablet::class, // 强制平板模式
HookWrapperCodec::class, // 注册服务处理器
HookForDebug::class,
MsfSignService::class,
IpcService::class,
FixLibraryLoad::class
)

View File

@ -21,9 +21,10 @@ import kotlinx.serialization.json.jsonObject
import com.tencent.qqnt.msg.ParamsException
import io.ktor.http.HttpMethod
import io.ktor.server.request.httpMethod
import moe.fuqiuluo.http.entries.CommonResult
import moe.fuqiuluo.http.entries.EmptyObject
import moe.fuqiuluo.http.entries.Status
import io.ktor.server.routing.route
import moe.fuqiuluo.remote.entries.CommonResult
import moe.fuqiuluo.remote.entries.EmptyObject
import moe.fuqiuluo.remote.entries.Status
@DslMarker
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE)
@ -145,8 +146,10 @@ suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostOrNull(key: String):
@io.ktor.util.KtorDsl
fun Routing.getOrPost(path: String, body: suspend PipelineContext<Unit, ApplicationCall>.(Unit) -> Unit) {
get(path, body)
post(path, body)
route(path) {
get(body)
post(body)
}
}
@ShamrockDsl