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);
@Nullable
IGProSimpleProfile getSimpleProfile(String str, String str2, int i2);
IGProSimpleProfile getSimpleProfile(String guildId, String tinyId, int appId);
List<IGProGuildInfo> getSortedGuildList();
@ -819,7 +819,7 @@ public interface IGPSService extends IRuntimeService {
void refreshGuildList(boolean z);
void refreshGuildUserProfileInfo(String str, String str2);
void refreshGuildUserProfileInfo(String guildId, String userTinyId);
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 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
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() {
fun getSelfTinyId(): Long {
val service = app.getRuntimeService(IGPSService::class.java, "all")
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.Serializable
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.remote.action.ActionSession
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 {
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
))
}
return ok(GuildListResult(
result
), echo, "success")
val result = GProSvc.getGuildList(refresh)
return ok(GuildListResult(result), echo, "success")
}
@Serializable
data class GuildListResult(
@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
import com.tencent.mobileqq.qqguildsdk.api.IGPSService
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
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.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("get_guild_service_profile")
@ -15,18 +16,24 @@ internal object GetGuildServiceProfile : IActionHandler() {
return invoke(echo = session.echo)
}
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
// TODO: get_guild_service_profile
return ok(EmptyJsonObject, echo, "此功能尚未实现")
val service = AppRuntimeFetcher.appRuntime
.getRuntimeService(IGPSService::class.java, "all")
if (!service.isGProSDKInitCompleted) {
return error("GPro服务没有初始化", echo = echo)
suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.getSelfGuildInfo()
result.onFailure {
return error(it.message ?: "unable to fetch self guild info", echo)
}
val tinyId = service.selfTinyId
return ok(echo = echo)
val info = result.getOrThrow()
LogCenter.log(info.toString())
return ok(GuildServiceProfile(
nickName = info.nickName ?: info.memberName ?: "",
tinyId = info.memberTinyid,
avatarUrl = info.url ?: ""
), echo = echo)
}
@Serializable
data class GuildServiceProfile(
@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
import com.tencent.mobileqq.qqguildsdk.api.IGPSService
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import moe.fuqiuluo.shamrock.remote.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.respond
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
fun Routing.guildAction() {
getOrPost("/get_guild_service_profile") {
val service = AppRuntimeFetcher.appRuntime
.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)
call.respondText(GetGuildServiceProfile(), ContentType.Application.Json)
}
getOrPost("/get_guild_list") {
call.respondText("ok")
call.respondText(GetGuildList(), ContentType.Application.Json)
}
}