From 5e989ebc83d2638bffbd6ce198c696a21dfa7645 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sat, 20 Mar 2021 22:46:17 +0800
Subject: [PATCH] feat(status): single sandbox bot instance
---
packages/koishi-core/src/adapter.ts | 14 ++--
packages/plugin-status/client/index.ts | 2 +-
packages/plugin-status/server/adapter.ts | 82 +++++++++++++-----------
packages/plugin-status/server/index.ts | 4 +-
packages/plugin-status/server/profile.ts | 2 +-
packages/plugin-status/server/webui.ts | 2 +-
6 files changed, 55 insertions(+), 51 deletions(-)
diff --git a/packages/koishi-core/src/adapter.ts b/packages/koishi-core/src/adapter.ts
index 6bac0727ba..77e0267cb1 100644
--- a/packages/koishi-core/src/adapter.ts
+++ b/packages/koishi-core/src/adapter.ts
@@ -1,4 +1,4 @@
-import { Logger, paramCase, remove, sleep, Time } from 'koishi-utils'
+import { Logger, paramCase, sleep, Time } from 'koishi-utils'
import { Session } from './session'
import { App } from './app'
import WebSocket from 'ws'
@@ -28,10 +28,10 @@ export abstract class Adapter
{
abstract start(): Promise
abstract stop?(): void
- constructor(public app: App, private Bot: Bot.Constructor) {}
+ constructor(public app: App, private Bot?: Bot.Constructor
) {}
- create(options: BotOptions) {
- const bot = new this.Bot(this, options)
+ create(options: BotOptions, constructor = this.Bot) {
+ const bot = new constructor(this, options)
this.bots.push(bot)
this.app.bots.push(bot)
return bot
@@ -255,12 +255,6 @@ export class Bot
{
}
return messageIds
}
-
- dispose() {
- const bot = this as Bot.Instance
- remove(this.adapter.bots, bot)
- remove(this.app.bots, bot)
- }
}
export namespace Bot {
diff --git a/packages/plugin-status/client/index.ts b/packages/plugin-status/client/index.ts
index b628388630..3c9b3154ca 100644
--- a/packages/plugin-status/client/index.ts
+++ b/packages/plugin-status/client/index.ts
@@ -22,7 +22,7 @@ export namespace storage {
}
export function create(key: string, fallback?: T) {
- const wrapper = ref(fallback && { ...fallback, ...get(key) })
+ const wrapper = ref(fallback ? { ...fallback, ...get(key) } : get(key))
watch(wrapper, () => set(key, wrapper.value), {
deep: typeof fallback === 'object',
})
diff --git a/packages/plugin-status/server/adapter.ts b/packages/plugin-status/server/adapter.ts
index 057e491121..55d305afdc 100644
--- a/packages/plugin-status/server/adapter.ts
+++ b/packages/plugin-status/server/adapter.ts
@@ -1,34 +1,27 @@
-import { Adapter, Bot, BotOptions, Context, Logger, omit, pick, Random, Session, Time, User } from 'koishi-core'
+import { Adapter, App, Bot, Context, Logger, omit, pick, Random, Session, Time, User } from 'koishi-core'
import Profile from './profile'
import WebSocket from 'ws'
const logger = new Logger('status')
-const states: Record = {}
+const states: Record = {}
const TOKEN_TIMEOUT = Time.minute * 10
-export class WebBot extends Bot<'sandbox'> {
- adapter: WebAdapter
+class SocketChannel {
+ readonly app: App
+ readonly id = Random.uuid()
- constructor(adapter: WebAdapter, options: BotOptions) {
- super(adapter, options)
- Profile.initBot(this)
- }
-
- async sendMessage(channelId: string, content: string) {
- this._send('sandbox', content)
- return Random.uuid()
+ constructor(public readonly adapter: WebAdapter, public socket: WebSocket) {
+ this.app = adapter.app
}
- // websocket api
-
- _send(type: string, body?: any) {
+ send(type: string, body?: any) {
this.socket.send(JSON.stringify({ type, body }))
}
async $token({ platform, userId }) {
const user = await this.app.database.getUser(platform, userId, ['name'])
- if (!user) return this._send('login', { message: '找不到此账户。' })
+ if (!user) return this.send('login', { message: '找不到此账户。' })
const id = `${platform}:${userId}`
const token = Random.uuid()
const expire = Date.now() + TOKEN_TIMEOUT
@@ -36,19 +29,19 @@ export class WebBot extends Bot<'sandbox'> {
setTimeout(() => {
if (states[id]?.[1] > Date.now()) delete states[id]
}, TOKEN_TIMEOUT)
- this._send('login', { token, name: user.name })
+ this.send('login', { token, name: user.name })
}
- async _validate(id: string, token: string, fields: T[] = []) {
+ async validate(id: string, token: string, fields: T[] = []) {
const user = await this.app.database.getUser('id', id, ['token', 'expire', ...fields])
if (!user || token !== user.token || user.expire <= Date.now()) {
- return this._send('expire')
+ return this.send('expire')
}
return user
}
async $password({ id, token, password }) {
- const user = await this._validate(id, token, ['password'])
+ const user = await this.validate(id, token, ['password'])
if (!user || password === user.password) return
await this.app.database.setUser('id', id, { password })
}
@@ -56,22 +49,23 @@ export class WebBot extends Bot<'sandbox'> {
async $login({ username, password }) {
const user = await this.app.database.getUser('name', username, ['password', 'authority', 'id', 'expire', 'token'])
if (!user || user.password !== password) {
- return this._send('login', { message: '用户名或密码错误。' })
+ return this.send('login', { message: '用户名或密码错误。' })
}
user.token = Random.uuid()
user.expire = Date.now() + this.adapter.config.expiration
await this.app.database.setUser('name', username, pick(user, ['token', 'expire']))
- this._send('user', omit(user, ['password']))
+ this.send('user', omit(user, ['password']))
}
async $sandbox({ id, token, content }) {
- const user = await this._validate(id, token, ['name'])
+ const user = await this.validate(id, token, ['name'])
if (!user) return
const session = new Session(this.app, {
- platform: 'sandbox',
+ platform: 'web',
userId: id,
content,
- selfId: this.selfId,
+ channelId: this.id,
+ selfId: 'sandbox',
type: 'message',
subtype: 'private',
author: {
@@ -84,6 +78,21 @@ export class WebBot extends Bot<'sandbox'> {
}
}
+export class SandboxBot extends Bot<'web'> {
+ username = 'sandbox'
+ status = Bot.Status.GOOD
+
+ constructor(public readonly adapter: WebAdapter) {
+ super(adapter, { type: 'web', selfId: 'sandbox' })
+ Profile.initBot(this)
+ }
+
+ async sendMessage(id: string, content: string) {
+ this.adapter.channels[id]?.send('sandbox', content)
+ return Random.uuid()
+ }
+}
+
export namespace WebAdapter {
export interface Config {
apiPath?: string
@@ -91,11 +100,14 @@ export namespace WebAdapter {
}
}
-export class WebAdapter extends Adapter<'sandbox'> {
+export class WebAdapter extends Adapter<'web'> {
server: WebSocket.Server
+ channels: Record = {}
+ sandbox = this.create({}, SandboxBot)
constructor(ctx: Context, public config: WebAdapter.Config) {
- super(ctx.app, WebBot)
+ super(ctx.app)
+
this.server = new WebSocket.Server({
path: config.apiPath,
server: ctx.app._httpServer,
@@ -108,7 +120,7 @@ export class WebAdapter extends Adapter<'sandbox'> {
const user = await session.observeUser(['id', 'name', 'authority', 'token', 'expire'])
user.token = Random.uuid()
user.expire = Date.now() + config.expiration
- return state[2]._send('user', user)
+ return state[2].send('user', user)
}
return next()
}, true)
@@ -116,23 +128,21 @@ export class WebAdapter extends Adapter<'sandbox'> {
async start() {
this.server.on('connection', async (socket) => {
- const bot = this.create({ type: 'sandbox', selfId: Random.uuid() })
- bot.socket = socket
- bot.username = '沙箱机器人'
- bot.status = Bot.Status.GOOD
+ const channel = new SocketChannel(this, socket)
+ this.channels[channel.id] = channel
socket.on('close', () => {
- bot.dispose()
+ delete this.channels[channel.id]
for (const id in states) {
- if (states[id][2] === bot) delete states[id]
+ if (states[id][2] === channel) delete states[id]
}
})
socket.on('message', async (data) => {
const { type, body } = JSON.parse(data.toString())
- const method = bot['$' + type]
+ const method = channel['$' + type]
if (method) {
- method.call(bot, body)
+ method.call(channel, body)
} else {
logger.info(type, body)
}
diff --git a/packages/plugin-status/server/index.ts b/packages/plugin-status/server/index.ts
index 15837ee42b..191b9ed17c 100644
--- a/packages/plugin-status/server/index.ts
+++ b/packages/plugin-status/server/index.ts
@@ -3,7 +3,7 @@ import { interpolate, Time } from 'koishi-utils'
import * as WebUI from './webui'
import Profile from './profile'
import Statistics, { Synchronizer } from './stats'
-import { WebBot } from './adapter'
+import { SandboxBot } from './adapter'
import './mongo'
import './mysql'
@@ -27,7 +27,7 @@ declare module 'koishi-core' {
namespace Bot {
interface Platforms {
- 'sandbox': WebBot
+ 'web': SandboxBot
}
}
diff --git a/packages/plugin-status/server/profile.ts b/packages/plugin-status/server/profile.ts
index 274b55b413..ebba3d0852 100644
--- a/packages/plugin-status/server/profile.ts
+++ b/packages/plugin-status/server/profile.ts
@@ -88,7 +88,7 @@ export namespace Profile {
export async function get(ctx: Context, config: Config) {
const [memory, bots] = await Promise.all([
memoryRate(),
- Promise.all(ctx.bots.filter(bot => bot.platform !== 'sandbox').map(BotData)),
+ Promise.all(ctx.bots.filter(bot => bot.platform !== 'web').map(BotData)),
])
const cpu: LoadRate = [appRate, usedRate]
return { bots, memory, cpu, ...await getMeta(ctx, config) } as Profile
diff --git a/packages/plugin-status/server/webui.ts b/packages/plugin-status/server/webui.ts
index 8ea5b3d3fb..4767c38d8a 100644
--- a/packages/plugin-status/server/webui.ts
+++ b/packages/plugin-status/server/webui.ts
@@ -67,7 +67,7 @@ export function apply(ctx: Context, config: Config = {}) {
}
async function createAdapter() {
- const adapter = ctx.app.adapters.sandbox = new WebAdapter(ctx, config)
+ const adapter = ctx.app.adapters.web = new WebAdapter(ctx, config)
adapter.server.on('connection', async (socket) => {
function send(type: string, body: any) {