Skip to content

Commit

Permalink
eval: user traps in eval
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Aug 26, 2020
1 parent 9193e94 commit e61eeb6
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 48 deletions.
33 changes: 7 additions & 26 deletions packages/plugin-eval-addons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { assertProperty, Logger, noop } from 'koishi-utils'
import { resolve } from 'path'
import { safeLoad } from 'js-yaml'
import { promises as fs } from 'fs'
import {} from 'koishi-plugin-eval'
import { UserTrap } from 'koishi-plugin-eval'
import Git, { CheckRepoActions } from 'simple-git'

const logger = new Logger('addon')
Expand All @@ -19,25 +19,14 @@ declare module 'koishi-plugin-eval' {
interface MainConfig extends AddonConfig {}
}

const proxyFields: Record<string, FieldTrap<any, any>> = {}

export function defineFieldTrap<T, K extends User.Field = never>(key: string, trap: FieldTrap<T, K>) {
proxyFields[key] = trap
}

export interface FieldTrap<T = any, K extends User.Field = never> {
fields: Iterable<K>
get(data: Pick<User, K>): T
}

interface OptionManifest extends OptionConfig {
name: string
desc: string
}

type Permission<T> = T[] | {
read?: T[]
write?: T[]
readable?: T[]
writable?: T[]
}

interface CommandManifest extends CommandConfig {
Expand Down Expand Up @@ -104,7 +93,7 @@ export function apply(ctx: Context, config: Config) {
const { commands = [] } = manifest
commands.forEach((config) => {
const { name: rawName, desc, options = [], userFields = [] } = config
const { read = [] } = Array.isArray(userFields) ? { read: userFields } : userFields
const { readable = [] } = Array.isArray(userFields) ? { readable: userFields } : userFields

const [name] = rawName.split(' ', 1)
if (!response.commands.includes(name)) {
Expand All @@ -116,24 +105,16 @@ export function apply(ctx: Context, config: Config) {
.action(async ({ session, command, options }, ...args) => {
const { $app, $user, $uuid } = session
const { name } = command
const user: Partial<User> = {}
for (const key of read) {
const trap = proxyFields[key]
Reflect.set(user, key, trap ? trap.get($user) : $user[key])
}
const result = await $app.evalRemote.addon($uuid, user, { name, args, options })
const user = UserTrap.get($user, readable)
const result = await $app.evalRemote.callAddon($uuid, user, { name, args, options })
return result
})

UserTrap.prepare(cmd, readable)
options.forEach((config) => {
const { name, desc } = config
cmd.option(name, desc, config)
})

for (const key of read) {
const trap = proxyFields[key]
cmd.userFields(trap ? trap.fields : [key])
}
})
})
})
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-eval-addons/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ declare module 'koishi-plugin-eval/dist/worker' {
}

interface WorkerAPI {
addon(sid: string, user: Partial<User>, argv: AddonArgv): Promise<string | void>
callAddon(sid: string, user: Partial<User>, argv: AddonArgv): Promise<string | void>
}

interface Response {
Expand All @@ -41,10 +41,10 @@ interface AddonContext extends AddonArgv {
type AddonAction = (ctx: AddonContext) => string | void | Promise<string | void>
const commandMap: Record<string, AddonAction> = {}

WorkerAPI.prototype.addon = async function (sid, user, argv) {
WorkerAPI.prototype.callAddon = async function (sid, user, argv) {
const callback = commandMap[argv.name]
try {
return await callback({ user, ...argv, ...createContext(sid) })
return await callback({ ...argv, ...createContext(sid, user) })
} catch (error) {
logger.warn(error)
}
Expand Down
17 changes: 13 additions & 4 deletions packages/plugin-eval/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { App, Context, Session } from 'koishi-core'
import { App, Context, Session, User } from 'koishi-core'
import { CQCode, Logger, defineProperty, Random, pick } from 'koishi-utils'
import { Worker, ResourceLimits } from 'worker_threads'
import { WorkerAPI, WorkerConfig, WorkerData, Response } from './worker'
import { wrap, expose, Remote } from './transfer'
import { resolve } from 'path'
import { UserTrap } from './trap'

export * from './trap'

declare module 'koishi-core/dist/app' {
interface App {
Expand Down Expand Up @@ -40,6 +43,7 @@ export interface MainConfig {
prefix?: string
timeout?: number
maxLogs?: number
userFields?: User.Field[]
resourceLimits?: ResourceLimits
dataKeys?: (keyof WorkerData)[]
}
Expand All @@ -53,6 +57,7 @@ const defaultConfig: EvalConfig = {
timeout: 1000,
setupFiles: {},
maxLogs: Infinity,
userFields: ['id', 'authority'],
dataKeys: ['inspect', 'setupFiles'],
}

Expand Down Expand Up @@ -152,9 +157,10 @@ export function apply(ctx: Context, config: Config = {}) {
}
})

const evaluate = ctx.command('evaluate [expr...]', '执行 JavaScript 脚本', { noEval: true })
const cmd = ctx.command('evaluate [expr...]', '执行 JavaScript 脚本', { noEval: true })
.alias('eval')
.userFields(['authority'])
.userFields(config.userFields)
.option('slient', '-s 不输出最后的结果')
.option('restart', '-r 重启子线程', { authority: 3 })
.before((session) => {
Expand Down Expand Up @@ -197,6 +203,7 @@ export function apply(ctx: Context, config: Config = {}) {
app.evalWorker.on('error', listener)
app.evalRemote.eval({
sid: session.$uuid,
user: UserTrap.get(session.$user, config.userFields),
silent: options.slient,
source: expr,
}).then(_resolve, (error) => {
Expand All @@ -206,9 +213,11 @@ export function apply(ctx: Context, config: Config = {}) {
})
})

UserTrap.prepare(cmd, config.userFields)

if (prefix) {
evaluate.shortcut(prefix, { oneArg: true, fuzzy: true })
evaluate.shortcut(prefix + prefix, { oneArg: true, fuzzy: true, options: { slient: true } })
cmd.shortcut(prefix, { oneArg: true, fuzzy: true })
cmd.shortcut(prefix + prefix, { oneArg: true, fuzzy: true, options: { slient: true } })
}
}

Expand Down
30 changes: 30 additions & 0 deletions packages/plugin-eval/src/trap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Command, User } from 'koishi-core'

export interface UserTrap<T = any, K extends User.Field = never> {
fields: Iterable<K>
get(data: Pick<User, K>): T
}

export namespace UserTrap {
const traps: Record<string, UserTrap<any, any>> = {}

export function define<T, K extends User.Field = never>(key: string, trap: UserTrap<T, K>) {
traps[key] = trap
}

export function prepare(cmd: Command, fields: string[]) {
for (const field of fields) {
const trap = traps[field]
cmd.userFields(trap ? trap.fields : [field])
}
}

export function get($user: {}, fields: string[]) {
const result: Partial<User> = {}
for (const field of fields) {
const trap = traps[field]
Reflect.set(result, field, trap ? trap.get($user) : $user[field])
}
return result
}
}
31 changes: 16 additions & 15 deletions packages/plugin-eval/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const logger = new Logger('eval')
import { expose, wrap } from './transfer'
import { VM } from './vm'
import { MainAPI } from '.'
import { User } from 'koishi-core'

export interface WorkerConfig {
setupFiles?: Record<string, string>
Expand All @@ -30,6 +31,7 @@ export const config: WorkerData = {

interface EvalOptions {
sid: string
user: Partial<User>
silent: boolean
source: string
}
Expand Down Expand Up @@ -72,19 +74,18 @@ function formatError(error: Error) {

const main = wrap<MainAPI>(parentPort)

export function createContext(sid: string) {
return {
async send(...param: [string, ...any[]]) {
return await main.send(sid, formatResult(...param))
},
async exec(message: string) {
if (typeof message !== 'string') {
throw new TypeError('The "message" argument must be of type string')
}
return await main.execute(sid, message)
},
}
}
export const createContext = (sid: string, user: Partial<User>) => ({
user,
async send(...param: [string, ...any[]]) {
return await main.send(sid, formatResult(...param))
},
async exec(message: string) {
if (typeof message !== 'string') {
throw new TypeError('The "message" argument must be of type string')
}
return await main.execute(sid, message)
},
})

export interface Response {}

Expand All @@ -96,10 +97,10 @@ export class WorkerAPI {
}

async eval(options: EvalOptions) {
const { sid, source, silent } = options
const { sid, user, source, silent } = options

const key = 'koishi-eval-session:' + sid
internal.setGlobal(Symbol.for(key), createContext(sid), false, true)
internal.setGlobal(Symbol.for(key), createContext(sid, user), false, true)

let result: any
try {
Expand Down

0 comments on commit e61eeb6

Please sign in to comment.