From ba3889cff805edcb146b33cf51272144bfb88d29 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 14 Jan 2020 14:38:50 +0800 Subject: [PATCH 01/23] init --- packages/plugin-record/README.md | 4 ++++ packages/plugin-record/package.json | 36 ++++++++++++++++++++++++++++ packages/plugin-record/tsconfig.json | 10 ++++++++ tsconfig.json | 1 + 4 files changed, 51 insertions(+) create mode 100644 packages/plugin-record/README.md create mode 100644 packages/plugin-record/package.json create mode 100644 packages/plugin-record/tsconfig.json diff --git a/packages/plugin-record/README.md b/packages/plugin-record/README.md new file mode 100644 index 0000000000..4f11714ae0 --- /dev/null +++ b/packages/plugin-record/README.md @@ -0,0 +1,4 @@ +# [koishi-plugin-recorder](https://koishi.js.org/plugins/recorder.html) + +[![npm](https://img.shields.io/npm/v/koishi-plugin-recorder?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-recorder) + diff --git a/packages/plugin-record/package.json b/packages/plugin-record/package.json new file mode 100644 index 0000000000..72305856e9 --- /dev/null +++ b/packages/plugin-record/package.json @@ -0,0 +1,36 @@ +{ + "name": "koishi-plugin-recorder", + "description": "Save Chat Records for Koishi", + "version": "1.0.0", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "author": "Shigma <1700011071@pku.edu.cn>", + "license": "MIT", + "scripts": { + "build": "tsc -b", + "lint": "eslint src --ext .ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/koishijs/koishi.git" + }, + "bugs": { + "url": "https://github.com/koishijs/koishi/issues" + }, + "homepage": "https://github.com/koishijs/koishi/packages/plugin-recorder#readme", + "keywords": [ + "bot", + "qqbot", + "cqhttp", + "coolq", + "chatbot", + "koishi", + "plugin", + "record", + "recorder" + ], + "dependencies": { + "koishi-core": "^1.3.0", + "koishi-utils": "^1.0.2" + } +} diff --git a/packages/plugin-record/tsconfig.json b/packages/plugin-record/tsconfig.json new file mode 100644 index 0000000000..3132edfc8f --- /dev/null +++ b/packages/plugin-record/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + }, + "include": [ + "src", + ], +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ed68a73def..ef2ceb9d0d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ { "path": "./packages/database-mysql" }, { "path": "./packages/database-sqlite" }, { "path": "./packages/plugin-common" }, + { "path": "./packages/plugin-recorder" }, { "path": "./packages/plugin-schedule" }, { "path": "./packages/plugin-teach" }, ], From f551d44e59351c60d12255b641407aa201505c8d Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Wed, 15 Jan 2020 11:06:03 +0800 Subject: [PATCH 02/23] feat(cli): support ts extension --- packages/koishi-cli/src/init.ts | 6 ++++-- packages/koishi-cli/src/worker.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/koishi-cli/src/init.ts b/packages/koishi-cli/src/init.ts index b2cf6217e6..3be9ced348 100644 --- a/packages/koishi-cli/src/init.ts +++ b/packages/koishi-cli/src/init.ts @@ -56,8 +56,8 @@ async function createConfig (options) { return config } -type ConfigFileType = 'js' | 'json' | 'yml' | 'yaml' -const supportedTypes: ConfigFileType[] = ['js', 'json', 'yml', 'yaml'] +const supportedTypes = ['js', 'json', 'ts', 'yml', 'yaml'] as const +type ConfigFileType = typeof supportedTypes[number] export default function (cli: CAC) { cli.command('init [file]', 'initialize a koishi configuration file') @@ -95,6 +95,8 @@ export default function (cli: CAC) { output = JSON.stringify(config, null, 2) if (extension === 'js') { output = 'module.exports = ' + output.replace(/^(\s+)"([\w$]+)":/mg, '$1$2:') + } else if (extension === 'ts') { + output = 'export = ' + output.replace(/^(\s+)"([\w$]+)":/mg, '$1$2:') } } diff --git a/packages/koishi-cli/src/worker.ts b/packages/koishi-cli/src/worker.ts index 662933cfa7..06fceb7908 100644 --- a/packages/koishi-cli/src/worker.ts +++ b/packages/koishi-cli/src/worker.ts @@ -1,4 +1,4 @@ -import { App, startAll, AppOptions, onStart, Context, Plugin, appList, logTypes, LogEvents } from 'koishi-core' +import { App, startAll, AppOptions, onStart, Context, Plugin, appList } from 'koishi-core' import { resolve, extname } from 'path' import { capitalize } from 'koishi-utils' import { performance } from 'perf_hooks' @@ -87,7 +87,7 @@ function tryCallback (callback: () => T) { } catch {} } -if (['.js', '.json'].includes(extension)) { +if (['.js', '.json', '.ts'].includes(extension)) { config = tryCallback(() => require(configFile)) } else if (['.yaml', '.yml'].includes(extension)) { config = tryCallback(() => safeLoad(readFileSync(configFile, 'utf8'))) From 644c6b36274a8fdc3c3b76c0e92bb266ca0302e3 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Wed, 15 Jan 2020 11:24:41 +0800 Subject: [PATCH 03/23] feat(cli): ensure dir --- packages/koishi-cli/src/init.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/koishi-cli/src/init.ts b/packages/koishi-cli/src/init.ts index 3be9ced348..76ea0f7569 100644 --- a/packages/koishi-cli/src/init.ts +++ b/packages/koishi-cli/src/init.ts @@ -1,6 +1,6 @@ -import { existsSync, writeFileSync } from 'fs' +import { existsSync, writeFileSync, mkdirSync } from 'fs' import { yellow } from 'kleur' -import { resolve, extname } from 'path' +import { resolve, extname, dirname } from 'path' import { logger } from './utils' import { safeDump } from 'js-yaml' import { AppConfig } from './worker' @@ -101,6 +101,8 @@ export default function (cli: CAC) { } // write to file + const folder = dirname(path) + if (!existsSync(folder)) mkdirSync(folder, { recursive: true }) writeFileSync(path, output) logger.success(`created config file: ${path}`) process.exit(0) From e9c3b9cb68d2b2db48cf7cc399f041baf162ef94 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Wed, 15 Jan 2020 12:39:00 +0800 Subject: [PATCH 04/23] feat(cli): export built-in plugins --- packages/koishi-cli/src/index.ts | 6 +++++- packages/koishi-cli/src/worker.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/koishi-cli/src/index.ts b/packages/koishi-cli/src/index.ts index 0f05d8b5f7..0b670b4a22 100644 --- a/packages/koishi-cli/src/index.ts +++ b/packages/koishi-cli/src/index.ts @@ -1,6 +1,10 @@ +import * as commonPlugin from 'koishi-plugin-common' +import * as schedulePlugin from 'koishi-plugin-schedule' +export { commonPlugin, schedulePlugin } + export * from 'koishi-core' export * from 'koishi-utils' -export { AppConfig } from './worker' +export { AppConfig, PluginConfig } from './worker' const { version } = require('../package') export { version } diff --git a/packages/koishi-cli/src/worker.ts b/packages/koishi-cli/src/worker.ts index 06fceb7908..61d92bee42 100644 --- a/packages/koishi-cli/src/worker.ts +++ b/packages/koishi-cli/src/worker.ts @@ -39,7 +39,7 @@ function loadEcosystem (type: string, name: string) { throw new Error(`cannot resolve ${type} ${name}`) } -type PluginConfig = (string | [string | Plugin, any])[] +export type PluginConfig = (string | [string | Plugin, any])[] export interface AppConfig extends AppOptions { plugins?: PluginConfig | Record From b3501a607ea4d450be388896b2c6e40c5fa2350b Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Wed, 15 Jan 2020 21:33:05 +0800 Subject: [PATCH 05/23] fix(cli): fix pluginConfig typings --- packages/koishi-cli/src/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/koishi-cli/src/worker.ts b/packages/koishi-cli/src/worker.ts index 61d92bee42..00ddb8fdbb 100644 --- a/packages/koishi-cli/src/worker.ts +++ b/packages/koishi-cli/src/worker.ts @@ -39,7 +39,7 @@ function loadEcosystem (type: string, name: string) { throw new Error(`cannot resolve ${type} ${name}`) } -export type PluginConfig = (string | [string | Plugin, any])[] +export type PluginConfig = (string | [string | Plugin, any?])[] export interface AppConfig extends AppOptions { plugins?: PluginConfig | Record From 9bbe685d6e28f0b17b141e05068d2a8642d82c8b Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 10:07:57 +0800 Subject: [PATCH 06/23] chore: record -> recorder --- packages/{plugin-record => plugin-recorder}/README.md | 1 - packages/{plugin-record => plugin-recorder}/package.json | 0 packages/{plugin-record => plugin-recorder}/tsconfig.json | 0 3 files changed, 1 deletion(-) rename packages/{plugin-record => plugin-recorder}/README.md (99%) rename packages/{plugin-record => plugin-recorder}/package.json (100%) rename packages/{plugin-record => plugin-recorder}/tsconfig.json (100%) diff --git a/packages/plugin-record/README.md b/packages/plugin-recorder/README.md similarity index 99% rename from packages/plugin-record/README.md rename to packages/plugin-recorder/README.md index 4f11714ae0..6d88da71be 100644 --- a/packages/plugin-record/README.md +++ b/packages/plugin-recorder/README.md @@ -1,4 +1,3 @@ # [koishi-plugin-recorder](https://koishi.js.org/plugins/recorder.html) [![npm](https://img.shields.io/npm/v/koishi-plugin-recorder?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-recorder) - diff --git a/packages/plugin-record/package.json b/packages/plugin-recorder/package.json similarity index 100% rename from packages/plugin-record/package.json rename to packages/plugin-recorder/package.json diff --git a/packages/plugin-record/tsconfig.json b/packages/plugin-recorder/tsconfig.json similarity index 100% rename from packages/plugin-record/tsconfig.json rename to packages/plugin-recorder/tsconfig.json From 75c64b3db2b8527dce0d5a19be6c84aa6f620e27 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 11:18:15 +0800 Subject: [PATCH 07/23] feat(core): status API --- packages/koishi-core/src/app.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/koishi-core/src/app.ts b/packages/koishi-core/src/app.ts index a02e730950..d88ca76c92 100644 --- a/packages/koishi-core/src/app.ts +++ b/packages/koishi-core/src/app.ts @@ -41,16 +41,10 @@ export function onStop (hook: (...app: App[]) => void) { export async function startAll () { await Promise.all(appList.map(async app => app.start())) - for (const hook of onStartHooks) { - hook(...appList) - } } export async function stopAll () { await Promise.all(appList.map(async app => app.stop())) - for (const hook of onStopHooks) { - hook(...appList) - } } let getSelfIdsPromise: Promise @@ -82,6 +76,8 @@ const defaultOptions: AppOptions = { maxMiddlewares: 64, } +export enum Status { closed, opening, open, closing } + export class App extends Context { app = this options: AppOptions @@ -99,6 +95,7 @@ export class App extends Context { _shortcutMap: Record = {} _middlewares: [Context, Middleware][] = [] + private status = Status.closed private _isReady = false private _middlewareCounter = 0 private _middlewareSet = new Set() @@ -208,6 +205,7 @@ export class App extends Context { } async start () { + this.status = Status.opening this.receiver.emit('before-connect') const tasks: Promise[] = [] if (this.database) { @@ -219,15 +217,20 @@ export class App extends Context { tasks.push(this.server.listen()) } await Promise.all(tasks) + this.status = Status.open this.logger('app').debug('started') this.receiver.emit('connect') if (this.selfId && !this._isReady) { this.receiver.emit('ready') this._isReady = true } + if (appList.every(app => app.status === Status.open)) { + onStartHooks.forEach(hook => hook(...appList)) + } } async stop () { + this.status = Status.closing this.receiver.emit('before-disconnect') const tasks: Promise[] = [] if (this.database) { @@ -239,8 +242,12 @@ export class App extends Context { if (this.server) { this.server.close() } + this.status = Status.closed this.logger('app').debug('stopped') this.receiver.emit('disconnect') + if (appList.every(app => app.status === Status.closed)) { + onStopHooks.forEach(hook => hook(...appList)) + } } emitEvent (meta: Meta, event: K, ...payload: Parameters) { From edee314c6e13ba8488e21c4912b8b02625f8a41d Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 11:19:09 +0800 Subject: [PATCH 08/23] feat(core): attach Meta<'send'>.userId --- packages/koishi-core/src/sender.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/koishi-core/src/sender.ts b/packages/koishi-core/src/sender.ts index 705afb1ad9..47d235ff1d 100644 --- a/packages/koishi-core/src/sender.ts +++ b/packages/koishi-core/src/sender.ts @@ -110,6 +110,7 @@ export class Sender { message, sendType, postType: 'send', + userId: this.app.selfId, [$ctxType + 'Id']: $ctxId, } as Meta<'send'> } From d4a5e98b95ae2ffcb806d33f5eb6dcc1bb765874 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 12:38:45 +0800 Subject: [PATCH 09/23] plugin-recorder: implementation --- packages/plugin-recorder/package.json | 6 +- packages/plugin-recorder/src/index.ts | 68 +++++++++++++++++++ .../tests/__snapshots__/index.spec.ts.snap | 7 ++ packages/plugin-recorder/tests/index.spec.ts | 39 +++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 packages/plugin-recorder/src/index.ts create mode 100644 packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap create mode 100644 packages/plugin-recorder/tests/index.spec.ts diff --git a/packages/plugin-recorder/package.json b/packages/plugin-recorder/package.json index 72305856e9..941f2b783a 100644 --- a/packages/plugin-recorder/package.json +++ b/packages/plugin-recorder/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-recorder", "description": "Save Chat Records for Koishi", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "main": "dist/index.js", "typings": "dist/index.d.ts", "author": "Shigma <1700011071@pku.edu.cn>", @@ -29,6 +29,10 @@ "record", "recorder" ], + "devDependencies": { + "del": "^5.1.0", + "koishi-test-utils": "^1.2.1" + }, "dependencies": { "koishi-core": "^1.3.0", "koishi-utils": "^1.0.2" diff --git a/packages/plugin-recorder/src/index.ts b/packages/plugin-recorder/src/index.ts new file mode 100644 index 0000000000..913140fa05 --- /dev/null +++ b/packages/plugin-recorder/src/index.ts @@ -0,0 +1,68 @@ +import { Context, Meta, onStop } from 'koishi-core' +import { WriteStream, createWriteStream, existsSync, mkdirSync } from 'fs' +import { resolve, dirname } from 'path' + +declare module 'koishi-core/dist/context' { + interface EventMap { + 'record-opened' (stream: WriteStream, path: string): any + 'record-closing' (stream: WriteStream, path: string): any + 'record-writing' (chunk: string, meta: Meta): any + 'record-written' (chunk: string, meta: Meta): any + } +} + +function pick (source: T, keys: K[]): Pick { + return keys.reduce((prev, curr) => (prev[curr] = source[curr], prev), {} as Pick) +} + +export interface RecorderOptions { + transform? (meta: Meta): string + target? (meta: Meta): string | void +} + +const refs = new WeakSet() + +const defaultOptions: RecorderOptions = { + target (meta) { + if (refs.has(meta) || meta.$ctxType !== 'group') return + return `messages/${meta.groupId}.txt` + }, + transform (meta) { + refs.add(meta) + return JSON.stringify(pick(meta, ['$ctxType', '$ctxId', 'userId', 'message'])) + }, +} + +const streams: Record = {} + +export function apply (ctx: Context, options: RecorderOptions = {}) { + options = { ...defaultOptions, ...options } + + function handleMessage (meta: Meta) { + const target = options.target(meta) + if (!target) return + const output = options.transform(meta) + '\n' + const path = resolve(process.cwd(), target) + if (!streams[path]) { + const folder = dirname(path) + if (!existsSync(folder)) mkdirSync(folder, { recursive: true }) + streams[path] = createWriteStream(path, { flags: 'a' }) + streams[path].on('close', () => delete streams[path]) + ctx.app.receiver.emit('record-opened', streams[path], path) + } + ctx.app.receiver.emit('record-writing', output, meta) + streams[path].write(output, () => { + ctx.app.receiver.emit('record-written', output, meta) + }) + } + + onStop(() => { + for (const key in streams) { + ctx.app.receiver.emit('record-closing', streams[key], key) + streams[key].close() + } + }) + + ctx.receiver.on('message', handleMessage) + ctx.receiver.on('before-send', handleMessage) +} diff --git a/packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap b/packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000..7b25290913 --- /dev/null +++ b/packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`check content 1`] = ` +"{\\"$ctxType\\":\\"group\\",\\"$ctxId\\":321,\\"userId\\":123,\\"message\\":\\"foo\\"} +{\\"$ctxType\\":\\"group\\",\\"$ctxId\\":321,\\"userId\\":789,\\"message\\":\\"baz\\"} +" +`; diff --git a/packages/plugin-recorder/tests/index.spec.ts b/packages/plugin-recorder/tests/index.spec.ts new file mode 100644 index 0000000000..99368e79bc --- /dev/null +++ b/packages/plugin-recorder/tests/index.spec.ts @@ -0,0 +1,39 @@ +import { MockedApp, BASE_SELF_ID } from 'koishi-test-utils' +import { startAll, stopAll } from 'koishi-core' +import * as recorder from '../src' +import del from 'del' +import { readFileSync } from 'fs-extra' +import { resolve } from 'path' + +const app1 = new MockedApp() +const app2 = new MockedApp({ selfId: BASE_SELF_ID + 1 }) + +app1.plugin(recorder) +app2.plugin(recorder) + +beforeAll(() => startAll()) + +const outFolder = resolve(process.cwd(), 'messages') + +afterAll(() => del(outFolder)) + +test('group message', async () => { + const mock = jest.fn() + app1.receiver.on('record-writing', mock) + await app1.receiveMessage('group', 'foo', 123, 321) + await app1.receiveMessage('group', 'bar', 456, 654) + await app1.receiveMessage('group', 'baz', 789, 321) + expect(mock).toBeCalledTimes(3) +}) + +test('private message', async () => { + const mock = jest.fn() + app1.receiver.on('record-writing', mock) + await app1.receiveMessage('user', 'foo', 123) + expect(mock).toBeCalledTimes(0) +}) + +test('check content', async () => { + await stopAll() + expect(readFileSync(resolve(outFolder, '321.txt'), 'utf8')).toMatchSnapshot() +}) From e4eea74e0608932e491ddd2486c7d43f806c9545 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 12:47:35 +0800 Subject: [PATCH 10/23] chore: tweak --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index ef2ceb9d0d..a75e550a9f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,8 +8,6 @@ "references": [ { "path": "./packages/koishi-utils" }, { "path": "./packages/koishi-core" }, - { "path": "./packages/koishi-cli" }, - { "path": "./packages/test-utils" }, { "path": "./packages/database-level" }, { "path": "./packages/database-mysql" }, { "path": "./packages/database-sqlite" }, @@ -17,6 +15,8 @@ { "path": "./packages/plugin-recorder" }, { "path": "./packages/plugin-schedule" }, { "path": "./packages/plugin-teach" }, + { "path": "./packages/test-utils" }, + { "path": "./packages/koishi-cli" }, ], "files": [], } \ No newline at end of file From 00ab2cec270d4fa439c50a5672c1dd2f5344e443 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 18:58:10 +0800 Subject: [PATCH 11/23] fix(core): check getTargetId arg type --- packages/koishi-core/src/utils.ts | 6 +++--- packages/koishi-core/tests/utils.spec.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/koishi-core/src/utils.ts b/packages/koishi-core/src/utils.ts index 65bef0d7ba..fb1560dc5a 100644 --- a/packages/koishi-core/src/utils.ts +++ b/packages/koishi-core/src/utils.ts @@ -13,11 +13,11 @@ export function getSenderName (meta: MessageMeta) { : meta.sender ? meta.sender.card || meta.sender.nickname : userId } -export function getTargetId (target: string) { - if (!target) return +export function getTargetId (target: string | number) { + if (typeof target !== 'string' && typeof target !== 'number') return let qq = +target if (!qq) { - const capture = /\[CQ:at,qq=(\d+)\]/.exec(target) + const capture = /\[CQ:at,qq=(\d+)\]/.exec(target as any) if (capture) qq = +capture[1] } if (!isInteger(qq)) return diff --git a/packages/koishi-core/tests/utils.spec.ts b/packages/koishi-core/tests/utils.spec.ts index 79ec558cbb..bae7329cd7 100644 --- a/packages/koishi-core/tests/utils.spec.ts +++ b/packages/koishi-core/tests/utils.spec.ts @@ -46,6 +46,7 @@ describe('getTargetId', () => { test('wrong syntax', () => { expect(getTargetId('')).toBeFalsy() + expect(getTargetId(true as any)).toBeFalsy() expect(getTargetId('[CQ:at,qq=]')).toBeFalsy() expect(getTargetId('foo123')).toBeFalsy() }) From 9bf455db39d45384a035bcb977d712fa77e5b613 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 18:58:22 +0800 Subject: [PATCH 12/23] test(plugin-common): info --- packages/plugin-common/tests/info.spec.ts | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/plugin-common/tests/info.spec.ts diff --git a/packages/plugin-common/tests/info.spec.ts b/packages/plugin-common/tests/info.spec.ts new file mode 100644 index 0000000000..0a31cc1eff --- /dev/null +++ b/packages/plugin-common/tests/info.spec.ts @@ -0,0 +1,29 @@ +import { MockedApp, MemoryDatabase } from 'koishi-test-utils' +import { registerDatabase } from 'koishi-core' +import info, { registerUserInfo } from '../src/info' + +registerDatabase('memory', MemoryDatabase) + +// make coverage happy +registerUserInfo(() => '') +registerUserInfo(() => 'foo', ['flag']) + +const app = new MockedApp({ database: { memory: {} } }) +const session = app.createSession('user', 123) + +app.plugin(info) + +beforeAll(async () => { + await app.start() + await app.database.getUser(123, 3) + await app.database.getUser(456, 4) +}) + +afterAll(() => app.stop()) + +test('basic support', async () => { + await session.shouldHaveReply('info', '123,您的权限为 3 级。\nfoo') + await session.shouldHaveReply('info -u', '未找到用户。') + await session.shouldHaveReply('info -u 654', '未找到用户。') + await session.shouldHaveReply('info -u 456', '用户 456 的权限为 4 级。\nfoo') +}) From 96740791da106eec7c828e941368134ee00413fd Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 20:04:28 +0800 Subject: [PATCH 13/23] feat(plugin-common): remove rank --- packages/plugin-common/src/index.ts | 4 -- packages/plugin-common/src/rank.ts | 101 ---------------------------- 2 files changed, 105 deletions(-) delete mode 100644 packages/plugin-common/src/rank.ts diff --git a/packages/plugin-common/src/index.ts b/packages/plugin-common/src/index.ts index 9573ea787b..837768ed2c 100644 --- a/packages/plugin-common/src/index.ts +++ b/packages/plugin-common/src/index.ts @@ -9,7 +9,6 @@ import exit from './exit' import help from './help' import info from './info' import likeme, { LikemeOptions } from './likeme' -import rank from './rank' import repeater, { RepeaterOptions } from './repeater' import requestHandler, { HandlerConfig } from './request-handler' import respondent, { Respondent } from './respondent' @@ -17,7 +16,6 @@ import welcome, { WelcomeMessage } from './welcome' export * from './admin' export * from './info' -export * from './rank' export { admin, @@ -30,7 +28,6 @@ export { help, info, likeme, - rank, repeater, requestHandler, respondent, @@ -74,6 +71,5 @@ export function apply (ctx: Context, options: CommonPluginConfig = {}) { .plugin(broadcast, options.broadcast) .plugin(callme, options.callme) .plugin(info, options.info) - .plugin(rank, options.rank) } } diff --git a/packages/plugin-common/src/rank.ts b/packages/plugin-common/src/rank.ts deleted file mode 100644 index f9711bc543..0000000000 --- a/packages/plugin-common/src/rank.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Meta, GroupMemberInfo, UserData, Context, UserField, CommandConfig } from 'koishi-core' -import { simplify } from 'koishi-utils' - -export interface Rank { - names: string[] - options?: any - groupOnly?: boolean - title?: (meta: Meta, options: any) => string - value: (user: UserData, meta: Meta, options: any) => number - fields: UserField[] - reverse?: boolean - format?: (value: number) => string - limit?: number -} - -const rankMap: Record = {} - -export function registerRank (name: string, rank: Rank) { - rankMap[name] = rank -} - -export default function apply (ctx: Context, options: CommandConfig) { - ctx.command('rank ', '显示排行', options) - .option('-g, --global', '使用全服数据', { authority: 2 }) - .option('--start ', '起始排名,默认为 1', { default: 1 }) - .option('--end ', '终止排名,默认为 10', { default: 10 }) - .action(async ({ meta, options }, type) => { - let data: { name: string, user: UserData }[] - const rank = rankMap[type] - if (!rank) return meta.$send('无法找到对应的排行。') - - if (meta.messageType === 'private' && !options.global) { - if (rank.groupOnly) return meta.$send('此排行只针对群。') - return meta.$send('私聊只能获取全服排行。') - } - - const { names: [name] } = rank - Object.assign(options, rank.options) - - if (options.global) { - const users = await ctx.app.database.getUsers(['id', 'name', ...rank.fields]) - data = users.map(user => ({ user, name: user.name })) - } else { - let members: GroupMemberInfo[] - try { - members = await ctx.sender.getGroupMemberList(meta.groupId) - } catch (error) { - return meta.$send('错误:无法获得群成员列表。') - } - const users = await ctx.app.database.getUsers(members.map(m => m.userId), ['id', 'name', ...rank.fields]) - data = users.map((user) => { - if (user.name !== String(user.id)) return { user, name: user.name } - const member = members.find(m => m.userId === user.id) - return { user, name: member.card || member.nickname } - }) - } - - const limit = rank.limit || 0 - const filter = rank.reverse - ? ({ value }) => value < limit - : ({ value }) => value > limit - - const sorter = rank.reverse - ? ({ value: x }, { value: y }) => x < y ? -1 : x > y ? 1 : 0 - : ({ value: x }, { value: y }) => x < y ? 1 : x > y ? -1 : 0 - - const formatter = rank.format || (value => String(value)) - - const output = data - .map((user) => ({ ...user, value: rank.value(user.user, meta, options) })) - .filter(filter) - .sort(sorter) - .slice(options.start - 1, options.end) - .map(({ value, name }, index) => { - return `${index + options.start}. ${name}:${formatter(value)}` - }) - output.unshift(rank.title ? rank.title(meta, options) : options.global ? `全服${name}排行为:` : `本群${name}排行为:`) - return meta.$send(output.join('\n')) - }) - - ctx.middleware((meta, next) => { - let message = simplify(meta.message).trimStart() - .replace(ctx.app.atMeRE, '') - .replace(ctx.app.nicknameRE, '') - .trim() - if (!message.endsWith('排行')) return next() - message = message.slice(0, -2) - let global = false - if (message.startsWith('全服')) { - global = true - message = message.slice(2) - } - for (const type in rankMap) { - const { names = [] } = rankMap[type] - if (names.includes(message)) { - return ctx.runCommand('rank', meta, [type], { global, start: 1, end: 10 }) - } - } - return next() - }) -} From 0497a9a9bd92678dbcd412baf76729d7ecd55714 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 21:09:26 +0800 Subject: [PATCH 14/23] test(plugin-common): respondent --- .../plugin-common/tests/respondent.spec.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/plugin-common/tests/respondent.spec.ts diff --git a/packages/plugin-common/tests/respondent.spec.ts b/packages/plugin-common/tests/respondent.spec.ts new file mode 100644 index 0000000000..2f8289ef04 --- /dev/null +++ b/packages/plugin-common/tests/respondent.spec.ts @@ -0,0 +1,21 @@ +import { MockedApp } from 'koishi-test-utils' +import respondent, { Respondent } from '../src/respondent' + +const app = new MockedApp() +const session = app.createSession('user', 123) + +// make coverage happy +app.plugin(respondent) +app.plugin(respondent, [{ + match: '挖坑一时爽', + reply: '填坑火葬场', +}, { + match: /^(.+)一时爽$/, + reply: (_, action) => `一直${action}一直爽`, +}]) + +test('basic support', async () => { + await session.shouldHaveReply('挖坑一时爽', '填坑火葬场') + await session.shouldHaveReply('填坑一时爽', '一直填坑一直爽') + await session.shouldHaveNoResponse('填坑一直爽') +}) From 87be3c1d5219c22f2390b214419bcf0a8e71d5ab Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 21:28:24 +0800 Subject: [PATCH 15/23] feat(plugin-common): remove likeme --- packages/plugin-common/src/index.ts | 5 ----- packages/plugin-common/src/likeme.ts | 21 --------------------- 2 files changed, 26 deletions(-) delete mode 100644 packages/plugin-common/src/likeme.ts diff --git a/packages/plugin-common/src/index.ts b/packages/plugin-common/src/index.ts index 837768ed2c..47a089dc44 100644 --- a/packages/plugin-common/src/index.ts +++ b/packages/plugin-common/src/index.ts @@ -8,7 +8,6 @@ import echo from './echo' import exit from './exit' import help from './help' import info from './info' -import likeme, { LikemeOptions } from './likeme' import repeater, { RepeaterOptions } from './repeater' import requestHandler, { HandlerConfig } from './request-handler' import respondent, { Respondent } from './respondent' @@ -27,7 +26,6 @@ export { exit, help, info, - likeme, repeater, requestHandler, respondent, @@ -43,8 +41,6 @@ interface CommonPluginConfig extends HandlerConfig, AuthorizeConfig { exit?: false | CommandConfig help?: false | CommandConfig info?: false | CommandConfig - likeme?: false | LikemeOptions - rank?: false | CommandConfig repeater?: false | RepeaterOptions respondent?: Respondent[] welcome?: false | WelcomeMessage @@ -58,7 +54,6 @@ export function apply (ctx: Context, options: CommonPluginConfig = {}) { .plugin(echo, options.echo) .plugin(exit, options.exit) .plugin(help, options.help) - .plugin(likeme, options.likeme) .plugin(repeater, options.repeater) .plugin(requestHandler, options) .plugin(respondent, options.respondent) diff --git a/packages/plugin-common/src/likeme.ts b/packages/plugin-common/src/likeme.ts deleted file mode 100644 index df9332a089..0000000000 --- a/packages/plugin-common/src/likeme.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Context, getSenderName, CommandConfig, UserType } from 'koishi-core' - -export interface LikemeOptions extends CommandConfig { - likes?: UserType -} - -export default function apply (ctx: Context, config: LikemeOptions = {}) { - ctx.command('likeme', '让四季酱点赞', { maxUsage: 1, ...config }) - .alias('给我点赞') - .alias('为我点赞') - .action(async ({ meta }) => { - const times = typeof config.likes === 'function' ? config.likes(meta.$user) : config.likes ?? 10 - if (!times) return meta.$send(`${getSenderName(meta)},你的好感度过低,四季酱无法为你点赞。`) - try { - await ctx.sender.sendLike(meta.userId, times) - return meta.$send(`${getSenderName(meta)},四季酱已为你点赞 ${times} 次。`) - } catch (error) { - return meta.$send('操作时发生异常。') - } - }) -} From 5c3834872334f2ec875cd987cea9d9adb30e32dc Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 22:21:00 +0800 Subject: [PATCH 16/23] feat(info): custom getSenderName --- packages/plugin-common/src/info.ts | 31 ++++++++++++++---- packages/plugin-common/tests/info.spec.ts | 40 ++++++++++++++++++----- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/packages/plugin-common/src/info.ts b/packages/plugin-common/src/info.ts index bf7b020a47..12e42a0701 100644 --- a/packages/plugin-common/src/info.ts +++ b/packages/plugin-common/src/info.ts @@ -1,4 +1,4 @@ -import { Context, getSenderName, UserField, UserData, getTargetId, CommandConfig } from 'koishi-core' +import { Context, UserField, UserData, getTargetId, CommandConfig, Meta } from 'koishi-core' type UserInfoCallback = (user: Pick) => string @@ -12,8 +12,22 @@ export function registerUserInfo (callback: UserInfoCallba } } -export default function apply (ctx: Context, config: CommandConfig = {}) { - ctx.command('info', '查看用户信息', { authority: 0, ...config }) +export interface InfoConfig extends CommandConfig { + getSenderName? (user: UserData, meta: Meta<'message'>): string +} + +const defaultConfig: InfoConfig = { + authority: 0, + getSenderName (user, meta) { + if (meta.userId === user.id && meta.sender) { + return meta.sender.card || meta.sender.nickname + } + }, +} + +export default function apply (ctx: Context, config: InfoConfig = {}) { + config = { ...defaultConfig, ...config } + ctx.command('info', '查看用户信息', config) .alias('i') .shortcut('我的信息') .option('-u, --user [target]', '指定目标', { authority: 3 }) @@ -25,10 +39,15 @@ export default function apply (ctx: Context, config: CommandConfig = {}) { if (!id) return meta.$send('未找到用户。') user = await ctx.database.getUser(id, -1, Array.from(infoFields)) if (!user) return meta.$send('未找到用户。') - output.push(`用户 ${id} 的权限为 ${user.authority} 级。`) + const name = config.getSenderName(user, meta) + if (!name) { + output.push(`${id} 的权限为 ${user.authority} 级。`) + } else { + output.push(`${name} (${id}) 的权限为 ${user.authority} 级。`) + } } else { - user = await ctx.database.getUser(meta.userId, 0, Array.from(infoFields)) - output.push(`${getSenderName(meta)},您的权限为 ${user.authority} 级。`) + user = await ctx.database.getUser(meta.userId, Array.from(infoFields)) + output.push(`${config.getSenderName(user, meta) || meta.userId},您的权限为 ${user.authority} 级。`) } for (const callback of infoCallbacks) { diff --git a/packages/plugin-common/tests/info.spec.ts b/packages/plugin-common/tests/info.spec.ts index 0a31cc1eff..9033244421 100644 --- a/packages/plugin-common/tests/info.spec.ts +++ b/packages/plugin-common/tests/info.spec.ts @@ -1,6 +1,6 @@ -import { MockedApp, MemoryDatabase } from 'koishi-test-utils' +import { MockedApp, MemoryDatabase, Session } from 'koishi-test-utils' import { registerDatabase } from 'koishi-core' -import info, { registerUserInfo } from '../src/info' +import info, { registerUserInfo, InfoConfig } from '../src/info' registerDatabase('memory', MemoryDatabase) @@ -8,22 +8,44 @@ registerDatabase('memory', MemoryDatabase) registerUserInfo(() => '') registerUserInfo(() => 'foo', ['flag']) -const app = new MockedApp({ database: { memory: {} } }) -const session = app.createSession('user', 123) +let app: MockedApp, session: Session -app.plugin(info) - -beforeAll(async () => { +beforeEach(async () => { + app = new MockedApp({ database: { memory: {} } }) + session = app.createSession('user', 123) await app.start() await app.database.getUser(123, 3) await app.database.getUser(456, 4) }) -afterAll(() => app.stop()) +afterEach(() => app.stop()) test('basic support', async () => { + app.plugin(info) + + session.meta.sender = { + userId: 123, + nickname: 'nick', + card: '', + sex: 'unknown', + age: 20, + } + + await session.shouldHaveReply('info', 'nick,您的权限为 3 级。\nfoo') + await session.shouldHaveReply('info -u', '未找到用户。') + await session.shouldHaveReply('info -u 654', '未找到用户。') + await session.shouldHaveReply('info -u 456', '456 的权限为 4 级。\nfoo') +}) + +test('getSenderName', async () => { + app.plugin(info, { + getSenderName (user, meta) { + if (user.id !== meta.userId) return 'bar' + }, + }) + await session.shouldHaveReply('info', '123,您的权限为 3 级。\nfoo') await session.shouldHaveReply('info -u', '未找到用户。') await session.shouldHaveReply('info -u 654', '未找到用户。') - await session.shouldHaveReply('info -u 456', '用户 456 的权限为 4 级。\nfoo') + await session.shouldHaveReply('info -u 456', 'bar (456) 的权限为 4 级。\nfoo') }) From 20ebbae66e975f73c5e00edc8eb538df62cc5286 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 22:31:42 +0800 Subject: [PATCH 17/23] feat: remove UserData.name --- packages/koishi-core/src/app.ts | 2 +- packages/koishi-core/src/database.ts | 1 - packages/koishi-core/src/utils.ts | 8 +---- packages/koishi-core/tests/utils.spec.ts | 37 +--------------------- packages/plugin-common/src/authorize.ts | 1 - packages/plugin-common/src/callme.ts | 38 ----------------------- packages/plugin-common/src/index.ts | 8 ++--- packages/plugin-common/src/info.ts | 6 ++-- packages/plugin-common/tests/info.spec.ts | 4 +-- packages/test-utils/src/database.ts | 2 +- packages/test-utils/src/utils.ts | 5 --- 11 files changed, 11 insertions(+), 101 deletions(-) delete mode 100644 packages/plugin-common/src/callme.ts diff --git a/packages/koishi-core/src/app.ts b/packages/koishi-core/src/app.ts index d88ca76c92..b4cea13b74 100644 --- a/packages/koishi-core/src/app.ts +++ b/packages/koishi-core/src/app.ts @@ -327,7 +327,7 @@ export class App extends Context { } // attach user data - const userFields = new Set(['name', 'flag']) + const userFields = new Set(['flag']) this.receiver.emit('before-user', userFields, parsedArgv || { meta }) const user = await this.database.observeUser(meta.userId, Array.from(userFields)) Object.defineProperty(meta, '$user', { value: user, writable: true }) diff --git a/packages/koishi-core/src/database.ts b/packages/koishi-core/src/database.ts index 7fc04ac9aa..ca28c5f48a 100644 --- a/packages/koishi-core/src/database.ts +++ b/packages/koishi-core/src/database.ts @@ -14,7 +14,6 @@ export const userFlags: (keyof typeof UserFlag)[] = ['ignore'] export interface UserData { id: number - name: string flag: number authority: number usage: Record diff --git a/packages/koishi-core/src/utils.ts b/packages/koishi-core/src/utils.ts index fb1560dc5a..7b21c1039a 100644 --- a/packages/koishi-core/src/utils.ts +++ b/packages/koishi-core/src/utils.ts @@ -7,12 +7,6 @@ import { messages } from './messages' import { format } from 'util' import leven from 'leven' -export function getSenderName (meta: MessageMeta) { - const userId = '' + meta.userId - return meta.$user && meta.$user.name !== userId ? meta.$user.name - : meta.sender ? meta.sender.card || meta.sender.nickname : userId -} - export function getTargetId (target: string | number) { if (typeof target !== 'string' && typeof target !== 'number') return let qq = +target @@ -51,7 +45,7 @@ export function showSuggestions (options: SuggestOptions): Promise { const [suggestion] = suggestions const command = typeof options.command === 'function' ? options.command(suggestion) : options.command const identifier = meta.userId + meta.$ctxType + meta.$ctxId - const userFields = new Set(['name']) + const userFields = new Set() const groupFields = new Set() Command.attachUserFields(userFields, { command, meta }) Command.attachGroupFields(groupFields, { command, meta }) diff --git a/packages/koishi-core/tests/utils.spec.ts b/packages/koishi-core/tests/utils.spec.ts index bae7329cd7..21a4ae80d1 100644 --- a/packages/koishi-core/tests/utils.spec.ts +++ b/packages/koishi-core/tests/utils.spec.ts @@ -1,39 +1,4 @@ -import { getSenderName, createUser, getTargetId } from 'koishi-core' -import { createSender } from 'koishi-test-utils' -import { observe } from 'koishi-utils' - -describe('getSenderName', () => { - test('userData with userId', () => { - expect(getSenderName({ - userId: 123, - $user: observe(createUser(123, 1)), - })).toBe('123') - }) - - test('userData with name', () => { - expect(getSenderName({ - userId: 123, - $user: observe({ ...createUser(123, 1), name: '456' }), - sender: createSender(123, 'bar', 'foo'), - })).toBe('456') - }) - - test('userData with card', () => { - expect(getSenderName({ - userId: 123, - $user: observe(createUser(123, 1)), - sender: createSender(123, 'bar', 'foo'), - })).toBe('foo') - }) - - test('userData with nickname', () => { - expect(getSenderName({ - userId: 123, - $user: observe(createUser(123, 1)), - sender: createSender(123, 'bar'), - })).toBe('bar') - }) -}) +import { getTargetId } from 'koishi-core' describe('getTargetId', () => { test('with id', () => { diff --git a/packages/plugin-common/src/authorize.ts b/packages/plugin-common/src/authorize.ts index 2a0356f81f..ded31f8ce3 100644 --- a/packages/plugin-common/src/authorize.ts +++ b/packages/plugin-common/src/authorize.ts @@ -31,7 +31,6 @@ interface AuthorizeInfo { export default function apply (ctx: Context, config: AuthorizeConfig) { const { app, database } = ctx const { authorizeUser = {}, authorizeGroup = {} } = config - const logger = ctx.logger('authorize') const authorityMap: Record = [] /** diff --git a/packages/plugin-common/src/callme.ts b/packages/plugin-common/src/callme.ts deleted file mode 100644 index df69deee8c..0000000000 --- a/packages/plugin-common/src/callme.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Context, CommandConfig, Meta } from 'koishi-core' - -export interface CallmeOptions extends CommandConfig { - validateName?: (name: string, meta: Meta) => string | void -} - -const defaultOptions: CallmeOptions = { - maxUsage: 3, - validateName (name, meta) { - if (name === meta.$user.name) return '称呼未发生变化。' - }, -} - -export default function apply (ctx: Context, options: CallmeOptions = {}) { - ctx.command('callme ', '修改自己的称呼', { ...defaultOptions, ...options }) - .userFields(['name']) - .shortcut('叫我', { prefix: true, fuzzy: true, oneArg: true }) - .action(async ({ meta }, name: string) => { - if (name === undefined || /^\s*$/.test(name)) { - return meta.$send(`好的,${meta.$user.name},请多指教!`) - } - - name = String(name).trim() - const message = options.validateName(name, meta) - if (message) return meta.$send(message) - - try { - await ctx.database.setUser(meta.userId, { name }) - return meta.$send(`好的,${name},请多指教!`) - } catch (error) { - if (error.code === 'ER_DUP_ENTRY') { - return meta.$send('禁止与其他用户重名。') - } else if (error.code === 'ER_DATA_TOO_LONG') { - return meta.$send('称呼超出长度限制。') - } - } - }) -} diff --git a/packages/plugin-common/src/index.ts b/packages/plugin-common/src/index.ts index 47a089dc44..0b49afac5e 100644 --- a/packages/plugin-common/src/index.ts +++ b/packages/plugin-common/src/index.ts @@ -2,12 +2,11 @@ import { Context, CommandConfig } from 'koishi-core' import admin from './admin' import authorize, { AuthorizeConfig } from './authorize' import broadcast, { BroadcastOptions } from './broadcast' -import callme, { CallmeOptions } from './callme' import contextify from './contextify' import echo from './echo' import exit from './exit' import help from './help' -import info from './info' +import info, { InfoOptions } from './info' import repeater, { RepeaterOptions } from './repeater' import requestHandler, { HandlerConfig } from './request-handler' import respondent, { Respondent } from './respondent' @@ -20,7 +19,6 @@ export { admin, authorize, broadcast, - callme, contextify, echo, exit, @@ -35,12 +33,11 @@ export { interface CommonPluginConfig extends HandlerConfig, AuthorizeConfig { admin?: false | CommandConfig broadcast?: false | BroadcastOptions - callme?: false | CallmeOptions contextify?: false | CommandConfig echo?: false | CommandConfig exit?: false | CommandConfig help?: false | CommandConfig - info?: false | CommandConfig + info?: false | InfoOptions repeater?: false | RepeaterOptions respondent?: Respondent[] welcome?: false | WelcomeMessage @@ -64,7 +61,6 @@ export function apply (ctx: Context, options: CommonPluginConfig = {}) { .plugin(admin, options.admin) .plugin(authorize, options) .plugin(broadcast, options.broadcast) - .plugin(callme, options.callme) .plugin(info, options.info) } } diff --git a/packages/plugin-common/src/info.ts b/packages/plugin-common/src/info.ts index 12e42a0701..3d9a24fea7 100644 --- a/packages/plugin-common/src/info.ts +++ b/packages/plugin-common/src/info.ts @@ -12,11 +12,11 @@ export function registerUserInfo (callback: UserInfoCallba } } -export interface InfoConfig extends CommandConfig { +export interface InfoOptions extends CommandConfig { getSenderName? (user: UserData, meta: Meta<'message'>): string } -const defaultConfig: InfoConfig = { +const defaultConfig: InfoOptions = { authority: 0, getSenderName (user, meta) { if (meta.userId === user.id && meta.sender) { @@ -25,7 +25,7 @@ const defaultConfig: InfoConfig = { }, } -export default function apply (ctx: Context, config: InfoConfig = {}) { +export default function apply (ctx: Context, config: InfoOptions = {}) { config = { ...defaultConfig, ...config } ctx.command('info', '查看用户信息', config) .alias('i') diff --git a/packages/plugin-common/tests/info.spec.ts b/packages/plugin-common/tests/info.spec.ts index 9033244421..4342ca5c59 100644 --- a/packages/plugin-common/tests/info.spec.ts +++ b/packages/plugin-common/tests/info.spec.ts @@ -1,6 +1,6 @@ import { MockedApp, MemoryDatabase, Session } from 'koishi-test-utils' import { registerDatabase } from 'koishi-core' -import info, { registerUserInfo, InfoConfig } from '../src/info' +import info, { registerUserInfo, InfoOptions } from '../src/info' registerDatabase('memory', MemoryDatabase) @@ -38,7 +38,7 @@ test('basic support', async () => { }) test('getSenderName', async () => { - app.plugin(info, { + app.plugin(info, { getSenderName (user, meta) { if (user.id !== meta.userId) return 'bar' }, diff --git a/packages/test-utils/src/database.ts b/packages/test-utils/src/database.ts index 7b1dac0c75..2cf37330cf 100644 --- a/packages/test-utils/src/database.ts +++ b/packages/test-utils/src/database.ts @@ -111,7 +111,7 @@ export function testDatabase (config: DatabaseConfig, options: TestDatabaseOptio }) test('observeUser merge', async () => { - const user: UserData = { id: 1000, flag: 3, name: '', authority: 1, usage: {} } + const user: UserData = { id: 1000, flag: 3, authority: 1, usage: {} } const observedUser = await db.observeUser(user, 1) expect(observedUser).toMatchObject(user) observedUser.flag = 5 diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index db55b2b17a..0329d4dda4 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -1,4 +1,3 @@ -import { SenderInfo } from 'koishi-core' import debug from 'debug' export const BASE_SELF_ID = 514 @@ -20,7 +19,3 @@ export function fromEntries (entries: Iterable) { export function createArray (length: number, create: (index: number) => T) { return Array(length).fill(undefined).map((_, index) => create(index)) } - -export function createSender (userId: number, nickname: string, card = '') { - return { userId, nickname, card, sex: 'unknown', age: 20 } as SenderInfo -} From ce4feb16f9aa692da62bc4f6c102815db9d74a37 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 22:43:24 +0800 Subject: [PATCH 18/23] chore: remove name --- packages/database-sqlite/src/database.ts | 1 - packages/plugin-teach/src/receiver.ts | 4 ++-- packages/plugin-teach/src/update.ts | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/database-sqlite/src/database.ts b/packages/database-sqlite/src/database.ts index 22fc4dc780..86d8e0938b 100644 --- a/packages/database-sqlite/src/database.ts +++ b/packages/database-sqlite/src/database.ts @@ -26,7 +26,6 @@ export type Pragma = { export const pragma: Pragma = { user: { id: 'BIGINT', - name: 'VARCHAR(64)', flag: 'INT', authority: 'INT', usage: 'TEXT', diff --git a/packages/plugin-teach/src/receiver.ts b/packages/plugin-teach/src/receiver.ts index 8ffd4d2ed5..8b27527775 100644 --- a/packages/plugin-teach/src/receiver.ts +++ b/packages/plugin-teach/src/receiver.ts @@ -1,4 +1,4 @@ -import { Context, getSenderName, Meta } from 'koishi-core' +import { Context, Meta } from 'koishi-core' import { randomPick, CQCode, sleep } from 'koishi-utils' import { simplifyQuestion, TeachConfig } from './utils' import { DialogueTest, Dialogue } from './database' @@ -39,7 +39,7 @@ export default function (ctx: Context, config: TeachConfig) { .replace(/\$a/g, `[CQ:at,qq=${meta.userId}]`) .replace(/\$A/g, '[CQ:at,qq=all]') .replace(/\$m/g, CQCode.stringify('at', { qq: meta.selfId })) - .replace(/\$s/g, escapeAnswer(getSenderName(meta))) + .replace(/\$s/g, escapeAnswer(meta.sender.card || meta.sender.nickname)) // TODO: name support .replace(/\$0/g, escapeAnswer(meta.message)) .split('$n') .map(str => str.trim().replace(/@@__DOLLARS_PLACEHOLDER__@@/g, '$')) diff --git a/packages/plugin-teach/src/update.ts b/packages/plugin-teach/src/update.ts index 1ea3438e1c..e6bc7e0b16 100644 --- a/packages/plugin-teach/src/update.ts +++ b/packages/plugin-teach/src/update.ts @@ -133,8 +133,9 @@ export default async function (parsedOptions: TeachOptions) { ] if (config.useWriter && dialogue.writer) { - const user = await ctx.database.getUser(dialogue.writer, 0, ['id', 'name']) - output.push(`来源:${user.name}`) + // TODO: name support + const user = await ctx.database.getUser(dialogue.writer, 0, ['id']) + output.push(`来源:${user.id}`) } if (config.useEnvironment) { From 9293540991161d466d5b5090d6f6d4d6cda6662b Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 16 Jan 2020 23:00:48 +0800 Subject: [PATCH 19/23] chore: remove name --- packages/koishi-core/src/database.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/koishi-core/src/database.ts b/packages/koishi-core/src/database.ts index ca28c5f48a..66ea154a32 100644 --- a/packages/koishi-core/src/database.ts +++ b/packages/koishi-core/src/database.ts @@ -35,7 +35,6 @@ extendUser((id, authority) => ({ id, authority, flag: 0, - name: String(id), usage: {}, })) From bec397855f70b1d0cba88bea56e8a5d5e95c9727 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 17 Jan 2020 16:12:23 +0800 Subject: [PATCH 20/23] init plugin-nlp --- packages/koishi-core/src/index.ts | 1 + packages/plugin-nlp/README.md | 3 ++ packages/plugin-nlp/package.json | 40 +++++++++++++++++ packages/plugin-nlp/src/index.ts | 71 +++++++++++++++++++++++++++++++ packages/plugin-nlp/tsconfig.json | 10 +++++ 5 files changed, 125 insertions(+) create mode 100644 packages/plugin-nlp/README.md create mode 100644 packages/plugin-nlp/package.json create mode 100644 packages/plugin-nlp/src/index.ts create mode 100644 packages/plugin-nlp/tsconfig.json diff --git a/packages/koishi-core/src/index.ts b/packages/koishi-core/src/index.ts index 269a02d915..35d70d2773 100644 --- a/packages/koishi-core/src/index.ts +++ b/packages/koishi-core/src/index.ts @@ -4,6 +4,7 @@ export * from './context' export * from './database' export * from './meta' export * from './messages' +export * from './parser' export * from './sender' export * from './server' export * from './utils' diff --git a/packages/plugin-nlp/README.md b/packages/plugin-nlp/README.md new file mode 100644 index 0000000000..92f05422fc --- /dev/null +++ b/packages/plugin-nlp/README.md @@ -0,0 +1,3 @@ +# [koishi-plugin-nlp](https://koishi.js.org/plugins/nlp.html) + +[![npm](https://img.shields.io/npm/v/koishi-plugin-nlp?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-nlp) diff --git a/packages/plugin-nlp/package.json b/packages/plugin-nlp/package.json new file mode 100644 index 0000000000..44ce9971c9 --- /dev/null +++ b/packages/plugin-nlp/package.json @@ -0,0 +1,40 @@ +{ + "name": "koishi-plugin-nlp", + "description": "Natural Language Processor for Koishi", + "version": "1.0.0-alpha.0", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "author": "Shigma <1700011071@pku.edu.cn>", + "license": "MIT", + "scripts": { + "build": "tsc -b", + "lint": "eslint src --ext .ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/koishijs/koishi.git" + }, + "bugs": { + "url": "https://github.com/koishijs/koishi/issues" + }, + "homepage": "https://github.com/koishijs/koishi/packages/plugin-nlp#readme", + "keywords": [ + "bot", + "qqbot", + "cqhttp", + "coolq", + "chatbot", + "koishi", + "plugin", + "nlp", + "jieba" + ], + "devDependencies": { + "koishi-test-utils": "^1.2.1" + }, + "dependencies": { + "koishi-core": "^1.3.0", + "koishi-utils": "^1.0.2", + "nodejieba": "^2.4.0" + } +} diff --git a/packages/plugin-nlp/src/index.ts b/packages/plugin-nlp/src/index.ts new file mode 100644 index 0000000000..79ecd48055 --- /dev/null +++ b/packages/plugin-nlp/src/index.ts @@ -0,0 +1,71 @@ +import { Command, Meta, Context, ParsedLine, ParsedCommandLine } from 'koishi-core' +import { tag } from 'nodejieba' + +declare module 'koishi-core/dist/command' { + interface Command { + intend (keyword: string, callback: IntenderCallback): void + intend (keywords: string[], callback: IntenderCallback): void + } +} + +declare module 'koishi-core/dist/parser' { + interface ParsedLine { + confidence?: number + } +} + +declare module 'koishi-core/dist/meta' { + interface Meta { + $tags?: TagResult[] + } +} + +type IntenderCallback = (meta: Meta, keyword: string) => ParsedLine + +interface TagResult { + word: string + tag: string +} + +interface Intender { + command: Command + keywords: string[] + callback: IntenderCallback +} + +interface NlpConfig { + threshold?: number +} + +const intenders: Intender[] = [] + +Command.prototype.intend = function (this: Command, arg0: string | string[], callback: IntenderCallback) { + const keywords = typeof arg0 === 'string' ? [arg0] : arg0 + intenders.push({ command: this, keywords, callback }) +} + +export const name = 'nlp' + +export function apply (ctx: Context, options: NlpConfig = {}) { + options = { threshold: 0.5, ...options } + + ctx.middleware((meta, next) => { + let confidence = options.threshold, bestFit: ParsedCommandLine + for (const { keywords, callback, command } of intenders) { + const keyword = keywords.find(k => meta.message.includes(k)) + if (keyword) { + if (!meta.$tags) meta.$tags = tag(meta.message) + const argv = callback(meta, keyword) + if (argv.confidence > confidence) { + confidence = argv.confidence + bestFit = { meta, command, ...argv } + } + } + } + if (bestFit) { + return bestFit.command.execute(bestFit, next) + } else { + return next() + } + }) +} diff --git a/packages/plugin-nlp/tsconfig.json b/packages/plugin-nlp/tsconfig.json new file mode 100644 index 0000000000..3132edfc8f --- /dev/null +++ b/packages/plugin-nlp/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + }, + "include": [ + "src", + ], +} \ No newline at end of file From 5be0ad223dc6665f0e83353d7837658486fa10bd Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 17 Jan 2020 16:26:24 +0800 Subject: [PATCH 21/23] chore: bump version --- packages/database-level/package.json | 6 +++--- packages/database-mysql/package.json | 4 ++-- packages/database-sqlite/package.json | 6 +++--- packages/koishi-cli/package.json | 8 ++++---- packages/koishi-core/package.json | 4 ++-- packages/plugin-common/package.json | 6 +++--- packages/plugin-nlp/package.json | 4 ++-- packages/plugin-recorder/package.json | 4 ++-- packages/plugin-schedule/package.json | 10 +++++----- packages/plugin-teach/package.json | 10 +++++----- packages/test-utils/package.json | 4 ++-- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/database-level/package.json b/packages/database-level/package.json index 75f21d27e6..222ef70c87 100644 --- a/packages/database-level/package.json +++ b/packages/database-level/package.json @@ -1,7 +1,7 @@ { "name": "koishi-database-level", "description": "Leveldb support for Koishi", - "version": "1.0.5", + "version": "1.0.6", "main": "dist/index.js", "files": [ "dist" @@ -33,12 +33,12 @@ "leveldb" ], "devDependencies": { - "koishi-test-utils": "^1.2.1" + "koishi-test-utils": "^1.2.2" }, "dependencies": { "@types/leveldown": "^4.0.2", "@types/levelup": "^4.3.0", - "koishi-core": "^1.3.1", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2", "leveldown": "^5.4.1", "levelup": "^4.3.2", diff --git a/packages/database-mysql/package.json b/packages/database-mysql/package.json index 74e1334290..829a64581b 100644 --- a/packages/database-mysql/package.json +++ b/packages/database-mysql/package.json @@ -1,7 +1,7 @@ { "name": "koishi-database-mysql", "description": "MySQL support for Koishi", - "version": "1.0.5", + "version": "1.0.6", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -35,7 +35,7 @@ "@types/mysql": "^2.15.8" }, "dependencies": { - "koishi-core": "^1.3.1", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2", "mysql": "^2.17.1" } diff --git a/packages/database-sqlite/package.json b/packages/database-sqlite/package.json index 0ab3624a36..e4e1ee7f91 100644 --- a/packages/database-sqlite/package.json +++ b/packages/database-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "koishi-database-sqlite", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -22,10 +22,10 @@ "homepage": "https://github.com/koishijs/koishi/tree/master/packages/database-sqlite#readme", "devDependencies": { "@types/sqlite3": "^3.1.6", - "koishi-test-utils": "^1.2.1" + "koishi-test-utils": "^1.2.2" }, "dependencies": { - "koishi-core": "^1.3.1", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2", "sqlite3": "^4.1.1" } diff --git a/packages/koishi-cli/package.json b/packages/koishi-cli/package.json index b483f295dc..50052b8f12 100644 --- a/packages/koishi-cli/package.json +++ b/packages/koishi-cli/package.json @@ -1,7 +1,7 @@ { "name": "koishi", "description": "A QQ bot framework based on CQHTTP", - "version": "1.3.1", + "version": "1.4.0", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -40,9 +40,9 @@ "cac": "^6.5.4", "js-yaml": "^3.13.1", "kleur": "^3.0.3", - "koishi-core": "^1.3.1", - "koishi-plugin-common": "^1.0.5", - "koishi-plugin-schedule": "^1.0.1", + "koishi-core": "^1.4.0", + "koishi-plugin-common": "^1.0.6", + "koishi-plugin-schedule": "^1.0.2", "koishi-utils": "^1.0.2", "prompts": "^2.3.0" } diff --git a/packages/koishi-core/package.json b/packages/koishi-core/package.json index 49f80434a6..3a16156dd4 100644 --- a/packages/koishi-core/package.json +++ b/packages/koishi-core/package.json @@ -1,7 +1,7 @@ { "name": "koishi-core", "description": "Core features for Koishi", - "version": "1.3.1", + "version": "1.4.0", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -35,7 +35,7 @@ "@types/debug": "^4.1.5", "@types/ws": "^6.0.4", "get-port": "^5.1.0", - "koishi-test-utils": "^1.2.1" + "koishi-test-utils": "^1.2.2" }, "dependencies": { "axios": "^0.19.1", diff --git a/packages/plugin-common/package.json b/packages/plugin-common/package.json index 5a2829f98a..4f11d861ed 100644 --- a/packages/plugin-common/package.json +++ b/packages/plugin-common/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-common", "description": "Common plugins for Koishi", - "version": "1.0.5", + "version": "1.0.6", "main": "dist/index.js", "typings": "dist/index.d.ts", "author": "Shigma <1700011071@pku.edu.cn>", @@ -28,10 +28,10 @@ "plugin" ], "devDependencies": { - "koishi-test-utils": "^1.2.1" + "koishi-test-utils": "^1.2.2" }, "dependencies": { - "koishi-core": "^1.3.1", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2" } } diff --git a/packages/plugin-nlp/package.json b/packages/plugin-nlp/package.json index 44ce9971c9..8874201556 100644 --- a/packages/plugin-nlp/package.json +++ b/packages/plugin-nlp/package.json @@ -30,10 +30,10 @@ "jieba" ], "devDependencies": { - "koishi-test-utils": "^1.2.1" + "koishi-test-utils": "^1.2.2" }, "dependencies": { - "koishi-core": "^1.3.0", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2", "nodejieba": "^2.4.0" } diff --git a/packages/plugin-recorder/package.json b/packages/plugin-recorder/package.json index 941f2b783a..31e965c1a1 100644 --- a/packages/plugin-recorder/package.json +++ b/packages/plugin-recorder/package.json @@ -31,10 +31,10 @@ ], "devDependencies": { "del": "^5.1.0", - "koishi-test-utils": "^1.2.1" + "koishi-test-utils": "^1.2.2" }, "dependencies": { - "koishi-core": "^1.3.0", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2" } } diff --git a/packages/plugin-schedule/package.json b/packages/plugin-schedule/package.json index 340010d036..f896914dc7 100644 --- a/packages/plugin-schedule/package.json +++ b/packages/plugin-schedule/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-schedule", "description": "Schedule plugin for Koishi", - "version": "1.0.1", + "version": "1.0.2", "main": "dist/index.js", "typings": "dist/index.d.ts", "author": "Shigma <1700011071@pku.edu.cn>", @@ -31,12 +31,12 @@ ], "devDependencies": { "@types/ms": "^0.7.31", - "koishi-database-level": "^1.0.5", - "koishi-database-mysql": "^1.0.5", - "koishi-test-utils": "^1.2.1" + "koishi-database-level": "^1.0.6", + "koishi-database-mysql": "^1.0.6", + "koishi-test-utils": "^1.2.2" }, "dependencies": { - "koishi-core": "^1.3.1", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2", "ms": "^2.1.2" } diff --git a/packages/plugin-teach/package.json b/packages/plugin-teach/package.json index bee7206305..29a2d306f6 100644 --- a/packages/plugin-teach/package.json +++ b/packages/plugin-teach/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-teach", "description": "Teach plugin for Koishi", - "version": "0.1.10", + "version": "0.1.11", "main": "dist/index.js", "typings": "dist/index.d.ts", "author": "Shigma <1700011071@pku.edu.cn>", @@ -31,12 +31,12 @@ "conversation" ], "devDependencies": { - "koishi-database-level": "^1.0.5", - "koishi-database-mysql": "^1.0.5", - "koishi-test-utils": "^1.2.1" + "koishi-database-level": "^1.0.6", + "koishi-database-mysql": "^1.0.6", + "koishi-test-utils": "^1.2.2" }, "dependencies": { - "koishi-core": "^1.3.1", + "koishi-core": "^1.4.0", "koishi-utils": "^1.0.2" } } diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 9eb8fc7f16..c633b9861b 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "koishi-test-utils", "description": "Test utilities for Koishi", - "version": "1.2.1", + "version": "1.2.2", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -34,7 +34,7 @@ ], "peerDependencies": { "jest": "^24.9.0", - "koishi-core": "^1.3.1" + "koishi-core": "^1.4.0" }, "devDependencies": { "@types/debug": "^4.1.5", From 19b540e5c49cc8b4ef3170551900a9b6c884f800 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 17 Jan 2020 18:05:04 +0800 Subject: [PATCH 22/23] optimize nlp plugin --- packages/plugin-nlp/src/index.ts | 62 +++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/plugin-nlp/src/index.ts b/packages/plugin-nlp/src/index.ts index 79ecd48055..f42f0e2416 100644 --- a/packages/plugin-nlp/src/index.ts +++ b/packages/plugin-nlp/src/index.ts @@ -1,5 +1,6 @@ import { Command, Meta, Context, ParsedLine, ParsedCommandLine } from 'koishi-core' import { tag } from 'nodejieba' +import { resolve } from 'path' declare module 'koishi-core/dist/command' { interface Command { @@ -8,32 +9,37 @@ declare module 'koishi-core/dist/command' { } } -declare module 'koishi-core/dist/parser' { - interface ParsedLine { - confidence?: number - } -} - declare module 'koishi-core/dist/meta' { interface Meta { $tags?: TagResult[] } } -type IntenderCallback = (meta: Meta, keyword: string) => ParsedLine +export interface Intension extends Partial { + confidence?: number +} +export type IntenderCallback = (meta: Meta, keyword: string) => Intension + +// TODO: pending nodejieba typings interface TagResult { word: string tag: string } -interface Intender { +export interface Intender { command: Command keywords: string[] callback: IntenderCallback } -interface NlpConfig { +export interface NlpConfig { + // TODO: pending nodejieba typings + dict?: string + hmmDict?: string + userDict?: string + idfDict?: string + stopWordDict?: string threshold?: number } @@ -46,22 +52,44 @@ Command.prototype.intend = function (this: Command, arg0: string | string[], cal export const name = 'nlp' +const cwd = process.cwd() + +function resolvePathConfig (value?: string) { + if (value) return resolve(cwd, value) +} + export function apply (ctx: Context, options: NlpConfig = {}) { options = { threshold: 0.5, ...options } + options.dict = resolvePathConfig(options.dict) + options.hmmDict = resolvePathConfig(options.hmmDict) + options.idfDict = resolvePathConfig(options.idfDict) + options.userDict = resolvePathConfig(options.userDict) + options.stopWordDict = resolvePathConfig(options.stopWordDict) ctx.middleware((meta, next) => { - let confidence = options.threshold, bestFit: ParsedCommandLine + let max = options.threshold + let bestFit: ParsedCommandLine + for (const { keywords, callback, command } of intenders) { + // find matched keyword const keyword = keywords.find(k => meta.message.includes(k)) - if (keyword) { - if (!meta.$tags) meta.$tags = tag(meta.message) - const argv = callback(meta, keyword) - if (argv.confidence > confidence) { - confidence = argv.confidence - bestFit = { meta, command, ...argv } - } + if (!keyword) continue + + // attach word tags + if (!meta.$tags) meta.$tags = tag(meta.message) + + // generate intension + const intension = callback(meta, keyword) + if (!intension) return + + // find most credible intension + const confidence = intension.confidence ?? 1 + if (confidence > max) { + max = confidence + bestFit = { meta, command, ...intension } } } + if (bestFit) { return bestFit.command.execute(bestFit, next) } else { From 17b327c5b70fe10a871f7e32d41a42f2b3f8120c Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 17 Jan 2020 18:07:54 +0800 Subject: [PATCH 23/23] chore: update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 02ad1fc05a..2fb6fe4074 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ koishi run | [koishi-database-mysql](https://github.com/koishijs/koishi/tree/master/packages/database-mysql) | [![npm](https://img.shields.io/npm/v/koishi-database-mysql?style=flat-square)](https://www.npmjs.com/package/koishi-database-mysql) | | [koishi-database-sqlite](https://github.com/koishijs/koishi/tree/master/packages/database-sqlite) | [![npm](https://img.shields.io/npm/v/koishi-database-sqlite?style=flat-square)](https://www.npmjs.com/package/koishi-database-sqlite) | | [koishi-plugin-common](https://github.com/koishijs/koishi/tree/master/packages/plugin-common) | [![npm](https://img.shields.io/npm/v/koishi-plugin-common?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-common) | +| [koishi-plugin-nlp](https://github.com/koishijs/koishi/tree/master/packages/plugin-nlp) | [![npm](https://img.shields.io/npm/v/koishi-plugin-nlp?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-nlp) | +| [koishi-plugin-recorder](https://github.com/koishijs/koishi/tree/master/packages/plugin-recorder) | [![npm](https://img.shields.io/npm/v/koishi-plugin-recorder?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-recorder) | | [koishi-plugin-schedule](https://github.com/koishijs/koishi/tree/master/packages/plugin-schedule) | [![npm](https://img.shields.io/npm/v/koishi-plugin-schedule?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-schedule) | | [koishi-plugin-teach](https://github.com/koishijs/koishi/tree/master/packages/plugin-teach) | [![npm](https://img.shields.io/npm/v/koishi-plugin-teach?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-teach) |