mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Shamrock
: グループファイルサービスの実装
Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
parent
cb4268edef
commit
ca47f9dbdf
2
kritor
2
kritor
@ -1 +1 @@
|
||||
Subproject commit 201e91e73225ce3f1ec098c06a5cf6a717d913e5
|
||||
Subproject commit f007631c7e17fe6220055a404fdaaa6e7a24a7ef
|
@ -21,6 +21,9 @@ class KritorServer(
|
||||
.addService(Authentication)
|
||||
.addService(ContactService)
|
||||
.addService(KritorService)
|
||||
.addService(FriendService)
|
||||
.addService(GroupService)
|
||||
.addService(GroupFileService)
|
||||
.build()!!
|
||||
|
||||
fun start(block: Boolean = false) {
|
||||
|
@ -21,8 +21,11 @@ import io.kritor.contact.SetProfileCardResponse
|
||||
import io.kritor.contact.StrangerExt
|
||||
import io.kritor.contact.StrangerInfo
|
||||
import io.kritor.contact.StrangerInfoRequest
|
||||
import io.kritor.contact.VoteUserRequest
|
||||
import io.kritor.contact.VoteUserResponse
|
||||
import io.kritor.contact.profileCard
|
||||
import io.kritor.contact.strangerInfo
|
||||
import io.kritor.contact.voteUserResponse
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import qq.service.QQInterfaces
|
||||
@ -31,10 +34,32 @@ import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
|
||||
@Grpc("ContactService", "VoteUser")
|
||||
override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse {
|
||||
ContactHelper.voteUser(when(request.accountCase!!) {
|
||||
VoteUserRequest.AccountCase.ACCOUNT_UIN -> request.accountUin
|
||||
VoteUserRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong()
|
||||
VoteUserRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("account not set")
|
||||
)
|
||||
}, request.voteCount).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
)
|
||||
}
|
||||
return voteUserResponse { }
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "GetProfileCard")
|
||||
override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard {
|
||||
val uin = if (request.hasUin()) request.uin
|
||||
else ContactHelper.getUinByUidAsync(request.uid).toLong()
|
||||
val uin = when (request.accountCase!!) {
|
||||
ProfileCardRequest.AccountCase.ACCOUNT_UIN -> request.accountUin
|
||||
ProfileCardRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong()
|
||||
ProfileCardRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("account not set")
|
||||
)
|
||||
}
|
||||
|
||||
val contact = ContactHelper.getProfileCard(uin)
|
||||
|
||||
contact.onFailure {
|
||||
@ -46,7 +71,7 @@ object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
|
||||
contact.onSuccess {
|
||||
return profileCard {
|
||||
this.uin = it.uin.toLong()
|
||||
this.uid = if (request.hasUid()) request.uid
|
||||
this.uid = if (request.hasAccountUid()) request.accountUid
|
||||
else ContactHelper.getUidByUinAsync(it.uin.toLong())
|
||||
this.name = it.strNick ?: ""
|
||||
this.remark = it.strReMark ?: ""
|
||||
|
41
xposed/src/main/java/kritor/service/FriendService.kt
Normal file
41
xposed/src/main/java/kritor/service/FriendService.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package kritor.service
|
||||
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.friend.FriendServiceGrpcKt
|
||||
import io.kritor.friend.GetFriendListRequest
|
||||
import io.kritor.friend.GetFriendListResponse
|
||||
import io.kritor.friend.friendData
|
||||
import io.kritor.friend.friendExt
|
||||
import io.kritor.friend.getFriendListResponse
|
||||
import qq.service.contact.ContactHelper
|
||||
import qq.service.friend.FriendHelper
|
||||
|
||||
object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
|
||||
@Grpc("FriendService", "GetFriendList")
|
||||
override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse {
|
||||
val friendList = FriendHelper.getFriendList(if(request.hasRefresh()) request.refresh else false).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
)
|
||||
}.getOrThrow()
|
||||
|
||||
return getFriendListResponse {
|
||||
friendList.forEach {
|
||||
this.friendList.add(friendData {
|
||||
uin = it.uin.toLong()
|
||||
uid = ContactHelper.getUidByUinAsync(uin)
|
||||
qid = ""
|
||||
nick = it.name ?: ""
|
||||
remark = it.remark ?: ""
|
||||
age = it.age
|
||||
level = 0
|
||||
gender = it.gender.toInt()
|
||||
groupId = it.groupid
|
||||
ext = friendExt {}.toByteString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
138
xposed/src/main/java/kritor/service/GroupFileService.kt
Normal file
138
xposed/src/main/java/kritor/service/GroupFileService.kt
Normal file
@ -0,0 +1,138 @@
|
||||
package kritor.service
|
||||
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.file.*
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.cmd0x6d7.CreateFolderReq
|
||||
import protobuf.oidb.cmd0x6d7.DeleteFolderReq
|
||||
import protobuf.oidb.cmd0x6d7.Oidb0x6d7ReqBody
|
||||
import protobuf.oidb.cmd0x6d7.Oidb0x6d7RespBody
|
||||
import protobuf.oidb.cmd0x6d7.RenameFolderReq
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.file.GroupFileHelper
|
||||
import qq.service.file.GroupFileHelper.getGroupFileSystemInfo
|
||||
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
|
||||
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
|
||||
import tencent.im.oidb.oidb_sso
|
||||
|
||||
internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
|
||||
@Grpc("GroupFileService", "CreateFolder")
|
||||
override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse {
|
||||
val data = Oidb0x6d7ReqBody(
|
||||
createFolder = CreateFolderReq(
|
||||
groupCode = request.groupId.toULong(),
|
||||
appId = 3u,
|
||||
parentFolderId = "/",
|
||||
folderName = request.name
|
||||
)
|
||||
).toByteArray()
|
||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
.decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
if (rsp.createFolder?.retCode != 0) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to create folder: ${rsp.createFolder?.retCode}"))
|
||||
}
|
||||
return createFolderResponse {
|
||||
this.id = rsp.createFolder?.folderInfo?.folderId ?: ""
|
||||
this.usedSpace = 0
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "DeleteFolder")
|
||||
override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse {
|
||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
|
||||
deleteFolder = DeleteFolderReq(
|
||||
groupCode = request.groupId.toULong(),
|
||||
appId = 3u,
|
||||
folderId = request.folderId
|
||||
)
|
||||
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
if (rsp.deleteFolder?.retCode != 0) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}"))
|
||||
}
|
||||
return deleteFolderResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "DeleteFile")
|
||||
override suspend fun deleteFile(request: DeleteFileRequest): DeleteFileResponse {
|
||||
val oidb0x6d6ReqBody = oidb_0x6d6.ReqBody().apply {
|
||||
delete_file_req.set(oidb_0x6d6.DeleteFileReqBody().apply {
|
||||
uint64_group_code.set(request.groupId)
|
||||
uint32_app_id.set(3)
|
||||
uint32_bus_id.set(request.busId)
|
||||
str_parent_folder_id.set("/")
|
||||
str_file_id.set(request.fileId)
|
||||
})
|
||||
}
|
||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val rsp = oidb_0x6d6.RspBody().apply {
|
||||
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
|
||||
}
|
||||
if (rsp.delete_file_rsp.int32_ret_code.get() != 0) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete file: ${rsp.delete_file_rsp.int32_ret_code.get()}"))
|
||||
}
|
||||
return deleteFileResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "RenameFolder")
|
||||
override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse {
|
||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
|
||||
renameFolder = RenameFolderReq(
|
||||
groupCode = request.groupId.toULong(),
|
||||
appId = 3u,
|
||||
folderId = request.folderId,
|
||||
folderName = request.name
|
||||
)
|
||||
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
if (rsp.renameFolder?.retCode != 0) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}"))
|
||||
}
|
||||
return renameFolderResponse { }
|
||||
}
|
||||
|
||||
override suspend fun getFileSystemInfo(request: GetFileSystemInfoRequest): GetFileSystemInfoResponse {
|
||||
return getGroupFileSystemInfo(request.groupId)
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "GetRootFiles")
|
||||
override suspend fun getRootFiles(request: GetRootFilesRequest): GetRootFilesResponse {
|
||||
return getRootFilesResponse {
|
||||
val response = GroupFileHelper.getGroupFiles(request.groupId)
|
||||
this.files.addAll(response.filesList)
|
||||
this.folders.addAll(response.foldersList)
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "GetFiles")
|
||||
override suspend fun getFiles(request: GetFilesRequest): GetFilesResponse {
|
||||
return GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
|
||||
}
|
||||
}
|
406
xposed/src/main/java/kritor/service/GroupService.kt
Normal file
406
xposed/src/main/java/kritor/service/GroupService.kt
Normal file
@ -0,0 +1,406 @@
|
||||
package kritor.service
|
||||
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.group.BanMemberRequest
|
||||
import io.kritor.group.BanMemberResponse
|
||||
import io.kritor.group.GetGroupHonorRequest
|
||||
import io.kritor.group.GetGroupHonorResponse
|
||||
import io.kritor.group.GetGroupInfoRequest
|
||||
import io.kritor.group.GetGroupInfoResponse
|
||||
import io.kritor.group.GetGroupListRequest
|
||||
import io.kritor.group.GetGroupListResponse
|
||||
import io.kritor.group.GetGroupMemberInfoRequest
|
||||
import io.kritor.group.GetGroupMemberInfoResponse
|
||||
import io.kritor.group.GetGroupMemberListRequest
|
||||
import io.kritor.group.GetGroupMemberListResponse
|
||||
import io.kritor.group.GetNotJoinedGroupInfoRequest
|
||||
import io.kritor.group.GetNotJoinedGroupInfoResponse
|
||||
import io.kritor.group.GetProhibitedUserListRequest
|
||||
import io.kritor.group.GetProhibitedUserListResponse
|
||||
import io.kritor.group.GetRemainCountAtAllRequest
|
||||
import io.kritor.group.GetRemainCountAtAllResponse
|
||||
import io.kritor.group.GroupServiceGrpcKt
|
||||
import io.kritor.group.KickMemberRequest
|
||||
import io.kritor.group.KickMemberResponse
|
||||
import io.kritor.group.LeaveGroupRequest
|
||||
import io.kritor.group.LeaveGroupResponse
|
||||
import io.kritor.group.ModifyGroupNameRequest
|
||||
import io.kritor.group.ModifyGroupNameResponse
|
||||
import io.kritor.group.ModifyGroupRemarkRequest
|
||||
import io.kritor.group.ModifyGroupRemarkResponse
|
||||
import io.kritor.group.ModifyMemberCardRequest
|
||||
import io.kritor.group.ModifyMemberCardResponse
|
||||
import io.kritor.group.PokeMemberRequest
|
||||
import io.kritor.group.PokeMemberResponse
|
||||
import io.kritor.group.SetGroupAdminRequest
|
||||
import io.kritor.group.SetGroupAdminResponse
|
||||
import io.kritor.group.SetGroupUniqueTitleRequest
|
||||
import io.kritor.group.SetGroupUniqueTitleResponse
|
||||
import io.kritor.group.SetGroupWholeBanRequest
|
||||
import io.kritor.group.SetGroupWholeBanResponse
|
||||
import io.kritor.group.banMemberResponse
|
||||
import io.kritor.group.getGroupHonorResponse
|
||||
import io.kritor.group.getGroupInfoResponse
|
||||
import io.kritor.group.getGroupListResponse
|
||||
import io.kritor.group.getGroupMemberInfoResponse
|
||||
import io.kritor.group.getGroupMemberListResponse
|
||||
import io.kritor.group.getNotJoinedGroupInfoResponse
|
||||
import io.kritor.group.getProhibitedUserListResponse
|
||||
import io.kritor.group.getRemainCountAtAllResponse
|
||||
import io.kritor.group.groupHonorInfo
|
||||
import io.kritor.group.groupMemberInfo
|
||||
import io.kritor.group.kickMemberResponse
|
||||
import io.kritor.group.leaveGroupResponse
|
||||
import io.kritor.group.modifyGroupNameResponse
|
||||
import io.kritor.group.modifyGroupRemarkResponse
|
||||
import io.kritor.group.modifyMemberCardResponse
|
||||
import io.kritor.group.notJoinedGroupInfo
|
||||
import io.kritor.group.pokeMemberResponse
|
||||
import io.kritor.group.prohibitedUserInfo
|
||||
import io.kritor.group.setGroupAdminResponse
|
||||
import io.kritor.group.setGroupUniqueTitleResponse
|
||||
import io.kritor.group.setGroupWholeBanResponse
|
||||
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper
|
||||
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import qq.service.contact.ContactHelper
|
||||
import qq.service.group.GroupHelper
|
||||
|
||||
internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase() {
|
||||
@Grpc("GroupService", "BanMember")
|
||||
override suspend fun banMember(request: BanMemberRequest): BanMemberResponse {
|
||||
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.banMember(request.groupId, when(request.targetCase!!) {
|
||||
BanMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
BanMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
}, request.duration)
|
||||
|
||||
return banMemberResponse {
|
||||
groupId = request.groupId
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "PokeMember")
|
||||
override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse {
|
||||
GroupHelper.pokeMember(request.groupId, when(request.targetCase!!) {
|
||||
PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
PokeMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
})
|
||||
return pokeMemberResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "KickMember")
|
||||
override suspend fun kickMember(request: KickMemberRequest): KickMemberResponse {
|
||||
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
GroupHelper.kickMember(request.groupId, request.rejectAddRequest, if (request.hasKickReason()) request.kickReason else "", when(request.targetCase!!) {
|
||||
KickMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
KickMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
})
|
||||
return kickMemberResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "LeaveGroup")
|
||||
override suspend fun leaveGroup(request: LeaveGroupRequest): LeaveGroupResponse {
|
||||
GroupHelper.resignTroop(request.groupId.toString())
|
||||
return leaveGroupResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "ModifyMemberCard")
|
||||
override suspend fun modifyMemberCard(request: ModifyMemberCardRequest): ModifyMemberCardResponse {
|
||||
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
GroupHelper.modifyGroupMemberCard(request.groupId, when(request.targetCase!!) {
|
||||
ModifyMemberCardRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
ModifyMemberCardRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
}, request.card)
|
||||
return modifyMemberCardResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "ModifyGroupName")
|
||||
override suspend fun modifyGroupName(request: ModifyGroupNameRequest): ModifyGroupNameResponse {
|
||||
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName)
|
||||
|
||||
return modifyGroupNameResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "ModifyGroupRemark")
|
||||
override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse {
|
||||
GroupHelper.modifyGroupRemark(request.groupId, request.remark)
|
||||
|
||||
return modifyGroupRemarkResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "SetGroupAdmin")
|
||||
override suspend fun setGroupAdmin(request: SetGroupAdminRequest): SetGroupAdminResponse {
|
||||
if (!GroupHelper.isOwner(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.setGroupAdmin(request.groupId, when(request.targetCase!!) {
|
||||
SetGroupAdminRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
SetGroupAdminRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
}, request.isAdmin)
|
||||
|
||||
return setGroupAdminResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "SetGroupUniqueTitle")
|
||||
override suspend fun setGroupUniqueTitle(request: SetGroupUniqueTitleRequest): SetGroupUniqueTitleResponse {
|
||||
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.setGroupUniqueTitle(request.groupId, when(request.targetCase!!) {
|
||||
SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
}, request.uniqueTitle)
|
||||
|
||||
return setGroupUniqueTitleResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "SetGroupWholeBan")
|
||||
override suspend fun setGroupWholeBan(request: SetGroupWholeBanRequest): SetGroupWholeBanResponse {
|
||||
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||
throw StatusRuntimeException(Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.setGroupWholeBan(request.groupId, request.isBan)
|
||||
return setGroupWholeBanResponse { }
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupInfo")
|
||||
override suspend fun getGroupInfo(request: GetGroupInfoRequest): GetGroupInfoResponse {
|
||||
val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getGroupInfoResponse {
|
||||
this.groupInfo = io.kritor.group.groupInfo {
|
||||
groupId = groupInfo.troopcode.toLong()
|
||||
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: ""
|
||||
groupRemark = groupInfo.troopRemark ?: ""
|
||||
owner = groupInfo.troopowneruin?.toLong() ?: 0
|
||||
admins.addAll(GroupHelper.getAdminList(groupId))
|
||||
maxMemberCount = groupInfo.wMemberMax
|
||||
memberCount = groupInfo.wMemberNum
|
||||
groupUin = groupInfo.troopuin?.toLong() ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupList")
|
||||
override suspend fun getGroupList(request: GetGroupListRequest): GetGroupListResponse {
|
||||
val groupList = GroupHelper.getGroupList(if (request.hasRefresh()) request.refresh else false).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group list").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getGroupListResponse {
|
||||
groupList.forEach { groupInfo ->
|
||||
this.groupInfo.add(io.kritor.group.groupInfo {
|
||||
groupId = groupInfo.troopcode.toLong()
|
||||
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: ""
|
||||
groupRemark = groupInfo.troopRemark ?: ""
|
||||
owner = groupInfo.troopowneruin?.toLong() ?: 0
|
||||
admins.addAll(GroupHelper.getAdminList(groupId))
|
||||
maxMemberCount = groupInfo.wMemberMax
|
||||
memberCount = groupInfo.wMemberNum
|
||||
groupUin = groupInfo.troopuin?.toLong() ?: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupMemberInfo")
|
||||
override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse {
|
||||
val memberInfo = GroupHelper.getTroopMemberInfoByUin(request.groupId, when(request.targetCase!!) {
|
||||
GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin
|
||||
GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
)
|
||||
}).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member info").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getGroupMemberInfoResponse {
|
||||
groupMemberInfo = groupMemberInfo {
|
||||
uid = if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.UID) request.uid else ContactHelper.getUidByUinAsync(request.uin)
|
||||
uin = memberInfo.memberuin?.toLong() ?: 0
|
||||
nick = memberInfo.troopnick
|
||||
.ifNullOrEmpty { memberInfo.hwName }
|
||||
.ifNullOrEmpty { memberInfo.troopColorNick }
|
||||
.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||
age = memberInfo.age.toInt()
|
||||
uniqueTitle = memberInfo.mUniqueTitle ?: ""
|
||||
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire
|
||||
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||
joinTime = memberInfo.join_time
|
||||
lastActiveTime = memberInfo.last_active_time
|
||||
level = memberInfo.level
|
||||
shutUpTimestamp = memberInfo.gagTimeStamp
|
||||
|
||||
distance = memberInfo.distance
|
||||
honor.addAll((memberInfo.honorList ?: "")
|
||||
.split("|")
|
||||
.filter { it.isNotBlank() }
|
||||
.map { it.toInt() })
|
||||
unfriendly = false
|
||||
cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupMemberList")
|
||||
override suspend fun getGroupMemberList(request: GetGroupMemberListRequest): GetGroupMemberListResponse {
|
||||
val memberList = GroupHelper.getGroupMemberList(request.groupId.toString(), if (request.hasRefresh()) request.refresh else false).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getGroupMemberListResponse {
|
||||
memberList.forEach { memberInfo ->
|
||||
this.groupMemberInfo.add(groupMemberInfo {
|
||||
uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0)
|
||||
uin = memberInfo.memberuin?.toLong() ?: 0
|
||||
nick = memberInfo.troopnick
|
||||
.ifNullOrEmpty { memberInfo.hwName }
|
||||
.ifNullOrEmpty { memberInfo.troopColorNick }
|
||||
.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||
age = memberInfo.age.toInt()
|
||||
uniqueTitle = memberInfo.mUniqueTitle ?: ""
|
||||
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire
|
||||
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||
joinTime = memberInfo.join_time
|
||||
lastActiveTime = memberInfo.last_active_time
|
||||
level = memberInfo.level
|
||||
shutUpTimestamp = memberInfo.gagTimeStamp
|
||||
|
||||
distance = memberInfo.distance
|
||||
honor.addAll((memberInfo.honorList ?: "")
|
||||
.split("|")
|
||||
.filter { it.isNotBlank() }
|
||||
.map { it.toInt() })
|
||||
unfriendly = false
|
||||
cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetProhibitedUserList")
|
||||
override suspend fun getProhibitedUserList(request: GetProhibitedUserListRequest): GetProhibitedUserListResponse {
|
||||
val prohibitedList = GroupHelper.getProhibitedMemberList(request.groupId).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getProhibitedUserListResponse {
|
||||
prohibitedList.forEach {
|
||||
this.prohibitedUserInfo.add(prohibitedUserInfo {
|
||||
uid = ContactHelper.getUidByUinAsync(it.memberUin)
|
||||
uin = it.memberUin
|
||||
prohibitedTime = it.shutuptimestap
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetRemainCountAtAll")
|
||||
override suspend fun getRemainCountAtAll(request: GetRemainCountAtAllRequest): GetRemainCountAtAllResponse {
|
||||
val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getRemainCountAtAllResponse {
|
||||
accessAtAll = remainAtAllRsp.bool_can_at_all.get()
|
||||
remainCountForGroup = remainAtAllRsp.uint32_remain_at_all_count_for_group.get()
|
||||
remainCountForSelf = remainAtAllRsp.uint32_remain_at_all_count_for_uin.get()
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetNotJoinedGroupInfo")
|
||||
override suspend fun getNotJoinedGroupInfo(request: GetNotJoinedGroupInfoRequest): GetNotJoinedGroupInfoResponse {
|
||||
val groupInfo = GroupHelper.getNotJoinedGroupInfo(request.groupId).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getNotJoinedGroupInfoResponse {
|
||||
this.groupInfo = notJoinedGroupInfo {
|
||||
groupId = groupInfo.groupId
|
||||
groupName = groupInfo.groupName
|
||||
owner = groupInfo.owner
|
||||
maxMemberCount = groupInfo.maxMember
|
||||
memberCount = groupInfo.memberCount
|
||||
groupDesc = groupInfo.groupDesc
|
||||
createTime = groupInfo.createTime.toInt()
|
||||
groupFlag = groupInfo.groupFlag
|
||||
groupFlagExt = groupInfo.groupFlagExt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupHonor")
|
||||
override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse {
|
||||
return getGroupHonorResponse {
|
||||
GroupHelper.getGroupMemberList(request.groupId.toString(), true).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it))
|
||||
}.onSuccess { memberList ->
|
||||
memberList.forEach { member ->
|
||||
(member.honorList ?: "").split("|")
|
||||
.filter { it.isNotBlank() }
|
||||
.map { it.toInt() }.forEach {
|
||||
val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag)
|
||||
if (honor != null) {
|
||||
groupHonorInfo.add(groupHonorInfo {
|
||||
uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong())
|
||||
uin = member.memberuin.toLong()
|
||||
nick = member.troopnick
|
||||
.ifNullOrEmpty { member.hwName }
|
||||
.ifNullOrEmpty { member.troopColorNick }
|
||||
.ifNullOrEmpty { member.friendnick } ?: ""
|
||||
honorName = honor.honorName
|
||||
avatar = honor.honorIconUrl
|
||||
id = honor.honorId
|
||||
description = honor.honorUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -104,8 +104,11 @@ object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
|
||||
|
||||
@Grpc("KritorService", "SwitchAccount")
|
||||
override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse {
|
||||
val uin = if (request.hasAccountUin()) request.accountUin.toString()
|
||||
else ContactHelper.getUinByUidAsync(request.accountUid)
|
||||
val uin = when(request.accountCase!!) {
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid)
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_UIN -> request.accountUin.toString()
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("account not found"))
|
||||
}
|
||||
val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == uin }
|
||||
?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("account not found"))
|
||||
runCatching {
|
||||
|
@ -47,8 +47,8 @@ fun ByteArray.slice(off: Int, length: Int = size - off): ByteArray {
|
||||
.let { s -> if (uppercase) s.lowercase(Locale.getDefault()) else s }
|
||||
} ?: "null"
|
||||
|
||||
fun String?.ifNullOrEmpty(defaultValue: String?): String? {
|
||||
return if (this.isNullOrEmpty()) defaultValue else this
|
||||
fun String?.ifNullOrEmpty(defaultValue: () -> String?): String? {
|
||||
return if (this.isNullOrEmpty()) defaultValue() else this
|
||||
}
|
||||
|
||||
@JvmOverloads fun String.hex2ByteArray(replace: Boolean = false): ByteArray {
|
||||
|
@ -1,15 +1,129 @@
|
||||
package qq.service
|
||||
|
||||
import android.os.Bundle
|
||||
import com.tencent.common.app.AppInterface
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.TrpcOidb
|
||||
import qq.service.internals.MSFHandler
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
abstract class QQInterfaces {
|
||||
|
||||
|
||||
companion object {
|
||||
val app = if (PlatformUtils.isMqqPackage())
|
||||
val app = (if (PlatformUtils.isMqqPackage())
|
||||
MobileQQ.getMobileQQ().waitAppRuntime()
|
||||
else
|
||||
MobileQQ.getMobileQQ().waitAppRuntime(null)
|
||||
MobileQQ.getMobileQQ().waitAppRuntime(null)) as AppInterface
|
||||
|
||||
fun sendToServiceMsg(to: ToServiceMsg) {
|
||||
app.sendToService(to)
|
||||
}
|
||||
|
||||
suspend fun sendToServiceMsgAW(
|
||||
to: ToServiceMsg,
|
||||
timeout: Duration = 30.seconds
|
||||
): FromServiceMsg? {
|
||||
val seq = MSFHandler.nextSeq()
|
||||
to.addAttribute("shamrock_uid", seq)
|
||||
val resp: Pair<ToServiceMsg, FromServiceMsg>? = withTimeoutOrNull(timeout) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
GlobalScope.launch {
|
||||
MSFHandler.registerResp(seq, continuation)
|
||||
sendToServiceMsg(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resp == null) {
|
||||
MSFHandler.unregisterResp(seq)
|
||||
}
|
||||
return resp?.second
|
||||
}
|
||||
|
||||
fun sendExtra(cmd: String, builder: (Bundle) -> Unit) {
|
||||
val toServiceMsg = createToServiceMsg(cmd)
|
||||
builder(toServiceMsg.extraData)
|
||||
app.sendToService(toServiceMsg)
|
||||
}
|
||||
|
||||
fun createToServiceMsg(cmd: String): ToServiceMsg {
|
||||
return ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd)
|
||||
}
|
||||
|
||||
fun sendOidb(cmd: String, command: Int, service: Int, data: ByteArray, trpc: Boolean = false) {
|
||||
val to = createToServiceMsg(cmd)
|
||||
if (trpc) {
|
||||
val oidb = TrpcOidb(
|
||||
cmd = command,
|
||||
service = service,
|
||||
buffer = data,
|
||||
flag = 1
|
||||
)
|
||||
to.putWupBuffer(oidb.toByteArray())
|
||||
} else {
|
||||
val oidb = oidb_sso.OIDBSSOPkg()
|
||||
oidb.uint32_command.set(command)
|
||||
oidb.uint32_service_type.set(service)
|
||||
oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(data))
|
||||
oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext()))
|
||||
to.putWupBuffer(oidb.toByteArray())
|
||||
}
|
||||
to.addAttribute("req_pb_protocol_flag", true)
|
||||
app.sendToService(to)
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
suspend fun sendBufferAW(
|
||||
cmd: String,
|
||||
isProto: Boolean,
|
||||
data: ByteArray,
|
||||
timeout: Duration = 30.seconds
|
||||
): FromServiceMsg? {
|
||||
val toServiceMsg = createToServiceMsg(cmd)
|
||||
toServiceMsg.putWupBuffer(data)
|
||||
toServiceMsg.addAttribute("req_pb_protocol_flag", isProto)
|
||||
return sendToServiceMsgAW(toServiceMsg, timeout)
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
suspend fun sendOidbAW(
|
||||
cmd: String,
|
||||
command: Int,
|
||||
service: Int,
|
||||
data: ByteArray,
|
||||
trpc: Boolean = false,
|
||||
timeout: Duration = 30.seconds
|
||||
): FromServiceMsg? {
|
||||
val to = createToServiceMsg(cmd)
|
||||
if (trpc) {
|
||||
val oidb = TrpcOidb(
|
||||
cmd = command,
|
||||
service = service,
|
||||
buffer = data,
|
||||
flag = 1
|
||||
)
|
||||
to.putWupBuffer(oidb.toByteArray())
|
||||
} else {
|
||||
val oidb = oidb_sso.OIDBSSOPkg()
|
||||
oidb.uint32_command.set(command)
|
||||
oidb.uint32_service_type.set(service)
|
||||
oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(data))
|
||||
oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext()))
|
||||
to.putWupBuffer(oidb.toByteArray())
|
||||
}
|
||||
to.addAttribute("req_pb_protocol_flag", true)
|
||||
return sendToServiceMsgAW(to, timeout)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ package qq.service.contact
|
||||
import com.tencent.common.app.AppInterface
|
||||
import com.tencent.mobileqq.data.Card
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileDataService
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_SELF_UIN
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_TARGET_UIN
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
|
||||
import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
@ -12,9 +14,100 @@ import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
|
||||
import qq.service.QQInterfaces
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
object ContactHelper: QQInterfaces() {
|
||||
internal object ContactHelper: QQInterfaces() {
|
||||
const val FROM_C2C_AIO = 2
|
||||
const val FROM_CONDITION_SEARCH = 9
|
||||
const val FROM_CONTACTS_TAB = 5
|
||||
const val FROM_FACE_2_FACE_ADD_FRIEND = 11
|
||||
const val FROM_MAYKNOW_FRIEND = 3
|
||||
const val FROM_QCIRCLE = 4
|
||||
const val FROM_QQ_TROOP = 1
|
||||
const val FROM_QZONE = 7
|
||||
const val FROM_SCAN = 6
|
||||
const val FROM_SEARCH = 8
|
||||
const val FROM_SETTING_ME = 12
|
||||
const val FROM_SHARE_CARD = 10
|
||||
|
||||
const val PROFILE_CARD_IS_BLACK = 2
|
||||
const val PROFILE_CARD_IS_BLACKED = 1
|
||||
const val PROFILE_CARD_NOT_BLACK = 3
|
||||
|
||||
const val SUB_FROM_C2C_AIO = 21
|
||||
const val SUB_FROM_C2C_INTERACTIVE_LOGO = 25
|
||||
const val SUB_FROM_C2C_LEFT_SLIDE = 23
|
||||
const val SUB_FROM_C2C_OTHER = 24
|
||||
const val SUB_FROM_C2C_SETTING = 22
|
||||
const val SUB_FROM_C2C_TOFU = 26
|
||||
const val SUB_FROM_CONDITION_SEARCH_OTHER = 99
|
||||
const val SUB_FROM_CONDITION_SEARCH_RESULT = 91
|
||||
const val SUB_FROM_CONTACTS_FRIEND_TAB = 51
|
||||
const val SUB_FROM_CONTACTS_TAB = 55
|
||||
const val SUB_FROM_FACE_2_FACE_ADD_FRIEND_RESULT_AVATAR = 111
|
||||
const val SUB_FROM_FACE_2_FACE_OTHER = 119
|
||||
const val SUB_FROM_FRIEND_APPLY = 56
|
||||
const val SUB_FROM_FRIEND_NOTIFY_MORE = 57
|
||||
const val SUB_FROM_FRIEND_NOTIFY_TAB = 54
|
||||
const val SUB_FROM_GROUPING_TAB = 52
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_CONTACT_TAB = 31
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_CONTACT_TAB_MORE = 37
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_FIND_PEOPLE = 34
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_FIND_PEOPLE_MORE = 39
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_FIND_PEOPLE_SEARCH = 36
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_NEW_FRIEND_PAGE = 32
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_OTHER = 35
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_SEARCH = 33
|
||||
const val SUB_FROM_MAYKNOW_FRIEND_SEARCH_MORE = 38
|
||||
const val SUB_FROM_PHONE_LIST_TAB = 53
|
||||
const val SUB_FROM_QCIRCLE_OTHER = 42
|
||||
const val SUB_FROM_QCIRCLE_PROFILE = 41
|
||||
const val SUB_FROM_QQ_TROOP_ACTIVE_MEMBER = 15
|
||||
const val SUB_FROM_QQ_TROOP_ADMIN = 16
|
||||
const val SUB_FROM_QQ_TROOP_AIO = 11
|
||||
const val SUB_FROM_QQ_TROOP_MEMBER = 12
|
||||
const val SUB_FROM_QQ_TROOP_OTHER = 14
|
||||
const val SUB_FROM_QQ_TROOP_SETTING_MEMBER_LIST = 17
|
||||
const val SUB_FROM_QQ_TROOP_TEMP_SESSION = 13
|
||||
const val SUB_FROM_QRCODE_SCAN_DRAWER = 64
|
||||
const val SUB_FROM_QRCODE_SCAN_NEW = 61
|
||||
const val SUB_FROM_QRCODE_SCAN_OLD = 62
|
||||
const val SUB_FROM_QRCODE_SCAN_OTHER = 69
|
||||
const val SUB_FROM_QRCODE_SCAN_PROFILE = 63
|
||||
const val SUB_FROM_QZONE_HOME = 71
|
||||
const val SUB_FROM_QZONE_OTHER = 79
|
||||
const val SUB_FROM_SEARCH_CONTACT_TAB_MORE_FIND_PROFILE = 83
|
||||
const val SUB_FROM_SEARCH_FIND_PROFILE_TAB = 82
|
||||
const val SUB_FROM_SEARCH_MESSAGE_TAB_MORE_FIND_PROFILE = 84
|
||||
const val SUB_FROM_SEARCH_NEW_FRIEND_MORE_FIND_PROFILE = 85
|
||||
const val SUB_FROM_SEARCH_OTHER = 89
|
||||
const val SUB_FROM_SEARCH_TAB = 81
|
||||
const val SUB_FROM_SETTING_ME_AVATAR = 121
|
||||
const val SUB_FROM_SETTING_ME_OTHER = 129
|
||||
const val SUB_FROM_SHARE_CARD_C2C = 101
|
||||
const val SUB_FROM_SHARE_CARD_OTHER = 109
|
||||
const val SUB_FROM_SHARE_CARD_TROOP = 102
|
||||
const val SUB_FROM_TYPE_DEFAULT = 0
|
||||
|
||||
private val refreshCardLock by lazy { Mutex() }
|
||||
|
||||
suspend fun voteUser(target: Long, count: Int): Result<Unit> {
|
||||
if(count !in 1 .. 20) {
|
||||
return Result.failure(IllegalArgumentException("vote count must be in 1 .. 20"))
|
||||
}
|
||||
val card = getProfileCard(target).onFailure {
|
||||
return Result.failure(RuntimeException("unable to fetch contact info"))
|
||||
}.getOrThrow()
|
||||
sendExtra("VisitorSvc.ReqFavorite") {
|
||||
it.putLong(PARAM_SELF_UIN, app.longAccountUin)
|
||||
it.putLong(PARAM_TARGET_UIN, target)
|
||||
it.putByteArray("vCookies", card.vCookies)
|
||||
it.putBoolean("nearby_people", true)
|
||||
it.putInt("favoriteSource", FROM_CONTACTS_TAB)
|
||||
it.putInt("iCount", count)
|
||||
it.putInt("from", FROM_CONTACTS_TAB)
|
||||
}
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
suspend fun getProfileCard(uin: Long): Result<Card> {
|
||||
return getProfileCardFromCache(uin).onFailure {
|
||||
return refreshAndGetProfileCard(uin)
|
||||
|
162
xposed/src/main/java/qq/service/file/GroupFileHelper.kt
Normal file
162
xposed/src/main/java/qq/service/file/GroupFileHelper.kt
Normal file
@ -0,0 +1,162 @@
|
||||
@file:OptIn(ExperimentalStdlibApi::class)
|
||||
|
||||
package qq.service.file
|
||||
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.file.File
|
||||
import io.kritor.file.Folder
|
||||
import io.kritor.file.GetFileSystemInfoResponse
|
||||
import io.kritor.file.GetFilesRequest
|
||||
import io.kritor.file.GetFilesResponse
|
||||
import io.kritor.file.folder
|
||||
import io.kritor.file.getFileSystemInfoResponse
|
||||
import io.kritor.file.getFilesRequest
|
||||
import io.kritor.file.getFilesResponse
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import qq.service.QQInterfaces
|
||||
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object GroupFileHelper: QQInterfaces() {
|
||||
suspend fun getGroupFileSystemInfo(groupId: Long): GetFileSystemInfoResponse {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 2, oidb_0x6d8.ReqBody().also {
|
||||
it.group_file_cnt_req.set(oidb_0x6d8.GetFileCountReqBody().also {
|
||||
it.uint64_group_code.set(groupId)
|
||||
it.uint32_app_id.set(3)
|
||||
it.uint32_bus_id.set(0)
|
||||
})
|
||||
}.toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val fileCnt: Int
|
||||
val limitCnt: Int
|
||||
if (fromServiceMsg.wupBuffer != null) {
|
||||
oidb_0x6d8.RspBody().mergeFrom(
|
||||
oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
).group_file_cnt_rsp.apply {
|
||||
fileCnt = uint32_all_file_count.get()
|
||||
limitCnt = uint32_limit_count.get()
|
||||
}
|
||||
} else {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response"))
|
||||
}
|
||||
|
||||
val fromServiceMsg2 = sendOidbAW("OidbSvc.0x6d8_1", 1752, 3, oidb_0x6d8.ReqBody().also {
|
||||
it.group_space_req.set(oidb_0x6d8.GetSpaceReqBody().apply {
|
||||
uint64_group_code.set(groupId)
|
||||
uint32_app_id.set(3)
|
||||
})
|
||||
}.toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
val totalSpace: Long
|
||||
val usedSpace: Long
|
||||
if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) {
|
||||
oidb_0x6d8.RspBody().mergeFrom(
|
||||
oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(fromServiceMsg2.wupBuffer.slice(4))
|
||||
.bytes_bodybuffer.get()
|
||||
.toByteArray()).group_space_rsp.apply {
|
||||
totalSpace = uint64_total_space.get()
|
||||
usedSpace = uint64_used_space.get()
|
||||
}
|
||||
} else {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response x2"))
|
||||
}
|
||||
|
||||
return getFileSystemInfoResponse {
|
||||
this.fileCount = fileCnt
|
||||
this.totalCount = limitCnt
|
||||
this.totalSpace = totalSpace.toInt()
|
||||
this.usedSpace = usedSpace.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFilesResponse {
|
||||
val fileSystemInfo = getGroupFileSystemInfo(groupId)
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also {
|
||||
it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply {
|
||||
uint64_group_code.set(groupId)
|
||||
uint32_app_id.set(3)
|
||||
str_folder_id.set(folderId)
|
||||
|
||||
uint32_file_count.set(fileSystemInfo.fileCount)
|
||||
uint32_all_file_count.set(0)
|
||||
uint32_req_from.set(3)
|
||||
uint32_sort_by.set(oidb_0x6d8.GetFileListReqBody.SORT_BY_TIMESTAMP)
|
||||
|
||||
uint32_filter_code.set(0)
|
||||
uint64_uin.set(0)
|
||||
|
||||
uint32_start_index.set(0)
|
||||
|
||||
bytes_context.set(ByteStringMicro.copyFrom(EMPTY_BYTE_ARRAY))
|
||||
|
||||
uint32_show_onlinedoc_folder.set(0)
|
||||
})
|
||||
}.toByteArray(), timeout = 15.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val files = arrayListOf<File>()
|
||||
val dirs = arrayListOf<Folder>()
|
||||
if (fromServiceMsg.wupBuffer != null) {
|
||||
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
|
||||
.file_list_info_rsp.apply {
|
||||
rpt_item_list.get().forEach { file ->
|
||||
if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) {
|
||||
val fileInfo = file.file_info
|
||||
files.add(io.kritor.file.file {
|
||||
this.fileId = fileInfo.str_file_id.get()
|
||||
this.fileName = fileInfo.str_file_name.get()
|
||||
this.fileSize = fileInfo.uint64_file_size.get()
|
||||
this.busId = fileInfo.uint32_bus_id.get()
|
||||
this.uploadTime = fileInfo.uint32_upload_time.get()
|
||||
this.deadTime = fileInfo.uint32_dead_time.get()
|
||||
this.modifyTime = fileInfo.uint32_modify_time.get()
|
||||
this.downloadTimes = fileInfo.uint32_download_times.get()
|
||||
this.uploader = fileInfo.uint64_uploader_uin.get()
|
||||
this.uploaderName = fileInfo.str_uploader_name.get()
|
||||
this.sha = fileInfo.bytes_sha.get().toByteArray().toHexString()
|
||||
this.sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString()
|
||||
this.md5 = fileInfo.bytes_md5.get().toByteArray().toHexString()
|
||||
})
|
||||
}
|
||||
else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) {
|
||||
val folderInfo = file.folder_info
|
||||
dirs.add(folder {
|
||||
this.folderId = folderInfo.str_folder_id.get()
|
||||
this.folderName = folderInfo.str_folder_name.get()
|
||||
this.totalFileCount = folderInfo.uint32_total_file_count.get()
|
||||
this.createTime = folderInfo.uint32_create_time.get()
|
||||
this.creator = folderInfo.uint64_create_uin.get()
|
||||
this.creatorName = folderInfo.str_creator_name.get()
|
||||
})
|
||||
} else {
|
||||
LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response"))
|
||||
}
|
||||
|
||||
return getFilesResponse {
|
||||
this.files.addAll(files)
|
||||
this.folders.addAll(folders)
|
||||
}
|
||||
}
|
||||
}
|
40
xposed/src/main/java/qq/service/friend/FriendHelper.kt
Normal file
40
xposed/src/main/java/qq/service/friend/FriendHelper.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package qq.service.friend
|
||||
|
||||
import com.tencent.mobileqq.data.Friends
|
||||
import com.tencent.mobileqq.friend.api.IFriendDataService
|
||||
import com.tencent.mobileqq.friend.api.IFriendHandlerService
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import qq.service.QQInterfaces
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object FriendHelper: QQInterfaces() {
|
||||
suspend fun getFriendList(refresh: Boolean): Result<List<Friends>> {
|
||||
val service = app.getRuntimeService(IFriendDataService::class.java, "all")
|
||||
if(refresh || !service.isInitFinished) {
|
||||
if(!requestFriendList(service)) {
|
||||
return Result.failure(Exception("获取好友列表失败"))
|
||||
}
|
||||
}
|
||||
return Result.success(service.allFriends)
|
||||
}
|
||||
|
||||
private suspend fun requestFriendList(dataService: IFriendDataService): Boolean {
|
||||
val service = app.getRuntimeService(IFriendHandlerService::class.java, "all")
|
||||
service.requestFriendList(true, 0)
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val waiter = GlobalScope.launch {
|
||||
while (!dataService.isInitFinished) {
|
||||
delay(200)
|
||||
}
|
||||
continuation.resume(true)
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
waiter.cancel()
|
||||
continuation.resume(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
617
xposed/src/main/java/qq/service/group/GroupHelper.kt
Normal file
617
xposed/src/main/java/qq/service/group/GroupHelper.kt
Normal file
@ -0,0 +1,617 @@
|
||||
package qq.service.group
|
||||
|
||||
import KQQ.RespBatchProcess
|
||||
import com.qq.jce.wup.UniPacket
|
||||
import com.tencent.mobileqq.app.BusinessHandlerFactory
|
||||
import com.tencent.mobileqq.data.troop.TroopInfo
|
||||
import com.tencent.mobileqq.data.troop.TroopMemberInfo
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
import com.tencent.mobileqq.troop.api.ITroopInfoService
|
||||
import com.tencent.mobileqq.troop.api.ITroopMemberInfoService
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
|
||||
import friendlist.stUinInfo
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.cmd0xf16.Oidb0xf16
|
||||
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
|
||||
import qq.service.QQInterfaces
|
||||
import tencent.im.group.group_member_info
|
||||
import tencent.im.oidb.cmd0x88d.oidb_0x88d
|
||||
import tencent.im.oidb.cmd0x899.oidb_0x899
|
||||
import tencent.im.oidb.cmd0x89a.oidb_0x89a
|
||||
import tencent.im.oidb.cmd0x8a0.oidb_0x8a0
|
||||
import tencent.im.oidb.cmd0x8a7.cmd0x8a7
|
||||
import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
|
||||
import tencent.im.oidb.cmd0xed3.oidb_cmd0xed3
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import tencent.im.troop.honor.troop_honor
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object GroupHelper: QQInterfaces() {
|
||||
private val RefreshTroopMemberInfoLock by lazy { Mutex() }
|
||||
private val RefreshTroopMemberListLock by lazy { Mutex() }
|
||||
|
||||
private lateinit var METHOD_REQ_MEMBER_INFO: Method
|
||||
private lateinit var METHOD_REQ_MEMBER_INFO_V2: Method
|
||||
private lateinit var METHOD_REQ_TROOP_LIST: Method
|
||||
private lateinit var METHOD_REQ_TROOP_MEM_LIST: Method
|
||||
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method
|
||||
|
||||
fun getGroupInfo(groupId: String): TroopInfo {
|
||||
val service = app
|
||||
.getRuntimeService(ITroopInfoService::class.java, "all")
|
||||
|
||||
return service.getTroopInfo(groupId)
|
||||
}
|
||||
|
||||
fun isAdmin(groupId: String): Boolean {
|
||||
val groupInfo = getGroupInfo(groupId)
|
||||
|
||||
return groupInfo.isAdmin || groupInfo.troopowneruin == app.account
|
||||
}
|
||||
|
||||
fun isOwner(groupId: String): Boolean {
|
||||
val groupInfo = getGroupInfo(groupId)
|
||||
return groupInfo.troopowneruin == app.account
|
||||
}
|
||||
|
||||
fun getAdminList(
|
||||
groupId: Long,
|
||||
withOwner: Boolean = false
|
||||
): List<Long> {
|
||||
val groupInfo = getGroupInfo(groupId.toString())
|
||||
return (groupInfo.Administrator ?: "")
|
||||
.split("|", ",")
|
||||
.also {
|
||||
if (withOwner && it is ArrayList<String>) {
|
||||
it.add(0, groupInfo.troopowneruin)
|
||||
}
|
||||
}.mapNotNull { it.ifNullOrEmpty { null }?.toLong() }
|
||||
}
|
||||
|
||||
suspend fun getGroupList(refresh: Boolean): Result<List<TroopInfo>> {
|
||||
val service = app.getRuntimeService(ITroopInfoService::class.java, "all")
|
||||
|
||||
var troopList = service.allTroopList
|
||||
if(refresh || !service.isTroopCacheInited || troopList == null) {
|
||||
if(!requestGroupInfo(service)) {
|
||||
return Result.failure(Exception("获取群列表失败"))
|
||||
} else {
|
||||
troopList = service.allTroopList
|
||||
}
|
||||
}
|
||||
return Result.success(troopList)
|
||||
}
|
||||
|
||||
private suspend fun requestGroupInfo(
|
||||
service: ITroopInfoService
|
||||
): Boolean {
|
||||
refreshTroopList()
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val waiter = GlobalScope.launch {
|
||||
do {
|
||||
delay(1000)
|
||||
} while (
|
||||
!service.isTroopCacheInited
|
||||
)
|
||||
continuation.resume(true)
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
waiter.cancel()
|
||||
continuation.resume(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun banMember(groupId: Long, memberUin: Long, time: Int) {
|
||||
val buffer = ByteBuffer.allocate(1 * 8 + 7)
|
||||
buffer.putBuf32Long(groupId)
|
||||
buffer.put(32.toByte())
|
||||
buffer.putShort(1)
|
||||
buffer.putBuf32Long(memberUin)
|
||||
buffer.putInt(time)
|
||||
val array = buffer.array()
|
||||
sendOidb("OidbSvc.0x570_8", 1392, 8, array)
|
||||
}
|
||||
|
||||
fun pokeMember(groupId: Long, memberUin: Long) {
|
||||
val req = oidb_cmd0xed3.ReqBody().apply {
|
||||
uint64_group_code.set(groupId)
|
||||
uint64_to_uin.set(memberUin)
|
||||
uint32_msg_seq.set(0)
|
||||
}
|
||||
sendOidb("OidbSvc.0xed3", 3795, 1, req.toByteArray())
|
||||
}
|
||||
|
||||
fun kickMember(groupId: Long, rejectAddRequest: Boolean, kickMsg: String, vararg memberUin: Long) {
|
||||
val reqBody = oidb_0x8a0.ReqBody()
|
||||
reqBody.opt_uint64_group_code.set(groupId)
|
||||
memberUin.forEach {
|
||||
val memberInfo = oidb_0x8a0.KickMemberInfo()
|
||||
memberInfo.opt_uint32_operate.set(5)
|
||||
memberInfo.opt_uint64_member_uin.set(it)
|
||||
memberInfo.opt_uint32_flag.set(if (rejectAddRequest) 1 else 0)
|
||||
reqBody.rpt_msg_kick_list.add(memberInfo)
|
||||
}
|
||||
if (kickMsg.isNotEmpty()) {
|
||||
reqBody.bytes_kick_msg.set(ByteStringMicro.copyFrom(kickMsg.toByteArray()))
|
||||
}
|
||||
sendOidb("OidbSvc.0x8a0_0", 2208, 0, reqBody.toByteArray())
|
||||
}
|
||||
|
||||
fun resignTroop(groupId: String) {
|
||||
sendExtra("ProfileService.GroupMngReq") {
|
||||
it.putInt("groupreqtype", 2)
|
||||
it.putString("troop_uin", groupId)
|
||||
it.putString("uin", app.currentUin)
|
||||
}
|
||||
}
|
||||
|
||||
fun modifyGroupMemberCard(groupId: Long, userId: Long, name: String): Boolean {
|
||||
val createToServiceMsg = createToServiceMsg("friendlist.ModifyGroupCardReq")
|
||||
createToServiceMsg.extraData.putLong("dwZero", 0L)
|
||||
createToServiceMsg.extraData.putLong("dwGroupCode", groupId)
|
||||
val info = stUinInfo()
|
||||
info.cGender = -1
|
||||
info.dwuin = userId
|
||||
info.sEmail = ""
|
||||
info.sName = name
|
||||
info.sPhone = ""
|
||||
info.sRemark = ""
|
||||
info.dwFlag = 1
|
||||
createToServiceMsg.extraData.putSerializable("vecUinInfo", arrayListOf(info))
|
||||
createToServiceMsg.extraData.putLong("dwNewSeq", 0L)
|
||||
sendToServiceMsg(createToServiceMsg)
|
||||
return true
|
||||
}
|
||||
|
||||
fun modifyTroopName(groupId: String, name: String) {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MODIFY_HANDLER)
|
||||
|
||||
if (!::METHOD_REQ_MODIFY_GROUP_NAME.isInitialized) {
|
||||
METHOD_REQ_MODIFY_GROUP_NAME = businessHandler.javaClass.declaredMethods.first {
|
||||
it.parameterCount == 3
|
||||
&& it.parameterTypes[0] == String::class.java
|
||||
&& it.parameterTypes[1] == String::class.java
|
||||
&& it.parameterTypes[2] == Boolean::class.java
|
||||
&& !Modifier.isPrivate(it.modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_MODIFY_GROUP_NAME.invoke(businessHandler, groupId, name, false)
|
||||
}
|
||||
|
||||
fun modifyGroupRemark(groupId: Long, remark: String): Boolean {
|
||||
sendOidb("OidbSvc.0xf16_1", 3862, 1, Oidb0xf16(
|
||||
setGroupRemarkReq = SetGroupRemarkReq(
|
||||
groupCode = groupId.toULong(),
|
||||
groupUin = groupCode2GroupUin(groupId).toULong(),
|
||||
groupRemark = remark
|
||||
)
|
||||
).toByteArray())
|
||||
return true
|
||||
}
|
||||
|
||||
fun setGroupAdmin(groupId: Long, userId: Long, enable: Boolean) {
|
||||
val buffer = ByteBuffer.allocate(9)
|
||||
buffer.putBuf32Long(groupId)
|
||||
buffer.putBuf32Long(userId)
|
||||
buffer.put(if (enable) 1 else 0)
|
||||
val array = buffer.array()
|
||||
sendOidb("OidbSvc.0x55c_1", 1372, 1, array)
|
||||
}
|
||||
|
||||
suspend fun setGroupUniqueTitle(groupId: Long, userId: Long, title: String) {
|
||||
val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow()
|
||||
val req = Oidb_0x8fc.ReqBody()
|
||||
req.uint64_group_code.set(groupId)
|
||||
val memberInfo = Oidb_0x8fc.MemberInfo()
|
||||
memberInfo.uint64_uin.set(userId)
|
||||
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty {
|
||||
localMemberInfo.troopremark.ifNullOrEmpty { "" }
|
||||
}))
|
||||
memberInfo.bytes_special_title.set(ByteStringMicro.copyFromUtf8(title))
|
||||
memberInfo.uint32_special_title_expire_time.set(-1)
|
||||
req.rpt_mem_level_info.add(memberInfo)
|
||||
sendOidb("OidbSvc.0x8fc_2", 2300, 2, req.toByteArray())
|
||||
}
|
||||
|
||||
fun setGroupWholeBan(groupId: Long, enable: Boolean) {
|
||||
val reqBody = oidb_0x89a.ReqBody()
|
||||
reqBody.uint64_group_code.set(groupId)
|
||||
reqBody.st_group_info.set(oidb_0x89a.groupinfo().apply {
|
||||
uint32_shutup_time.set(if (enable) 268435455 else 0)
|
||||
})
|
||||
sendOidb("OidbSvc.0x89a_0", 2202, 0, reqBody.toByteArray())
|
||||
}
|
||||
|
||||
suspend fun getGroupMemberList(groupId: String, refresh: Boolean): Result<List<TroopMemberInfo>> {
|
||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||
var memberList = service.getAllTroopMembers(groupId)
|
||||
if (refresh || memberList == null) {
|
||||
memberList = requestTroopMemberInfo(service, groupId).onFailure {
|
||||
return Result.failure(Exception("获取群成员列表失败"))
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
getGroupInfo(groupId, true).onSuccess {
|
||||
if(it.wMemberNum > memberList.size) {
|
||||
return getGroupMemberList(groupId, true)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(memberList)
|
||||
}
|
||||
|
||||
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply {
|
||||
uint64_group_code.set(groupId)
|
||||
uint64_start_uin.set(0)
|
||||
uint32_identify_flag.set(6)
|
||||
memberlist_opt.set(oidb_0x899.memberlist().apply {
|
||||
uint64_member_uin.set(0)
|
||||
uint32_shutup_timestap.set(0)
|
||||
})
|
||||
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
return Result.failure(RuntimeException("[oidb] failed"))
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
val resp = oidb_0x899.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return Result.success(resp.rpt_memberlist.get().map {
|
||||
ProhibitedMemberInfo(it.uint64_member_uin.get(), it.uint32_shutup_timestap.get())
|
||||
})
|
||||
}
|
||||
|
||||
suspend fun getGroupRemainAtAllRemain (groupId: Long): Result<cmd0x8a7.RspBody> {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x8a7_0", 2215, 0, cmd0x8a7.ReqBody().apply {
|
||||
uint32_sub_cmd.set(1)
|
||||
uint32_limit_interval_type_for_uin.set(2)
|
||||
uint32_limit_interval_type_for_group.set(1)
|
||||
uint64_uin.set(app.longAccountUin)
|
||||
uint64_group_code.set(groupId)
|
||||
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
return Result.failure(RuntimeException("[oidb] failed"))
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
|
||||
val resp = cmd0x8a7.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return Result.success(resp)
|
||||
}
|
||||
|
||||
suspend fun getNotJoinedGroupInfo(groupId: Long): Result<NotJoinedGroupInfo> {
|
||||
return withTimeoutOrNull(5000) timeout@{
|
||||
val toServiceMsg = createToServiceMsg("ProfileService.ReqBatchProcess")
|
||||
toServiceMsg.extraData.putLong("troop_code", groupId)
|
||||
toServiceMsg.extraData.putBoolean("is_admin", false)
|
||||
toServiceMsg.extraData.putInt("from", 0)
|
||||
val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
return@timeout Result.failure(Exception("获取群信息失败"))
|
||||
}
|
||||
val uniPacket = UniPacket(true)
|
||||
uniPacket.encodeName = "utf-8"
|
||||
uniPacket.decode(fromServiceMsg.wupBuffer)
|
||||
val respBatchProcess = uniPacket.getByClass("RespBatchProcess", RespBatchProcess())
|
||||
val batchRespInfo = oidb_0x88d.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(respBatchProcess.batch_response_list.first().buffer)
|
||||
.bytes_bodybuffer.get().toByteArray()).stzrspgroupinfo.get().firstOrNull()
|
||||
?: return@timeout Result.failure(Exception("获取群信息失败"))
|
||||
val info = batchRespInfo.stgroupinfo
|
||||
Result.success(NotJoinedGroupInfo(
|
||||
groupId = batchRespInfo.uint64_group_code.get(),
|
||||
maxMember = info.uint32_group_member_max_num.get(),
|
||||
memberCount = info.uint32_group_member_num.get(),
|
||||
groupName = info.string_group_name.get().toStringUtf8(),
|
||||
groupDesc = info.string_group_finger_memo.get().toStringUtf8(),
|
||||
owner = info.uint64_group_owner.get(),
|
||||
createTime = info.uint32_group_create_time.get().toLong(),
|
||||
groupFlag = info.uint32_group_flag.get(),
|
||||
groupFlagExt = info.uint32_group_flag_ext.get()
|
||||
))
|
||||
} ?: Result.failure(Exception("获取群信息超时"))
|
||||
}
|
||||
|
||||
suspend fun getGroupInfo(groupId: String, refresh: Boolean): Result<TroopInfo> {
|
||||
val service = app
|
||||
.getRuntimeService(ITroopInfoService::class.java, "all")
|
||||
|
||||
val groupInfo = getGroupInfo(groupId)
|
||||
|
||||
return if(refresh || !service.isTroopCacheInited || groupInfo.troopuin.isNullOrBlank()) {
|
||||
requestGroupInfo(service, groupId)
|
||||
} else {
|
||||
Result.success(groupInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestGroupInfo(dataService: ITroopInfoService, uin: String): Result<TroopInfo> {
|
||||
val info = withTimeoutOrNull(1000) {
|
||||
var troopInfo: TroopInfo?
|
||||
do {
|
||||
troopInfo = dataService.getTroopInfo(uin)
|
||||
delay(100)
|
||||
} while (troopInfo == null || troopInfo.troopuin.isNullOrBlank())
|
||||
return@withTimeoutOrNull troopInfo
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群列表失败"))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTroopMemberInfoByUin(
|
||||
groupId: Long,
|
||||
uin: Long,
|
||||
refresh: Boolean = false
|
||||
): Result<TroopMemberInfo> {
|
||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||
var info = service.getTroopMember(groupId.toString(), uin.toString())
|
||||
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) {
|
||||
info = requestTroopMemberInfo(service, groupId, uin).getOrNull()
|
||||
}
|
||||
if (info == null) {
|
||||
info = getTroopMemberInfoByUinViaNt(groupId, uin).getOrNull()?.let {
|
||||
TroopMemberInfo().apply {
|
||||
troopnick = it.cardName
|
||||
friendnick = it.nick
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
||||
val req = group_member_info.ReqBody()
|
||||
req.uint64_group_code.set(groupId)
|
||||
req.uint64_uin.set(uin)
|
||||
req.bool_new_client.set(true)
|
||||
req.uint32_client_type.set(1)
|
||||
req.uint32_rich_card_name_ver.set(1)
|
||||
val fromServiceMsg = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
||||
if (fromServiceMsg != null && fromServiceMsg.isSuccess) {
|
||||
val rsp = group_member_info.RspBody()
|
||||
rsp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
if (rsp.msg_meminfo.str_location.has()) {
|
||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||
}
|
||||
if (rsp.msg_meminfo.uint32_age.has()) {
|
||||
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
|
||||
}
|
||||
if (rsp.msg_meminfo.bytes_group_honor.has()) {
|
||||
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
|
||||
val honor = troop_honor.GroupUserCardHonor()
|
||||
honor.mergeFrom(honorBytes)
|
||||
info.level = honor.level.get()
|
||||
// 10315: medal_id not real group level
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LogCenter.log("getTroopMemberInfoByUin: " + err.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群成员信息失败"))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTroopMemberInfoByUinViaNt(
|
||||
groupId: Long,
|
||||
qq: Long,
|
||||
timeout: Long = 5000L
|
||||
): Result<MemberInfo> {
|
||||
return runCatching {
|
||||
val kernelService = NTServiceFetcher.kernelService
|
||||
val sessionService = kernelService.wrapperSession
|
||||
val groupService = sessionService.groupService
|
||||
val info = withTimeoutOrNull(timeout) {
|
||||
suspendCancellableCoroutine {
|
||||
groupService.getTransferableMemberInfo(groupId) { code, _, data ->
|
||||
if (code != 0) {
|
||||
it.resume(null)
|
||||
return@getTransferableMemberInfo
|
||||
}
|
||||
data.forEach { (_, info) ->
|
||||
if (info.uin == qq) {
|
||||
it.resume(info)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
it.resume(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群成员信息失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long, timeout: Long = 10_000): Result<TroopMemberInfo> {
|
||||
val info = RefreshTroopMemberInfoLock.withLock {
|
||||
val groupIdStr = groupId.toString()
|
||||
val memberUinStr = memberUin.toString()
|
||||
|
||||
service.deleteTroopMember(groupIdStr, memberUinStr)
|
||||
|
||||
requestMemberInfoV2(groupId, memberUin)
|
||||
requestMemberInfo(groupId, memberUin)
|
||||
|
||||
withTimeoutOrNull(timeout) {
|
||||
while (!service.isMemberInCache(groupIdStr, memberUinStr)) {
|
||||
delay(200)
|
||||
}
|
||||
return@withTimeoutOrNull service.getTroopMember(groupIdStr, memberUinStr)
|
||||
}
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群成员信息失败"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestMemberInfo(groupId: Long, memberUin: Long) {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER)
|
||||
|
||||
if (!::METHOD_REQ_MEMBER_INFO.isInitialized) {
|
||||
METHOD_REQ_MEMBER_INFO = businessHandler.javaClass.declaredMethods.first {
|
||||
it.parameterCount == 2 &&
|
||||
it.parameterTypes[0] == Long::class.java &&
|
||||
it.parameterTypes[1] == Long::class.java &&
|
||||
!Modifier.isPrivate(it.modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_MEMBER_INFO.invoke(businessHandler, groupId, memberUin)
|
||||
}
|
||||
|
||||
private fun requestMemberInfoV2(groupId: Long, memberUin: Long) {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER)
|
||||
|
||||
if (!::METHOD_REQ_MEMBER_INFO_V2.isInitialized) {
|
||||
METHOD_REQ_MEMBER_INFO_V2 = businessHandler.javaClass.declaredMethods.first {
|
||||
it.parameterCount == 3 &&
|
||||
it.parameterTypes[0] == String::class.java &&
|
||||
it.parameterTypes[1] == String::class.java &&
|
||||
!Modifier.isPrivate(it.modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler, groupId.toString(), groupUin2GroupCode(groupId).toString(), arrayListOf(memberUin.toString()))
|
||||
}
|
||||
|
||||
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String): Result<List<TroopMemberInfo>> {
|
||||
val info = RefreshTroopMemberListLock.withLock {
|
||||
service.deleteTroopMembers(groupId)
|
||||
refreshTroopMemberList(groupId)
|
||||
|
||||
withTimeoutOrNull(10000) {
|
||||
var memberList: List<TroopMemberInfo>?
|
||||
do {
|
||||
delay(100)
|
||||
memberList = service.getAllTroopMembers(groupId)
|
||||
} while (memberList.isNullOrEmpty())
|
||||
return@withTimeoutOrNull memberList
|
||||
}
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群成员信息失败"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshTroopMemberList(groupId: String) {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_LIST_HANDLER)
|
||||
|
||||
// void C(boolean forceRefresh, String groupId, String troopcode, int reqType); // RequestedTroopList/refreshMemberListFromServer
|
||||
if (!::METHOD_REQ_TROOP_MEM_LIST.isInitialized) {
|
||||
METHOD_REQ_TROOP_MEM_LIST = businessHandler.javaClass.declaredMethods.first {
|
||||
it.parameterCount == 4
|
||||
&& it.parameterTypes[0] == Boolean::class.java
|
||||
&& it.parameterTypes[1] == String::class.java
|
||||
&& it.parameterTypes[2] == String::class.java
|
||||
&& it.parameterTypes[3] == Int::class.java
|
||||
&& !Modifier.isPrivate(it.modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_TROOP_MEM_LIST.invoke(businessHandler, true, groupId, groupUin2GroupCode(groupId.toLong()).toString(), 5)
|
||||
}
|
||||
|
||||
private fun refreshTroopList() {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_LIST_HANDLER)
|
||||
|
||||
if (!::METHOD_REQ_TROOP_LIST.isInitialized) {
|
||||
METHOD_REQ_TROOP_LIST = businessHandler.javaClass.declaredMethods.first {
|
||||
it.parameterCount == 0 && !Modifier.isPrivate(it.modifiers) && it.returnType == Void.TYPE
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_TROOP_LIST.invoke(businessHandler)
|
||||
}
|
||||
|
||||
fun groupUin2GroupCode(groupuin: Long): Long {
|
||||
var calc = groupuin / 1000000L
|
||||
while (true) {
|
||||
calc -= if (calc >= 0 + 202 && calc + 202 <= 10) {
|
||||
(202 - 0).toLong()
|
||||
} else if (calc >= 11 + 480 && calc <= 19 + 480) {
|
||||
(480 - 11).toLong()
|
||||
} else if (calc >= 20 + 2100 && calc <= 66 + 2100) {
|
||||
(2100 - 20).toLong()
|
||||
} else if (calc >= 67 + 2010 && calc <= 156 + 2010) {
|
||||
(2010 - 67).toLong()
|
||||
} else if (calc >= 157 + 2147 && calc <= 209 + 2147) {
|
||||
(2147 - 157).toLong()
|
||||
} else if (calc >= 210 + 4100 && calc <= 309 + 4100) {
|
||||
(4100 - 210).toLong()
|
||||
} else if (calc >= 310 + 3800 && calc <= 499 + 3800) {
|
||||
(3800 - 310).toLong()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return calc * 1000000L + groupuin % 1000000L
|
||||
}
|
||||
|
||||
fun groupCode2GroupUin(groupcode: Long): Long {
|
||||
var calc = groupcode / 1000000L
|
||||
loop@ while (true) calc += when (calc) {
|
||||
in 0..10 -> {
|
||||
(202 - 0).toLong()
|
||||
}
|
||||
in 11..19 -> {
|
||||
(480 - 11).toLong()
|
||||
}
|
||||
in 20..66 -> {
|
||||
(2100 - 20).toLong()
|
||||
}
|
||||
in 67..156 -> {
|
||||
(2010 - 67).toLong()
|
||||
}
|
||||
in 157..209 -> {
|
||||
(2147 - 157).toLong()
|
||||
}
|
||||
in 210..309 -> {
|
||||
(4100 - 210).toLong()
|
||||
}
|
||||
in 310..499 -> {
|
||||
(3800 - 310).toLong()
|
||||
}
|
||||
else -> {
|
||||
break@loop
|
||||
}
|
||||
}
|
||||
return calc * 1000000L + groupcode % 1000000L
|
||||
}
|
||||
}
|
17
xposed/src/main/java/qq/service/group/NotJoinedGroupInfo.kt
Normal file
17
xposed/src/main/java/qq/service/group/NotJoinedGroupInfo.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package qq.service.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class NotJoinedGroupInfo(
|
||||
@SerialName("group_id") val groupId: Long,
|
||||
@SerialName("max_member_cnt") val maxMember: Int,
|
||||
@SerialName("member_count") val memberCount: Int,
|
||||
@SerialName("group_name") val groupName: String,
|
||||
@SerialName("group_desc") val groupDesc: String,
|
||||
@SerialName("owner") val owner: Long,
|
||||
@SerialName("create_time") val createTime: Long,
|
||||
@SerialName("group_flag") val groupFlag: Int,
|
||||
@SerialName("group_flag_ext") val groupFlagExt: Int,
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package qq.service.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class ProhibitedMemberInfo(
|
||||
@SerialName("user_id") val memberUin: Long,
|
||||
@SerialName("time") val shutuptimestap: Int
|
||||
)
|
@ -2,13 +2,16 @@ package qq.service.internals
|
||||
|
||||
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
typealias MsfPush = (FromServiceMsg) -> Unit
|
||||
typealias MsfResp = (ToServiceMsg, FromServiceMsg) -> Unit
|
||||
typealias MsfResp = CancellableContinuation<Pair<ToServiceMsg, FromServiceMsg>>
|
||||
|
||||
internal object MSFHandler {
|
||||
private val mPushHandlers = hashMapOf<String, MsfPush>()
|
||||
@ -16,6 +19,13 @@ internal object MSFHandler {
|
||||
private val mPushLock = Mutex()
|
||||
private val mRespLock = Mutex()
|
||||
|
||||
private val seq = atomic(0)
|
||||
|
||||
fun nextSeq(): Int {
|
||||
seq.compareAndSet(0xFFFFFFF, 0)
|
||||
return seq.incrementAndGet()
|
||||
}
|
||||
|
||||
suspend fun registerPush(cmd: String, push: MsfPush) {
|
||||
mPushLock.withLock {
|
||||
mPushHandlers[cmd] = push
|
||||
@ -51,7 +61,7 @@ internal object MSFHandler {
|
||||
val cmd = toServiceMsg.getAttribute("shamrock_uid") as? Int?
|
||||
?: return@runCatching
|
||||
val resp = mRespHandler[cmd]
|
||||
resp?.invoke(toServiceMsg, fromServiceMsg)
|
||||
resp?.resume(toServiceMsg to fromServiceMsg)
|
||||
}.onFailure {
|
||||
LogCenter.log("MSF.onResp failed: ${it.message}", Level.ERROR)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user