Skip to content

Commit

Permalink
feat(status): single sandbox bot instance
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 20, 2021
1 parent b7ce209 commit 5e989eb
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 51 deletions.
14 changes: 4 additions & 10 deletions packages/koishi-core/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -28,10 +28,10 @@ export abstract class Adapter<P extends Platform = Platform> {
abstract start(): Promise<void>
abstract stop?(): void

constructor(public app: App, private Bot: Bot.Constructor<P>) {}
constructor(public app: App, private Bot?: Bot.Constructor<P>) {}

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
Expand Down Expand Up @@ -255,12 +255,6 @@ export class Bot<P extends Platform> {
}
return messageIds
}

dispose() {
const bot = this as Bot.Instance<P>
remove(this.adapter.bots, bot)
remove(this.app.bots, bot)
}
}

export namespace Bot {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-status/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export namespace storage {
}

export function create<T>(key: string, fallback?: T) {
const wrapper = ref<T>(fallback && { ...fallback, ...get(key) })
const wrapper = ref<T>(fallback ? { ...fallback, ...get(key) } : get(key))
watch(wrapper, () => set(key, wrapper.value), {
deep: typeof fallback === 'object',
})
Expand Down
82 changes: 46 additions & 36 deletions packages/plugin-status/server/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,71 @@
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<string, [string, number, WebBot]> = {}
const states: Record<string, [string, number, SocketChannel]> = {}

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
states[id] = [token, expire, this]
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<T extends User.Field>(id: string, token: string, fields: T[] = []) {
async validate<T extends User.Field>(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 })
}

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: {
Expand All @@ -84,18 +78,36 @@ 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
expiration?: number
}
}

export class WebAdapter extends Adapter<'sandbox'> {
export class WebAdapter extends Adapter<'web'> {
server: WebSocket.Server
channels: Record<string, SocketChannel> = {}
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,
Expand All @@ -108,31 +120,29 @@ 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)
}

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)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-status/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -27,7 +27,7 @@ declare module 'koishi-core' {

namespace Bot {
interface Platforms {
'sandbox': WebBot
'web': SandboxBot
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-status/server/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-status/server/webui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 5e989eb

Please sign in to comment.