From c1620a31748707cea39a8e0c1f3916e378286783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=BA=E9=96=93=E5=B7=A5=E4=BD=9C?= Date: Sun, 4 Feb 2024 21:35:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E5=85=BC=E5=AE=B9=20cssxsh?= =?UTF-8?q?=20=E7=9A=84=E6=89=80=E6=9C=89=E6=8F=92=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=20mirai-core=20=E5=86=85=E9=83=A8?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [skip ci] good job * add QQAndroidClient get keys methods * move MarketFace to mirai internal * add Stub MessageRefiner * [skip ci] Update TODO.md --- TODO.md | 1 + .../net/mamoe/mirai/internal/AbstractBot.kt | 14 ++++ .../net/mamoe/mirai/internal/QQAndroidBot.kt | 30 ++++++++ .../internal/message/RefinableMessage.kt | 29 ++++++++ .../internal/message/data/MarketFaceImpl.kt | 28 +++++++ .../mirai/internal/network/QQAndroidClient.kt | 18 +++++ .../network/components/SsoProcessor.kt | 29 ++++++++ .../net/mamoe/mirai/internal/network/keys.kt | 46 ++++++++++++ .../network/protocol/data/proto/Msg.kt | 73 +++++++++++++++++++ .../overflow/internal/contact/BotWrapper.kt | 5 +- .../internal/message/OnebotMessages.kt | 8 +- .../message/data/WrappedMarketFace.kt | 26 ------- 12 files changed, 277 insertions(+), 30 deletions(-) create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/AbstractBot.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/QQAndroidBot.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/RefinableMessage.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/data/MarketFaceImpl.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/QQAndroidClient.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/components/SsoProcessor.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/keys.kt create mode 100644 overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/protocol/data/proto/Msg.kt delete mode 100644 overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/data/WrappedMarketFace.kt diff --git a/TODO.md b/TODO.md index 64db26a1..6a8cb408 100644 --- a/TODO.md +++ b/TODO.md @@ -15,3 +15,4 @@ - [x] 解决登录后 mirai-console 被阻塞导致无法关闭的问题 - [x] 接口与实现分离,新建 `overflow-core-api` 模块 - [x] 反向 WebSocket 支持 +- [x] 主动兼容 [cssxsh](https://github.com/search?q=user%3Acssxsh+internal+language%3AKotlin&type=code&l=Kotlin) 的所有插件中使用的 mirai-core 内部方法 diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/AbstractBot.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/AbstractBot.kt new file mode 100644 index 00000000..f70cef08 --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/AbstractBot.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.internal + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot + +abstract class AbstractBot : Bot, CoroutineScope diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/QQAndroidBot.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/QQAndroidBot.kt new file mode 100644 index 00000000..642806b5 --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/QQAndroidBot.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.internal + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.internal.network.QQAndroidClient +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +internal fun Bot.asQQAndroidBot(): QQAndroidBot { + contract { + returns() implies (this@asQQAndroidBot is QQAndroidBot) + } + + return this as QQAndroidBot +} + +internal abstract class QQAndroidBot : AbstractBot() { + abstract val implGetter: () -> cn.evolvefield.onebot.client.core.Bot + val client: QQAndroidClient by lazy { + QQAndroidClient(implGetter) + } +} diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/RefinableMessage.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/RefinableMessage.kt new file mode 100644 index 00000000..b297577e --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/RefinableMessage.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2022 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ +package net.mamoe.mirai.internal.message + +import net.mamoe.mirai.message.data.* + +internal sealed class MessageRefiner + +/** + * 执行不需要 `suspend` 的 refine. 用于 [MessageSource.originalMessage]. + * + * 兼容 cssxsh/mirai-hibernate-plugin + * + * https://github.com/cssxsh/mirai-hibernate-plugin/blob/8f425db01629ff84900b53b1bf86c741ef7f81be/src/main/kotlin/xyz/cssxsh/mirai/hibernate/MiraiHibernateRecorder.kt + */ +internal object LightMessageRefiner : MessageRefiner() { + /** + * 去除 [MessageChain] 携带的内部标识 + * + * 用于 [createMessageReceipt] <- `RemoteFile.uploadAndSend` (文件操作API v1) + */ + fun MessageChain.dropMiraiInternalFlags(): MessageChain = this +} diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/data/MarketFaceImpl.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/data/MarketFaceImpl.kt new file mode 100644 index 00000000..a2695d6c --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/data/MarketFaceImpl.kt @@ -0,0 +1,28 @@ +package net.mamoe.mirai.internal.message.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.message.data.MarketFace +import net.mamoe.mirai.utils.MiraiExperimentalApi + +@SerialName(MarketFaceImpl.SERIAL_NAME) +@Serializable +internal data class MarketFaceImpl internal constructor( + internal val delegate: ImMsgBody.MarketFace, +) : MarketFace { + + override val name: String get() = delegate.faceName.decodeToString() + + @Transient + @MiraiExperimentalApi + override val id: Int = delegate.tabId + + @OptIn(MiraiExperimentalApi::class) + override fun toString() = "[mirai:marketface:$id,$name]" + + companion object { + const val SERIAL_NAME = MarketFace.SERIAL_NAME + } +} \ No newline at end of file diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/QQAndroidClient.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/QQAndroidClient.kt new file mode 100644 index 00000000..6253f1c2 --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/QQAndroidClient.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.internal.network + +import net.mamoe.mirai.internal.network.components.SsoSession + +internal open class QQAndroidClient( + implGetter: () -> cn.evolvefield.onebot.client.core.Bot +) : SsoSession { + override var loginState: Int = 0 + override var wLoginSigInfo: WLoginSigInfo = WLoginSigInfo(implGetter) +} diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/components/SsoProcessor.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/components/SsoProcessor.kt new file mode 100644 index 00000000..054ceadd --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/components/SsoProcessor.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.internal.network.components + +import net.mamoe.mirai.internal.network.WLoginSigInfo + +/** + * Contains secrets for encryption and decryption during a session created by [SsoProcessor] and [PacketCodec]. + * + * @see AccountSecrets + */ +internal interface SsoSession { + //var outgoingPacketSessionId: ByteArray + + /** + * always 0 for now. + */ + var loginState: Int + + // also present in AccountSecrets + var wLoginSigInfo: WLoginSigInfo + //val randomKey: ByteArray +} \ No newline at end of file diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/keys.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/keys.kt new file mode 100644 index 00000000..376a0908 --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/keys.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.internal.network + +import kotlinx.coroutines.runBlocking +import net.mamoe.mirai.internal.AbstractBot +import net.mamoe.mirai.internal.QQAndroidBot + +/** + * 兼容 cssxsh/meme-helper 中请求网络接口所需的 bkn、sKey、psKey 等参数 + * + * https://github.com/cssxsh/meme-helper/blob/61fbfd967a5e45dd9923c470622cd90d5bce1ce4/src/main/kotlin/face/MarketFaceHelper.kt + */ +internal data class WLoginSigInfo( + val impl: () -> cn.evolvefield.onebot.client.core.Bot +) { + val bkn: Int + get() = sKey.encodeToByteArray() + .fold(5381) { acc: Int, b: Byte -> acc + acc.shl(5) + b.toInt() } + .and(Int.MAX_VALUE) + + val sKey: String + get() = runBlocking { + val data = impl().getCredentials("qun.qq.com").data ?: throw IllegalStateException("Onebot 获取 Credentials (sKey) 失败") + val matches = Regex("[^_]skey=([^;]+);?").find(data.cookies) ?: throw IllegalStateException("Onebot 获取 Credentials 返回的 cookie 中没有 skey") + return@runBlocking matches.groupValues[1] + } + + fun getPsKey(name: String): String { + return runBlocking { + val data = impl().getCredentials(name).data ?: throw IllegalStateException("Onebot 获取 Credentials (psKey) 失败") + val matches = Regex("p_skey=([^;]+);?").find(data.cookies) ?: throw IllegalStateException("Onebot 获取 Credentials 返回的 cookie 中没有 p_skey") + return@runBlocking matches.groupValues[1] + } + } +} + +internal val AbstractBot.sKey get() = client.wLoginSigInfo.sKey +internal fun AbstractBot.psKey(name: String) = client.wLoginSigInfo.getPsKey(name) +internal val AbstractBot.client get() = (this as QQAndroidBot).client diff --git a/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/protocol/data/proto/Msg.kt b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/protocol/data/proto/Msg.kt new file mode 100644 index 00000000..b889d5f9 --- /dev/null +++ b/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/protocol/data/proto/Msg.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2019-2022 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ +package net.mamoe.mirai.internal.network.protocol.data.proto + +import kotlinx.serialization.Serializable +import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.utils.isSameType + +@Serializable +internal class ImMsgBody /*: ProtoBuf*/ { + + @Serializable + internal data class MarketFace( + /*@ProtoNumber(1) */@JvmField var faceName: ByteArray = EMPTY_BYTE_ARRAY, + /*@ProtoNumber(2) */@JvmField val itemType: Int = 0, + /*@ProtoNumber(3) */@JvmField val faceInfo: Int = 0, + /*@ProtoNumber(4) */@JvmField val faceId: ByteArray = EMPTY_BYTE_ARRAY, + /*@ProtoNumber(5) */@JvmField val tabId: Int = 0, + /*@ProtoNumber(6) */@JvmField val subType: Int = 0, + /*@ProtoNumber(7) */@JvmField val key: ByteArray = EMPTY_BYTE_ARRAY, + /*@ProtoNumber(8) */@JvmField val param: ByteArray = EMPTY_BYTE_ARRAY, + /*@ProtoNumber(9) */@JvmField val mediaType: Int = 0, + /*@ProtoNumber(10) */@JvmField val imageWidth: Int = 0, + /*@ProtoNumber(11) */@JvmField val imageHeight: Int = 0, + /*@ProtoNumber(12) */@JvmField val mobileParam: ByteArray = EMPTY_BYTE_ARRAY, + /*@ProtoNumber(13) */@JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + ) /*: ProtoBuf*/ { + @Suppress("DuplicatedCode") + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (!isSameType(this, other)) return false + + if (!faceName.contentEquals(other.faceName)) return false + if (itemType != other.itemType) return false + if (faceInfo != other.faceInfo) return false + if (!faceId.contentEquals(other.faceId)) return false + if (tabId != other.tabId) return false + if (subType != other.subType) return false + if (!key.contentEquals(other.key)) return false + if (!param.contentEquals(other.param)) return false + if (mediaType != other.mediaType) return false + if (imageWidth != other.imageWidth) return false + if (imageHeight != other.imageHeight) return false + if (!mobileParam.contentEquals(other.mobileParam)) return false + if (!pbReserve.contentEquals(other.pbReserve)) return false + + return true + } + + override fun hashCode(): Int { + var result = faceName.contentHashCode() + result = 31 * result + itemType + result = 31 * result + faceInfo + result = 31 * result + faceId.contentHashCode() + result = 31 * result + tabId + result = 31 * result + subType + result = 31 * result + key.contentHashCode() + result = 31 * result + param.contentHashCode() + result = 31 * result + mediaType + result = 31 * result + imageWidth + result = 31 * result + imageHeight + result = 31 * result + mobileParam.contentHashCode() + result = 31 * result + pbReserve.contentHashCode() + return result + } + } +} \ No newline at end of file diff --git a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/BotWrapper.kt b/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/BotWrapper.kt index 4ad5ae22..cc84c3dc 100644 --- a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/BotWrapper.kt +++ b/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/BotWrapper.kt @@ -11,6 +11,7 @@ import net.mamoe.mirai.contact.friendgroup.FriendGroups import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.events.BotEvent +import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.internal.network.components.EventDispatcherImpl import net.mamoe.mirai.message.data.MessageChain @@ -36,9 +37,10 @@ internal class BotWrapper private constructor( private var implBot: Bot, defLoginInfo: LoginInfoResp, override val configuration: BotConfiguration -) : net.mamoe.mirai.Bot, RemoteBot, Updatable, CoroutineScope { +) : QQAndroidBot(), RemoteBot, Updatable, CoroutineScope { val impl: Bot get() = implBot + override val implGetter: () -> Bot = { impl } private var loginInfo: LoginInfoResp = defLoginInfo private var friendsInternal: ContactList = ContactList() private var groupsInternal: ContactList = ContactList() @@ -92,7 +94,6 @@ internal class BotWrapper private constructor( if (data.message == null) return null return OnebotMessages.deserializeFromOneBot(bot, data.message) } - override val id: Long = loginInfo.userId override val logger: MiraiLogger = configuration.botLoggerSupplier(this) internal val networkLogger: MiraiLogger by lazy { configuration.networkLoggerSupplier(this) } diff --git a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/OnebotMessages.kt b/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/OnebotMessages.kt index acaec3c9..8a73e5e8 100644 --- a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/OnebotMessages.kt +++ b/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/OnebotMessages.kt @@ -6,6 +6,8 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai +import net.mamoe.mirai.internal.message.data.MarketFaceImpl +import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.MiraiExperimentalApi @@ -49,7 +51,7 @@ internal object OnebotMessages { registerSerializer(WrappedFileMessage::class, WrappedFileMessage.serializer()) registerSerializer(UnknownMessage::class, UnknownMessage.serializer()) registerSerializer(WrappedFileMessage::class, WrappedFileMessage.serializer()) - registerSerializer(WrappedMarketFace::class, WrappedMarketFace.serializer()) + registerSerializer(MarketFaceImpl::class, MarketFaceImpl.serializer()) registerSerializer(ContactRecommend::class, ContactRecommend.serializer()) registerSerializer(Location::class, Location.serializer()) } @@ -247,7 +249,9 @@ internal object OnebotMessages { add(raw.render(ForwardMessage.DisplayStrategy)) } } - "mface" -> add(WrappedMarketFace(data["id"].string, "[商城表情]")) // TODO 根据 emojiId 获取 name + "mface" -> add(MarketFaceImpl(ImMsgBody.MarketFace( // TODO 根据 emojiId 获取 name + faceId = data["id"].string.encodeToByteArray() + ))) "xml" -> add(SimpleServiceMessage(60, data["data"].string)) "json" -> add(LightApp(data["data"].string)) diff --git a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/data/WrappedMarketFace.kt b/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/data/WrappedMarketFace.kt deleted file mode 100644 index 865d3449..00000000 --- a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/data/WrappedMarketFace.kt +++ /dev/null @@ -1,26 +0,0 @@ -package top.mrxiaom.overflow.internal.message.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.MiraiExperimentalApi -import net.mamoe.mirai.utils.safeCast - -@MiraiExperimentalApi -@Serializable -@SerialName(WrappedMarketFace.SERIAL_NAME) -internal data class WrappedMarketFace( - val emojiId: String, - override val name: String, - override val id: Int = 0 -): MarketFace { - override fun toString(): String { - return "[overflow:MarketFace,$emojiId]" - } - - override val key: MessageKey get() = Key - public companion object Key : - AbstractPolymorphicMessageKey(MessageContent, { it.safeCast() }) { - public const val SERIAL_NAME: String = "WrappedMarketFace" - } -}