-
-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(common): refactor admin command
- Loading branch information
Showing
5 changed files
with
251 additions
and
248 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,184 +1,202 @@ | ||
import { isInteger, difference, Observed, paramCase, observe, Time, enumKeys } from 'koishi-utils' | ||
import { Context, Session, getTargetId, User, Group } from 'koishi-core' | ||
import { isInteger, difference, observe, Time, enumKeys } from 'koishi-utils' | ||
import { Context, getTargetId, User, Group, Command, ParsedArgv } from 'koishi-core' | ||
|
||
type ActionCallback<T extends {}, K extends keyof T> = | ||
(this: Context, session: Session<'authority'>, target: Observed<Pick<T, K>>, ...args: string[]) => Promise<void | string> | ||
type AdminAction<U extends User.Field, G extends Group.Field, O extends {}, T> | ||
= (argv: ParsedArgv<U | 'authority', G, O> & { target: T }, ...args: string[]) | ||
=> void | string | Promise<void | string> | ||
|
||
export interface ActionItem<T extends {}> { | ||
callback: ActionCallback<T, keyof T> | ||
fields: (keyof T)[] | ||
declare module 'koishi-core/dist/command' { | ||
interface Command<U, G, O> { | ||
adminUser(callback: AdminAction<U, G, O, User.Observed<U | 'authority'>>): this | ||
adminGruop(callback: AdminAction<U, G, O, Group.Observed<G>>): this | ||
} | ||
} | ||
|
||
export class Action<T extends {}> { | ||
commands: Record<string, ActionItem<T>> = {} | ||
interface FlagOptions { | ||
set?: boolean | ||
unset?: boolean | ||
} | ||
|
||
add<K extends keyof T = never>(name: string, callback: ActionCallback<T, K>, fields?: K[]) { | ||
this.commands[paramCase(name)] = { callback, fields } | ||
} | ||
interface FlagArgv extends ParsedArgv<never, never, FlagOptions> { | ||
target: User.Observed<'flag'> | Group.Observed<'flag'> | ||
} | ||
|
||
export const UserAction = new Action<User>() | ||
export const GroupAction = new Action<Group>() | ||
|
||
UserAction.add('setAuth', async (session, user, value) => { | ||
const authority = Number(value) | ||
if (!isInteger(authority) || authority < 0) return '参数错误。' | ||
if (authority >= session.$user.authority) return '权限不足。' | ||
if (authority === user.authority) { | ||
return '用户权限未改动。' | ||
} else { | ||
user.authority = authority | ||
await user._update() | ||
return '用户权限已修改。' | ||
} | ||
}, ['authority']) | ||
|
||
UserAction.add('setFlag', async (session, user, ...flags) => { | ||
const userFlags = enumKeys(User.Flag) | ||
if (!flags.length) return `可用的标记有 ${userFlags.join(', ')}。` | ||
const notFound = difference(flags, userFlags) | ||
if (notFound.length) return `未找到标记 ${notFound.join(', ')}。` | ||
for (const name of flags) { | ||
user.flag |= User.Flag[name] | ||
function flagAction({ target, options }: FlagArgv, ...flags: string[]) { | ||
if (options.set || options.unset) { | ||
const notFound = difference(flags, enumKeys(User.Flag)) | ||
if (notFound.length) return `未找到标记 ${notFound.join(', ')}。` | ||
for (const name of flags) { | ||
options.set ? target.flag |= User.Flag[name] : target.flag &= ~User.Flag[name] | ||
} | ||
return | ||
} | ||
await user._update() | ||
return '用户信息已修改。' | ||
}, ['flag']) | ||
|
||
UserAction.add('unsetFlag', async (session, user, ...flags) => { | ||
const userFlags = enumKeys(User.Flag) | ||
if (!flags.length) return `可用的标记有 ${userFlags.join(', ')}。` | ||
const notFound = difference(flags, userFlags) | ||
if (notFound.length) return `未找到标记 ${notFound.join(', ')}。` | ||
for (const name of flags) { | ||
user.flag &= ~User.Flag[name] | ||
|
||
let flag = target.flag | ||
const keys: string[] = [] | ||
while (flag) { | ||
const value = 2 ** Math.floor(Math.log2(flag)) | ||
flag -= value | ||
keys.unshift(User.Flag[value]) | ||
} | ||
await user._update() | ||
return '用户信息已修改。' | ||
}, ['flag']) | ||
|
||
UserAction.add('setUsage', async (session, user, name, _count) => { | ||
const count = +_count | ||
if (!isInteger(count) || count < 0) return '参数错误。' | ||
user.usage[name] = count | ||
await user._update() | ||
return '用户信息已修改。' | ||
}, ['usage']) | ||
|
||
UserAction.add('clearUsage', async (session, user, ...commands) => { | ||
if (commands.length) { | ||
for (const command of commands) { | ||
delete user.usage[command] | ||
if (!keys.length) return '未设置任何标记。' | ||
return `当前的标记为:${keys.join(', ')}。` | ||
} | ||
|
||
Command.prototype.adminUser = function (this: Command<never, never, { user?: string }>, callback) { | ||
const command = this | ||
.userFields(['authority']) | ||
.option('user', '-u [user] 指定目标用户', { authority: 4 }) | ||
|
||
command._action = async (argv) => { | ||
const { options, session, args } = argv | ||
const fields = Command.collect(argv, 'user') | ||
let target: User.Observed | ||
if (options.user) { | ||
const qq = getTargetId(options.user) | ||
if (!qq) return '请指定正确的目标。' | ||
const { database } = session.$app | ||
const data = await database.getUser(qq, -1, [...fields]) | ||
if (!data) return '未找到指定的用户。' | ||
if (qq === session.userId) { | ||
target = await session.$observeUser(fields) | ||
} else if (session.$user.authority <= data.authority) { | ||
return '权限不足。' | ||
} else { | ||
target = observe(data, diff => database.setUser(qq, diff), `user ${qq}`) | ||
} | ||
} else { | ||
target = await session.$observeUser(fields) | ||
} | ||
} else { | ||
user.usage = {} | ||
const result = await callback({ ...argv, target }, ...args) | ||
if (result) return result | ||
if (!Object.keys(target._diff).length) return '用户数据未改动。' | ||
await target._update() | ||
return '用户数据已修改。' | ||
} | ||
await user._update() | ||
return '用户信息已修改。' | ||
}, ['usage']) | ||
|
||
UserAction.add('setTimer', async (session, user, name, offset) => { | ||
if (!name || !offset) return '参数不足。' | ||
const timestamp = Time.parseTime(offset) | ||
if (!timestamp) return '请输入合法的时间。' | ||
user.timers[name] = Date.now() + timestamp | ||
await user._update() | ||
return '用户信息已修改。' | ||
}, ['timers']) | ||
|
||
UserAction.add('clearTimer', async (session, user, ...commands) => { | ||
if (commands.length) { | ||
for (const command of commands) { | ||
delete user.timers[command] | ||
|
||
return command | ||
} | ||
|
||
Command.prototype.adminGruop = function (this: Command<never, never, { group?: string }>, callback) { | ||
const command = this | ||
.userFields(['authority']) | ||
.option('group', '-g [group] 指定目标群', { authority: 4 }) | ||
|
||
command._action = async (argv) => { | ||
const { options, session, args } = argv | ||
const fields = Command.collect(argv, 'group') | ||
let target: Group.Observed | ||
if (options.group) { | ||
const { database } = session.$app | ||
if (!isInteger(options.group) || options.group <= 0) return '请指定正确的目标。' | ||
const data = await database.getGroup(options.group, -1, [...fields]) | ||
if (!data) return '未找到指定的群。' | ||
target = observe(data, diff => database.setGroup(options.group, diff), `group ${options.group}`) | ||
} else { | ||
target = await session.$observeGroup(fields) | ||
} | ||
} else { | ||
user.timers = {} | ||
const result = await callback({ ...argv, target }, ...args) | ||
if (result) return result | ||
if (!Object.keys(target._diff).length) return '群数据未改动。' | ||
await target._update() | ||
return '群数据已修改。' | ||
} | ||
await user._update() | ||
return '用户信息已修改。' | ||
}, ['timers']) | ||
|
||
GroupAction.add('setFlag', async (session, group, ...flags) => { | ||
const groupFlags = enumKeys(Group.Flag) | ||
if (!flags.length) return `可用的标记有 ${groupFlags.join(', ')}。` | ||
const notFound = difference(flags, groupFlags) | ||
if (notFound.length) return `未找到标记 ${notFound.join(', ')}。` | ||
for (const name of flags) { | ||
group.flag |= Group.Flag[name] | ||
} | ||
await group._update() | ||
return '群信息已修改。' | ||
}, ['flag']) | ||
|
||
GroupAction.add('unsetFlag', async (session, group, ...flags) => { | ||
const groupFlags = enumKeys(Group.Flag) | ||
if (!flags.length) return `可用的标记有 ${groupFlags.join(', ')}。` | ||
const notFound = difference(flags, groupFlags) | ||
if (notFound.length) return `未找到标记 ${notFound.join(', ')}。` | ||
for (const name of flags) { | ||
group.flag &= ~Group.Flag[name] | ||
} | ||
await group._update() | ||
return '群信息已修改。' | ||
}, ['flag']) | ||
|
||
GroupAction.add('setAssignee', async (session, group, _assignee) => { | ||
const assignee = _assignee ? +_assignee : session.selfId | ||
if (!isInteger(assignee) || assignee < 0) return '参数错误。' | ||
group.assignee = assignee | ||
await group._update() | ||
return '群信息已修改。' | ||
}, ['assignee']) | ||
|
||
return command | ||
} | ||
|
||
export function apply(ctx: Context) { | ||
ctx.command('admin <action> [...args]', '管理用户', { authority: 4 }) | ||
.userFields(['authority']) | ||
.before(session => !session.$app.database) | ||
.option('user', '-u [user] 指定目标用户') | ||
.option('group', '-g [group] 指定目标群') | ||
.option('thisGroup', '-G, --this-group 指定目标群为本群') | ||
.action(async ({ session, options }, name, ...args) => { | ||
const isGroup = 'group' in options || 'thisGroup' in options | ||
if ('user' in options && isGroup) return '不能同时目标为指定用户和群。' | ||
|
||
const actionMap = isGroup ? GroupAction.commands : UserAction.commands | ||
const actionList = Object.keys(actionMap).map(paramCase).join(', ') | ||
if (!name) return `当前的可用指令有:${actionList}。` | ||
|
||
const action = actionMap[paramCase(name)] | ||
if (!action) return `指令未找到。当前的可用指令有:${actionList}。` | ||
|
||
if (isGroup) { | ||
const fields = action.fields ? action.fields.slice() as Group.Field[] : Group.fields | ||
let group: Group.Observed | ||
if (options.thisGroup) { | ||
group = await session.$observeGroup(fields) | ||
} else if (isInteger(options.group) && options.group > 0) { | ||
const data = await ctx.database.getGroup(options.group, fields) | ||
if (!data) return '未找到指定的群。' | ||
group = observe(data, diff => ctx.database.setGroup(options.group, diff), `group ${options.group}`) | ||
} | ||
return (action as ActionItem<Group>).callback.call(ctx, session, group, ...args) | ||
} else { | ||
const fields = action.fields ? action.fields.slice() as User.Field[] : User.fields | ||
if (!fields.includes('authority')) fields.push('authority') | ||
let user: User.Observed | ||
if (options.user) { | ||
const qq = getTargetId(options.user) | ||
if (!qq) return '未指定目标。' | ||
const data = await ctx.database.getUser(qq, -1, fields) | ||
if (!data) return '未找到指定的用户。' | ||
if (qq === session.userId) { | ||
user = await session.$observeUser(fields) | ||
} else if (session.$user.authority <= data.authority) { | ||
return '权限不足。' | ||
} else { | ||
user = observe(data, diff => ctx.database.setUser(qq, diff), `user ${qq}`) | ||
} | ||
} else { | ||
user = await session.$observeUser(fields) | ||
} | ||
return (action as ActionItem<User>).callback.call(ctx, session, user, ...args) | ||
ctx.command('user', '用户管理', { authority: 3 }) | ||
ctx.command('group', '群管理', { authority: 3 }) | ||
|
||
ctx.command('user.auth <value>', '权限信息', { authority: 4 }) | ||
.adminUser(({ session, target }, value) => { | ||
const authority = Number(value) | ||
if (!isInteger(authority) || authority < 0) return '参数错误。' | ||
if (authority >= session.$user.authority) return '权限不足。' | ||
target.authority = authority | ||
}) | ||
|
||
ctx.command('user.flag [-s|-S] [...flags]', '标记信息', { authority: 3 }) | ||
.userFields(['flag']) | ||
.option('set', '-s 添加标记', { authority: 4 }) | ||
.option('unset', '-S 删除标记', { authority: 4 }) | ||
.adminUser(flagAction) | ||
|
||
ctx.command('user.usage [key]', '调用次数信息') | ||
.userFields(['usage']) | ||
.option('set', '-s 设置调用次数', { authority: 4 }) | ||
.option('clear', '-c 清空调用次数', { authority: 4 }) | ||
.adminUser(({ target, options }, name, value) => { | ||
if (options.clear) { | ||
name ? delete target.usage[name] : target.usage = {} | ||
return | ||
} | ||
|
||
if (options.set) { | ||
if (value === undefined) return '参数不足。' | ||
const count = +value | ||
if (!isInteger(count) || count < 0) return '参数错误。' | ||
target.usage[name] = count | ||
return | ||
} | ||
|
||
if (name) return `今日 ${name} 功能的调用次数为:${target.usage[name] || 0}` | ||
const output: string[] = [] | ||
for (const name of Object.keys(target.usage).sort()) { | ||
if (name.startsWith('$')) continue | ||
output.push(`${name}:${target.usage[name]}`) | ||
} | ||
if (!output.length) return '今日没有调用过消耗次数的功能。' | ||
output.unshift('今日各功能的调用次数为:') | ||
return output.join('\n') | ||
}) | ||
|
||
ctx.command('user.timer [key]', '定时器信息', { authority: 3 }) | ||
.userFields(['timers']) | ||
.option('set', '-s 设置定时器', { authority: 4 }) | ||
.option('clear', '-c 清空定时器', { authority: 4 }) | ||
.adminUser(({ target, options }, name, value) => { | ||
if (options.clear) { | ||
name ? delete target.timers[name] : target.timers = {} | ||
return | ||
} | ||
|
||
if (options.set) { | ||
if (value === undefined) return '参数不足。' | ||
const timestamp = +Time.parseDate(value) | ||
if (!timestamp) return '请输入合法的时间。' | ||
target.timers[name] = timestamp | ||
return | ||
} | ||
|
||
const now = Date.now() | ||
if (name) { | ||
const delta = target.timers[name] - now | ||
if (delta > 0) return `定时器 ${name} 的生效时间为:剩余 ${Time.formatTime(delta)}` | ||
return `定时器 ${name} 当前并未生效。` | ||
} | ||
const output: string[] = [] | ||
for (const name of Object.keys(target.timers).sort()) { | ||
if (name.startsWith('$')) continue | ||
output.push(`${name}:剩余 ${Time.formatTime(target.timers[name] - now)}`) | ||
} | ||
if (!output.length) return '当前没有生效的定时器。' | ||
output.unshift('各定时器的生效时间为:') | ||
return output.join('\n') | ||
}) | ||
|
||
ctx.command('group.assign [bot]', '受理者账号', { authority: 4 }) | ||
.groupFields(['assignee']) | ||
.adminGruop(({ session, target }, value) => { | ||
const assignee = value ? +value : session.selfId | ||
if (!isInteger(assignee) || assignee < 0) return '参数错误。' | ||
target.assignee = assignee | ||
}) | ||
|
||
ctx.command('group.flag [-s|-S] [...flags]', '标记信息', { authority: 3 }) | ||
.groupFields(['flag']) | ||
.option('set', '-s 添加标记', { authority: 4 }) | ||
.option('unset', '-S 删除标记', { authority: 4 }) | ||
.adminGruop(flagAction) | ||
} |
Oops, something went wrong.