Shamrock: support /get_guild_service_profile

This commit is contained in:
白池 2024-01-31 16:52:28 +08:00
parent 30b48c6677
commit 7540ef04bb
9 changed files with 272 additions and 87 deletions

View File

@ -0,0 +1,117 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.oidb.cmd0xf88
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class Oidb0xf88Req(
@ProtoNumber(1) val filter: GProFilter,
@ProtoNumber(2) val memberId: ULong,
@ProtoNumber(3) val tinyId: ULong,
@ProtoNumber(4) val guildId: ULong,
)
@Serializable
data class Oidb0xf88Rsp(
@ProtoNumber(1) val userInfo: GProUserInfo?
)
@Serializable
data class GProUserInfo(
@ProtoNumber(1) var memberId: ULong = ULong.MIN_VALUE,
@ProtoNumber(2) var memberTinyid: ULong = ULong.MIN_VALUE,
@ProtoNumber(3) var nickName: String? = null,
@ProtoNumber(4) var gender: UInt = UInt.MIN_VALUE,
@ProtoNumber(5) var allow: UInt = UInt.MIN_VALUE,
@ProtoNumber(6) var url: String? = null,
@ProtoNumber(7) var birthday: String? = null,
@ProtoNumber(8) var fullBirthday: String? = null,
@ProtoNumber(9) var fullAge: String? = null,
@ProtoNumber(10) var country: String? = null,
@ProtoNumber(11) var province: String? = null,
@ProtoNumber(12) var city: String? = null,
@ProtoNumber(13) var cityId: String? = null,
@ProtoNumber(14) var cityZoneId: UInt = UInt.MIN_VALUE,
@ProtoNumber(15) var msgHeadInfo: GProHeadInfo? = null,
@ProtoNumber(16) var joinTime: ULong = ULong.MIN_VALUE,
@ProtoNumber(17) var memberRole: UInt = UInt.MIN_VALUE,
@ProtoNumber(18) var member_type: UInt = UInt.MIN_VALUE,
@ProtoNumber(19) var be_admin_time: ULong = ULong.MIN_VALUE,
@ProtoNumber(20) var memberName: String? = null,
//@ProtoNumber(21) var clientPresence: Any? = null,
//@ProtoNumber(22) var client_archive: ArrayList<>? = null,
//@ProtoNumber(23) var bind_client_account: ArrayList<>? = null,
@ProtoNumber(24) var hasMoreArchive: Boolean = false,
//@ProtoNumber(25) var firstArchiveArkData: Any? = null,
@ProtoNumber(26) var directMsgBlackFlag: UInt = UInt.MIN_VALUE,
@ProtoNumber(27) var setGroupProProfile: Boolean = false,
@ProtoNumber(28) var joinGroupProTimestamp: ULong = ULong.MIN_VALUE,
@ProtoNumber(29) var shutUpExpireTime: ULong = ULong.MIN_VALUE,
@ProtoNumber(30) var avatarMeta: ByteArray? = null,
@ProtoNumber(31) var memberNameFlag: UInt = UInt.MIN_VALUE,
@ProtoNumber(32) var faceAuthStatus: UInt = UInt.MIN_VALUE,
@ProtoNumber(33) var verifyUrl: String? = null,
@ProtoNumber(34) var constellation: UInt = UInt.MIN_VALUE,
@ProtoNumber(35) var personalSign: ByteArray? = null,
//@ProtoNumber(36) var voice_live_info: Any? = null,
@ProtoNumber(37) var avatarFlag: UInt = UInt.MIN_VALUE,
//@ProtoNumber(38) var isQQFriend: Any? = null,
//@ProtoNumber(39) var openid: Any? = null,
//@ProtoNumber(40) var personalSignTemplate: Any? = null,
//@ProtoNumber(41) var showVoiceLiveStatusSwitch: Any? = null,
@ProtoNumber(99) var isMember: UInt = UInt.MIN_VALUE,
)
@Serializable
data class GProHeadInfo(
@ProtoNumber(1) var timestamp: UInt = UInt.MIN_VALUE,
@ProtoNumber(2) var faceFlag: UInt = UInt.MIN_VALUE,
@ProtoNumber(3) var baseUrl: String? = null,
@ProtoNumber(4) var type: UInt = UInt.MIN_VALUE
)
@Serializable
data class GProFilter(
@ProtoNumber(3) val nickName: UInt = UInt.MIN_VALUE,
@ProtoNumber(4) val gender: UInt = UInt.MIN_VALUE,
@ProtoNumber(5) val allow: UInt = UInt.MIN_VALUE,
@ProtoNumber(6) val url: UInt = UInt.MIN_VALUE,
@ProtoNumber(7) val birthday: UInt = UInt.MIN_VALUE,
@ProtoNumber(8) val fullBirthday: UInt = UInt.MIN_VALUE,
@ProtoNumber(9) val fullAge: UInt = UInt.MIN_VALUE,
@ProtoNumber(10) val country: UInt = UInt.MIN_VALUE,
@ProtoNumber(11) val province: UInt = UInt.MIN_VALUE,
@ProtoNumber(12) val city: UInt = UInt.MIN_VALUE,
@ProtoNumber(13) val cityId: UInt = UInt.MIN_VALUE,
@ProtoNumber(14) val cityZoneId: UInt = UInt.MIN_VALUE,
@ProtoNumber(15) val headInfo: UInt = UInt.MIN_VALUE,
@ProtoNumber(16) val joinTime: UInt = UInt.MIN_VALUE,
@ProtoNumber(17) val memberRole: UInt = UInt.MIN_VALUE,
@ProtoNumber(18) val memberType: UInt = UInt.MIN_VALUE,
@ProtoNumber(19) val beAdminTime: UInt = UInt.MIN_VALUE,
@ProtoNumber(20) val memberName: UInt = UInt.MIN_VALUE,
@ProtoNumber(21) val clientPresence: UInt = UInt.MIN_VALUE,
@ProtoNumber(22) val clientArchive: UInt = UInt.MIN_VALUE,
@ProtoNumber(23) val bindClientAccount: UInt = UInt.MIN_VALUE,
@ProtoNumber(24) val hasMoreArchive: UInt = UInt.MIN_VALUE,
@ProtoNumber(25) val firstArchiveBaseInfo: UInt = UInt.MIN_VALUE,
@ProtoNumber(26) val directMsgBlackFlag: UInt = UInt.MIN_VALUE,
@ProtoNumber(27) val joinGroupProTimestamp: UInt = UInt.MIN_VALUE,
@ProtoNumber(28) val shutupExpireTime: UInt = UInt.MIN_VALUE,
@ProtoNumber(29) val faceAuthStatus: UInt = UInt.MIN_VALUE,
@ProtoNumber(30) val constellation: UInt = UInt.MIN_VALUE,
@ProtoNumber(31) val personalSign: UInt = UInt.MIN_VALUE,
@ProtoNumber(32) val voiceLiveInfo: UInt = UInt.MIN_VALUE,
@ProtoNumber(33) val isQQFriend: UInt = UInt.MIN_VALUE,
@ProtoNumber(34) val personalSignTemplate: UInt = UInt.MIN_VALUE,
@ProtoNumber(35) val showVoiceLiveStatusSwitch: UInt = UInt.MIN_VALUE,
@ProtoNumber(36) val openid: UInt = UInt.MIN_VALUE,
@ProtoNumber(37) val isMember: UInt = UInt.MIN_VALUE,
@ProtoNumber(99) val needGroupProProfile: UInt = UInt.MIN_VALUE,
@ProtoNumber(100) val avatarMeta: UInt = UInt.MIN_VALUE,
) {
}

View File

@ -712,7 +712,7 @@ public interface IGPSService extends IRuntimeService {
int getShareButtonFromCache(long j2, long j3, boolean z); int getShareButtonFromCache(long j2, long j3, boolean z);
@Nullable @Nullable
IGProSimpleProfile getSimpleProfile(String str, String str2, int i2); IGProSimpleProfile getSimpleProfile(String guildId, String tinyId, int appId);
List<IGProGuildInfo> getSortedGuildList(); List<IGProGuildInfo> getSortedGuildList();
@ -819,7 +819,7 @@ public interface IGPSService extends IRuntimeService {
void refreshGuildList(boolean z); void refreshGuildList(boolean z);
void refreshGuildUserProfileInfo(String str, String str2); void refreshGuildUserProfileInfo(String guildId, String userTinyId);
void refreshPollingData(); void refreshPollingData();

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProGetUserInfoCallback {
void onGetUserInfo(int i2, String str, ArrayList<GProUser> arrayList, ArrayList<Long> arrayList2);
}

View File

@ -34,4 +34,14 @@ public interface IKernelGuildService {
void refreshGuildInfo(long guildId, boolean force, int appId); void refreshGuildInfo(long guildId, boolean force, int appId);
void refreshGuildInfoOnly(long j2, boolean z, int i2); void refreshGuildInfoOnly(long j2, boolean z, int i2);
void fetchUserInfo(long j2, long j3, ArrayList<Long> tinyIdList, int i2, IGProGetUserInfoCallback iGProGetUserInfoCallback);
GProSimpleProfile getSimpleProfile(long guildId, long tinyId, int aid);
GProFaceAuthInfo getFaceAuthInfo();
String getGuildUserAvatarUrl(long guildId, long tinyId, int aid);
String getGuildUserNickname(long guildId);
} }

View File

@ -1,12 +1,92 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.fuqiuluo.qqinterface.servlet package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.qqguildsdk.api.IGPSService import com.tencent.mobileqq.qqguildsdk.api.IGPSService
import com.tencent.qqnt.kernel.nativeinterface.GProUser
import com.tencent.qqnt.kernel.nativeinterface.IGProGetUserInfoCallback
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo
import moe.fuqiuluo.qqinterface.servlet.structures.GuildStatus
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.whitechi73.protobuf.oidb.cmd0xf88.GProFilter
import moe.whitechi73.protobuf.oidb.cmd0xf88.GProUserInfo
import moe.whitechi73.protobuf.oidb.cmd0xf88.Oidb0xf88Req
import moe.whitechi73.protobuf.oidb.cmd0xf88.Oidb0xf88Rsp
import tencent.im.oidb.cmd0xeac.oidb_0xeac
import tencent.im.oidb.oidb_sso
internal object GProSvc: BaseSvc() { internal object GProSvc: BaseSvc() {
fun getSelfTinyId(): Long { fun getSelfTinyId(): Long {
val service = app.getRuntimeService(IGPSService::class.java, "all") val service = app.getRuntimeService(IGPSService::class.java, "all")
return service.selfTinyId.toLong() return service.selfTinyId.toLong()
} }
suspend fun getSelfGuildInfo(): Result<GProUserInfo> {
PlatformUtils.requireMinQQVersion(version = PlatformUtils.QQ_9_0_8_VER)
//val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
//val service = app.getRuntimeService(IGPSService::class.java, "all")
val selfTinyId = getSelfTinyId()
//LogCenter.log(kernelGProService.faceAuthInfo.toString())
//kernelGProService.fetchUserInfo(0, 0, arrayListOf(selfTinyId), 0) { result, reason, userInfoList, tinyIdList ->
// LogCenter.log("selfTinyId: $selfTinyId, $result $reason profile: $userInfoList")
//}
//LogCenter.log(kernelGProService.getGuildUserNickname(0))
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf88_1", 0xf88, 1, ProtoBuf.encodeToByteArray(Oidb0xf88Req(
filter = GProFilter(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u),
memberId = 0uL,
tinyId = selfTinyId.toULong(),
guildId = 0uL
)))
val body = oidb_sso.OIDBSSOPkg()
if (respBuffer == null) {
return Result.failure(Exception("unable to send packet"))
}
body.mergeFrom(respBuffer.slice(4))
return runCatching {
ProtoBuf.decodeFromByteArray<Oidb0xf88Rsp>(
body.bytes_bodybuffer.get().toByteArray()
).userInfo!!
}
}
fun getGuildList(refresh: Boolean = false): ArrayList<GuildInfo> {
PlatformUtils.requireMinQQVersion(version = PlatformUtils.QQ_9_0_8_VER)
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
if (refresh) {
kernelGProService.refreshGuildList(true)
kernelGProService.guildListFromCache.forEach {
kernelGProService.refreshGuildInfo(it.guildId, true, 1)
}
}
val result = arrayListOf<GuildInfo>()
kernelGProService.guildListFromCache.forEach {
if (it.result != 0) return@forEach
val guildInfo = it.guildInfo
result.add(GuildInfo(
guildId = it.guildId,
guildName = guildInfo.guildName ?: "",
guildDisplayId = guildInfo.guildNumber ?: "",
profile = guildInfo.profile ?: "",
status = GuildStatus(
isEnable = guildInfo.guildStatus?.isEnable == 1,
isBanned = guildInfo.guildStatus?.isBanned == 1,
isFrozen = guildInfo.guildStatus?.isFrozen == 1
),
ownerId = guildInfo.ownerTinyid,
shutUpTime = guildInfo.shutupExpireTime,
allowSearch = guildInfo.allowSearch == 1
))
}
return result
}
} }

View File

@ -0,0 +1,23 @@
package moe.fuqiuluo.qqinterface.servlet.structures
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GuildInfo(
@SerialName("guild_id") var guildId: Long,
@SerialName("guild_name") var guildName: String,
@SerialName("guild_display_id") var guildDisplayId: String,
@SerialName("profile") var profile: String,
@SerialName("status") var status: GuildStatus,
@SerialName("owner_id") var ownerId: Long,
@SerialName("shutup_expire_time") var shutUpTime: Long,
@SerialName("allow_search") var allowSearch: Boolean
)
@Serializable
data class GuildStatus(
@SerialName("is_enable") var isEnable: Boolean,
@SerialName("is_banned") var isBanned: Boolean,
@SerialName("is_frozen") var isFrozen: Boolean
)

View File

@ -3,6 +3,8 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.action.ActionSession import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.remote.action.IActionHandler
@ -20,59 +22,12 @@ internal object GetGuildList : IActionHandler() {
} }
operator fun invoke(refresh: Boolean = true, echo: JsonElement = EmptyJsonString): String { operator fun invoke(refresh: Boolean = true, echo: JsonElement = EmptyJsonString): String {
PlatformUtils.requireMinQQVersion(version = PlatformUtils.QQ_9_0_8_VER) val result = GProSvc.getGuildList(refresh)
return ok(GuildListResult(result), echo, "success")
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
if (refresh) {
kernelGProService.refreshGuildList(true)
kernelGProService.guildListFromCache.forEach {
kernelGProService.refreshGuildInfo(it.guildId, true, 1)
}
}
val result = arrayListOf<GuildInfo>()
kernelGProService.guildListFromCache.forEach {
if (it.result != 0) return@forEach
val guildInfo = it.guildInfo
result.add(
GuildInfo(
guildId = it.guildId,
guildName = guildInfo.guildName ?: "",
guildDisplayId = guildInfo.guildNumber ?: "",
profile = guildInfo.profile ?: "",
status = GuildStatus(
isEnable = guildInfo.guildStatus?.isEnable == 1,
isBanned = guildInfo.guildStatus?.isBanned == 1,
isFrozen = guildInfo.guildStatus?.isFrozen == 1
),
ownerId = guildInfo.ownerTinyid,
shutUpTime = guildInfo.shutupExpireTime
))
}
return ok(GuildListResult(
result
), echo, "success")
} }
@Serializable @Serializable
data class GuildListResult( data class GuildListResult(
@SerialName("guild_list") var guildList: List<GuildInfo> = arrayListOf() @SerialName("guild_list") var guildList: List<GuildInfo> = arrayListOf()
) )
@Serializable
data class GuildInfo(
@SerialName("guild_id") var guildId: Long,
@SerialName("guild_name") var guildName: String,
@SerialName("guild_display_id") var guildDisplayId: String,
@SerialName("profile") var profile: String,
@SerialName("status") var status: GuildStatus,
@SerialName("owner_id") var ownerId: Long,
@SerialName("shutup_expire_time") var shutUpTime: Long,
)
@Serializable
data class GuildStatus(
@SerialName("is_enable") var isEnable: Boolean,
@SerialName("is_banned") var isBanned: Boolean,
@SerialName("is_frozen") var isFrozen: Boolean
)
} }

View File

@ -1,12 +1,13 @@
package moe.fuqiuluo.shamrock.remote.action.handlers package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.mobileqq.qqguildsdk.api.IGPSService import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.action.ActionSession import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.symbols.OneBotHandler import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("get_guild_service_profile") @OneBotHandler("get_guild_service_profile")
@ -15,18 +16,24 @@ internal object GetGuildServiceProfile : IActionHandler() {
return invoke(echo = session.echo) return invoke(echo = session.echo)
} }
operator fun invoke(echo: JsonElement = EmptyJsonString): String { suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String {
// TODO: get_guild_service_profile val result = GProSvc.getSelfGuildInfo()
return ok(EmptyJsonObject, echo, "此功能尚未实现") result.onFailure {
return error(it.message ?: "unable to fetch self guild info", echo)
val service = AppRuntimeFetcher.appRuntime }
.getRuntimeService(IGPSService::class.java, "all") val info = result.getOrThrow()
if (!service.isGProSDKInitCompleted) { LogCenter.log(info.toString())
return error("GPro服务没有初始化", echo = echo) return ok(GuildServiceProfile(
nickName = info.nickName ?: info.memberName ?: "",
tinyId = info.memberTinyid,
avatarUrl = info.url ?: ""
), echo = echo)
} }
val tinyId = service.selfTinyId @Serializable
data class GuildServiceProfile(
return ok(echo = echo) @SerialName("nickname") val nickName: String,
} @SerialName("tiny_id") val tinyId: ULong,
@SerialName("avatar_url") val avatarUrl: String,
)
} }

View File

@ -1,33 +1,19 @@
package moe.fuqiuluo.shamrock.remote.api package moe.fuqiuluo.shamrock.remote.api
import com.tencent.mobileqq.qqguildsdk.api.IGPSService import io.ktor.http.ContentType
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.response.respondText import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
import moe.fuqiuluo.shamrock.remote.structures.EmptyObject import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildList
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildServiceProfile
import moe.fuqiuluo.shamrock.tools.getOrPost import moe.fuqiuluo.shamrock.tools.getOrPost
import moe.fuqiuluo.shamrock.tools.respond
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
fun Routing.guildAction() { fun Routing.guildAction() {
getOrPost("/get_guild_service_profile") { getOrPost("/get_guild_service_profile") {
val service = AppRuntimeFetcher.appRuntime call.respondText(GetGuildServiceProfile(), ContentType.Application.Json)
.getRuntimeService(IGPSService::class.java, "all")
val tinyId = service.selfTinyId
}
getOrPost("/refresh_guild_list") {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val guildService = sessionService.guildService
guildService.refreshGuildList(true)
respond(false, -100, msg = "测试接口", data = EmptyObject)
} }
getOrPost("/get_guild_list") { getOrPost("/get_guild_list") {
call.respondText(GetGuildList(), ContentType.Application.Json)
call.respondText("ok")
} }
} }