`Shamrock`: remake code struct
This commit is contained in:
parent
31306ef99c
commit
7baacda0ee
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package mqq.manager;
|
||||
|
||||
public interface Manager {
|
||||
void onDestroy();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -20,6 +20,9 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// IQSign.aidl
|
||||
package moe.fuqiuluo.xposed.ipc;
|
||||
|
||||
parcelable IQSign;
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
))))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -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."))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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."))
|
||||
}
|
||||
}
|
|
@ -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."))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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() {
|
|
@ -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
|
|
@ -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回调 把事件回调回去
|
|
@ -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
|
|
@ -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() // 防止重复注册监视器导致错误
|
|
@ -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() {
|
|
@ -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() {
|
|
@ -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"
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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() {
|
|
@ -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() {
|
|
@ -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() {
|
|
@ -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 {
|
|
@ -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
|
|
@ -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() {
|
|
@ -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() {
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)))
|
||||
}
|
|
@ -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() {
|
|||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
|
@ -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."))
|
||||
}
|
||||
}
|
|
@ -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."))
|
||||
}
|
||||
}
|
|
@ -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."))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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())))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package moe.fuqiuluo.http.entries
|
||||
package moe.fuqiuluo.remote.entries
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
|
@ -1,4 +1,4 @@
|
|||
package moe.fuqiuluo.http.entries
|
||||
package moe.fuqiuluo.remote.entries
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
|
@ -1,4 +1,4 @@
|
|||
package moe.fuqiuluo.http.entries
|
||||
package moe.fuqiuluo.remote.entries
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.fuqiuluo.http.entries
|
||||
package moe.fuqiuluo.remote.entries
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
|
@ -1,4 +1,4 @@
|
|||
package moe.fuqiuluo.http.entries
|
||||
package moe.fuqiuluo.remote.entries
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue