@koishi-ce/plugin-broadcast 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets/zh-CN-Bnc3gtS6.yml +8 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.mjs +32 -0
- package/locales/de-DE.yml +8 -0
- package/locales/en-US.yml +8 -0
- package/locales/fr-FR.yml +8 -0
- package/locales/ja-JP.yml +8 -0
- package/locales/ru-RU.yml +8 -0
- package/locales/zh-CN.yml +8 -0
- package/locales/zh-TW.yml +8 -0
- package/package.json +66 -0
- package/src/index.test.ts +65 -0
- package/src/index.ts +50 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Context, Schema } from "@koishi-ce/koishi";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/** 配置项(当前无可用配置) */
|
|
4
|
+
type Config = Record<never, never>;
|
|
5
|
+
declare const name = "broadcast";
|
|
6
|
+
declare const inject: string[];
|
|
7
|
+
declare const Config: Schema<Config>;
|
|
8
|
+
declare function apply(ctx: Context): void;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Config, apply, inject, name };
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Channel, Schema } from "@koishi-ce/koishi";
|
|
2
|
+
import zhCN from "./assets/zh-CN-Bnc3gtS6.yml";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* 全体广播插件(broadcast)。
|
|
6
|
+
*
|
|
7
|
+
* 提供 `broadcast <message>` 指令(权限 4):向全部频道推送消息,
|
|
8
|
+
* 默认跳过标记为静默(silent)的频道。
|
|
9
|
+
* 选项:`-f` 强制发送到静默频道;`-o` 仅发送到当前 bot 被指派的频道。
|
|
10
|
+
* 依赖数据库(channel 表的 assignee / flag 字段)。
|
|
11
|
+
*/
|
|
12
|
+
const name = "broadcast";
|
|
13
|
+
const inject = ["database"];
|
|
14
|
+
const Config = Schema.object({});
|
|
15
|
+
function apply(ctx) {
|
|
16
|
+
ctx.i18n.define("zh-CN", zhCN);
|
|
17
|
+
ctx.command("broadcast <message:text>", { authority: 4 }).option("forced", "-f").option("only", "-o").action(async ({ options, session }, message) => {
|
|
18
|
+
if (!session || !options) return;
|
|
19
|
+
if (!message) return session.text(".expect-text");
|
|
20
|
+
if (!options.only) {
|
|
21
|
+
await ctx.broadcast(message, Boolean(options.forced));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const fields = ["id"];
|
|
25
|
+
if (!options.forced) fields.push("flag");
|
|
26
|
+
let channels = await ctx.database.getAssignedChannels(fields, { [session.platform]: [session.selfId] });
|
|
27
|
+
if (!options.forced) channels = channels.filter((g) => !(g.flag & Channel.Flag.silent));
|
|
28
|
+
await session.bot.broadcast(channels.map((channel) => channel.id), message);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { Config, apply, inject, name };
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@koishi-ce/plugin-broadcast",
|
|
3
|
+
"description": "Broadcast messages in Koishi",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.mjs",
|
|
7
|
+
"typings": "lib/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"lib",
|
|
10
|
+
"src",
|
|
11
|
+
"locales"
|
|
12
|
+
],
|
|
13
|
+
"contributors": [
|
|
14
|
+
"Shigma <shigma10826@gmail.com>",
|
|
15
|
+
"Oppenheymu <oppenheymu@gmail.com>"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Koishi-CE/koishi.git",
|
|
21
|
+
"directory": "plugins/common/broadcast"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Koishi-CE/koishi/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://koishi.chat/plugins/common/broadcast.html",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"bot",
|
|
29
|
+
"chatbot",
|
|
30
|
+
"koishi",
|
|
31
|
+
"plugin",
|
|
32
|
+
"broadcast"
|
|
33
|
+
],
|
|
34
|
+
"koishi": {
|
|
35
|
+
"category": "tool",
|
|
36
|
+
"description": {
|
|
37
|
+
"en": "Broadcast messages",
|
|
38
|
+
"zh": "发送广播消息"
|
|
39
|
+
},
|
|
40
|
+
"service": {
|
|
41
|
+
"required": [
|
|
42
|
+
"database"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"locales": [
|
|
46
|
+
"zh"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"koishi": "^4.18.11"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@koishi-ce/plugin-mock": "^1.0.0",
|
|
54
|
+
"@minatojs/driver-memory": "^3.7.0",
|
|
55
|
+
"@koishi-ce/koishi": "^1.0.0",
|
|
56
|
+
"minato": "^3.7.0"
|
|
57
|
+
},
|
|
58
|
+
"exports": {
|
|
59
|
+
".": {
|
|
60
|
+
"source": "./src/index.ts",
|
|
61
|
+
"types": "./lib/index.d.ts",
|
|
62
|
+
"import": "./lib/index.mjs",
|
|
63
|
+
"default": "./lib/index.mjs"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* broadcast 插件测试:用两个 mock bot 验证全局广播、
|
|
3
|
+
* 仅本 bot 广播(-o)与静默频道过滤(-f)的目标频道集合。
|
|
4
|
+
*/
|
|
5
|
+
import { beforeAll, describe, expect, it, jest } from "bun:test";
|
|
6
|
+
import { App, type Bot, Channel } from "@koishi-ce/koishi";
|
|
7
|
+
import * as broadcast from "@koishi-ce/plugin-broadcast";
|
|
8
|
+
import mock from "@koishi-ce/plugin-mock";
|
|
9
|
+
import memory from "@minatojs/driver-memory";
|
|
10
|
+
|
|
11
|
+
const app = new App({
|
|
12
|
+
delay: { broadcast: 0 },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
app.plugin(mock, { selfId: "514" });
|
|
16
|
+
app.plugin(mock, { selfId: "114" });
|
|
17
|
+
app.plugin(memory);
|
|
18
|
+
app.plugin(broadcast);
|
|
19
|
+
|
|
20
|
+
const client = app.mock.client("123");
|
|
21
|
+
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
await app.start();
|
|
24
|
+
await app.mock.initUser("123", 4);
|
|
25
|
+
await app.mock.initChannel("111", "114");
|
|
26
|
+
await app.mock.initChannel("222", "514");
|
|
27
|
+
await app.mock.initChannel("333", "514", { flag: Channel.Flag.silent });
|
|
28
|
+
await app.mock.initChannel("444", "810");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("@koishi-ce/plugin-broadcast", () => {
|
|
32
|
+
// 拦截各 bot 的 sendMessage,断言广播命中了正确的频道且静默频道被正确跳过或强制发送
|
|
33
|
+
it("basic support", async () => {
|
|
34
|
+
// 在拦截前验证缺参数提示(此时回复链路完整,消息能送达 mock 客户端)
|
|
35
|
+
await client.shouldReply("broadcast", "请输入要发送的文本。");
|
|
36
|
+
|
|
37
|
+
// 替换 sendMessage 为仅记录参数的 mock;返回空 ID 列表以维持 bot.broadcast 的展开推送
|
|
38
|
+
const send1 = (app.bots.find((bot) => bot.selfId === "514")!.sendMessage =
|
|
39
|
+
jest.fn<Bot["sendMessage"]>(async () => []));
|
|
40
|
+
const send2 = (app.bots.find((bot) => bot.selfId === "114")!.sendMessage =
|
|
41
|
+
jest.fn<Bot["sendMessage"]>(async () => []));
|
|
42
|
+
|
|
43
|
+
await client.shouldNotReply("broadcast foo");
|
|
44
|
+
// 全局广播:两个 bot 各发送被指派的频道,静默频道 333 被跳过
|
|
45
|
+
expect(send1.mock.calls).toHaveLength(1);
|
|
46
|
+
expect(send1.mock.calls[0]?.[0]).toBe("222");
|
|
47
|
+
expect(send2.mock.calls).toHaveLength(1);
|
|
48
|
+
expect(send2.mock.calls[0]?.[0]).toBe("111");
|
|
49
|
+
send1.mockClear();
|
|
50
|
+
send2.mockClear();
|
|
51
|
+
|
|
52
|
+
await client.shouldNotReply("broadcast -o foo");
|
|
53
|
+
// 仅本 bot:只发送当前 bot(514)被指派的频道,114 不发送
|
|
54
|
+
expect(send1.mock.calls).toHaveLength(1);
|
|
55
|
+
expect(send1.mock.calls[0]?.[0]).toBe("222");
|
|
56
|
+
expect(send2.mock.calls).toHaveLength(0);
|
|
57
|
+
send1.mockClear();
|
|
58
|
+
|
|
59
|
+
await client.shouldNotReply("broadcast -of foo");
|
|
60
|
+
// 强制模式:静默频道 333 也被发送
|
|
61
|
+
expect(send1.mock.calls).toHaveLength(2);
|
|
62
|
+
expect(send1.mock.calls[0]?.[0]).toBe("222");
|
|
63
|
+
expect(send1.mock.calls[1]?.[0]).toBe("333");
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全体广播插件(broadcast)。
|
|
3
|
+
*
|
|
4
|
+
* 提供 `broadcast <message>` 指令(权限 4):向全部频道推送消息,
|
|
5
|
+
* 默认跳过标记为静默(silent)的频道。
|
|
6
|
+
* 选项:`-f` 强制发送到静默频道;`-o` 仅发送到当前 bot 被指派的频道。
|
|
7
|
+
* 依赖数据库(channel 表的 assignee / flag 字段)。
|
|
8
|
+
*/
|
|
9
|
+
import { Channel, type Context, Schema } from "@koishi-ce/koishi";
|
|
10
|
+
import zhCN from "../locales/zh-CN.yml";
|
|
11
|
+
|
|
12
|
+
/** 配置项(当前无可用配置) */
|
|
13
|
+
export type Config = Record<never, never>;
|
|
14
|
+
|
|
15
|
+
export const name = "broadcast";
|
|
16
|
+
export const inject = ["database"];
|
|
17
|
+
export const Config: Schema<Config> = Schema.object({});
|
|
18
|
+
|
|
19
|
+
export function apply(ctx: Context) {
|
|
20
|
+
ctx.i18n.define("zh-CN", zhCN);
|
|
21
|
+
|
|
22
|
+
ctx
|
|
23
|
+
.command("broadcast <message:text>", { authority: 4 })
|
|
24
|
+
.option("forced", "-f")
|
|
25
|
+
.option("only", "-o")
|
|
26
|
+
.action(async ({ options, session }, message) => {
|
|
27
|
+
if (!session || !options) return;
|
|
28
|
+
if (!message) return session.text(".expect-text");
|
|
29
|
+
if (!options.only) {
|
|
30
|
+
// 非仅本 bot 模式:交给全局广播(覆盖所有 bot 的指派频道)
|
|
31
|
+
await ctx.broadcast(message, Boolean(options.forced));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// -o 模式:只取当前平台、当前 bot 被指派的频道;非强制时需 flag 字段用于过滤静默频道
|
|
36
|
+
const fields: ("id" | "flag")[] = ["id"];
|
|
37
|
+
if (!options.forced) fields.push("flag");
|
|
38
|
+
let channels = await ctx.database.getAssignedChannels(fields, {
|
|
39
|
+
[session.platform]: [session.selfId],
|
|
40
|
+
});
|
|
41
|
+
if (!options.forced) {
|
|
42
|
+
channels = channels.filter((g) => !(g.flag & Channel.Flag.silent));
|
|
43
|
+
}
|
|
44
|
+
await session.bot.broadcast(
|
|
45
|
+
channels.map((channel) => channel.id),
|
|
46
|
+
message,
|
|
47
|
+
);
|
|
48
|
+
return undefined;
|
|
49
|
+
});
|
|
50
|
+
}
|