适用于只使用overflow-core开发bot,并且使用spring自带的IoC能力的一套ws解决方案。可以避免早期overflow版本中反向websocket存在的各种问题。
- 定义handler
class WSHandler : WebSocketHandler, IAdapter {
override val scope = CoroutineScope(Dispatchers.IO) + SupervisorJob()
override val logger: Logger = LoggerFactory.getLogger(ActionHandler::class.java)
override val actionHandler: ActionHandler = ActionHandler(scope.coroutineContext[Job], logger)
init {
top.mrxiaom.overflow.internal.Overflow.setup()
log.info("Youmu WebSocket 已接管默认Overflow Bot Handler,WSBufferSize: ${System.getProperty("org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE")}")
}
override fun afterConnectionEstablished(session: WebSocketSession) {
//在这里进行鉴权
scope.launch {
val botImpl = Bot(
SpringDelegatedWebSocket(session),
BotConfig(),
actionHandler
)
val versionInfo = botImpl.getVersionInfo()
if (botImpl.onebotVersion == 12) {
session.close(CloseStatus.PROTOCOL_ERROR.withReason("不支持的onebot版本:12"))
return@launch
}
net.mamoe.mirai.Bot.getInstanceOrNull(botImpl.id)?.getOriginChannel()
?.close(CloseStatus.NORMAL.withReason("当前连接下线"))
val bot = with(BotWrapper) {
val result = runCatching {
botImpl.wrap(
configuration = BotConfiguration {
botLoggerSupplier = {
LoggerFactory.getLogger("Bot#${botImpl.id}").asMiraiLogger()
}
networkLoggerSupplier = {
LoggerFactory.getLogger("Net#${botImpl.id}").asMiraiLogger()
}
}
)
}
if (result.isFailure) {
session.close(CloseStatus.PROTOCOL_ERROR.withReason("无法实例化bot"))
return@launch
}
result.getOrThrow()
}
log.info("Bot ${bot.id} 已连接,协议版本信息:$versionInfo")
net.mamoe.mirai.Bot._instances[botImpl.id] = bot
}
}
override fun handleMessage(session: WebSocketSession, message: WebSocketMessage<*>) {
if (message is TextMessage) {
val text = message.payload
onReceiveMessage(text)
}
}
override fun handleTransportError(session: WebSocketSession, exception: Throwable) {
}
@OptIn(MiraiInternalApi::class)
override fun afterConnectionClosed(session: WebSocketSession, closeStatus: CloseStatus) {
val bot = net.mamoe.mirai.Bot.instances.find {
it.getOriginChannel() == session
}
if (bot !== null) {
bot as BotWrapper
bot.eventDispatcher.broadcastAsync(BotOfflineEvent.Dropped(bot, cause = RuntimeException("连接断开")))
net.mamoe.mirai.Bot._instances.remove(bot.id)
log.info("${bot.id}断开连接")
}
}
override fun supportsPartialMessages(): Boolean = false
}
- 定义ws session的wrapper。overflow中未使用的方法均可以使用TODO替代。
class SpringDelegatedWebSocket(val delegated: WebSocketSession) : WebSocket {
override fun close(p0: Int, p1: String?) {
delegated.close(CloseStatus(p0, p1))
}
override fun close(p0: Int) {
delegated.close(CloseStatus(p0))
}
override fun close() {
delegated.close()
}
override fun closeConnection(p0: Int, p1: String?) {
delegated.close(CloseStatus(p0, p1))
}
override fun send(p0: String?) {
delegated.sendMessage(TextMessage(p0!!))
}
override fun send(p0: ByteBuffer?) {
delegated.sendMessage(BinaryMessage(p0!!))
}
override fun send(p0: ByteArray?) {
delegated.sendMessage(BinaryMessage(p0!!))
}
override fun sendFrame(p0: Framedata?) {
TODO("Not yet implemented")
}
override fun sendFrame(p0: MutableCollection<Framedata>?) {
TODO("Not yet implemented")
}
override fun sendPing() {
delegated.sendMessage(PingMessage())
}
override fun sendFragmentedFrame(p0: Opcode?, p1: ByteBuffer?, p2: Boolean) {
TODO("Not yet implemented")
}
override fun hasBufferedData(): Boolean {
TODO("Not yet implemented")
}
override fun getRemoteSocketAddress(): InetSocketAddress = delegated.remoteAddress!!
override fun getLocalSocketAddress(): InetSocketAddress = delegated.localAddress!!
override fun isOpen(): Boolean = delegated.isOpen
override fun isClosing(): Boolean = !isOpen
override fun isFlushAndClose(): Boolean {
TODO("Not yet implemented")
}
override fun isClosed(): Boolean = !isOpen
override fun getDraft(): Draft {
TODO("Not yet implemented")
}
override fun getReadyState(): ReadyState {
TODO("Not yet implemented")
}
override fun getResourceDescriptor(): String {
TODO("Not yet implemented")
}
override fun <T : Any?> setAttachment(p0: T) {
TODO("Not yet implemented")
}
override fun <T : Any?> getAttachment(): T {
TODO("Not yet implemented")
}
override fun hasSSLSupport(): Boolean {
TODO("Not yet implemented")
}
override fun getSSLSession(): SSLSession {
TODO("Not yet implemented")
}
override fun getProtocol(): IProtocol {
TODO("Not yet implemented")
}
}
- 最后自行注册即可
@Configuration
@EnableWebSocket
class WSConfig:WebSocketConfigurer {
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
registry.addHandler(
WSHandler(),
"bot"
)
}
}
- 补一个上文中出现的工具类
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package top.kagg886.youmu.backend.socket
import net.mamoe.mirai.Bot
import org.springframework.web.socket.WebSocketSession
import top.kagg886.youmu.bot.internal.spring.SpringDelegatedWebSocket
import top.mrxiaom.overflow.internal.contact.BotWrapper
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
fun Bot.getOriginChannel(): WebSocketSession {
val prop = BotWrapper::class.memberProperties.first { it.name == "implBot" } as KProperty1<BotWrapper, cn.evolvefield.onebot.client.core.Bot>
prop.isAccessible = true
val wrapper = prop.get(this as BotWrapper).channel
return (wrapper as SpringDelegatedWebSocket).delegated
}
最后附赠一套使用此方案部署在公网上的反向websocket bot:
wss://youmu.kagg886.top/bot
接入教程见此:部署bot