From 3a0711609359f415a179024f32d8e243ae35e298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E6=B1=A0?= Date: Fri, 15 Mar 2024 01:37:28 +0800 Subject: [PATCH] =?UTF-8?q?`Shamrock`:=20=E5=AE=9E=E7=8E=B0=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 白池 --- .../main/java/kritor/service/EventService.kt | 27 +- .../internals/GlobalEventTransmitter.kt | 473 +++++++------ .../java/qq/service/friend/FriendHelper.kt | 67 ++ .../main/java/qq/service/group/GroupHelper.kt | 101 +++ .../java/qq/service/internals/AioListener.kt | 62 ++ .../java/qq/service/internals/MSFHandler.kt | 8 +- .../qq/service/internals/PrimitiveListener.kt | 668 ++++++++++++++++++ .../service/kernel/SimpleKernelMsgListener.kt | 2 +- .../main/java/qq/service/msg/MessageHelper.kt | 21 +- .../main/java/qq/service/msg/MsgConvertor.kt | 24 +- 10 files changed, 1217 insertions(+), 236 deletions(-) create mode 100644 xposed/src/main/java/qq/service/internals/PrimitiveListener.kt diff --git a/xposed/src/main/java/kritor/service/EventService.kt b/xposed/src/main/java/kritor/service/EventService.kt index 58c1235b..826e752e 100644 --- a/xposed/src/main/java/kritor/service/EventService.kt +++ b/xposed/src/main/java/kritor/service/EventService.kt @@ -1,28 +1,41 @@ package kritor.service +import io.grpc.Status +import io.grpc.StatusRuntimeException import io.kritor.event.EventRequest import io.kritor.event.EventServiceGrpcKt import io.kritor.event.EventStructure import io.kritor.event.EventType +import io.kritor.event.RequestPushEvent 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 { + override fun registerActiveListener(request: RequestPushEvent): Flow { return channelFlow { when(request.type!!) { - EventType.CORE_EVENT -> TODO() - EventType.MESSAGE -> GlobalEventTransmitter.onMessageEvent { + EventType.EVENT_TYPE_CORE_EVENT -> {} + EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent { send(eventStructure { - this.type = EventType.MESSAGE + this.type = EventType.EVENT_TYPE_MESSAGE this.message = it.second }) } - EventType.NOTICE -> TODO() - EventType.REQUEST -> TODO() - EventType.UNRECOGNIZED -> TODO() + EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onRequestEvent { + send(eventStructure { + this.type = EventType.EVENT_TYPE_NOTICE + this.request = it + }) + } + EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onNoticeEvent { + send(eventStructure { + this.type = EventType.EVENT_TYPE_NOTICE + this.notice = it + }) + } + EventType.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT) } } } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt index eb2de001..9c18f34a 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt @@ -4,17 +4,42 @@ package moe.fuqiuluo.shamrock.internals import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgRecord +import io.kritor.event.GroupApplyType +import io.kritor.event.GroupMemberBanType +import io.kritor.event.GroupMemberDecreasedType +import io.kritor.event.GroupMemberIncreasedType import io.kritor.event.MessageEvent +import io.kritor.event.NoticeEvent +import io.kritor.event.NoticeType +import io.kritor.event.RequestType +import io.kritor.event.RequestsEvent import io.kritor.event.Scene import io.kritor.event.contact +import io.kritor.event.essenceMessageNotice +import io.kritor.event.friendApplyRequest +import io.kritor.event.friendFileComeNotice +import io.kritor.event.friendPokeNotice +import io.kritor.event.friendRecallNotice +import io.kritor.event.groupAdminChangedNotice +import io.kritor.event.groupApplyRequest +import io.kritor.event.groupFileComeNotice +import io.kritor.event.groupMemberBannedNotice +import io.kritor.event.groupMemberDecreasedNotice +import io.kritor.event.groupMemberIncreasedNotice +import io.kritor.event.groupPokeNotice +import io.kritor.event.groupRecallNotice +import io.kritor.event.groupSignNotice +import io.kritor.event.groupUniqueTitleChangedNotice +import io.kritor.event.groupWholeBanNotice import io.kritor.event.messageEvent +import io.kritor.event.noticeEvent +import io.kritor.event.requestsEvent import io.kritor.event.sender import kotlinx.coroutines.DelicateCoroutinesApi 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 import qq.service.msg.toKritorMessages @@ -22,16 +47,16 @@ internal object GlobalEventTransmitter: QQInterfaces() { private val messageEventFlow by lazy { MutableSharedFlow>() } - //private val noticeEventFlow by lazy { - // MutableSharedFlow() - //} - //private val requestEventFlow by lazy { - // MutableSharedFlow() - //} + private val noticeEventFlow by lazy { + MutableSharedFlow() + } + private val requestEventFlow by lazy { + MutableSharedFlow() + } - //private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent) + private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent) - //private suspend fun pushRequest(requestEvent: RequestEvent) = requestEventFlow.emit(requestEvent) + private suspend fun pushRequest(requestEvent: RequestsEvent) = requestEventFlow.emit(requestEvent) private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message) @@ -135,10 +160,9 @@ internal object GlobalEventTransmitter: QQInterfaces() { } } - /* /** * 文件通知 通知器 - */ + **/ object FileNoticeTransmitter { /** * 推送私聊文件事件 @@ -153,23 +177,19 @@ internal object GlobalEventTransmitter: QQInterfaces() { 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 - ) - )) + pushNotice(noticeEvent { + this.type = NoticeType.FRIEND_FILE_COME + this.time = msgTime.toInt() + this.friendFileCome = friendFileComeNotice { + this.fileId = fileId + this.fileName = fileName + this.operator = userId + this.fileSize = fileSize + this.expireTime = expireTime.toInt() + this.fileSubId = fileSubId + this.url = url + } + }) return true } @@ -186,22 +206,19 @@ internal object GlobalEventTransmitter: QQInterfaces() { 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 - ) - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_FILE_COME + this.time = msgTime.toInt() + this.groupFileCome = groupFileComeNotice { + this.groupId = groupId + this.operator = userId + this.fileId = uuid + this.fileName = fileName + this.fileSize = fileSize + this.biz = bizId + this.url = url + } + }) return true } } @@ -211,68 +228,80 @@ internal object GlobalEventTransmitter: QQInterfaces() { */ 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 - ) - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_SIGN + this.time = time.toInt() + this.groupSign = groupSignNotice { + this.groupId = groupCode + this.targetUin = target + this.action = action ?: "" + this.suffix = "" + this.rankImage = rankImg ?: "" + } + }) 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 - ) - )) + suspend fun transGroupPoke(time: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean { + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_POKE + this.time = time.toInt() + this.groupPoke = groupPokeNotice { + this.action = action ?: "" + this.target = target + this.operator = operator + this.suffix = suffix ?: "" + this.actionImage = actionImg ?: "" + } + }) return true } - suspend fun transGroupMemberNumChanged( + suspend fun transGroupMemberNumIncreased( time: Long, target: Long, targetUid: String, groupCode: Long, operator: Long, operatorUid: String, - noticeType: NoticeType, - noticeSubType: NoticeSubType + type: GroupMemberIncreasedType ): 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 - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_MEMBER_INCREASE + this.time = time.toInt() + this.groupMemberIncrease = groupMemberIncreasedNotice { + this.groupId = groupCode + this.operatorUid = operatorUid + this.operatorUin = operator + this.targetUid = targetUid + this.targetUin = target + this.type = type + } + }) + return true + } + + suspend fun transGroupMemberNumDecreased( + time: Long, + target: Long, + targetUid: String, + groupCode: Long, + operator: Long, + operatorUid: String, + type: GroupMemberDecreasedType + ): Boolean { + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_MEMBER_INCREASE + this.time = time.toInt() + this.groupMemberDecrease = groupMemberDecreasedNotice { + this.groupId = groupCode + this.operatorUid = operatorUid + this.operatorUin = operator + this.targetUid = targetUid + this.targetUin = target + this.type = type + } + }) return true } @@ -283,25 +312,39 @@ internal object GlobalEventTransmitter: QQInterfaces() { 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 - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_ADMIN_CHANGED + this.time = msgTime.toInt() + this.groupAdminChanged = groupAdminChangedNotice { + this.groupId = groupCode + this.targetUid = targetUid + this.targetUin = target + this.isAdmin = setAdmin + } + }) + return true + } + + suspend fun transGroupWholeBan( + msgTime: Long, + operator: Long, + groupCode: Long, + isOpen: Boolean + ): Boolean { + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_WHOLE_BAN + this.time = msgTime.toInt() + this.groupWholeBan = groupWholeBanNotice { + this.groupId = groupCode + this.isWholeBan = isOpen + this.operator = operator + } + }) return true } suspend fun transGroupBan( msgTime: Long, - subType: NoticeSubType, operator: Long, operatorUid: String, target: Long, @@ -309,43 +352,46 @@ internal object GlobalEventTransmitter: QQInterfaces() { 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 - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_MEMBER_BANNED + this.time = msgTime.toInt() + this.groupMemberBanned = groupMemberBannedNotice { + this.groupId = groupCode + this.operatorUid = operatorUid + this.operatorUin = operator + this.targetUid = targetUid + this.targetUin = target + this.duration = duration + this.type = if (duration > 0) GroupMemberBanType.BAN + else GroupMemberBanType.LIFT_BAN + } + }) return true } suspend fun transGroupMsgRecall( time: Long, operator: Long, + operatorUid: String, target: Long, + targetUid: String, groupCode: Long, - msgHash: Int, + msgId: Long, 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 - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_RECALL + this.time = time.toInt() + this.groupRecall = groupRecallNotice { + this.groupId = groupCode + this.operatorUid = operatorUid + this.operatorUin = operator + this.targetUid = targetUid + this.targetUin = target + this.messageId = msgId + this.tipText = tipText + } + }) return true } @@ -356,16 +402,7 @@ internal object GlobalEventTransmitter: QQInterfaces() { 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 } @@ -375,16 +412,15 @@ internal object GlobalEventTransmitter: QQInterfaces() { 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 - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED + this.time = time.toInt() + this.groupMemberUniqueTitleChanged = groupUniqueTitleChangedNotice { + this.groupId = groupId + this.target = targetId + this.title = title + } + }) return true } @@ -392,21 +428,21 @@ internal object GlobalEventTransmitter: QQInterfaces() { time: Long, senderUin: Long, operatorUin: Long, - msgId: Int, + msgId: Long, groupId: Long, - subType: NoticeSubType + subType: UInt ): Boolean { - pushNotice(NoticeEvent( - time = time, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.Essence, - senderId = senderUin, - groupId = groupId, - operatorId = operatorUin, - msgId = msgId, - subType = subType - )) + pushNotice(noticeEvent { + this.type = NoticeType.GROUP_ESSENCE_CHANGED + this.time = time.toInt() + this.groupEssenceChanged = essenceMessageNotice { + this.groupId = groupId + this.messageId = msgId + this.sender = senderUin + this.operator = operatorUin + this.subType = subType.toInt() + } + }) return true } } @@ -415,37 +451,31 @@ internal object GlobalEventTransmitter: QQInterfaces() { * 私聊通知 通知器 */ 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 - ) - )) + suspend fun transPrivatePoke(msgTime: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean { + pushNotice(noticeEvent { + this.type = NoticeType.FRIEND_POKE + this.time = msgTime.toInt() + this.friendPoke = friendPokeNotice { + this.action = action ?: "" + this.target = target + this.operator = operator + this.suffix = suffix ?: "" + this.actionImage = actionImg ?: "" + } + }) 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 - )) + suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean { + pushNotice(noticeEvent { + this.type = NoticeType.FRIEND_RECALL + this.time = time.toInt() + this.friendRecall = friendRecallNotice { + this.operator = operator + this.messageId = msgId + this.tipText = tipText + } + }) return true } @@ -455,16 +485,16 @@ internal object GlobalEventTransmitter: QQInterfaces() { * 请求 通知器 */ 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 - )) + suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean { + pushRequest(requestsEvent { + this.type = RequestType.FRIEND_APPLY + this.time = time.toInt() + this.friendApply = friendApplyRequest { + this.applierUin = operator + this.message = tipText + this.flag = flag + } + }) return true } @@ -475,23 +505,23 @@ internal object GlobalEventTransmitter: QQInterfaces() { reason: String, groupCode: Long, flag: String, - subType: RequestSubType + type: GroupApplyType ): 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 - )) + pushRequest(requestsEvent { + this.type = RequestType.GROUP_APPLY + this.time = time.toInt() + this.groupApply = groupApplyRequest { + this.applierUid = applierUid + this.applierUin = applier + this.groupId = groupCode + this.reason = reason + this.flag = flag + this.type = type + } + }) return true } - }*/ + } suspend inline fun onMessageEvent(collector: FlowCollector>) { messageEventFlow.collect { @@ -501,7 +531,6 @@ internal object GlobalEventTransmitter: QQInterfaces() { } } - /* suspend inline fun onNoticeEvent(collector: FlowCollector) { noticeEventFlow.collect { GlobalScope.launch { @@ -510,11 +539,11 @@ internal object GlobalEventTransmitter: QQInterfaces() { } } - suspend inline fun onRequestEvent(collector: FlowCollector) { + suspend inline fun onRequestEvent(collector: FlowCollector) { requestEventFlow.collect { GlobalScope.launch { collector.emit(it) } } - }*/ + } } \ No newline at end of file diff --git a/xposed/src/main/java/qq/service/friend/FriendHelper.kt b/xposed/src/main/java/qq/service/friend/FriendHelper.kt index d93921ed..a71205bf 100644 --- a/xposed/src/main/java/qq/service/friend/FriendHelper.kt +++ b/xposed/src/main/java/qq/service/friend/FriendHelper.kt @@ -3,11 +3,15 @@ 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 com.tencent.mobileqq.qroute.QRoute +import com.tencent.mobileqq.relation.api.IAddFriendTempApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import moe.fuqiuluo.shamrock.tools.slice import qq.service.QQInterfaces +import tencent.mobileim.structmsg.structmsg import kotlin.coroutines.resume internal object FriendHelper: QQInterfaces() { @@ -21,6 +25,69 @@ internal object FriendHelper: QQInterfaces() { return Result.success(service.allFriends) } + // ProfileService.Pb.ReqSystemMsgAction.Friend + fun requestFriendRequest(msgSeq: Long, uin: Long, remark: String = "", approve: Boolean? = true, notSee: Boolean? = false) { + val service = QRoute.api(IAddFriendTempApi::class.java) + val action = structmsg.SystemMsgActionInfo() + action.type.set(if (approve != false) 2 else 3) + action.group_id.set(0) + action.remark.set(remark) + val snInfo = structmsg.AddFrdSNInfo() + snInfo.uint32_not_see_dynamic.set(if (notSee != false) 1 else 0) + snInfo.uint32_set_sn.set(0) + action.addFrdSNInfo.set(snInfo) + service.sendFriendSystemMsgAction(1, msgSeq, uin, 1, 2004, 11, 0, action, 0, structmsg.StructMsg(), false, app) + } + + suspend fun requestFriendSystemMsgNew(msgNum: Int, latestFriendSeq: Long = 0, latestGroupSeq: Long = 0, retryCnt: Int = 3): List? { + if (retryCnt < 0) { + return ArrayList() + } + val req = structmsg.ReqSystemMsgNew() + req.msg_num.set(msgNum) + req.latest_friend_seq.set(latestFriendSeq) + req.latest_group_seq.set(latestGroupSeq) + req.version.set(1000) + req.checktype.set(2) + val flag = structmsg.FlagInfo() +// flag.GrpMsg_Kick_Admin.set(1) +// flag.GrpMsg_HiddenGrp.set(1) +// flag.GrpMsg_WordingDown.set(1) + flag.FrdMsg_GetBusiCard.set(1) +// flag.GrpMsg_GetOfficialAccount.set(1) +// flag.GrpMsg_GetPayInGroup.set(1) + flag.FrdMsg_Discuss2ManyChat.set(1) +// flag.GrpMsg_NotAllowJoinGrp_InviteNotFrd.set(1) + flag.FrdMsg_NeedWaitingMsg.set(1) + flag.FrdMsg_uint32_need_all_unread_msg.set(1) +// flag.GrpMsg_NeedAutoAdminWording.set(1) +// flag.GrpMsg_get_transfer_group_msg_flag.set(1) +// flag.GrpMsg_get_quit_pay_group_msg_flag.set(1) +// flag.GrpMsg_support_invite_auto_join.set(1) +// flag.GrpMsg_mask_invite_auto_join.set(1) +// flag.GrpMsg_GetDisbandedByAdmin.set(1) + flag.GrpMsg_GetC2cInviteJoinGroup.set(1) + req.flag.set(flag) + req.is_get_frd_ribbon.set(false) + req.is_get_grp_ribbon.set(false) + req.friend_msg_type_flag.set(1) + req.uint32_req_msg_type.set(1) + req.uint32_need_uid.set(1) + val fromServiceMsg = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Friend", true, req.toByteArray()) + return if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { + ArrayList() + } else { + try { + val msg = structmsg.RspSystemMsgNew() + msg.mergeFrom(fromServiceMsg.wupBuffer.slice(4)) + return msg.friendmsgs.get() + } catch (err: Throwable) { + requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1) + } + + } + } + private suspend fun requestFriendList(dataService: IFriendDataService): Boolean { val service = app.getRuntimeService(IFriendHandlerService::class.java, "all") service.requestFriendList(true, 0) diff --git a/xposed/src/main/java/qq/service/group/GroupHelper.kt b/xposed/src/main/java/qq/service/group/GroupHelper.kt index 01893992..7062b6c7 100644 --- a/xposed/src/main/java/qq/service/group/GroupHelper.kt +++ b/xposed/src/main/java/qq/service/group/GroupHelper.kt @@ -37,6 +37,7 @@ 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 tencent.mobileim.structmsg.structmsg import java.lang.reflect.Method import java.lang.reflect.Modifier import java.nio.ByteBuffer @@ -217,6 +218,106 @@ internal object GroupHelper: QQInterfaces() { sendOidb("OidbSvc.0x55c_1", 1372, 1, array) } + // ProfileService.Pb.ReqSystemMsgAction.Group + suspend fun requestGroupRequest( + msgSeq: Long, + uin: Long, + gid: Long, + msg: String? = "", + approve: Boolean? = true, + notSee: Boolean? = false, + subType: String + ): Result{ + val req = structmsg.ReqSystemMsgAction().apply { + if (subType == "invite") { + msg_type.set(1) + src_id.set(3) + sub_src_id.set(10016) + group_msg_type.set(2) + } else { + msg_type.set(2) + src_id.set(2) + sub_src_id.set(30024) + group_msg_type.set(1) + } + msg_seq.set(msgSeq) + req_uin.set(uin) + sub_type.set(1) + action_info.set(structmsg.SystemMsgActionInfo().apply { + type.set(if (approve != false) 11 else 12) + group_code.set(gid) + if (subType == "add") { + this.msg.set(msg) + this.blacklist.set(notSee != false) + } + }) + language.set(1000) + } + val fromServiceMsg = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray()) + ?: return Result.failure(Exception("ReqSystemMsgAction.Group: No Response")) + if (fromServiceMsg.wupBuffer == null) { + return Result.failure(Exception("ReqSystemMsgAction.Group: No WupBuffer")) + } + val rsp = structmsg.RspSystemMsgAction().mergeFrom(fromServiceMsg.wupBuffer.slice(4)) + return if (rsp.head.result.has()) { + if (rsp.head.result.get() == 0) { + Result.success(rsp.msg_detail.get()) + } else { + Result.failure(Exception(rsp.head.msg_fail.get())) + } + } else { + Result.failure(Exception("操作失败")) + } + } + + suspend fun requestGroupSystemMsgNew(msgNum: Int, reqMsgType: Int = 1, latestFriendSeq: Long = 0, latestGroupSeq: Long = 0, retryCnt: Int = 5): List { + if (retryCnt < 0) { + return ArrayList() + } + val req = structmsg.ReqSystemMsgNew() + req.msg_num.set(msgNum) + req.latest_friend_seq.set(latestFriendSeq) + req.latest_group_seq.set(latestGroupSeq) + req.version.set(1000) + req.checktype.set(3) + val flag = structmsg.FlagInfo() + flag.GrpMsg_Kick_Admin.set(1) + flag.GrpMsg_HiddenGrp.set(1) + flag.GrpMsg_WordingDown.set(1) +// flag.FrdMsg_GetBusiCard.set(1) + flag.GrpMsg_GetOfficialAccount.set(1) + flag.GrpMsg_GetPayInGroup.set(1) + flag.FrdMsg_Discuss2ManyChat.set(1) + flag.GrpMsg_NotAllowJoinGrp_InviteNotFrd.set(1) + flag.FrdMsg_NeedWaitingMsg.set(1) +// flag.FrdMsg_uint32_need_all_unread_msg.set(1) + flag.GrpMsg_NeedAutoAdminWording.set(1) + flag.GrpMsg_get_transfer_group_msg_flag.set(1) + flag.GrpMsg_get_quit_pay_group_msg_flag.set(1) + flag.GrpMsg_support_invite_auto_join.set(1) + flag.GrpMsg_mask_invite_auto_join.set(1) + flag.GrpMsg_GetDisbandedByAdmin.set(1) + flag.GrpMsg_GetC2cInviteJoinGroup.set(1) + req.flag.set(flag) + req.is_get_frd_ribbon.set(false) + req.is_get_grp_ribbon.set(false) + req.friend_msg_type_flag.set(1) + req.uint32_req_msg_type.set(reqMsgType) + req.uint32_need_uid.set(1) + val fromServiceMsg = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Group", true, req.toByteArray()) + return if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { + ArrayList() + } else { + try { + val msg = structmsg.RspSystemMsgNew() + msg.mergeFrom(fromServiceMsg.wupBuffer.slice(4)) + return msg.groupmsgs.get().orEmpty() + } catch (err: Throwable) { + requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1) + } + } + } + suspend fun setGroupUniqueTitle(groupId: Long, userId: Long, title: String) { val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow() val req = Oidb_0x8fc.ReqBody() diff --git a/xposed/src/main/java/qq/service/internals/AioListener.kt b/xposed/src/main/java/qq/service/internals/AioListener.kt index d8d9ade5..44125410 100644 --- a/xposed/src/main/java/qq/service/internals/AioListener.kt +++ b/xposed/src/main/java/qq/service/internals/AioListener.kt @@ -10,6 +10,7 @@ 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.bdh.RichProtoSvc import qq.service.kernel.SimpleKernelMsgListener import qq.service.msg.MessageHelper @@ -29,6 +30,8 @@ object AioListener: SimpleKernelMsgListener() { private suspend fun onMsg(record: MsgRecord) { when (record.chatType) { MsgConstant.KCHATTYPEGROUP -> { + if (record.senderUin == 0L) return + LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = ${record.msgId})") if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(record, record.elements)) { @@ -75,4 +78,63 @@ object AioListener: SimpleKernelMsgListener() { else -> LogCenter.log("不支持PUSH事件: ${record.chatType}") } } + + override fun onFileMsgCome(arrayList: ArrayList?) { + arrayList?.forEach { record -> + GlobalScope.launch { + when (record.chatType) { + MsgConstant.KCHATTYPEGROUP -> onGroupFileMsg(record) + MsgConstant.KCHATTYPEC2C -> onC2CFileMsg(record) + else -> LogCenter.log("不支持该来源的文件上传事件:${record}", Level.WARN) + } + } + } + } + + private suspend fun onC2CFileMsg(record: MsgRecord) { + val userId = record.senderUin + val fileMsg = record.elements.firstOrNull { + it.elementType == MsgConstant.KELEMTYPEFILE + }?.fileElement ?: kotlin.run { + LogCenter.log("消息为私聊文件消息但不包含文件消息,来自:${record.peerUin}", Level.WARN) + return + } + + val fileName = fileMsg.fileName + val fileSize = fileMsg.fileSize + val expireTime = fileMsg.expireTime ?: 0 + val fileId = fileMsg.fileUuid + val fileSubId = fileMsg.fileSubId ?: "" + val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) + + if (!GlobalEventTransmitter.FileNoticeTransmitter + .transPrivateFileEvent(record.msgTime, userId, fileId, fileSubId, fileName, fileSize, expireTime, url) + ) { + LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN) + } + } + + private suspend fun onGroupFileMsg(record: MsgRecord) { + val groupId = record.peerUin + val userId = record.senderUin + val fileMsg = record.elements.firstOrNull { + it.elementType == MsgConstant.KELEMTYPEFILE + }?.fileElement ?: kotlin.run { + LogCenter.log("消息为群聊文件消息但不包含文件消息,来自:${record.peerUin}", Level.WARN) + return + } + //val fileMd5 = fileMsg.fileMd5 + val fileName = fileMsg.fileName + val fileSize = fileMsg.fileSize + val uuid = fileMsg.fileUuid + val bizId = fileMsg.fileBizId + + val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId) + + if (!GlobalEventTransmitter.FileNoticeTransmitter + .transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url) + ) { + LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN) + } + } } \ No newline at end of file diff --git a/xposed/src/main/java/qq/service/internals/MSFHandler.kt b/xposed/src/main/java/qq/service/internals/MSFHandler.kt index 2317332d..0cc4c13a 100644 --- a/xposed/src/main/java/qq/service/internals/MSFHandler.kt +++ b/xposed/src/main/java/qq/service/internals/MSFHandler.kt @@ -52,8 +52,12 @@ internal object MSFHandler { fun onPush(fromServiceMsg: FromServiceMsg) { val cmd = fromServiceMsg.serviceCmd - val push = mPushHandlers[cmd] - push?.invoke(fromServiceMsg) + if (cmd == "trpc.msg.olpush.OlPushService.MsgPush") { + PrimitiveListener.onPush(fromServiceMsg) + } else { + val push = mPushHandlers[cmd] + push?.invoke(fromServiceMsg) + } } fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) { diff --git a/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt b/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt new file mode 100644 index 00000000..c2a9b918 --- /dev/null +++ b/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt @@ -0,0 +1,668 @@ +@file:OptIn(DelicateCoroutinesApi::class) +package qq.service.internals + +import com.tencent.mobileqq.qroute.QRoute +import com.tencent.qphone.base.remote.FromServiceMsg +import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.msg.api.IMsgService +import io.kritor.event.GroupApplyType +import io.kritor.event.GroupMemberDecreasedType +import io.kritor.event.GroupMemberIncreasedType +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.discardExact +import kotlinx.io.core.readBytes +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter +import moe.fuqiuluo.shamrock.tools.asJsonObject +import moe.fuqiuluo.shamrock.tools.asString +import moe.fuqiuluo.shamrock.tools.readBuf32Long +import moe.fuqiuluo.shamrock.tools.slice +import moe.fuqiuluo.symbols.decodeProtobuf +import protobuf.message.ContentHead +import protobuf.message.MsgBody +import protobuf.message.ResponseHead +import protobuf.push.C2CCommonTipsEvent +import protobuf.push.C2CRecallEvent +import protobuf.push.FriendApplyEvent +import protobuf.push.GroupAdminChangeEvent +import protobuf.push.GroupApplyEvent +import protobuf.push.GroupBanEvent +import protobuf.push.GroupCommonTipsEvent +import protobuf.push.GroupInviteEvent +import protobuf.push.GroupInvitedApplyEvent +import protobuf.push.GroupListChangeEvent +import protobuf.push.MessagePush +import protobuf.push.MessagePushClientInfo +import qq.service.QQInterfaces +import qq.service.contact.ContactHelper +import qq.service.friend.FriendHelper.requestFriendSystemMsgNew +import qq.service.group.GroupHelper +import qq.service.group.GroupHelper.requestGroupSystemMsgNew +import qq.service.msg.MessageHelper +import kotlin.coroutines.resume + +internal object PrimitiveListener { + + fun onPush(fromServiceMsg: FromServiceMsg) { + if (fromServiceMsg.wupBuffer == null) return + try { + val push = fromServiceMsg.wupBuffer.slice(4) + .decodeProtobuf() + GlobalScope.launch { + onMsgPush(push) + } + } catch (e: Exception) { + LogCenter.log(e.stackTraceToString(), Level.WARN) + } + } + + private suspend fun onMsgPush(push: MessagePush) { + if ( + push.msgBody == null || + push.msgBody!!.contentHead == null || + push.msgBody!!.body == null || + push.msgBody!!.contentHead!!.msgTime == null + ) return + val msgBody = push.msgBody!! + val contentHead = msgBody.contentHead!! + val msgType = contentHead.msgType + val subType = contentHead.msgSubType + val msgTime = contentHead.msgTime!! + val body = msgBody.body!! + try { + when (msgType) { + 33 -> onGroupMemIncreased(msgTime, body) + 34 -> onGroupMemberDecreased(msgTime, body) + 44 -> onGroupAdminChange(msgTime, body) + 82 -> onGroupMessage(msgTime, body) + 84 -> onGroupApply(msgTime, contentHead, body) + 87 -> onInviteGroup(msgTime, msgBody.msgHead!!, body) + 528 -> when (subType) { + 35 -> onFriendApply(msgTime, push.clientInfo!!, body) + 39 -> onCardChange(msgTime, body) + 68 -> onGroupApply(msgTime, contentHead, body) + 138 -> onC2CRecall(msgTime, body) + 290 -> onC2CPoke(msgTime, body) + } + + 732 -> when (subType) { + 12 -> onGroupBan(msgTime, body) + 16 -> onGroupUniqueTitleChange(msgTime, body) + 17 -> onGroupRecall(msgTime, body) + 20 -> onGroupCommonTips(msgTime, body) + 21 -> onEssenceMessage(msgTime, push.clientInfo, body) + } + } + } catch (e: Exception) { + LogCenter.log("onMsgPush(msgType: $msgType, subType: $subType): " + e.stackTraceToString(), Level.WARN) + } + } + + private fun onGroupMessage(msgTime: Long, body: MsgBody) { + + } + + private suspend fun onC2CPoke(msgTime: Long, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + if (event.params == null) return + + val params = event.params!!.associate { + it.key to it.value + } + + val target = params["uin_str2"] ?: return + val operation = params["uin_str1"] ?: return + val suffix = params["suffix_str"] ?: "" + val actionImg = params["action_img_url"] ?: "" + val action = params["alt_str1"] ?: "" + + LogCenter.log("私聊戳一戳: $operation $action $target $suffix") + + if (!GlobalEventTransmitter.PrivateNoticeTransmitter + .transPrivatePoke(msgTime, operation.toLong(), target.toLong(), action, suffix, actionImg) + ) { + LogCenter.log("私聊戳一戳推送失败!", Level.WARN) + } + } + + private suspend fun onFriendApply( + msgTime: Long, + clientInfo: MessagePushClientInfo, + body: MsgBody + ) { + val event = body.msgContent!!.decodeProtobuf() + if (event.head == null) return + val head = event.head!! + val applierUid = head.applierUid + val msg = head.applyMsg ?: "" + val source = head.source ?: "" + var applier = ContactHelper.getUinByUidAsync(applierUid).toLong() + if (applier == 0L) { + applier = clientInfo.liteHead?.sender?.toLong() ?: 0 + } + val src = head.srcId + val subSrc = head.subSrc + val flag: String = try { + val reqs = requestFriendSystemMsgNew(20, 0, 0) + val req = reqs?.first { + it.msg_time.get() == msgTime + } + val seq = req?.msg_seq?.get() + "$seq;$src;$subSrc;$applier" + } catch (err: Throwable) { + "$msgTime;$src;$subSrc;$applier" + } + LogCenter.log("来自$applier 的好友申请:$msg ($source)") + if (!GlobalEventTransmitter.RequestTransmitter + .transFriendApp(msgTime, applier, msg, flag) + ) { + LogCenter.log("好友申请推送失败!", Level.WARN) + } + } + + + private suspend fun onCardChange(msgTime: Long, body: MsgBody) { +// val event = runCatching { +// body.msgContent!!.decodeProtobuf() +// }.getOrElse { +// val readPacket = ByteReadPacket(body.msgContent!!) +// readPacket.readBuf32Long() +// readPacket.discardExact(1) +// +// readPacket.readBytes(readPacket.readShort().toInt()).also { +// readPacket.release() +// }.decodeProtobuf() +// } +// +// val targetId = detail[1, 13, 2].asUtf8String +// val newCardList = detail[1, 13, 3].asList +// var newCard = "" +// newCardList +// .value +// .forEach { +// if (it[1].asInt == 1) { +// newCard = it[2].asUtf8String +// } +// } +// val groupId = detail[1, 13, 4].asLong +// var oldCard = "" +// val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong() +// LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard") +// // oldCard暂时获取不到 +// if (!GlobalEventTransmitter.GroupNoticeTransmitter +// .transCardChange(msgTime, targetQQ, oldCard, newCard, groupId) +// ) { +// LogCenter.log("群名片变动推送失败!", Level.WARN) +// } + } + + private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) { + val event = runCatching { + body.msgContent!!.decodeProtobuf() + }.getOrElse { + val readPacket = ByteReadPacket(body.msgContent!!) + readPacket.readBuf32Long() + readPacket.discardExact(1) + + readPacket.readBytes(readPacket.readShort().toInt()).also { + readPacket.release() + }.decodeProtobuf() + } + val groupId = event.groupCode.toLong() + val detail = event.uniqueTitleChangeDetail!!.first() + + //detail = if (detail[5] is ProtoList) { + // (detail[5] as ProtoList).value[0] + //} else { + // detail[5] + // } + + val targetUin = detail.targetUin.toLong() + + // 恭喜<{\"cmd\":5,\"data\":\"qq\",\"text}\":\"nickname\"}>获得群主授予的<{\"cmd\":1,\"data\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\",\"text\":\"title\",\"url\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\"}>头衔 + val titleChangeInfo = detail.wording + if (titleChangeInfo.indexOf("群主授予") == -1) { + return + } + val titleJson = titleChangeInfo.split("获得群主授予的<")[1].replace(">头衔", "") + val titleJsonObj = Json.decodeFromString(titleJson).asJsonObject + val title = titleJsonObj["text"].asString + + LogCenter.log("群组[$groupId]成员$targetUin 获得群头衔 -> $title") + + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transTitleChange(msgTime, targetUin, title, groupId) + ) { + LogCenter.log("群头衔变动推送失败!", Level.WARN) + } + } + + private suspend fun onEssenceMessage( + msgTime: Long, + clientInfo: MessagePushClientInfo?, + body: MsgBody + ) { + if (clientInfo == null) return + val event = runCatching { + body.msgContent!!.decodeProtobuf() + }.getOrElse { + val readPacket = ByteReadPacket(body.msgContent!!) + readPacket.readBuf32Long() + readPacket.discardExact(1) + + readPacket.readBytes(readPacket.readShort().toInt()).also { + readPacket.release() + }.decodeProtobuf() + } + val groupId = event.groupCode.toLong() + val detail = event.essenceMsgInfo!!.first() + + val msgSeq = event.msgSeq.toLong() + val senderUin = detail.sender.toLong() + val operatorUin = detail.operator.toLong() + + when (val type = detail.type) { + 1u -> { + LogCenter.log("群设精消息(groupId=$groupId, sender=$senderUin, msgSeq=$msgSeq, operator=$operatorUin)") + } + 2u -> { + LogCenter.log("群撤精消息(groupId=$groupId, sender=$senderUin, msgId=$msgSeq, operator=$operatorUin)") + } + else -> error("onEssenceMessage unknown type: $type") + } + + val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId.toString()) + val sourceRecord = withTimeoutOrNull(3000) { + suspendCancellableCoroutine { + QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records -> + it.resume(records) + } + } + }?.firstOrNull() + + if (sourceRecord == null) { + LogCenter.log("无法获取源消息记录,无法推送精华消息变动!", Level.WARN) + return + } + + val msgId = sourceRecord.msgId + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transEssenceChange(msgTime, senderUin, operatorUin, msgId, groupId, detail.type) + ) { + LogCenter.log("精华消息变动推送失败!", Level.WARN) + } + } + + + private suspend fun onGroupCommonTips(time: Long, body: MsgBody) { + val event = runCatching { + body.msgContent!!.decodeProtobuf() + }.getOrElse { + val readPacket = ByteReadPacket(body.msgContent!!) + readPacket.discardExact(4) + readPacket.discardExact(1) + + readPacket.readBytes(readPacket.readShort().toInt()).also { + readPacket.release() + }.decodeProtobuf() + } + val groupId = event.groupCode.toLong() + val detail = event.baseTips!!.first() + + val params = detail.params!!.associate { + it.key to it.value + } + + val target = params["uin_str2"] ?: params["mqq_uin"] ?: return + val operation = params["uin_str1"] ?: return + val suffix = params["suffix_str"] ?: "" + val actionImg = params["action_img_url"] ?: "" + val action = params["alt_str1"] + ?: params["action_str"] + ?: params["user_sign"] + ?: "" + val rankImg = params["rank_img"] ?: "" + + when (detail.type) { + 1061u -> { + LogCenter.log("群戳一戳($groupId): $operation $action $target $suffix") + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupId) + ) { + LogCenter.log("群戳一戳推送失败!", Level.WARN) + } + } + + 1068u -> { + LogCenter.log("群打卡($groupId): $action $target") + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupSign(time, target.toLong(), action, rankImg, groupId) + ) { + LogCenter.log("群打卡推送失败!", Level.WARN) + } + } + + else -> { + LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail.type}", Level.WARN) + } + } + } + + private suspend fun onC2CRecall(time: Long, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + val head = event.head!! + + val operationUid = head.operator!! + val operator = ContactHelper.getUinByUidAsync(operationUid).toLong() + + val msgSeq = head.msgSeq + val tipText = head.wording?.wording ?: "" + + val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, operationUid) + val sourceRecord = withTimeoutOrNull(3000) { + suspendCancellableCoroutine { + QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records -> + it.resume(records) + } + } + }?.firstOrNull() + + if (sourceRecord == null) { + LogCenter.log("无法获取源消息记录,无法推送撤回消息!", Level.WARN) + return + } + + val msgId = sourceRecord.msgId + + LogCenter.log("私聊消息撤回: $operator, seq = $msgSeq, msgId = ${msgId}, tip = $tipText") + + if (!GlobalEventTransmitter.PrivateNoticeTransmitter + .transPrivateRecall(time, operator, msgId, tipText) + ) { + LogCenter.log("私聊消息撤回推送失败!", Level.WARN) + } + } + + private suspend fun onGroupMemIncreased(time: Long, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.groupCode + val targetUid = event.memberUid + val type = event.type + + GroupHelper.getGroupMemberList(groupCode.toString(), true).onFailure { + LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN) + }.onSuccess { + LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO) + } + + val operatorUid = event.operatorUid + val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong() + val target = ContactHelper.getUinByUidAsync(targetUid).toLong() + LogCenter.log("群成员增加($groupCode): $target, type = $type") + + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupMemberNumIncreased( + time, + target, + targetUid, + groupCode, + operator, + operatorUid, + when (type) { + 130 -> GroupMemberIncreasedType.APPROVE + 131 -> GroupMemberIncreasedType.INVITE + else -> GroupMemberIncreasedType.APPROVE + } + ) + ) { + LogCenter.log("群成员增加推送失败!", Level.WARN) + } + } + + private suspend fun onGroupMemberDecreased(time: Long, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.groupCode + val targetUid = event.memberUid + val type = event.type + val operatorUid = event.operatorUid + + GroupHelper.getGroupMemberList(groupCode.toString(), true).onFailure { + LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN) + }.onSuccess { + LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO) + } + + val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong() + val target = ContactHelper.getUinByUidAsync(targetUid).toLong() + val subtype = when (type) { + 130 -> GroupMemberDecreasedType.LEAVE + 131 -> GroupMemberDecreasedType.KICK + 3 -> GroupMemberDecreasedType.KICK_ME + else -> GroupMemberDecreasedType.KICK + } + + LogCenter.log("群成员减少($groupCode): $target, type = $subtype ($type)") + + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupMemberNumDecreased( + time, + target, + targetUid, + groupCode, + operator, + operatorUid, + subtype + ) + ) { + LogCenter.log("群成员减少推送失败!", Level.WARN) + } + } + + private suspend fun onGroupAdminChange(msgTime: Long, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.groupCode + if (event.operation == null) return + val operation = event.operation!! + if (operation.setInfo == null && operation.unsetInfo == null) return + + val isSetAdmin: Boolean + val targetUid: String + if (operation.setInfo == null) { + isSetAdmin = false + targetUid = operation.unsetInfo!!.targetUid!! + } else { + isSetAdmin = true + targetUid = operation.setInfo!!.targetUid!! + } + + val target = ContactHelper.getUinByUidAsync(targetUid).toLong() + LogCenter.log("群管理员变动($groupCode): $target, isSetAdmin = $isSetAdmin") + + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupAdminChanged(msgTime, target, targetUid, groupCode, isSetAdmin) + ) { + LogCenter.log("群管理员变动推送失败!", Level.WARN) + } + } + + private suspend fun onGroupBan(msgTime: Long, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.groupCode.toLong() + val operatorUid = event.operatorUid + val wholeBan = event.target?.target?.targetUid == null + val targetUid = event.target?.target?.targetUid ?: "" + val rawDuration = event.target?.target?.rawDuration?.toInt() ?: 0 + val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong() + val duration = if (wholeBan) -1 else rawDuration + val target = if (wholeBan) 0 else ContactHelper.getUinByUidAsync(targetUid).toLong() + + if (wholeBan) { + LogCenter.log("群全员禁言($groupCode): $operator -> ${if (rawDuration != 0) "开启" else "关闭"}") + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupWholeBan(msgTime, groupCode, operator, rawDuration != 0) + ) { + LogCenter.log("群禁言推送失败!", Level.WARN) + } + } else { + LogCenter.log("群禁言($groupCode): $operator -> $target, 时长 = ${duration}s") + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupBan(msgTime, operator, operatorUid, target, targetUid, groupCode, duration) + ) { + LogCenter.log("群禁言推送失败!", Level.WARN) + } + } + } + + private suspend fun onGroupRecall(time: Long, body: MsgBody) { + val event = runCatching { + body.msgContent!!.decodeProtobuf() + }.getOrElse { + val readPacket = ByteReadPacket(body.msgContent!!) + readPacket.discardExact(4) + readPacket.discardExact(1) + readPacket.readBytes(readPacket.readShort().toInt()).also { + readPacket.release() + }.decodeProtobuf() + } + val groupCode = event.groupCode.toLong() + val detail = event.recallDetails!! + val operatorUid = detail.operatorUid + val targetUid = detail.msgInfo!!.senderUid + val msgSeq = detail.msgInfo!!.msgSeq.toLong() + val tipText = detail.wording?.wording ?: "" + + val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupCode.toString()) + val sourceRecord = withTimeoutOrNull(3000) { + suspendCancellableCoroutine { + QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records -> + it.resume(records) + } + } + }?.firstOrNull() + + if (sourceRecord == null) { + LogCenter.log("无法获取源消息记录,无法推送撤回消息!", Level.WARN) + return + } + + val msgId = sourceRecord.msgId + + val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong() + val target = ContactHelper.getUinByUidAsync(targetUid).toLong() + LogCenter.log("群消息撤回($groupCode): $operator -> $target, seq = $msgSeq, id = $msgId, tip = $tipText") + + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupMsgRecall(time, operator, operatorUid, target, targetUid, groupCode, msgId, tipText) + ) { + LogCenter.log("群消息撤回推送失败!", Level.WARN) + } + } + + private suspend fun onGroupApply(time: Long, contentHead: ContentHead, body: MsgBody) { + when (contentHead.msgType) { + 84 -> { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.groupCode + val applierUid = event.applierUid + val reason = event.applyMsg ?: "" + var applier = ContactHelper.getUinByUidAsync(applierUid).toLong() + if (applier == QQInterfaces.app.longAccountUin) { + return + } + val msgSeq = contentHead.msgSeq + val flag = try { + var reqs = requestGroupSystemMsgNew(10, 1) + val riskReqs = requestGroupSystemMsgNew(5, 2) + reqs = reqs + riskReqs + val req = reqs.firstOrNull { + it.msg_time.get() == time && it.msg?.group_code?.get() == groupCode + } + val seq = req?.msg_seq?.get() ?: time + if (applier == 0L) { + applier = req?.req_uin?.get() ?: 0L + } + "$seq;$groupCode;$applier" + } catch (err: Throwable) { + "$time;$groupCode;$applier" + } + LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq") + if (!GlobalEventTransmitter.RequestTransmitter + .transGroupApply(time, applier, applierUid, reason, groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) + ) { + LogCenter.log("入群申请推送失败!", Level.WARN) + } + } + + 528 -> { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.applyInfo?.groupCode ?: return + val applierUid = event.applyInfo?.applierUid ?: return + var applier = ContactHelper.getUinByUidAsync(applierUid).toLong() + if (applier == QQInterfaces.app.longAccountUin) { + return + } + if ((event.applyInfo?.type ?: return) < 3) { + // todo + return + } + val flag = try { + var reqs = requestGroupSystemMsgNew(10, 1) + val riskReqs = requestGroupSystemMsgNew(5, 2) + reqs = reqs + riskReqs + val req = reqs.firstOrNull() { + it.msg_time.get() == time + } + val seq = req?.msg_seq?.get() ?: time + if (applier == 0L) { + applier = req?.req_uin?.get() ?: 0L + } + "$seq;$groupCode;$applier" + } catch (err: Throwable) { + "$time;$groupCode;$applier" + } + LogCenter.log("邀请入群申请($groupCode): $applier") + if (!GlobalEventTransmitter.RequestTransmitter + .transGroupApply(time, applier, applierUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) + ) { + LogCenter.log("邀请入群申请推送失败!", Level.WARN) + } + } + } + } + + private suspend fun onInviteGroup(time: Long, msgHead: ResponseHead, body: MsgBody) { + val event = body.msgContent!!.decodeProtobuf() + val groupCode = event.groupCode + val invitorUid = event.inviterUid + val invitor = ContactHelper.getUinByUidAsync(invitorUid).toLong() + val uin = msgHead.receiver + LogCenter.log("邀请入群: $groupCode, 邀请者: \"$invitor\"") + val flag = try { + var reqs = requestGroupSystemMsgNew(10, 1) + val riskReqs = requestGroupSystemMsgNew(10, 2) + reqs = reqs + riskReqs + val req = reqs.firstOrNull { + it.msg_time.get() == time + } + val seq = req?.msg_seq?.get() ?: time + "$seq;$groupCode;$uin" + } catch (err: Throwable) { + "$time;$groupCode;$uin" + } + if (!GlobalEventTransmitter.RequestTransmitter + .transGroupApply(time, invitor, invitorUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_INVITE) + ) { + LogCenter.log("邀请入群推送失败!", Level.WARN) + } + } + + +} \ No newline at end of file diff --git a/xposed/src/main/java/qq/service/kernel/SimpleKernelMsgListener.kt b/xposed/src/main/java/qq/service/kernel/SimpleKernelMsgListener.kt index 47c3c567..d82a2924 100644 --- a/xposed/src/main/java/qq/service/kernel/SimpleKernelMsgListener.kt +++ b/xposed/src/main/java/qq/service/kernel/SimpleKernelMsgListener.kt @@ -195,7 +195,7 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener { } - override fun onMsgRecall(i2: Int, str: String?, j2: Long) { + override fun onMsgRecall(chatType: Int, tips: String?, msgId: Long) { } diff --git a/xposed/src/main/java/qq/service/msg/MessageHelper.kt b/xposed/src/main/java/qq/service/msg/MessageHelper.kt index 8249ac98..c66bb73e 100644 --- a/xposed/src/main/java/qq/service/msg/MessageHelper.kt +++ b/xposed/src/main/java/qq/service/msg/MessageHelper.kt @@ -3,6 +3,7 @@ package qq.service.msg import com.tencent.qqnt.kernel.api.IKernelService import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull @@ -32,12 +33,28 @@ internal object MessageHelper: QQInterfaces() { return Result.success(info) } + suspend fun generateContact(record: MsgRecord): Contact { + val peerId = when (record.chatType) { + MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> record.senderUid + MsgConstant.KCHATTYPEGUILD -> record.channelId + else -> record.peerUin.toString() + } + return Contact(record.chatType, peerId, if (record.chatType == MsgConstant.KCHATTYPEGUILD) { + record.guildId + } else if(record.chatType == MsgConstant.KCHATTYPETEMPC2CFROMGROUP) { + val tempInfo = getTempChatInfo(record.chatType, peerId).getOrThrow() + tempInfo.groupCode + } else { + null + }) + } + suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact { val peerId = when (chatType) { MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { - ContactHelper.getUidByUinAsync(id.toLong()) + if (id.startsWith("u_")) id + else ContactHelper.getUidByUinAsync(id.toLong()) } - else -> id } return if (chatType == MsgConstant.KCHATTYPEGUILD) { diff --git a/xposed/src/main/java/qq/service/msg/MsgConvertor.kt b/xposed/src/main/java/qq/service/msg/MsgConvertor.kt index 28016f9c..9d38347b 100644 --- a/xposed/src/main/java/qq/service/msg/MsgConvertor.kt +++ b/xposed/src/main/java/qq/service/msg/MsgConvertor.kt @@ -1,8 +1,10 @@ package qq.service.msg +import com.tencent.mobileqq.qroute.QRoute 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.msg.api.IMsgService import io.kritor.event.AtElement import io.kritor.event.Element import io.kritor.event.ElementKt @@ -21,10 +23,13 @@ import io.kritor.event.imageElement import io.kritor.event.jsonElement import io.kritor.event.locationElement import io.kritor.event.pokeElement +import io.kritor.event.replyElement import io.kritor.event.rpsElement import io.kritor.event.textElement import io.kritor.event.videoElement import io.kritor.event.voiceElement +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter @@ -40,6 +45,7 @@ import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER import qq.service.bdh.RichProtoSvc import qq.service.contact.ContactHelper +import kotlin.coroutines.resume typealias NtMessages = ArrayList typealias Convertor = suspend (MsgRecord, MsgElement) -> Result @@ -308,8 +314,22 @@ private object MsgConvertor { suspend fun convertReply(record: MsgRecord, element: MsgElement): Result { val reply = element.replyElement val elem = Element.newBuilder() - elem.setReply(io.kritor.event.replyElement { - this.messageId = reply.replayMsgId + elem.setReply(replyElement { + val msgSeq = reply.replayMsgSeq + val contact = MessageHelper.generateContact(record) + val sourceRecords = withTimeoutOrNull(3000) { + suspendCancellableCoroutine { + QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records -> + it.resume(records) + } + } + } + if (sourceRecords.isNullOrEmpty()) { + LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN) + this.messageId = reply.replayMsgId + } else { + this.messageId = sourceRecords.first().msgId + } }) return Result.success(elem.build()) }