Shamrock: support receiving all guild msg

This commit is contained in:
白池 2024-02-02 22:07:56 +08:00
parent 1c7f6bd034
commit 137c354acc
11 changed files with 314 additions and 68 deletions

View File

@ -249,6 +249,11 @@ object ShamrockConfig {
return preferences.getBoolean("enable_auto_start", false)
}
fun disableAutoSyncSetting(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("disable_auto_sync_setting", false)
}
fun enableAliveReply(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("alive_reply", false)
@ -264,6 +269,11 @@ object ShamrockConfig {
preferences.edit().putBoolean("enable_auto_start", v).apply()
}
fun setDisableAutoSyncSetting(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("disable_auto_sync_setting", v).apply()
}
fun setAliveReply(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("alive_reply", v).apply()
@ -322,6 +332,7 @@ object ShamrockConfig {
"shell" to preferences.getBoolean("shell", false),
"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),
)
}

View File

@ -91,7 +91,7 @@ fun LabFragment() {
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.round_logo_dev_24),
title = "实验功能"
title = "基础设置"
) { color ->
Column {
Divider(
@ -142,6 +142,16 @@ fun LabFragment() {
return@Function true
}
Function(
title = "禁止Shamrock同步设置",
desc = "禁止Shamrock同步设置防止恢复手动修改后的配置文件。",
descColor = color,
isSwitch = ShamrockConfig.disableAutoSyncSetting(ctx)
) {
ShamrockConfig.setDisableAutoSyncSetting(ctx, it)
return@Function true
}
kotlin.runCatching {
ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE)
}.onSuccess {

View File

@ -0,0 +1,61 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.oidb.cmd0xfc2
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class Oidb0xfc2ReqBody(
@ProtoNumber(1) var msgCmd: Int? = null,
@ProtoNumber(3) var msgBusType: Int? = null,
@ProtoNumber(4) var msgChannelInfo: Oidb0xfc2ChannelInfo? = null,
@ProtoNumber(5) var msgTerminalType: Int? = null,
//@ProtoNumber(100) var msg_apply_upload_req: Any? = null,
//@ProtoNumber(200) var msg_upload_completed_req: Any? = null,
@ProtoNumber(300) var msgApplyDownloadReq: Oidb0xfc2MsgApplyDownloadReq? = null,
//@ProtoNumber(400) var msg_apply_preview_req: Any? = null,
//@ProtoNumber(500) var msg_apply_security_strike_req: Any? = null,
)
@Serializable
data class Oidb0xfc2RspBody(
@ProtoNumber(1) var msgCmd: Int? = null,
@ProtoNumber(5) var msgBusType: Int? = null,
//@ProtoNumber(110) var msg_apply_upload_rsp: Any? = null,
//@ProtoNumber(210) var msg_upload_completed_rsp: Any? = null,
@ProtoNumber(310) var msgApplyDownloadRsp: Oidb0xfc2MsgApplyDownloadRsp? = null,
//@ProtoNumber(410) var msg_apply_preview_rsp: Any? = null,
//@ProtoNumber(510) var msg_apply_security_strike_rsp: Any? = null,
)
@Serializable
data class Oidb0xfc2MsgApplyDownloadRsp(
@ProtoNumber(1) var msgDownloadInfo: Oidb0xfc2MsgDownloadInfo? = null,
//@ProtoNumber(2) var msgFileInfo: Any? = null,
//@ProtoNumber(3) var msgChacha20Param: Any? = null,
//@ProtoNumber(4) var useEncrypt: UInt = UInt.MIN_VALUE,
)
@Serializable
data class Oidb0xfc2MsgDownloadInfo(
@ProtoNumber(1) var downloadKey: ByteArray? = null,
//@ProtoNumber(2) var msg_out_addr: Any? = null,
//@ProtoNumber(3) var msg_inner_addr: Any? = null,
//@ProtoNumber(4) var msg_out_addr_ipv6: Any? = null,
@ProtoNumber(5) var downloadDomain: String? = null,
@ProtoNumber(6) var downloadUrl: String? = null,
//@ProtoNumber(7) var str_cookie: Any? = null,
)
@Serializable
data class Oidb0xfc2MsgApplyDownloadReq(
@ProtoNumber(1) val fieldId: String,
@ProtoNumber(2) val supportEncrypt: Int,
)
@Serializable
data class Oidb0xfc2ChannelInfo(
@ProtoNumber(3) val guildId: ULong,
@ProtoNumber(4) val channelId: ULong,
)

View File

@ -32,12 +32,12 @@ internal suspend fun MsgRecord.toCQCode(): String {
return MessageConvert.convertMessageRecordToCQCode(this)
}
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String): MessageSegmentList {
return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId)
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String, subPeer: String): MessageSegmentList {
return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId, subPeer)
}
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String): String {
return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId)
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String, subPeer: String): String {
return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId, subPeer)
}
@ -64,14 +64,15 @@ internal object MessageConvert {
suspend fun convertMessageElementsToMsgSegment(
chatType: Int,
elements: List<MsgElement>,
peerId: String
peerId: String,
subPeer: String
): ArrayList<MessageSegment> {
val messageData = arrayListOf<MessageSegment>()
elements.forEach { msg ->
kotlin.runCatching {
val elementId = msg.elementType
val converter = convertMap[elementId]
converter?.convert(chatType, peerId, msg)
converter?.convert(chatType, peerId, subPeer, msg)
?: throw UnsupportedOperationException("不支持的消息element类型$elementId")
}.onSuccess {
messageData.add(it)
@ -87,35 +88,45 @@ internal object MessageConvert {
}
suspend fun convertMessageRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList<MessageSegment> {
return convertMessageElementsToMsgSegment(chatType, record.elements, record.peerUin.toString())
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
return convertMessageElementsToMsgSegment(chatType, record.elements, peerId, record.channelId ?: peerId)
}
suspend fun convertMsgElementsToCQCode(
elements: List<MsgElement>,
chatType: Int,
peerId: String
peerId: String,
subPeer: String
): String {
if(elements.isEmpty()) {
return ""
}
val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId).map {
val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId, subPeer).map {
it.toJson()
}
return MessageHelper.encodeCQCode(msgList)
}
suspend fun convertMessageRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String {
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
return MessageHelper.encodeCQCode(
convertMessageElementsToMsgSegment(
chatType,
record.elements,
record.peerUin.toString()
peerId,
record.channelId ?: peerId
).map { it.toJson() }
)
}
}
internal fun interface IMessageConvert {
suspend fun convert(chatType: Int, peerId: String, element: MsgElement): MessageSegment
suspend fun convert(chatType: Int, peerId: String, subPeer: String, element: MsgElement): MessageSegment
}

View File

@ -19,7 +19,12 @@ internal sealed class MessageElemConverter: IMessageConvert {
* 文本 / 艾特 消息转换消息段
*/
data object TextConverter: MessageElemConverter() {
override suspend fun convert(chatType: Int, peerId: String, element: MsgElement): MessageSegment {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val text = element.textElement
return if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
MessageSegment(
@ -43,7 +48,12 @@ internal sealed class MessageElemConverter: IMessageConvert {
* 小表情 / 戳一戳 消息转换消息段
*/
data object FaceConverter: MessageElemConverter() {
override suspend fun convert(chatType: Int, peerId: String, element: MsgElement): MessageSegment {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.faceElement
if (face.faceType == 5) {
@ -112,6 +122,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val image = element.picElement
@ -131,6 +142,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(md5)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(md5)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(md5)
else -> unknownChatType(chatType)
},
"subType" to image.picSubType,
@ -147,6 +159,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val record = element.pttElement
@ -162,6 +175,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid)
else -> unknownChatType(chatType)
}
).also {
@ -183,6 +197,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val video = element.videoElement
@ -195,6 +210,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl(peerId, md5, video.fileUuid)
else -> unknownChatType(chatType)
}
).also {
@ -212,6 +228,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.marketFaceElement
@ -235,6 +252,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val data = element.arkElement.bytesData.asJsonObject
@ -297,6 +315,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val reply = element.replyElement
@ -329,6 +348,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val tip = element.grayTipElement
@ -364,6 +384,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val fileMsg = element.fileElement
@ -373,8 +394,11 @@ internal sealed class MessageElemConverter: IMessageConvert {
val fileId = fileMsg.fileUuid
val bizId = fileMsg.fileBizId ?: 0
val fileSubId = fileMsg.fileSubId ?: ""
val url = if (chatType == MsgConstant.KCHATTYPEC2C) RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
else RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
val url = when (chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
}
return MessageSegment(
type = "file",
@ -398,6 +422,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val multiMsg = element.multiForwardMsgElement
@ -414,6 +439,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val longMsg = element.structLongMsgElement
@ -430,6 +456,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val markdown = element.markdownElement
@ -446,6 +473,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val bubbleElement = element.faceBubbleElement

View File

@ -1,3 +1,4 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.fuqiuluo.qqinterface.servlet.transfile
import com.tencent.mobileqq.pb.ByteStringMicro
@ -6,6 +7,10 @@ import com.tencent.mobileqq.transfile.api.IProtoReqManager
import com.tencent.mobileqq.transfile.protohandler.RichProto
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
@ -14,6 +19,10 @@ import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.whitechi73.protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo
import moe.whitechi73.protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq
import moe.whitechi73.protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody
import moe.whitechi73.protobuf.oidb.cmd0xfc2.Oidb0xfc2RspBody
import mqq.app.MobileQQ
import tencent.im.cs.cmd0x346.cmd0x346
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
@ -22,6 +31,30 @@ import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume
internal object RichProtoSvc: BaseSvc() {
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, ProtoBuf.encodeToByteArray(Oidb0xfc2ReqBody(
msgCmd = 1200,
msgBusType = 4202,
msgChannelInfo = Oidb0xfc2ChannelInfo(
guildId = peerId.toULong(),
channelId = channelId.toULong()
),
msgTerminalType = 2,
msgApplyDownloadReq = Oidb0xfc2MsgApplyDownloadReq(
fieldId = fileId,
supportEncrypt = 0
)
))) ?: return ""
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
ProtoBuf.decodeFromByteArray<Oidb0xfc2RspBody>(body.bytes_bodybuffer.get().toByteArray()).msgApplyDownloadRsp?.let {
it.msgDownloadInfo?.let {
return "https://${it.downloadDomain}${it.downloadUrl}&fname=$fileId&isthumb=0"
}
}
return ""
}
suspend fun getGroupFileDownUrl(
peerId: Long,
fileId: String,
@ -34,27 +67,23 @@ internal object RichProtoSvc: BaseSvc() {
uint32_bus_id.set(bizId)
str_file_id.set(fileId)
})
}.toByteArray())
if (buffer == null) {
}.toByteArray()) ?: return ""
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
if (body.uint32_result.get() != 0
|| result.download_file_rsp.int32_ret_code.get() != 0) {
return ""
} else {
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
if (body.uint32_result.get() != 0
|| result.download_file_rsp.int32_ret_code.get() != 0) {
return ""
}
val domain = if (!result.download_file_rsp.str_download_dns.has())
("https://" + result.download_file_rsp.str_download_ip.get())
else ("http://" + result.download_file_rsp.str_download_dns.get())
val downloadUrl = result.download_file_rsp.bytes_download_url.get().toByteArray().toHexString()
val appId = MobileQQ.getMobileQQ().appId
val version = PlatformUtils.getQQVersion(MobileQQ.getContext())
return "$domain/ftn_handler/$downloadUrl/?fname=$fileId&client_proto=qq&client_appid=$appId&client_type=android&client_ver=$version&client_down_type=auto&client_aio_type=unk"
}
val domain = if (!result.download_file_rsp.str_download_dns.has())
("https://" + result.download_file_rsp.str_download_ip.get())
else ("http://" + result.download_file_rsp.str_download_dns.get())
val downloadUrl = result.download_file_rsp.bytes_download_url.get().toByteArray().toHexString()
val appId = MobileQQ.getMobileQQ().appId
val version = PlatformUtils.getQQVersion(MobileQQ.getContext())
return "$domain/ftn_handler/$downloadUrl/?fname=$fileId&client_proto=qq&client_appid=$appId&client_type=android&client_ver=$version&client_down_type=auto&client_aio_type=unk"
}
suspend fun getC2CFileDownUrl(
@ -83,7 +112,7 @@ internal object RichProtoSvc: BaseSvc() {
}.toByteArray())
if (buffer == null) {
if (retryCnt < 3) {
if (retryCnt < 5) {
return getC2CFileDownUrl(fileId, subId, retryCnt + 1)
}
return ""
@ -122,6 +151,10 @@ internal object RichProtoSvc: BaseSvc() {
return "https://c2cpicdw.qpic.cn/offpic_new/0/123-0-${md5.uppercase()}/0?term=2"
}
fun getGuildPicDownUrl(md5: String): String {
return "https://gchat.qpic.cn/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2"
}
suspend fun getC2CVideoDownUrl(
peerId: String,
md5Hex: String,
@ -287,4 +320,11 @@ internal object RichProtoSvc: BaseSvc() {
}
}
suspend fun getGuildPttDownUrl(
peerId: String,
md5Hex: String,
fileUUId: String
): String {
return "unsupported"
}
}

View File

@ -308,6 +308,7 @@ internal object MessageHelper {
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"
MsgConstant.KCHATTYPEGUILD -> "guild$msgId"
else -> error("不支持的消息来源类型 | generateMsgIdHash: $chatType")
}
return abs(key.hashCode())

View File

@ -74,7 +74,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
peerId = uin,
userId = record.senderUin,
message = if(ShamrockConfig.useCQ()) rawMsg.json
else elements.toSegments(record.chatType, record.peerUin.toString()).map {
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map {
it.toJson()
}.json,
rawMessage = rawMsg,
@ -129,7 +129,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
peerId = botUin,
userId = record.senderUin,
message = if(ShamrockConfig.useCQ()) rawMsg.json
else elements.toSegments(record.chatType, record.peerUin.toString()).map {
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map {
it.toJson()
}.json,
rawMessage = rawMsg,
@ -147,6 +147,53 @@ internal object GlobalEventTransmitter: BaseSvc() {
)
return true
}
/**
* 推送私聊消息
*/
suspend fun transGuildMessage(
record: MsgRecord,
elements: ArrayList<MsgElement>,
rawMsg: String,
msgHash: Int,
postType: PostType,
): Boolean {
val botUin = app.longAccountUin
var nickName = record.sendNickName
if (nickName.isNullOrEmpty()) {
CardSvc.getProfileCard(record.senderUin.toString()).onSuccess {
nickName = it.strNick ?: record.peerName
}
}
transMessageEvent(record,
MessageEvent(
time = record.msgTime,
selfId = botUin,
postType = postType,
messageType = MsgType.Guild,
subType = MsgSubType.Channel,
messageId = msgHash,
targetId = record.peerUin,
peerId = botUin,
userId = record.senderUid.toLong(),
message = if(ShamrockConfig.useCQ()) rawMsg.json
else elements.toSegments(record.chatType, record.guildId, record.channelId).map {
it.toJson()
}.json,
rawMessage = rawMsg,
font = 0,
sender = Sender(
userId = record.senderUid.toLong(),
nickname = nickName,
card = record.sendMemberName,
role = MemberRole.Member,
title = record.sendNickName,
level = record.roleId.toString(),
),
)
)
return true
}
}
/**

View File

@ -39,19 +39,46 @@ internal object ShamrockConfig {
fun updateConfig(intent: Intent) {
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
mmkv.apply {
putBoolean( "tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式
putInt( "port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
putBoolean( "ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
putBoolean( "http", intent.getBooleanExtra("http", false)) // HTTP回调开关
putString( "http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址
putBoolean( "ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关
putBoolean( "use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码
putBoolean( "inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包
putBoolean( "debug", intent.getBooleanExtra("debug", false)) // 调试模式
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
putBoolean(
"tablet",
intent.getBooleanExtra("tablet", false)
) // 强制平板模式
putInt("port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
putBoolean("ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
putBoolean(
"http",
intent.getBooleanExtra("http", false)
) // HTTP回调开关
putString(
"http_addr",
intent.getStringExtra("http_addr")
) // WebHook回调地址
putBoolean(
"ws_client",
intent.getBooleanExtra("ws_client", false)
) // 被动WS开关
putBoolean(
"use_cqcode",
intent.getBooleanExtra("use_cqcode", false)
) // 使用CQ码
putBoolean(
"inject_packet",
intent.getBooleanExtra("inject_packet", false)
) // 拦截无用包
putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试
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)) // 推送同步消息
}
Config.defaultToken = intent.getStringExtra("token")
Config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true)
val wsPort = intent.getIntExtra("ws_port", 5800)
Config.activeWebSocket = if (Config.activeWebSocket == null) ConnectionConfig(
address = "0.0.0.0",
@ -59,28 +86,17 @@ internal object ShamrockConfig {
) else Config.activeWebSocket?.also {
it.port = wsPort
}
Config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", "")?.filter { address ->
address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://"))
}?.map {
ConnectionConfig(address = it)
}?.toMutableList()
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试
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("isInit", true)
}
updateConfig()
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
updateConfig()
}
}
private val mmkv: MMKV

View File

@ -21,12 +21,15 @@ internal enum class MsgSubType {
@SerialName("group") GroupLess,
@SerialName("friend") Friend,
@SerialName("other") Other,
@SerialName("channel") Channel
}
@Serializable
internal enum class MsgType {
@SerialName("group") Group,
@SerialName("private") Private
@SerialName("private") Private,
@SerialName("guild") Guild
}
@Serializable

View File

@ -38,8 +38,6 @@ internal object AioListener : IKernelMsgListener {
private suspend fun handleMsg(record: MsgRecord) {
try {
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
messageLessListenerMap.firstNotNullOfOrNull {
if (it.key == record.msgSeq) it else null
}?.let {
@ -60,7 +58,12 @@ internal object AioListener : IKernelMsgListener {
time = record.msgTime
)
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
val peerId = when(record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
val rawMsg = record.elements.toCQCode(record.chatType, peerId, record.channelId ?: peerId)
if (rawMsg.isEmpty()) return
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
@ -119,6 +122,16 @@ internal object AioListener : IKernelMsgListener {
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
}
}
MsgConstant.KCHATTYPEGUILD -> {
LogCenter.log("频道消息(guildId = ${record.guildId}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)")
if(!GlobalEventTransmitter.MessageTransmitter
.transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType)
) {
LogCenter.log("频道消息推送失败 -> MessageTransmitter", Level.WARN)
}
}
else -> LogCenter.log("不支持PUSH事件: ${record.chatType}")
}
} catch (e: Throwable) {
@ -190,7 +203,12 @@ internal object AioListener : IKernelMsgListener {
|| record.peerUin == TicketSvc.getLongUin()
) return@launch
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
val peerId = when(record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
val rawMsg = record.elements.toCQCode(record.chatType, peerId, record.channelId ?: peerId)
if (rawMsg.isEmpty()) return@launch
LogCenter.log("自发消息(target = ${record.peerUin}, id = $msgHash, msg = $rawMsg)")