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) {