koishi-plugin-common
Advanced tools
Comparing version 0.1.4 to 0.1.5
@@ -8,6 +8,8 @@ import { GroupContext, Context } from 'koishi-core'; | ||
interface AuthorizeOptions { | ||
authority: number; | ||
memberAuthority?: number; | ||
adminAuthority?: number; | ||
ownerAuthority?: number; | ||
} | ||
export declare function authorize(ctx: GroupContext, { authority }: AuthorizeOptions): void; | ||
export default function apply(ctx: Context, authorityMap?: Record<number, number>): void; | ||
export declare function authorize(ctx: GroupContext, options: AuthorizeOptions): void; | ||
export default function apply(ctx: Context, authorityMap?: Record<number, number | AuthorizeOptions>): void; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const koishi_core_1 = require("koishi-core"); | ||
const koishi_utils_1 = require("koishi-utils"); | ||
koishi_core_1.injectMethods('mysql', { | ||
@@ -11,25 +12,36 @@ async getUsersWithAuthorityBelow(ids, authority) { | ||
}); | ||
function authorize(ctx, { authority }) { | ||
function authorize(ctx, options) { | ||
koishi_core_1.assertContextType(ctx, 'group'); | ||
if (!('memberAuthority' in options)) | ||
options.memberAuthority = 1; | ||
if (!('adminAuthority' in options)) | ||
options.adminAuthority = options.memberAuthority; | ||
if (!('ownerAuthority' in options)) | ||
options.ownerAuthority = options.adminAuthority; | ||
ctx.app.receiver.once('connected', async () => { | ||
await ctx.database.getGroup(ctx.id, ctx.app.options.selfId); | ||
const memberIds = (await ctx.sender.getGroupMemberList(ctx.id)).map(m => m.userId); | ||
const users = ctx.app.database.getUsersWithAuthorityBelow | ||
? await ctx.app.database.getUsersWithAuthorityBelow(memberIds, authority) | ||
: await ctx.app.database.getUsers(memberIds, ['id', 'authority']); | ||
const userIds = users.map(u => u.id); | ||
const insertIds = memberIds.filter((id) => !userIds.includes(id)); | ||
const updateIds = memberIds.filter((id) => { | ||
const user = users.find(u => u.id === id); | ||
return user && user.authority < authority; | ||
}); | ||
for (const id of insertIds) { | ||
await ctx.database.getUser(id, authority); | ||
const memberList = await ctx.sender.getGroupMemberList(ctx.id); | ||
for (const role of ['member', 'admin', 'owner']) { | ||
const authority = options[role + 'Authority']; | ||
const memberIds = memberList.filter(m => m.role === role).map(m => m.userId); | ||
const users = ctx.app.database.getUsersWithAuthorityBelow | ||
? await ctx.app.database.getUsersWithAuthorityBelow(memberIds, authority) | ||
: await ctx.app.database.getUsers(memberIds, ['id', 'authority']); | ||
const userIds = users.map(u => u.id); | ||
const insertIds = koishi_utils_1.complement(memberIds, userIds); | ||
const updateIds = memberIds.filter((id) => { | ||
const user = users.find(u => u.id === id); | ||
return user && user.authority < authority; | ||
}); | ||
for (const id of insertIds) { | ||
await ctx.database.getUser(id, authority); | ||
} | ||
for (const id of updateIds) { | ||
await ctx.database.setUser(id, { authority }); | ||
} | ||
} | ||
for (const id of updateIds) { | ||
await ctx.database.setUser(id, { authority }); | ||
} | ||
}); | ||
ctx.receiver.on('group_increase', updateAuthority); | ||
async function updateAuthority({ userId }) { | ||
async function updateAuthority({ userId, role }) { | ||
const authority = options[role + 'Authority']; | ||
const user = await ctx.database.getUser(userId, authority); | ||
@@ -44,5 +56,9 @@ if (user.authority < authority) { | ||
for (const id in authorityMap) { | ||
ctx.app.group(+id).plugin(authorize, { authority: authorityMap[id] }); | ||
const config = authorityMap[id]; | ||
if (typeof config === 'number') { | ||
authorityMap[id] = { memberAuthority: config }; | ||
} | ||
ctx.app.group(+id).plugin(authorize, authorityMap[id]); | ||
} | ||
} | ||
exports.default = apply; |
import { Context, CommandConfig } from 'koishi-core'; | ||
export declare const CODE_STOP = 0; | ||
export declare const CODE_RESTART = -1; | ||
export default function apply(ctx: Context, options: CommandConfig): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.CODE_STOP = 0; | ||
exports.CODE_RESTART = -1; | ||
const CODE_STOP = 0; | ||
const CODE_RESTART = 1; | ||
function apply(ctx, options) { | ||
ctx.command('exit', '停止运行', { authority: 4, ...options }) | ||
.option('-c, --code [code]', '设置 exit code') | ||
.shortcut('关机', { prefix: true, options: { code: exports.CODE_STOP } }) | ||
.shortcut('重启', { prefix: true, options: { code: exports.CODE_RESTART } }) | ||
ctx.command('exit', '停止机器人运行', { authority: 4, ...options }) | ||
.option('-c, --code [code]', '设置 exit code', { default: 0 }) | ||
.shortcut('关机', { prefix: true, options: { code: CODE_STOP } }) | ||
.shortcut('重启', { prefix: true, options: { code: CODE_RESTART } }) | ||
.action(({ options }) => { | ||
@@ -11,0 +11,0 @@ process.exit(+options.code); |
{ | ||
"name": "koishi-plugin-common", | ||
"version": "0.1.4", | ||
"version": "0.1.5", | ||
"main": "dist/index.js", | ||
@@ -30,2 +30,2 @@ "typings": "dist/index.d.ts", | ||
} | ||
} | ||
} |
340
README.md
@@ -1,2 +0,338 @@ | ||
# plugin-common | ||
# [koishi-plugin-common](https://koishijs.github.io/plugins/common.html) | ||
koishi-plugin-common 包含了一些基本插件,它们在你使用 `koishi` 库时是默认安装的。尽管如此,你仍然可以在 `koishi.config.js` 中显式地配置或禁用其中的一些功能: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
// 禁用此功能 | ||
repeater: false, | ||
// 配置应答器 | ||
replies: [{ | ||
match: /^(.+)一时爽$/, | ||
reply: (_, str) => `一直${str}一直爽`, | ||
}], | ||
}], | ||
} | ||
``` | ||
下面将介绍每个插件的功能。 | ||
## 插件:repeater | ||
repeater 插件会自动监测聊天记录,并根据你的配置做出反应。可能的反应包括复读、打断复读、检测重复复读、检测打断复读。你还可以配置每个行为触发的条件和对应的输出文本。repeater 的完整配置如下: | ||
```ts | ||
type SessionSwitch = boolean | ((repeated: boolean, times: number) => boolean) | ||
type SessionText = string | ((userId: number, message: string) => string) | ||
interface RepeaterOptions { | ||
repeat: SessionSwitch | ||
interrupt: SessionSwitch | ||
repeatCheck: SessionSwitch | ||
interruptCheck: SessionSwitch | ||
interruptText: SessionText | ||
repeatCheckText: SessionText | ||
interruptCheckText: SessionText | ||
} | ||
``` | ||
例如,如果你想让你的 Bot 在一条信息已经复读过 5 次以上,且自己也已经复读过后,对任何打断复读的人以 50% 的概率出警,你可以这样配置: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
repeater: { | ||
repeatCheck: (repeated, times) => repeated && times >= 5 && Math.random() > 0.5, | ||
repeatCheckText: (userId) => `[CQ:at,qq=${userId}] 在?为什么打断复读?`, | ||
}, | ||
}], | ||
} | ||
``` | ||
::: tip 提示 | ||
这个插件的默认行为已经包含了复读、检测重复复读和检测打断复读,但是不包含打断复读。如果希望修改这种行为,你需要手动覆盖这其中的部分配置。 | ||
::: | ||
## 插件:respondent | ||
respondent 插件允许设置一套内置问答,就像这样: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
respondent: [{ | ||
match: 'awsl', | ||
reply: '爱我苏联', | ||
}, { | ||
match: /^(.+)一时爽$/, | ||
reply: (_, str) => `一直${str}一直爽`, | ||
}], | ||
}], | ||
} | ||
``` | ||
其中 `match` 可以是一个字符串或正则表达式,用来表示要匹配的内容;`reply` 可以是一个字符串或传入字符串的函数,用来表示输出的结果。`respondent` 数组会按照从上到下的顺序进行匹配。 | ||
如果想要加入更高级和用户可定义的问答系统,可以参见 [koishi-plugin-teach](https://github.com/koishijs/../../../../plugin-teach/LICENSE)。 | ||
## 插件:welcome | ||
welcome 插件用于欢迎群中的新人。欢迎信息默认是“欢迎新大佬 @XXX!”。你也可以手动设置欢迎信息的内容: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
welcome: ({ userId }) => `欢迎新大佬 [CQ:at,qq=${userId}]!群地位-1`, | ||
}], | ||
} | ||
``` | ||
## 插件:authorize | ||
authorize 插件用于设置某个群中默认的玩家权限: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
authorize: { | ||
// 设置全群玩家权限为 2 级 | ||
111222333: 2, | ||
// 默认行为:全群玩家权限为 1 级 | ||
444555666: {}, | ||
// 分别设置每类成员 | ||
777888999: { | ||
memberAuthority: 1, | ||
adminAuthority: 2, | ||
ownerAuthority: 3, | ||
}, | ||
} | ||
}], | ||
} | ||
``` | ||
这里的权限设置不仅会在机器人每次启动时生效,也会在有人加群时生效。 | ||
::: warning 注意 | ||
由于 CoolQ 的机制问题,机器人刚加某个群时可能无法获取成员列表,从而导致插件无法运行。遇到这种情况一般等待 1-2 天即可恢复正常。 | ||
::: | ||
## 插件:requestHandler | ||
requestHandler 插件用于处理好友和群申请。默认情况下 Koishi 会通过所有 1 级以上用户的好友申请,忽略所有群申请。你可以手动设置忽略和通过的函数: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
// requestHandler 的配置被拆分成了这三个 | ||
handleFriend: true, // 通过所有好友申请 | ||
handleGroupAdd: undefined, // 忽略所有加群申请(当然这没必要写出来) | ||
async handleGroupInvite (meta) { | ||
// 拒绝所有来自 1 级以下,通过所有来自 3 级或以上权限用户的加群邀请,其他不处理 | ||
const user = await ctx.database.getUser(meta.userId, 0, ['authority']) | ||
if (user.authority >= 3) { | ||
return true | ||
} else if (user.authority <= 1) { | ||
return ctx.sender.setGroupAddRequest(meta.flag, 'invite', false) | ||
} | ||
}, | ||
}], | ||
} | ||
``` | ||
## 指令:help | ||
help 指令用于输出全部或特定指令的使用方法。你可以这样调用它: | ||
```sh | ||
help # 打印一级指令列表 | ||
help -s # 打印快捷方式列表 | ||
help command-name # 显示指令的使用方法,同时会打印该指令下一级指令列表 | ||
``` | ||
## 指令:echo | ||
echo 指令用于发送一段文本。你可以这样调用它: | ||
```sh | ||
echo foo bar # 向你发送 foo bar | ||
echo -u 123 foo bar # 向用户 123 私聊发送 foo bar | ||
echo -g 456,789 foo bar # 向群 456 和 789 同时发送 foo bar | ||
``` | ||
## 指令:broadcast | ||
broadcast 指令用于向所有 Bot 所在的群发送一段文本。你可以这样调用它: | ||
```sh | ||
broadcast foo bar baz # 向所有群发送 foo bar baz | ||
``` | ||
这看起来只是 echo 的一个简写版本,但实际上这两个指令有下面的差别: | ||
- 对于多个 App 实例同时运行的情况,echo 只会让收到调用的 Bot 发送信息,broadcast 会同时控制所有 Bot 发送信息 | ||
- echo 的发送信息是几乎同时的,而 broadcast 会让每个要发送信息的 Bot 按照一定的时间间隔发送,这个时间间隔可以显式地设置: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
broadcast: { | ||
broadcastInterval: 1000, // 默认值为 1s | ||
}, | ||
}], | ||
} | ||
``` | ||
## 指令:contextify | ||
contextify 指令可以让你临时切换上下文调用指令。例如这样(假设你在私聊上下文): | ||
```sh | ||
teach foo bar # 无效,因为 teach 指令只对群上下文生效 | ||
contextify -g 456 teach foo bar # 有效,相当于在群 456 调用 teach foo bar | ||
``` | ||
尽管切换了调用上下文,但 contextify 指令的输出仍然产生在原上下文中。这在你想调用群指令的时候是很有用的。 | ||
## 指令:exit | ||
exit 指令可以让你退出或重启机器人进程: | ||
```sh | ||
Koishi,关机 # 退出进程,相当于 exit -c 0 | ||
Koishi,重启 # 重启进程,相当于 exit -c 1 | ||
``` | ||
::: tip 提示 | ||
Koishi 的命令行工具使用**子进程**来实现对机器人的管理。当子进程退出时,主进程可以通过查看 exit code 来确定退出的原因,并执行相应的操作。 | ||
::: | ||
## 指令:info | ||
info 指令用于查看用户的信息: | ||
```sh | ||
info # 查看自己的用户信息 | ||
info -u 123456789 # 查看其他用户的信息,需要额外的权限 | ||
``` | ||
如果你是插件开发者,你也可以为这个指令添加其他输出结果: | ||
```js | ||
const { registerUserInfo } = require('koishi-plugin-common') | ||
// registerUserInfo 传入两个参数 | ||
// 第一个参数是回调函数,传入用户数据,返回输出结果 | ||
// 第二个参数是需要用到的字段列表(可选) | ||
registerUserInfo(user => `余额:${user.money}¥`, ['money']) | ||
``` | ||
## 指令:likeme | ||
likeme 指令用于让四季酱为你点赞。点赞的次数可以自行配置(但不能超过 10 次): | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
likeme: { | ||
userFields: [/* 所需字段 */], | ||
likeCount (user) { | ||
return // 点赞次数 | ||
}, | ||
}, | ||
}], | ||
} | ||
``` | ||
::: warning 注意 | ||
由于 CoolQ 的限制,本功能只能由四季酱的好友使用。 | ||
::: | ||
## 指令:callme | ||
callme 指令用于修改四季酱对你的称呼。可以自行配置禁止使用的名称和响应的回复信息: | ||
```js | ||
module.exports = { | ||
plugins: ['common', { | ||
callme: { | ||
validateName (name, meta) { | ||
if (name === meta.$user.name) return '称呼未发生变化。' | ||
if (name.includes('foo')) return '称呼中禁止含有 foo。' | ||
}, | ||
}, | ||
}], | ||
} | ||
``` | ||
## 指令:admin | ||
admin 指令可以直接修改数据库中的数据。你可以这样调用它: | ||
```sh | ||
admin show-usage teach # 输出你今天 teach 指令的调用次数 | ||
admin -u 123 set-auth 2 # 将用户 123 的权限更改为 2 级 | ||
admin -g # 输出可用于群的操作列表 | ||
admin -g 456 set-flag no-emit # 设置群 456 不主动发送任何信息 | ||
``` | ||
一般的调用格式为 `admin [target] [action <...args>]`,其中: | ||
- `target` 用于指定针对目标,`-u <id>` 表示要修改的是用户,`-g <id>` 表示要修改的是群,缺省表示要修改的是自己 | ||
- `action` 用于指定要做的操作,对用户和群是不同的;缺省则会输出可用的操作列表 | ||
- `args` 是执行操作所需的参数,由 `action` 决定 | ||
如果你是插件开发者,你也可以为这个指令添加可用的操作: | ||
```js | ||
const { registerUserAction } = require('koishi-plugin-common') | ||
registerUserAction('my-action', (meta, user, ...args) => { | ||
user.foo = 'bar' | ||
user._update() | ||
return meta.$send('用户信息已成功修改!') | ||
}, ['foo']) | ||
``` | ||
## 指令:rank | ||
rank 指令可以输出用户排行。你可以这样调用它: | ||
```sh | ||
rank talkativeness # 输出聊天排行 | ||
聊天排行 # 输出聊天排行 | ||
rank wealth -g # 输出全服财富排行 | ||
全服财富排行 # 输出全服财富排行 | ||
``` | ||
如你所见,rank 指令注册了一个中间件,因此你通常不需要直接调用这个指令,只需输入“XX 排行”即可。全服排行可以通过加前缀“全服”来实现。当然,所有的这些都是可以自定义的: | ||
```ts | ||
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 | ||
} | ||
function registerRank (name: string, rank: Rank) | ||
``` | ||
例如,上面的“财富排行”可以这样实现: | ||
```js | ||
const { registerRank } = require('koishi-plugin-common') | ||
registerRank('wealth', { | ||
names: ['财富'], | ||
fields: ['money'], | ||
value: user => user.money, | ||
format: value => value + '¥', | ||
limit: 100, | ||
}) | ||
``` |
@@ -1,2 +0,3 @@ | ||
import { GroupContext, GroupMemberInfo, Context, assertContextType, injectMethods } from 'koishi-core' | ||
import { GroupContext, GroupMemberInfo, Context, assertContextType, injectMethods, GroupRole } from 'koishi-core' | ||
import { complement } from 'koishi-utils' | ||
import {} from 'koishi-database-mysql' | ||
@@ -19,28 +20,36 @@ | ||
interface AuthorizeOptions { | ||
authority: number | ||
memberAuthority?: number | ||
adminAuthority?: number | ||
ownerAuthority?: number | ||
} | ||
export function authorize (ctx: GroupContext, { authority }: AuthorizeOptions) { | ||
export function authorize (ctx: GroupContext, options: AuthorizeOptions) { | ||
assertContextType(ctx, 'group') | ||
if (!('memberAuthority' in options)) options.memberAuthority = 1 | ||
if (!('adminAuthority' in options)) options.adminAuthority = options.memberAuthority | ||
if (!('ownerAuthority' in options)) options.ownerAuthority = options.adminAuthority | ||
ctx.app.receiver.once('connected', async () => { | ||
await ctx.database.getGroup(ctx.id, ctx.app.options.selfId) | ||
const memberIds = (await ctx.sender.getGroupMemberList(ctx.id)).map(m => m.userId) | ||
const users = ctx.app.database.getUsersWithAuthorityBelow | ||
? await ctx.app.database.getUsersWithAuthorityBelow(memberIds, authority) | ||
: await ctx.app.database.getUsers(memberIds, ['id', 'authority']) | ||
const userIds = users.map(u => u.id) | ||
const insertIds = memberIds.filter((id) => !userIds.includes(id)) | ||
const updateIds = memberIds.filter((id) => { | ||
const user = users.find(u => u.id === id) | ||
return user && user.authority < authority | ||
}) | ||
for (const id of insertIds) { | ||
await ctx.database.getUser(id, authority) | ||
const memberList = await ctx.sender.getGroupMemberList(ctx.id) | ||
for (const role of ['member', 'admin', 'owner'] as GroupRole[]) { | ||
const authority = options[role + 'Authority'] | ||
const memberIds = memberList.filter(m => m.role === role).map(m => m.userId) | ||
const users = ctx.app.database.getUsersWithAuthorityBelow | ||
? await ctx.app.database.getUsersWithAuthorityBelow(memberIds, authority) | ||
: await ctx.app.database.getUsers(memberIds, ['id', 'authority']) | ||
const userIds = users.map(u => u.id) | ||
const insertIds = complement(memberIds, userIds) | ||
const updateIds = memberIds.filter((id) => { | ||
const user = users.find(u => u.id === id) | ||
return user && user.authority < authority | ||
}) | ||
for (const id of insertIds) { | ||
await ctx.database.getUser(id, authority) | ||
} | ||
for (const id of updateIds) { | ||
await ctx.database.setUser(id, { authority }) | ||
} | ||
} | ||
for (const id of updateIds) { | ||
await ctx.database.setUser(id, { authority }) | ||
} | ||
}) | ||
@@ -50,3 +59,4 @@ | ||
async function updateAuthority ({ userId }: GroupMemberInfo) { | ||
async function updateAuthority ({ userId, role }: GroupMemberInfo) { | ||
const authority = options[role + 'Authority'] | ||
const user = await ctx.database.getUser(userId, authority) | ||
@@ -59,6 +69,10 @@ if (user.authority < authority) { | ||
export default function apply (ctx: Context, authorityMap: Record<number, number> = {}) { | ||
export default function apply (ctx: Context, authorityMap: Record<number, number | AuthorizeOptions> = {}) { | ||
for (const id in authorityMap) { | ||
ctx.app.group(+id).plugin(authorize, { authority: authorityMap[id] }) | ||
const config = authorityMap[id] | ||
if (typeof config === 'number') { | ||
authorityMap[id] = { memberAuthority: config } | ||
} | ||
ctx.app.group(+id).plugin(authorize, authorityMap[id]) | ||
} | ||
} |
import { Context, CommandConfig } from 'koishi-core' | ||
export const CODE_STOP = 0 | ||
export const CODE_RESTART = -1 | ||
const CODE_STOP = 0 | ||
const CODE_RESTART = 1 | ||
export default function apply (ctx: Context, options: CommandConfig) { | ||
ctx.command('exit', '停止运行', { authority: 4, ...options }) | ||
.option('-c, --code [code]', '设置 exit code') | ||
ctx.command('exit', '停止机器人运行', { authority: 4, ...options }) | ||
.option('-c, --code [code]', '设置 exit code', { default: 0 }) | ||
.shortcut('关机', { prefix: true, options: { code: CODE_STOP } }) | ||
@@ -10,0 +10,0 @@ .shortcut('重启', { prefix: true, options: { code: CODE_RESTART } }) |
95190
1871
339