Shamrock: イベントプッシュの実装

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-03-12 21:02:36 +08:00
parent ca47f9dbdf
commit 7bacea3288
11 changed files with 611 additions and 8 deletions

2
kritor

@ -1 +1 @@
Subproject commit f007631c7e17fe6220055a404fdaaa6e7a24a7ef
Subproject commit e4aac653e14249cbb6f27567ffd463165f6deebe

View File

@ -24,6 +24,8 @@ class KritorServer(
.addService(FriendService)
.addService(GroupService)
.addService(GroupFileService)
.addService(MessageService)
.addService(EventService)
.build()!!
fun start(block: Boolean = false) {

View 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()
}
}
}
}

View File

@ -0,0 +1,7 @@
package kritor.service
import io.kritor.message.MessageServiceGrpcKt
internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImplBase() {
}

View File

@ -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)
}
}
}*/
}

View File

@ -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

View File

@ -10,7 +10,7 @@ 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

View File

@ -19,7 +19,7 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
import qq.service.internals.NTServiceFetcher
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.putBuf32Long
import moe.fuqiuluo.shamrock.tools.slice

View File

@ -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) {

View File

@ -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

View 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)
}
}