Skip to content

Commit

Permalink
主动兼容 cssxsh 的所有插件中使用的 mirai-core 内部方法 (#19)
Browse files Browse the repository at this point in the history
* [skip ci] good job

* add QQAndroidClient get keys methods

* move MarketFace to mirai internal

* add Stub MessageRefiner

* [skip ci] Update TODO.md
  • Loading branch information
MrXiaoM authored Feb 4, 2024
1 parent 2eaa06d commit c1620a3
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 30 deletions.
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 内部方法
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<FriendWrapper> = ContactList()
private var groupsInternal: ContactList<GroupWrapper> = ContactList()
Expand Down Expand Up @@ -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) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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))

Expand Down

This file was deleted.

0 comments on commit c1620a3

Please sign in to comment.