18 Commits

Author SHA1 Message Date
494b1f1fd0 Shamrock: 允许禁止QQ启动无关紧要的进程服务 2024-02-16 00:09:35 +08:00
cf943fd13a Shamrock: atメッセージ優先nameパラメータ 2024-02-15 13:18:43 +08:00
9608b46799 Shamrock: 是正メッセージプッシュアイデンティティの取得が遅い 2024-02-15 13:14:37 +08:00
502956e3ec Shamrock: エイト・メッセージにニックネームの迅速なクエリを許可する 2024-02-15 13:01:34 +08:00
27b4c26da7 Shamrock: fix GlobalEventTransmitter x2 2024-02-11 14:28:07 +08:00
65f54360f8 Shamrock: not fix GlobalEventTransmitter x2 2024-02-11 14:06:10 +08:00
9a9fad975f Shamrock: not fix GlobalEventTransmitter 2024-02-11 13:57:58 +08:00
7153b21cd4 Shamrock: fix GlobalEventTransmitter 2024-02-11 13:42:28 +08:00
fdb2486090 Shamrock: Disable lost connection detection 2024-02-10 00:41:38 +08:00
d60b2a25d1 Update SECURITY.md 2024-02-09 08:04:40 +08:00
2d8dde6951 add history msg to database 2024-02-08 23:52:21 +08:00
78fd60dade Merge pull request #228 from Mythologyli/master
feat: get group applier uin from request msg
2024-02-08 22:34:40 +08:00
80dbf6af28 feat: get group applier uin from request msg 2024-02-08 22:27:55 +08:00
1e53753b5a Shamrock: fix #227 2024-02-08 20:17:51 +08:00
e727877268 Merge pull request #225 from MrXiaoM/fix-guild-message
修复 频道消息事件不符合 go-cqhttp 规范
2024-02-08 20:16:01 +08:00
63b69df3ea fix missing guild_id and channel_id 2024-02-08 14:51:14 +08:00
b03e02675b Shamrock: add timeout #223 2024-02-05 22:16:12 +08:00
e68a1ffd37 Shamrock: fix guild sync 2024-02-05 22:12:20 +08:00
21 changed files with 273 additions and 83 deletions

View File

@ -1,11 +1,19 @@
# Security Policy
## Support Version
## 支持的版本
| Version | Supported |
| 版本 | 支持状态 |
| ------- | ------------------ |
| 9.0.15 | :white_check_mark: |
| 8.9.75 | :white_check_mark: |
| 8.9.73 | :white_check_mark: |
| 8.9.98 | :white_check_mark: |
| < 8.9.68| :x: |
## 频道支持性说明
如果需要使用`频道`相关功能请升级QQ到9.0.8版本
## Riru检测问题
QQ自`9.0.8`开始将会检测riru可能作为封号因素

View File

@ -229,6 +229,16 @@ object ShamrockConfig {
return preferences.getBoolean("anti_qq_trace", true)
}
fun isForbidUselessProcess(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("forbid_useless_process", false)
}
fun setForbidUselessProcess(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("forbid_useless_process", v).apply()
}
fun setAntiTrace(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("anti_qq_trace", v).apply()
@ -333,6 +343,7 @@ object ShamrockConfig {
"alive_reply" to preferences.getBoolean("alive_reply", false),
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
"disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false),
"forbid_useless_process" to preferences.getBoolean("forbid_useless_process", false)
)
}

View File

@ -100,17 +100,16 @@ fun LabFragment() {
thickness = 0.2.dp
)
/*
Function(
title = "自动清理QQ垃圾",
desc = "也许会导致奇怪的问题(无效)。",
title = "禁止无用进程",
desc = "禁止QQ生成无用进程浪费内存",
descColor = color,
isSwitch = ShamrockConfig.isAutoClean(ctx)
isSwitch = ShamrockConfig.isForbidUselessProcess(ctx)
) {
ShamrockConfig.setAutoClean(ctx, it)
ShamrockConfig.setForbidUselessProcess(ctx, it)
ShamrockConfig.pushUpdate(ctx)
return@Function false
}*/
return@Function true
}
Function(
title = "自回复测试",

View File

@ -59,6 +59,7 @@ import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncement
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessageImage
import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole
import moe.fuqiuluo.shamrock.tools.EmptyJsonArray
import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt
@ -394,6 +395,20 @@ internal object GroupSvc: BaseSvc() {
.filter { it != 0L }
}
suspend fun getMemberRole(groupId: Long, memberUin: Long): MemberRole {
return when(getTroopMemberInfoByUinViaNt(groupId.toString(), memberUin, 3000).getOrNull()?.role) {
com.tencent.qqnt.kernel.nativeinterface.MemberRole.STRANGER -> MemberRole.Stranger
com.tencent.qqnt.kernel.nativeinterface.MemberRole.MEMBER -> MemberRole.Member
com.tencent.qqnt.kernel.nativeinterface.MemberRole.ADMIN -> MemberRole.Admin
com.tencent.qqnt.kernel.nativeinterface.MemberRole.OWNER -> MemberRole.Owner
com.tencent.qqnt.kernel.nativeinterface.MemberRole.UNSPECIFIED, null -> when (memberUin) {
getOwner(groupId.toString()) -> MemberRole.Owner
in getAdminList(groupId.toString()) -> MemberRole.Admin
else -> MemberRole.Member
}
}
}
fun getOwner(groupId: String): Long {
val groupInfo = getGroupInfo(groupId)
return groupInfo.troopowneruin?.toLong() ?: 0
@ -566,11 +581,71 @@ internal object GroupSvc: BaseSvc() {
}
}
private suspend fun getTroopMemberInfoByUinViaNt(groupId: String, qq: Long): Result<MemberInfo> {
suspend fun getTroopMemberInfoByUinV2(
groupId: String,
uin: String,
refresh: Boolean = false
): Result<TroopMemberInfo> {
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
var info = service.getTroopMember(groupId, uin)
if (refresh || !service.isMemberInCache(groupId, uin) || info == null || info.troopnick == null) {
info = requestTroopMemberInfo(service, groupId.toLong(), uin.toLong(), timeout = 2000).getOrNull()
}
if (info == null) {
info = getTroopMemberInfoByUinViaNt(groupId, uin.toLong(), timeout = 2000L).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.toLong())
req.uint64_uin.set(uin.toLong())
req.bool_new_client.set(true)
req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1)
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000)
if (respBuffer != null) {
val rsp = group_member_info.RspBody()
rsp.mergeFrom(respBuffer.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(err.stackTraceToString(), Level.WARN)
}
return if (info != null) {
Result.success(info)
} else {
Result.failure(Exception("获取群成员信息失败"))
}
}
suspend fun getTroopMemberInfoByUinViaNt(
groupId: String,
qq: Long,
timeout: Long = 5000L
): Result<MemberInfo> {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val groupService = sessionService.groupService
val info = suspendCancellableCoroutine {
val info = withTimeoutOrNull(timeout) {
suspendCancellableCoroutine {
groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data ->
if (code != 0) {
it.resume(null)
@ -585,6 +660,7 @@ internal object GroupSvc: BaseSvc() {
it.resume(null)
}
}
}
return if (info != null) {
Result.success(info)
} else {
@ -748,7 +824,7 @@ internal object GroupSvc: BaseSvc() {
}
}
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long): Result<TroopMemberInfo> {
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()
@ -758,7 +834,7 @@ internal object GroupSvc: BaseSvc() {
requestMemberInfoV2(groupId, memberUin)
requestMemberInfo(groupId, memberUin)
withTimeoutOrNull(10000) {
withTimeoutOrNull(timeout) {
while (!service.isMemberInCache(groupIdStr, memberUinStr)) {
delay(200)
}

View File

@ -670,7 +670,9 @@ internal object MessageMaker {
at.atNtUid = "0"
}
else -> {
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure {
val name = data["name"].asStringOrNull
if (name == null) {
val info = GroupSvc.getTroopMemberInfoByUinV2(peerId, qq, true).onFailure {
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
}.getOrNull()
if (info != null) {
@ -680,7 +682,10 @@ internal object MessageMaker {
.ifNullOrEmpty(qq)
}"
} else {
at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}"
at.content = "@$qq"
}
} else {
at.content = "@$name"
}
at.atType = MsgConstant.ATTYPEONE
at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong())

View File

@ -353,6 +353,21 @@ internal object MessageHelper {
database.messageMappingDao().insert(mapping)
}
fun saveMsgMappingNotExist(
hash: Int,
qqMsgId: Long,
time: Long,
chatType: Int,
peerId: String,
subPeerId: String,
msgSeq: Int,
subChatType: Int = chatType
) {
val database = MessageDB.getInstance()
val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq, subPeerId)
database.messageMappingDao().insertNotExist(mapping)
}
external fun createMessageUniseq(chatType: Int, time: Long): Long
fun decodeCQCode(code: String): JsonArray {

View File

@ -30,6 +30,9 @@ interface MessageMappingDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(mapping: MessageMapping)
@Insert(onConflict = OnConflictStrategy.ABORT)
fun insertNotExist(mapping: MessageMapping)
@Query("UPDATE message_mapping_v2 SET msgSeq = :msgSeq WHERE msgHashId = :hash")
fun updateMsgSeqByMsgHash(hash: Int, msgSeq: Int)

View File

@ -1,5 +1,6 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@ -18,8 +19,10 @@ internal object GetGProChannelList: IActionHandler() {
return invoke(guildId.toULong(), refresh, echo = session.echo)
}
operator fun invoke(guildId: ULong, refresh: Boolean, echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.getChannelList(guildId, refresh)
suspend operator fun invoke(guildId: ULong, refresh: Boolean, echo: JsonElement = EmptyJsonString): String {
val result = withTimeoutOrNull(5000) {
GProSvc.getChannelList(guildId, refresh)
} ?: return error("timeout", echo)
result.onFailure {
return error(it.message ?: "unable to fetch channel list", echo)
}

View File

@ -59,6 +59,16 @@ internal object GetHistoryMsg: IActionHandler() {
val msgList = ArrayList<MessageDetail>().apply {
addAll(result.data!!.map { msg ->
val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId)
MessageHelper.saveMsgMappingNotExist(
hash = msgHash,
qqMsgId = msg.msgId,
chatType = msg.chatType,
subChatType = msg.chatType,
peerId = peerId,
msgSeq = msg.msgSeq.toInt(),
time = msg.msgTime,
subPeerId = msg.channelId ?: peerId
)
MessageDetail(
time = msg.msgTime.toInt(),
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),

View File

@ -52,11 +52,7 @@ internal object GetTroopMemberInfo : IActionHandler() {
area = info.alias ?: "",
lastSentTime = info.last_active_time,
level = info.level,
role = when {
GroupSvc.getOwner(groupId).toString() == uin -> MemberRole.Owner
uin.toLong() in GroupSvc.getAdminList(groupId) -> MemberRole.Admin
else -> MemberRole.Member
},
role = GroupSvc.getMemberRole(groupId.toLong(), uin.toLong()),
unfriendly = false,
title = info.mUniqueTitle ?: "",
titleExpireTime = info.mUniqueTitleExpire,

View File

@ -54,12 +54,13 @@ internal object GetTroopMemberList : IActionHandler() {
area = info.alias ?: "",
lastSentTime = info.last_active_time,
level = info.level,
role = when {
role = GroupSvc.getMemberRole(groupId.toLong(), info.memberuin.toLong())
/*when {
GroupSvc.getOwner(groupId)
.toString() == info.memberuin -> MemberRole.Owner
info.memberuin.toLong() in GroupSvc.getAdminList(groupId) -> MemberRole.Admin
else -> MemberRole.Member
},
}*/,
unfriendly = false,
title = info.mUniqueTitle ?: "",
titleExpireTime = info.mUniqueTitleExpire,

View File

@ -2,6 +2,7 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
@ -17,7 +18,7 @@ internal object ModifyTroopMemberName: IActionHandler() {
}
operator fun invoke(groupId: String, userId: String, card: String, echo: JsonElement = EmptyJsonString): String {
if (!GroupSvc.isAdmin(groupId)) {
if (!GroupSvc.isAdmin(groupId) && userId != TicketSvc.getUin()) {
return logic("you are not admin", echo)
}
return if(GroupSvc.modifyGroupMemberCard(groupId.toLong(), userId.toLong(), card))

View File

@ -5,6 +5,7 @@ import com.tencent.mobileqq.transfile.TransferRequest
import com.tencent.mobileqq.transfile.api.ITransFileController
import io.ktor.server.routing.Routing
import io.ktor.server.routing.post
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
import moe.fuqiuluo.shamrock.remote.structures.Status
import moe.fuqiuluo.shamrock.tools.fetchPost
import moe.fuqiuluo.shamrock.tools.respond
@ -15,7 +16,7 @@ import kotlin.random.Random
import kotlin.random.nextLong
fun Routing.registerBDH() {
post("/upload_group_image") {
if(ShamrockConfig.isDev()) post("/upload_group_image") {
val troop = fetchPost("troop")
val picBytes = Base64.decode(fetchPost("pic"), Base64.DEFAULT)
val md5Str = MD5.getMd5Hex(picBytes)
@ -46,5 +47,4 @@ fun Routing.registerBDH() {
.transferAsync(transferRequest)
respond(isOk = true, Status.Ok, "$md5Str.jpg")
}
}

View File

@ -52,7 +52,6 @@ internal object HttpService: HttpTransmitServlet() {
GlobalEventTransmitter.onNoticeEvent { event ->
pushTo(event)
}
})
submitFlowJob(GlobalScope.launch {
GlobalEventTransmitter.onRequestEvent {

View File

@ -1,9 +1,15 @@
@file:OptIn(DelicateCoroutinesApi::class)
package moe.fuqiuluo.shamrock.remote.service.api
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.qqinterface.servlet.CardSvc
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
@ -48,7 +54,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message)
/**
* 消息 手淫器
* 消息
*/
object MessageTransmitter {
/**
@ -86,11 +92,11 @@ internal object GlobalEventTransmitter: BaseSvc() {
.ifEmpty { record.sendMemberName }
.ifEmpty { record.peerName },
card = record.sendMemberName,
role = when (record.senderUin) {
role = GroupSvc.getMemberRole(record.peerUin, record.senderUin)/*when (record.senderUin) {
GroupSvc.getOwner(record.peerUin.toString()) -> MemberRole.Owner
in GroupSvc.getAdminList(record.peerUin.toString()) -> MemberRole.Admin
else -> MemberRole.Member
},
}*/,
title = "",
level = "",
)
@ -172,6 +178,8 @@ internal object GlobalEventTransmitter: BaseSvc() {
postType = postType,
messageType = MsgType.Guild,
subType = MsgSubType.Channel,
guildId = record.guildId,
channelId = record.channelId,
messageId = msgHash,
targetId = record.peerUin,
peerId = botUin,
@ -186,7 +194,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
userId = record.senderUid.toLong(),
nickname = nickName,
card = record.sendMemberName,
role = MemberRole.Member,
role = MemberRole.Member, // TODO(GUILD ROLE)
title = record.sendNickName,
level = record.roleId.toString(),
tinyId = record.senderUid
@ -554,19 +562,29 @@ internal object GlobalEventTransmitter: BaseSvc() {
@ShamrockDsl
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) {
messageEventFlow.collect(collector)
messageEventFlow.collect {
GlobalScope.launch {
collector.emit(it)
}
}
}
@ShamrockDsl
suspend inline fun onNoticeEvent(collector: FlowCollector<NoticeEvent>) {
noticeEventFlow
.collect(collector)
noticeEventFlow.collect {
GlobalScope.launch {
collector.emit(it)
}
}
}
@ShamrockDsl
suspend inline fun onRequestEvent(collector: FlowCollector<RequestEvent>) {
requestEventFlow
.collect(collector)
requestEventFlow.collect {
GlobalScope.launch {
collector.emit(it)
}
}
}
}

View File

@ -41,6 +41,10 @@ internal abstract class WebSocketTransmitServlet(
private val sendLock = Mutex()
protected val eventReceivers: MutableList<WebSocket> = Collections.synchronizedList(mutableListOf<WebSocket>())
init {
connectionLostTimeout = 0
}
override val address: String
get() = "-"

View File

@ -76,6 +76,7 @@ internal object ShamrockConfig {
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息
putBoolean("forbid_useless_process", intent.getBooleanExtra("forbid_useless_process", false)) // 禁用QQ生成无用进程
}
Config.defaultToken = intent.getStringExtra("token")
Config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true)
@ -126,6 +127,10 @@ internal object ShamrockConfig {
return mmkv.getBoolean("enable_self_msg", false)
}
fun forbidUselessProcess(): Boolean {
return mmkv.getBoolean("forbid_useless_process", false)
}
fun openWebSocketClient(): Boolean {
return mmkv.getBoolean("ws_client", false)
}

View File

@ -53,6 +53,8 @@ internal data class MessageEvent (
@SerialName("sub_type") val subType: MsgSubType,
@SerialName("message_id") val messageId: Int,
@SerialName("group_id") val groupId: Long = 0,
@SerialName("guild_id") val guildId: String? = null,
@SerialName("channel_id") val channelId: String? = null,
@SerialName("target_id") val targetId: Long = 0,
@SerialName("peer_id") val peerId: Long,
@SerialName("user_id") val userId: Long,
@ -86,7 +88,9 @@ internal data class Anonymous(
internal enum class MemberRole {
@SerialName("owner") Owner,
@SerialName("admin") Admin,
@SerialName("member") Member
@SerialName("member") Member,
@SerialName("stranger") Stranger,
@SerialName("unknown") Unknown
}
@Serializable

View File

@ -89,10 +89,12 @@ internal object AioListener : IKernelMsgListener {
if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(
record, record.elements, rawMsg, msgHash, postType
)) {
)
) {
LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN)
}
}
MsgConstant.KCHATTYPEC2C -> {
LogCenter.log("私聊消息(private = ${record.senderUin}, id = [$msgHash | ${record.msgId} | ${record.msgSeq}], msg = $rawMsg)")
ShamrockConfig.getPrivateRule()?.let { rule ->
@ -102,7 +104,8 @@ internal object AioListener : IKernelMsgListener {
if (!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
record, record.elements, rawMsg, msgHash, postType
)) {
)
) {
LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN)
}
}
@ -117,8 +120,14 @@ internal object AioListener : IKernelMsgListener {
}
if (!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
record, record.elements, rawMsg, msgHash, tempSource = MessageTempSource.Group, postType = postType
)) {
record,
record.elements,
rawMsg,
msgHash,
tempSource = MessageTempSource.Group,
postType = postType
)
) {
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
}
}
@ -174,8 +183,6 @@ internal object AioListener : IKernelMsgListener {
override fun onMsgInfoListUpdate(msgList: ArrayList<MsgRecord>?) {
msgList?.forEach { record ->
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return@forEach// TODO: 频道消息暂不处理
if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED
|| record.sendStatus == MsgConstant.KSENDSTATUSSENDING
) {
@ -449,6 +456,7 @@ internal object AioListener : IKernelMsgListener {
override fun onGuildMsgAbFlagChanged(guildMsgAbFlag: GuildMsgAbFlag?) {
}
override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) {
}

View File

@ -534,12 +534,11 @@ internal object PrimitiveListener {
val groupCode = event.groupCode
val applierUid = event.applierUid
val reason = event.applyMsg ?: ""
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
if (applier == getLongUin()) {
return
}
val msgSeq = contentHead.msgSeq
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
val flag = try {
var reqs = requestGroupSystemMsgNew(10, 1)
val riskReqs = requestGroupSystemMsgNew(5, 2)
@ -548,10 +547,14 @@ internal object PrimitiveListener {
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, RequestSubType.Add)
) {
@ -562,7 +565,7 @@ internal object PrimitiveListener {
val event = ProtoBuf.decodeFromByteArray<GroupInvitedApplyEvent>(richMsg.rawBuffer!!)
val groupCode = event.applyInfo?.groupCode ?: return
val applierUid = event.applyInfo?.applierUid ?: return
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
if (applier == getLongUin()) {
return
}
@ -570,7 +573,6 @@ internal object PrimitiveListener {
// todo
return
}
LogCenter.log("邀请入群申请($groupCode): $applier")
val flag = try {
var reqs = requestGroupSystemMsgNew(10, 1)
val riskReqs = requestGroupSystemMsgNew(5, 2)
@ -579,10 +581,14 @@ internal object PrimitiveListener {
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, RequestSubType.Add)
) {

View File

@ -1,10 +1,14 @@
package moe.fuqiuluo.shamrock.xposed
import android.content.Context
import android.os.Process
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage
import de.robv.android.xposed.XposedBridge.log
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import moe.fuqiuluo.shamrock.xposed.loader.KeepAlive
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
@ -15,12 +19,14 @@ import moe.fuqiuluo.shamrock.xposed.hooks.runFirstActions
import mqq.app.MobileQQ
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import kotlin.system.exitProcess
internal const val PACKAGE_NAME_QQ = "com.tencent.mobileqq"
internal const val PACKAGE_NAME_QQ_INTERNATIONAL = "com.tencent.mobileqqi"
internal const val PACKAGE_NAME_QQ_LITE = "com.tencent.qqlite"
internal const val PACKAGE_NAME_TIM = "com.tencent.tim"
private const val PACKAGE_NAME_QQ = "com.tencent.mobileqq"
private const val PACKAGE_NAME_QQ_INTERNATIONAL = "com.tencent.mobileqqi"
private const val PACKAGE_NAME_QQ_LITE = "com.tencent.qqlite"
private const val PACKAGE_NAME_TIM = "com.tencent.tim"
private val uselessProcess = listOf("peak", "tool", "mini", "qzone")
internal class XposedEntry: IXposedHookLoadPackage {
companion object {
@ -121,9 +127,7 @@ internal class XposedEntry: IXposedHookLoadPackage {
System.setProperty("qxbot_flag", "1")
} else return
log("Process Name = " + MobileQQ.getMobileQQ().qqProcessName)
PlatformUtils.isTim()
val processName = MobileQQ.getMobileQQ().qqProcessName
// MSG LISTENER 进程运行在主进程
// API 也应该开放在主进程
@ -134,6 +138,20 @@ internal class XposedEntry: IXposedHookLoadPackage {
MMKVFetcher.initMMKV(ctx)
}
if (ShamrockConfig.forbidUselessProcess()) {
if(uselessProcess.any {
processName.contains(it, ignoreCase = true)
}) {
log("[Shamrock] Useless process detected: $processName, exit.")
Process.killProcess(Process.myPid())
exitProcess(0)
}
} else {
log("[Shamrock] Useless process detection is disabled.")
}
log("Process Name = $processName")
runFirstActions(ctx)
}
}