17 Commits

Author SHA1 Message Date
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
17 changed files with 226 additions and 69 deletions

View File

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

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.GroupAnnouncement
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessageImage 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.EmptyJsonArray
import moe.fuqiuluo.shamrock.tools.GlobalClient import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt import moe.fuqiuluo.shamrock.tools.asInt
@ -394,6 +395,20 @@ internal object GroupSvc: BaseSvc() {
.filter { it != 0L } .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 { fun getOwner(groupId: String): Long {
val groupInfo = getGroupInfo(groupId) val groupInfo = getGroupInfo(groupId)
return groupInfo.troopowneruin?.toLong() ?: 0 return groupInfo.troopowneruin?.toLong() ?: 0
@ -566,23 +581,84 @@ 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 kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession val sessionService = kernelService.wrapperSession
val groupService = sessionService.groupService val groupService = sessionService.groupService
val info = suspendCancellableCoroutine { val info = withTimeoutOrNull(timeout) {
groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data -> suspendCancellableCoroutine {
if (code != 0) { groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data ->
it.resume(null) if (code != 0) {
return@getTransferableMemberInfo it.resume(null)
} return@getTransferableMemberInfo
data.forEach { (_, info) ->
if (info.uin == qq) {
it.resume(info)
return@forEach
} }
data.forEach { (_, info) ->
if (info.uin == qq) {
it.resume(info)
return@forEach
}
}
it.resume(null)
} }
it.resume(null)
} }
} }
return if (info != null) { return if (info != null) {
@ -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 info = RefreshTroopMemberInfoLock.withLock {
val groupIdStr = groupId.toString() val groupIdStr = groupId.toString()
val memberUinStr = memberUin.toString() val memberUinStr = memberUin.toString()
@ -758,7 +834,7 @@ internal object GroupSvc: BaseSvc() {
requestMemberInfoV2(groupId, memberUin) requestMemberInfoV2(groupId, memberUin)
requestMemberInfo(groupId, memberUin) requestMemberInfo(groupId, memberUin)
withTimeoutOrNull(10000) { withTimeoutOrNull(timeout) {
while (!service.isMemberInCache(groupIdStr, memberUinStr)) { while (!service.isMemberInCache(groupIdStr, memberUinStr)) {
delay(200) delay(200)
} }

View File

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

View File

@ -353,6 +353,21 @@ internal object MessageHelper {
database.messageMappingDao().insert(mapping) 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 external fun createMessageUniseq(chatType: Int, time: Long): Long
fun decodeCQCode(code: String): JsonArray { fun decodeCQCode(code: String): JsonArray {

View File

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

View File

@ -1,5 +1,6 @@
package moe.fuqiuluo.shamrock.remote.action.handlers package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
@ -18,8 +19,10 @@ internal object GetGProChannelList: IActionHandler() {
return invoke(guildId.toULong(), refresh, echo = session.echo) return invoke(guildId.toULong(), refresh, echo = session.echo)
} }
operator fun invoke(guildId: ULong, refresh: Boolean, echo: JsonElement = EmptyJsonString): String { suspend operator fun invoke(guildId: ULong, refresh: Boolean, echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.getChannelList(guildId, refresh) val result = withTimeoutOrNull(5000) {
GProSvc.getChannelList(guildId, refresh)
} ?: return error("timeout", echo)
result.onFailure { result.onFailure {
return error(it.message ?: "unable to fetch channel list", echo) 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 { val msgList = ArrayList<MessageDetail>().apply {
addAll(result.data!!.map { msg -> addAll(result.data!!.map { msg ->
val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId) 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( MessageDetail(
time = msg.msgTime.toInt(), time = msg.msgTime.toInt(),
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType), msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GroupSvc 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.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString 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 { 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 logic("you are not admin", echo)
} }
return if(GroupSvc.modifyGroupMemberCard(groupId.toLong(), userId.toLong(), card)) 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 com.tencent.mobileqq.transfile.api.ITransFileController
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
import io.ktor.server.routing.post 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.remote.structures.Status
import moe.fuqiuluo.shamrock.tools.fetchPost import moe.fuqiuluo.shamrock.tools.fetchPost
import moe.fuqiuluo.shamrock.tools.respond import moe.fuqiuluo.shamrock.tools.respond
@ -15,7 +16,7 @@ import kotlin.random.Random
import kotlin.random.nextLong import kotlin.random.nextLong
fun Routing.registerBDH() { fun Routing.registerBDH() {
post("/upload_group_image") { if(ShamrockConfig.isDev()) post("/upload_group_image") {
val troop = fetchPost("troop") val troop = fetchPost("troop")
val picBytes = Base64.decode(fetchPost("pic"), Base64.DEFAULT) val picBytes = Base64.decode(fetchPost("pic"), Base64.DEFAULT)
val md5Str = MD5.getMd5Hex(picBytes) val md5Str = MD5.getMd5Hex(picBytes)
@ -46,5 +47,4 @@ fun Routing.registerBDH() {
.transferAsync(transferRequest) .transferAsync(transferRequest)
respond(isOk = true, Status.Ok, "$md5Str.jpg") respond(isOk = true, Status.Ok, "$md5Str.jpg")
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -44,11 +44,11 @@ internal object AioListener : IKernelMsgListener {
it.value(record) it.value(record)
messageLessListenerMap.remove(it.key) messageLessListenerMap.remove(it.key)
} }
if (record.msgSeq < 0) return if (record.msgSeq < 0) return
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when(record.chatType) { val peerId = when (record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString() else -> record.peerUin.toString()
} }
@ -87,12 +87,14 @@ internal object AioListener : IKernelMsgListener {
if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return
} }
if(!GlobalEventTransmitter.MessageTransmitter.transGroupMessage( if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(
record, record.elements, rawMsg, msgHash, postType record, record.elements, rawMsg, msgHash, postType
)) { )
) {
LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN) LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN)
} }
} }
MsgConstant.KCHATTYPEC2C -> { MsgConstant.KCHATTYPEC2C -> {
LogCenter.log("私聊消息(private = ${record.senderUin}, id = [$msgHash | ${record.msgId} | ${record.msgSeq}], msg = $rawMsg)") LogCenter.log("私聊消息(private = ${record.senderUin}, id = [$msgHash | ${record.msgId} | ${record.msgSeq}], msg = $rawMsg)")
ShamrockConfig.getPrivateRule()?.let { rule -> ShamrockConfig.getPrivateRule()?.let { rule ->
@ -100,9 +102,10 @@ internal object AioListener : IKernelMsgListener {
if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return
} }
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage( if (!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
record, record.elements, rawMsg, msgHash, postType record, record.elements, rawMsg, msgHash, postType
)) { )
) {
LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN) LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN)
} }
} }
@ -116,18 +119,24 @@ internal object AioListener : IKernelMsgListener {
if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return
} }
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage( 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) LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
} }
} }
MsgConstant.KCHATTYPEGUILD -> { MsgConstant.KCHATTYPEGUILD -> {
LogCenter.log("频道消息(guildId = ${record.guildId}, sender=${record.senderUid}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)") LogCenter.log("频道消息(guildId = ${record.guildId}, sender=${record.senderUid}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)")
if(!GlobalEventTransmitter.MessageTransmitter if (!GlobalEventTransmitter.MessageTransmitter
.transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType) .transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType)
) { ) {
LogCenter.log("频道消息推送失败 -> MessageTransmitter", Level.WARN) LogCenter.log("频道消息推送失败 -> MessageTransmitter", Level.WARN)
} }
} }
@ -150,7 +159,7 @@ internal object AioListener : IKernelMsgListener {
try { try {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when(record.chatType) { val peerId = when (record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString() else -> record.peerUin.toString()
} }
@ -174,8 +183,6 @@ internal object AioListener : IKernelMsgListener {
override fun onMsgInfoListUpdate(msgList: ArrayList<MsgRecord>?) { override fun onMsgInfoListUpdate(msgList: ArrayList<MsgRecord>?) {
msgList?.forEach { record -> msgList?.forEach { record ->
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return@forEach// TODO: 频道消息暂不处理
if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED
|| record.sendStatus == MsgConstant.KSENDSTATUSSENDING || record.sendStatus == MsgConstant.KSENDSTATUSSENDING
) { ) {
@ -184,7 +191,7 @@ internal object AioListener : IKernelMsgListener {
GlobalScope.launch { GlobalScope.launch {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when(record.chatType) { val peerId = when (record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString() else -> record.peerUin.toString()
} }
@ -449,6 +456,7 @@ internal object AioListener : IKernelMsgListener {
override fun onGuildMsgAbFlagChanged(guildMsgAbFlag: GuildMsgAbFlag?) { override fun onGuildMsgAbFlagChanged(guildMsgAbFlag: GuildMsgAbFlag?) {
} }
override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) { override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) {
} }

View File

@ -534,12 +534,11 @@ internal object PrimitiveListener {
val groupCode = event.groupCode val groupCode = event.groupCode
val applierUid = event.applierUid val applierUid = event.applierUid
val reason = event.applyMsg ?: "" val reason = event.applyMsg ?: ""
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong() var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
if (applier == getLongUin()) { if (applier == getLongUin()) {
return return
} }
val msgSeq = contentHead.msgSeq val msgSeq = contentHead.msgSeq
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
val flag = try { val flag = try {
var reqs = requestGroupSystemMsgNew(10, 1) var reqs = requestGroupSystemMsgNew(10, 1)
val riskReqs = requestGroupSystemMsgNew(5, 2) val riskReqs = requestGroupSystemMsgNew(5, 2)
@ -548,10 +547,14 @@ internal object PrimitiveListener {
it.msg_time.get() == time && it.msg?.group_code?.get() == groupCode it.msg_time.get() == time && it.msg?.group_code?.get() == groupCode
} }
val seq = req?.msg_seq?.get() ?: time val seq = req?.msg_seq?.get() ?: time
if (applier == 0L) {
applier = req?.req_uin?.get() ?: 0L
}
"$seq;$groupCode;$applier" "$seq;$groupCode;$applier"
} catch (err: Throwable) { } catch (err: Throwable) {
"$time;$groupCode;$applier" "$time;$groupCode;$applier"
} }
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, reason, groupCode, flag, RequestSubType.Add) .transGroupApply(time, applier, applierUid, reason, groupCode, flag, RequestSubType.Add)
) { ) {
@ -562,7 +565,7 @@ internal object PrimitiveListener {
val event = ProtoBuf.decodeFromByteArray<GroupInvitedApplyEvent>(richMsg.rawBuffer!!) val event = ProtoBuf.decodeFromByteArray<GroupInvitedApplyEvent>(richMsg.rawBuffer!!)
val groupCode = event.applyInfo?.groupCode ?: return val groupCode = event.applyInfo?.groupCode ?: return
val applierUid = event.applyInfo?.applierUid ?: return val applierUid = event.applyInfo?.applierUid ?: return
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong() var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
if (applier == getLongUin()) { if (applier == getLongUin()) {
return return
} }
@ -570,7 +573,6 @@ internal object PrimitiveListener {
// todo // todo
return return
} }
LogCenter.log("邀请入群申请($groupCode): $applier")
val flag = try { val flag = try {
var reqs = requestGroupSystemMsgNew(10, 1) var reqs = requestGroupSystemMsgNew(10, 1)
val riskReqs = requestGroupSystemMsgNew(5, 2) val riskReqs = requestGroupSystemMsgNew(5, 2)
@ -579,10 +581,14 @@ internal object PrimitiveListener {
it.msg_time.get() == time it.msg_time.get() == time
} }
val seq = req?.msg_seq?.get() ?: time val seq = req?.msg_seq?.get() ?: time
if (applier == 0L) {
applier = req?.req_uin?.get() ?: 0L
}
"$seq;$groupCode;$applier" "$seq;$groupCode;$applier"
} catch (err: Throwable) { } catch (err: Throwable) {
"$time;$groupCode;$applier" "$time;$groupCode;$applier"
} }
LogCenter.log("邀请入群申请($groupCode): $applier")
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, "", groupCode, flag, RequestSubType.Add) .transGroupApply(time, applier, applierUid, "", groupCode, flag, RequestSubType.Add)
) { ) {