Skip to content

Commit

Permalink
feat: new plugin: koishi-plugin-schedule (#9)
Browse files Browse the repository at this point in the history
feat(core): app.prototype.executeCommandLine
  • Loading branch information
shigma authored Jan 2, 2020
1 parent 073f709 commit bcd3ed6
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ temp

/packages/plugin-*
!/packages/plugin-common
!/packages/plugin-schedule
!/packages/plugin-teach

todo.md
Expand Down
8 changes: 4 additions & 4 deletions packages/database-mysql/src/database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createPool, Pool, PoolConfig, escape, escapeId } from 'mysql'
import { registerDatabase, AbstractDatabase } from 'koishi-core'
import { registerDatabase, AbstractDatabase, TableType, TableData } from 'koishi-core'
import { types } from 'util'

declare module 'koishi-core/dist/database' {
Expand Down Expand Up @@ -102,7 +102,7 @@ export class MysqlDatabase implements AbstractDatabase {
return this.query<T>(`SELECT ${this.joinKeys(fields)} FROM ?? ${conditional ? ' WHERE ' + conditional : ''}`, [table, ...values])
}

create = async <T extends {}> (table: string, data: Partial<T>): Promise<T> => {
async create <K extends TableType> (table: K, data: Partial<TableData[K]>): Promise<TableData[K]> {
const keys = Object.keys(data)
if (!keys.length) return
const header = await this.query<OkPacket>(
Expand All @@ -112,7 +112,7 @@ export class MysqlDatabase implements AbstractDatabase {
return { ...data, id: header.insertId } as any
}

update = async (table: string, id: number | string, data: object) => {
async update <K extends TableType> (table: K, id: number | string, data: Partial<TableData[K]>) {
const keys = Object.keys(data)
if (!keys.length) return
const header = await this.query(
Expand All @@ -122,7 +122,7 @@ export class MysqlDatabase implements AbstractDatabase {
return header as OkPacket
}

count = async (table: string) => {
async count <K extends TableType> (table: K) {
const [{ 'COUNT(*)': count }] = await this.query('SELECT COUNT(*) FROM ??', [table])
return count as number
}
Expand Down
12 changes: 6 additions & 6 deletions packages/database-mysql/src/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ injectMethods('mysql', 'group', {
const upToDate = timestamp - cache._timestamp < (this.config.groupRefreshInterval ?? defaultRefreshInterval)
if (cache && contain(Object.keys(cache), fields) && upToDate) return cache

const [data] = await this.select<GroupData[]>('groups', fields, '`id` = ?', [groupId])
const [data] = await this.select<GroupData[]>('group', fields, '`id` = ?', [groupId])
let fallback: GroupData
if (!data) {
fallback = createGroup(groupId, selfId)
if (selfId && groupId) {
await this.query(
'INSERT INTO `groups` (' + this.joinKeys(groupFields) + ') VALUES (' + groupFields.map(() => '?').join(', ') + ')',
this.formatValues('groups', fallback, groupFields),
'INSERT INTO `group` (' + this.joinKeys(groupFields) + ') VALUES (' + groupFields.map(() => '?').join(', ') + ')',
this.formatValues('group', fallback, groupFields),
)
}
} else {
Expand All @@ -53,11 +53,11 @@ injectMethods('mysql', 'group', {
assignees = await getSelfIds()
}
if (!assignees.length) return []
return this.select('groups', fields, `\`assignee\` IN (${assignees.join(',')})`)
return this.select('group', fields, `\`assignee\` IN (${assignees.join(',')})`)
},

async setGroup (groupId, data) {
const result = await this.update('groups', groupId, data)
const result = await this.update('group', groupId, data)
if (!groupCache[groupId]) {
groupCache[groupId] = {} as CachedGroupData
Object.defineProperty(groupCache[groupId], '_timestamp', { value: Date.now() })
Expand Down Expand Up @@ -86,6 +86,6 @@ injectMethods('mysql', 'group', {
},

async getGroupCount () {
return this.count('groups')
return this.count('group')
},
})
14 changes: 7 additions & 7 deletions packages/database-mysql/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { injectMethods, userFields, UserData, createUser, User, UserField } from
import { observe, difference } from 'koishi-utils'
import { arrayTypes } from './database'

arrayTypes.push('users.endings', 'users.achievement', 'users.inference')
arrayTypes.push('user.endings', 'user.achievement', 'user.inference')

injectMethods('mysql', 'user', {
async getUser (userId, ...args) {
const authority = typeof args[0] === 'number' ? args.shift() as number : 0
const fields = args[0] as never || userFields
const [data] = await this.select<UserData[]>('users', fields, '`id` = ?', [userId])
const [data] = await this.select<UserData[]>('user', fields, '`id` = ?', [userId])
let fallback: UserData
if (data) {
data.id = userId
Expand All @@ -18,8 +18,8 @@ injectMethods('mysql', 'user', {
fallback = createUser(userId, authority)
if (authority) {
await this.query(
'INSERT INTO `users` (' + this.joinKeys(userFields) + ') VALUES (' + userFields.map(() => '?').join(', ') + ')',
this.formatValues('users', fallback, userFields),
'INSERT INTO `user` (' + this.joinKeys(userFields) + ') VALUES (' + userFields.map(() => '?').join(', ') + ')',
this.formatValues('user', fallback, userFields),
)
}
}
Expand All @@ -38,11 +38,11 @@ injectMethods('mysql', 'user', {
fields = args[0] as any
}
if (ids && !ids.length) return []
return this.select('users', fields, ids && `\`id\` IN (${ids.join(', ')})`)
return this.select('user', fields, ids && `\`id\` IN (${ids.join(', ')})`)
},

async setUser (userId, data) {
return this.update('users', userId, data)
return this.update('user', userId, data)
},

async observeUser (user, ...args) {
Expand All @@ -65,6 +65,6 @@ injectMethods('mysql', 'user', {
},

async getUserCount () {
return this.count('users')
return this.count('user')
},
})
11 changes: 9 additions & 2 deletions packages/koishi-core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Context, Middleware, NextFunction, ContextScope, Events, EventMap } fro
import { GroupFlag, UserFlag, UserField, createDatabase, DatabaseConfig, GroupField } from './database'
import { showSuggestions } from './utils'
import { Meta, MessageMeta } from './meta'
import { simplify } from 'koishi-utils'
import { errors } from './messages'
import { simplify, noop } from 'koishi-utils'
import { errors, messages } from './messages'

export interface AppOptions {
port?: number
Expand Down Expand Up @@ -367,6 +367,13 @@ export class App extends Context {
}
}

executeCommandLine (message: string, meta: MessageMeta, next: NextFunction = noop) {
if (!meta.$path) this.server.parseMeta(meta)
const argv = this.parseCommandLine(message, meta)
if (argv) return argv.command.execute(argv, next)
return next()
}

private _applyMiddlewares = async (meta: MessageMeta) => {
// preparation
const counter = this._middlewareCounter++
Expand Down
2 changes: 1 addition & 1 deletion packages/koishi-core/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface CommandOption extends OptionConfig {
description: string
}

export function parseOption (rawName: string, description: string, config: OptionConfig = {}, optsDef: Record<string, CommandOption>): CommandOption {
export function parseOption (rawName: string, description: string, config: OptionConfig, optsDef: Record<string, CommandOption>): CommandOption {
config = { authority: 0, ...config }

const negated: string[] = []
Expand Down
11 changes: 8 additions & 3 deletions packages/koishi-core/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export abstract class Server {
return meta
}

async dispatchMeta (meta: Meta) {
parseMeta (meta: Meta) {
// prepare prefix
let ctxType: ContextType, ctxId: number
if (meta.groupId) {
Expand Down Expand Up @@ -94,7 +94,7 @@ export abstract class Server {
Object.defineProperty(meta, '$ctxType', { value: ctxType })

const app = this.appMap[meta.selfId]
if (!app) return
if (!app) return events

// add context properties
if (meta.postType === 'message') {
Expand Down Expand Up @@ -137,7 +137,12 @@ export abstract class Server {
}
}

// emit events
return events
}

dispatchMeta (meta: Meta) {
const app = this.appMap[meta.selfId]
const events = this.parseMeta(meta)
for (const event of events) {
app.emitEvent(meta, paramCase(event) as any, meta)
}
Expand Down
5 changes: 5 additions & 0 deletions packages/koishi-core/tests/command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ describe('register commands', () => {
app.command('g').alias('y')
app.command('h').alias('y')
}).toThrow(errors.DUPLICATE_COMMAND)

expect(() => {
app.command('i').alias('z')
app.command('i').alias('z')
}).not.toThrow()
})

test('remove options', () => {
Expand Down
37 changes: 26 additions & 11 deletions packages/koishi-core/tests/parser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { App, Command } from '../src'
import { errors } from '../src/messages'
import { App, Command, errors } from '../src'
import { ParsedLine } from '../src/parser'

const app = new App()

Expand All @@ -12,8 +12,8 @@ const cmd2 = app
.command('cmd2 [foo] [bar...]')
.option('-a [alpha]', '', { isString: true })
.option('-b [beta]', '', { default: 1000 })
.option('-C, --no-gamma')
.option('-D, --no-delta')
.option('--no-gamma, -C')
.option('--no-delta, -D')

describe('arguments', () => {
test('sufficient arguments', () => {
Expand Down Expand Up @@ -45,6 +45,8 @@ describe('arguments', () => {
})

describe('options', () => {
let result: ParsedLine

test('duplicate options', () => {
expect(() => app
.command('cmd-duplicate-options')
Expand All @@ -54,35 +56,39 @@ describe('options', () => {
})

test('option without parameter', () => {
const result = cmd1.parse('--alpha a')
result = cmd1.parse('--alpha a')
expect(result.args).toMatchObject(['a'])
expect(result.options).toMatchObject({ a: true, alpha: true })
})

test('option with parameter', () => {
const result = cmd1.parse('--beta 10')
result = cmd1.parse('--beta 10')
expect(result.options).toMatchObject({ b: 10, beta: 10 })
result = cmd1.parse('--beta=10')
expect(result.options).toMatchObject({ b: 10, beta: 10 })
})

test('quoted parameter', () => {
const result = cmd1.parse('-c "" -d')
result = cmd1.parse('-c "" -d')
expect(result.options).toMatchObject({ c: '', d: true })
})

test('unknown options', () => {
const result = cmd1.parse('--unknown-gamma c -de 10')
result = cmd1.parse('--unknown-gamma b --unknown-gamma c -de 10')
expect(result.unknown).toMatchObject(['unknown-gamma', 'd', 'e'])
expect(result.options).toMatchObject({ unknownGamma: 'c', d: true, e: 10 })
})

test('negated options', () => {
const result = cmd2.parse('-C --no-delta -E --no-epsilon')
result = cmd2.parse('-C --no-delta -E --no-epsilon')
expect(result.options).toMatchObject({ C: true, gamma: false, D: true, delta: false, E: true, epsilon: false })
})

test('option configuration', () => {
const result = cmd2.parse('-a 123 -d 456')
expect(result.options).toMatchObject({ a: '123', b: 1000, d: 456 })
result = cmd2.parse('-ba 123')
expect(result.options).toMatchObject({ a: '123', b: 1000 })
result = cmd2.parse('-ad 456')
expect(result.options).toMatchObject({ a: '', b: 1000, d: 456 })
})
})

Expand All @@ -95,6 +101,15 @@ describe('user fields', () => {
expect(cmd._userFields).toHaveProperty('size', 3)
})

describe('group fields', () => {
const cmd = app.command('cmd-group-fields')
expect(cmd._groupFields).toHaveProperty('size', 0)
cmd.groupFields(['id', 'assignee'])
expect(cmd._groupFields).toHaveProperty('size', 2)
cmd.groupFields(new Set(['id', 'flag']))
expect(cmd._groupFields).toHaveProperty('size', 3)
})

describe('edge cases', () => {
let cmd3: Command

Expand Down
26 changes: 13 additions & 13 deletions packages/koishi-core/tests/receiver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,79 +37,79 @@ afterAll(() => {
describe('meta.$path', () => {
test('user/*/message/friend', async () => {
const meta = createMeta('message', 'private', 'friend', { userId: 10000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/user/10000/message/friend')
})

test('user/*/friend_add', async () => {
const meta = createMeta('notice', 'friend_add', null, { userId: 10000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/user/10000/friend_add')
})

test('user/*/request/friend', async () => {
const meta = createMeta('request', 'friend', null, { userId: 10000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/user/10000/request/friend')
})

test('group/*/message/normal', async () => {
const meta = createMeta('message', 'group', 'normal', { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/message/normal')
})

test('group/*/group_upload', async () => {
const meta = createMeta('notice', 'group_upload', null, { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/group_upload')
})

test('group/*/group_admin/unset', async () => {
const meta = createMeta('notice', 'group_admin', 'unset', { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/group_admin/unset')
})

test('group/*/group_decrease/kick', async () => {
const meta = createMeta('notice', 'group_decrease', 'kick', { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/group_decrease/kick')
})

test('group/*/group_increase/invite', async () => {
const meta = createMeta('notice', 'group_increase', 'invite', { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/group_increase/invite')
})

test('group/*/group_ban/ban', async () => {
const meta = createMeta('notice', 'group_ban', 'ban', { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/group_ban/ban')
})

test('group/*/request/group/invite', async () => {
const meta = createMeta('request', 'group', 'invite', { groupId: 20000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/group/20000/request/group/invite')
})

test('discuss/*/message', async () => {
const meta = createMeta('message', 'discuss', null, { discussId: 30000 })
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/discuss/30000/message')
})

test('lifecycle/enable', async () => {
const meta = createMeta('meta_event', 'lifecycle', 'enable')
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/lifecycle/enable')
})

test('heartbeat', async () => {
const meta = createMeta('meta_event', 'heartbeat', null)
await app.server.dispatchMeta(meta)
app.server.parseMeta(meta)
expect(meta.$path).toBe('/heartbeat')
})
})
Expand Down
Loading

0 comments on commit bcd3ed6

Please sign in to comment.