mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
2 Commits
cb4268edef
...
7bacea3288
Author | SHA1 | Date | |
---|---|---|---|
7bacea3288 | |||
ca47f9dbdf |
2
kritor
2
kritor
Submodule kritor updated: 201e91e732...e4aac653e1
@ -21,6 +21,11 @@ class KritorServer(
|
||||
.addService(Authentication)
|
||||
.addService(ContactService)
|
||||
.addService(KritorService)
|
||||
.addService(FriendService)
|
||||
.addService(GroupService)
|
||||
.addService(GroupFileService)
|
||||
.addService(MessageService)
|
||||
.addService(EventService)
|
||||
.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 ?: ""
|
||||
|
29
xposed/src/main/java/kritor/service/EventService.kt
Normal file
29
xposed/src/main/java/kritor/service/EventService.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package kritor.service
|
||||
|
||||
import io.kritor.event.EventRequest
|
||||
import io.kritor.event.EventServiceGrpcKt
|
||||
import io.kritor.event.EventStructure
|
||||
import io.kritor.event.EventType
|
||||
import io.kritor.event.eventStructure
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||
|
||||
object EventService: EventServiceGrpcKt.EventServiceCoroutineImplBase() {
|
||||
override fun registerActiveListener(request: EventRequest): Flow<EventStructure> {
|
||||
return channelFlow {
|
||||
when(request.type!!) {
|
||||
EventType.CORE_EVENT -> TODO()
|
||||
EventType.MESSAGE -> GlobalEventTransmitter.onMessageEvent {
|
||||
send(eventStructure {
|
||||
this.type = EventType.MESSAGE
|
||||
this.message = it.second
|
||||
})
|
||||
}
|
||||
EventType.NOTICE -> TODO()
|
||||
EventType.REQUEST -> TODO()
|
||||
EventType.UNRECOGNIZED -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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 {
|
||||
|
7
xposed/src/main/java/kritor/service/MessageService.kt
Normal file
7
xposed/src/main/java/kritor/service/MessageService.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package kritor.service
|
||||
|
||||
import io.kritor.message.MessageServiceGrpcKt
|
||||
|
||||
internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImplBase() {
|
||||
|
||||
}
|
@ -0,0 +1,473 @@
|
||||
package moe.fuqiuluo.shamrock.internals
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import io.kritor.Scene
|
||||
import io.kritor.contact
|
||||
import io.kritor.event.MessageEvent
|
||||
import io.kritor.event.messageEvent
|
||||
import io.kritor.sender
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.io.core.BytePacketBuilder
|
||||
import qq.service.QQInterfaces
|
||||
|
||||
internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
private val messageEventFlow by lazy {
|
||||
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
||||
}
|
||||
//private val noticeEventFlow by lazy {
|
||||
// MutableSharedFlow<NoticeEvent>()
|
||||
//}
|
||||
//private val requestEventFlow by lazy {
|
||||
// MutableSharedFlow<RequestEvent>()
|
||||
//}
|
||||
|
||||
//private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent)
|
||||
|
||||
//private suspend fun pushRequest(requestEvent: RequestEvent) = requestEventFlow.emit(requestEvent)
|
||||
|
||||
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message)
|
||||
|
||||
object MessageTransmitter {
|
||||
suspend fun transGroupMessage(
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
): Boolean {
|
||||
transMessageEvent(record, messageEvent {
|
||||
this.time = record.msgTime.toInt()
|
||||
this.scene = Scene.GROUP
|
||||
this.messageId = record.msgId
|
||||
this.messageSeq = record.msgSeq
|
||||
this.contact = contact {
|
||||
this.scene = scene
|
||||
this.peer = record.peerUin.toString()
|
||||
this.subPeer = record.peerUid
|
||||
}
|
||||
this.sender = sender {
|
||||
this.uin = record.senderUin
|
||||
this.uid = record.senderUid
|
||||
this.nick = record.sendNickName
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transPrivateMessage(
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transTempMessage(
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
groupCode: Long,
|
||||
fromNick: String,
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGuildMessage(
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* 文件通知 通知器
|
||||
*/
|
||||
object FileNoticeTransmitter {
|
||||
/**
|
||||
* 推送私聊文件事件
|
||||
*/
|
||||
suspend fun transPrivateFileEvent(
|
||||
msgTime: Long,
|
||||
userId: Long,
|
||||
fileId: String,
|
||||
fileSubId: String,
|
||||
fileName: String,
|
||||
fileSize: Long,
|
||||
expireTime: Long,
|
||||
url: String
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.PrivateUpload,
|
||||
operatorId = userId,
|
||||
userId = userId,
|
||||
senderId = userId,
|
||||
privateFile = PrivateFileMsg(
|
||||
id = fileId,
|
||||
name = fileName,
|
||||
size = fileSize,
|
||||
url = url,
|
||||
subId = fileSubId,
|
||||
expire = expireTime
|
||||
)
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送私聊文件事件
|
||||
*/
|
||||
suspend fun transGroupFileEvent(
|
||||
msgTime: Long,
|
||||
userId: Long,
|
||||
groupId: Long,
|
||||
uuid: String,
|
||||
fileName: String,
|
||||
fileSize: Long,
|
||||
bizId: Int,
|
||||
url: String
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupUpload,
|
||||
operatorId = userId,
|
||||
userId = userId,
|
||||
groupId = groupId,
|
||||
file = GroupFileMsg(
|
||||
id = uuid,
|
||||
name = fileName,
|
||||
size = fileSize,
|
||||
busid = bizId.toLong(),
|
||||
url = url
|
||||
)
|
||||
))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 群聊通知 通知器
|
||||
*/
|
||||
object GroupNoticeTransmitter {
|
||||
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
subType = NoticeSubType.Sign,
|
||||
userId = target,
|
||||
groupId = groupCode,
|
||||
target = target,
|
||||
signDetail = SignDetail(
|
||||
rankImg = rankImg,
|
||||
action = action
|
||||
)
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
subType = NoticeSubType.Poke,
|
||||
operatorId = operation,
|
||||
userId = operation,
|
||||
groupId = groupCode,
|
||||
target = target,
|
||||
pokeDetail = PokeDetail(
|
||||
action = action,
|
||||
suffix = suffix,
|
||||
actionImg = actionImg
|
||||
)
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupMemberNumChanged(
|
||||
time: Long,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
noticeType: NoticeType,
|
||||
noticeSubType: NoticeSubType
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = noticeType,
|
||||
subType = noticeSubType,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
senderId = operator,
|
||||
target = target,
|
||||
groupId = groupCode,
|
||||
targetUid = targetUid,
|
||||
operatorUid = operatorUid,
|
||||
userUid = targetUid
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupAdminChanged(
|
||||
msgTime: Long,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
setAdmin: Boolean
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupAdminChange,
|
||||
subType = if (setAdmin) NoticeSubType.Set else NoticeSubType.UnSet,
|
||||
operatorId = 0,
|
||||
userId = target,
|
||||
userUid = targetUid,
|
||||
target = target,
|
||||
targetUid = targetUid,
|
||||
groupId = groupCode
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupBan(
|
||||
msgTime: Long,
|
||||
subType: NoticeSubType,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
duration: Int
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupBan,
|
||||
subType = subType,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
senderId = operator,
|
||||
target = target,
|
||||
groupId = groupCode,
|
||||
duration = duration,
|
||||
operatorUid = operatorUid,
|
||||
targetUid = targetUid
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupMsgRecall(
|
||||
time: Long,
|
||||
operator: Long,
|
||||
target: Long,
|
||||
groupCode: Long,
|
||||
msgHash: Int,
|
||||
tipText: String
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupRecall,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
msgId = msgHash,
|
||||
tip = tipText,
|
||||
groupId = groupCode
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transCardChange(
|
||||
time: Long,
|
||||
targetId: Long,
|
||||
oldCard: String,
|
||||
newCard: String,
|
||||
groupId: Long
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupCard,
|
||||
userId = targetId,
|
||||
cardNew = newCard,
|
||||
cardOld = oldCard,
|
||||
groupId = groupId
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transTitleChange(
|
||||
time: Long,
|
||||
targetId: Long,
|
||||
title: String,
|
||||
groupId: Long
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
userId = targetId,
|
||||
groupId = groupId,
|
||||
title = title,
|
||||
subType = NoticeSubType.Title
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transEssenceChange(
|
||||
time: Long,
|
||||
senderUin: Long,
|
||||
operatorUin: Long,
|
||||
msgId: Int,
|
||||
groupId: Long,
|
||||
subType: NoticeSubType
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Essence,
|
||||
senderId = senderUin,
|
||||
groupId = groupId,
|
||||
operatorId = operatorUin,
|
||||
msgId = msgId,
|
||||
subType = subType
|
||||
))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 私聊通知 通知器
|
||||
*/
|
||||
object PrivateNoticeTransmitter {
|
||||
suspend fun transPrivatePoke(msgTime: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
subType = NoticeSubType.Poke,
|
||||
operatorId = operation,
|
||||
userId = operation,
|
||||
senderId = operation,
|
||||
target = target,
|
||||
pokeDetail = PokeDetail(
|
||||
actionImg = actionImg,
|
||||
action = action,
|
||||
suffix = suffix
|
||||
)
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transPrivateRecall(time: Long, operation: Long, msgHashId: Int, tipText: String): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.FriendRecall,
|
||||
operatorId = operation,
|
||||
userId = operation,
|
||||
msgId = msgHashId,
|
||||
tip = tipText
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 通知器
|
||||
*/
|
||||
object RequestTransmitter {
|
||||
suspend fun transFriendApp(time: Long, operation: Long, tipText: String, flag: String): Boolean {
|
||||
pushRequest(RequestEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Request,
|
||||
type = RequestType.Friend,
|
||||
userId = operation,
|
||||
comment = tipText,
|
||||
flag = flag
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupApply(
|
||||
time: Long,
|
||||
applier: Long,
|
||||
applierUid: String,
|
||||
reason: String,
|
||||
groupCode: Long,
|
||||
flag: String,
|
||||
subType: RequestSubType
|
||||
): Boolean {
|
||||
pushRequest(RequestEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Request,
|
||||
type = RequestType.Group,
|
||||
userId = applier,
|
||||
userUid = applierUid,
|
||||
comment = reason,
|
||||
groupId = groupCode,
|
||||
subType = subType,
|
||||
flag = flag
|
||||
))
|
||||
return true
|
||||
}
|
||||
}*/
|
||||
|
||||
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) {
|
||||
messageEventFlow.collect {
|
||||
GlobalScope.launch {
|
||||
collector.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
suspend inline fun onNoticeEvent(collector: FlowCollector<NoticeEvent>) {
|
||||
noticeEventFlow.collect {
|
||||
GlobalScope.launch {
|
||||
collector.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun onRequestEvent(collector: FlowCollector<RequestEvent>) {
|
||||
requestEventFlow.collect {
|
||||
GlobalScope.launch {
|
||||
collector.emit(it)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
@ -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 {
|
||||
|
@ -11,7 +11,7 @@ import moe.fuqiuluo.shamrock.tools.hookMethod
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||
import moe.fuqiuluo.symbols.XposedHook
|
||||
|
||||
|
@ -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,18 +3,111 @@ 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
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
|
||||
import qq.service.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 qq.service.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
|
||||
)
|
@ -24,6 +24,7 @@ import com.tencent.qqnt.kernel.nativeinterface.ImportOldDbMsgNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.InputStatusInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.KickedInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgAbstract
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
|
||||
@ -33,12 +34,74 @@ import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TabStatusInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.UnreadCntInfo
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||
import qq.service.msg.MessageHelper
|
||||
|
||||
object AioListener: IKernelMsgListener {
|
||||
override fun onRecvMsg(arrayList: ArrayList<MsgRecord>?) {
|
||||
override fun onRecvMsg(msgs: ArrayList<MsgRecord>) {
|
||||
msgs.forEach {
|
||||
GlobalScope.launch {
|
||||
try {
|
||||
onMsg(it)
|
||||
} catch (e: Exception) {
|
||||
LogCenter.log("OnMessage: " + e.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onMsg(record: MsgRecord) {
|
||||
when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = ${record.msgId})")
|
||||
|
||||
if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(record, record.elements)) {
|
||||
LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> {
|
||||
LogCenter.log("私聊消息(private = ${record.senderUin}, id = [${record.msgId} | ${record.msgSeq}])")
|
||||
|
||||
if (!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
|
||||
record, record.elements
|
||||
)
|
||||
) {
|
||||
LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||
var groupCode = 0L
|
||||
var fromNick = ""
|
||||
MessageHelper.getTempChatInfo(record.chatType, record.senderUid).onSuccess {
|
||||
groupCode = it.groupCode.toLong()
|
||||
fromNick = it.fromNick
|
||||
}
|
||||
|
||||
LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)")
|
||||
|
||||
if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(record, record.elements, groupCode, fromNick)
|
||||
) {
|
||||
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
MsgConstant.KCHATTYPEGUILD -> {
|
||||
LogCenter.log("频道消息(guildId = ${record.guildId}, sender = ${record.senderUid}, id = [${record.msgId}])")
|
||||
if (!GlobalEventTransmitter.MessageTransmitter
|
||||
.transGuildMessage(record, record.elements)
|
||||
) {
|
||||
LogCenter.log("频道消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
else -> LogCenter.log("不支持PUSH事件: ${record.chatType}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMsgRecall(chatType: Int, peerId: String, msgId: Long) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package moe.fuqiuluo.shamrock.internals
|
||||
package qq.service.internals
|
||||
|
||||
import com.tencent.qqnt.kernel.api.IKernelService
|
||||
import com.tencent.qqnt.kernel.api.impl.MsgService
|
||||
@ -10,8 +10,6 @@ import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.hookMethod
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import qq.service.internals.AioListener
|
||||
import qq.service.internals.msgService
|
||||
|
||||
internal object NTServiceFetcher {
|
||||
private lateinit var iKernelService: IKernelService
|
31
xposed/src/main/java/qq/service/msg/MessageHelper.kt
Normal file
31
xposed/src/main/java/qq/service/msg/MessageHelper.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package qq.service.msg
|
||||
|
||||
import com.tencent.qqnt.kernel.api.IKernelService
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.internals.msgService
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object MessageHelper: QQInterfaces() {
|
||||
suspend fun getTempChatInfo(chatType: Int, uid: String): Result<TempChatInfo> {
|
||||
val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService
|
||||
?: return Result.failure(Exception("获取消息服务失败"))
|
||||
val info: TempChatInfo = withTimeoutOrNull(5000) {
|
||||
suspendCancellableCoroutine {
|
||||
msgService.getTempChatInfo(chatType, uid) { code, msg, tempChatInfo ->
|
||||
if (code == 0) {
|
||||
it.resume(tempChatInfo)
|
||||
} else {
|
||||
LogCenter.log("获取临时会话信息失败: $code:$msg", Level.ERROR)
|
||||
it.resume(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: return Result.failure(Exception("获取临时会话信息失败"))
|
||||
return Result.success(info)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user