koishi-plugin-maple-broadcast 0.0.1

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/index.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const using: readonly ["database"];
3
+ declare module 'koishi' {
4
+ interface Tables {
5
+ broadcast_groups: BroadcastGroup;
6
+ broadcast_groups_bots: BroadcastGroupBot;
7
+ }
8
+ }
9
+ interface BroadcastGroup {
10
+ id: number;
11
+ name: string;
12
+ created_at: Date;
13
+ }
14
+ interface BroadcastGroupBot {
15
+ id: number;
16
+ group_id: number;
17
+ guildId: string;
18
+ guildName: string;
19
+ platform: string;
20
+ created_at: Date;
21
+ }
22
+ export declare const name = "maple-broadcast";
23
+ export interface Config {
24
+ broadcastInterval: number;
25
+ broadcastTimeout: number;
26
+ }
27
+ export declare const Config: Schema<Config>;
28
+ export declare function apply(ctx: Context, config: Config): void;
29
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,370 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ var __export = (target, all) => {
7
+ for (var name2 in all)
8
+ __defProp(target, name2, { get: all[name2], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ Config: () => Config,
24
+ apply: () => apply,
25
+ name: () => name,
26
+ using: () => using
27
+ });
28
+ module.exports = __toCommonJS(src_exports);
29
+ var import_koishi = require("koishi");
30
+ var using = ["database"];
31
+ var name = "maple-broadcast";
32
+ var Config = import_koishi.Schema.object({
33
+ broadcastInterval: import_koishi.Schema.number().default(1e3).description("广播发送间隔(毫秒)").min(100).max(1e4),
34
+ broadcastTimeout: import_koishi.Schema.number().default(30).description("广播发送超时时间(秒)").min(5).max(300)
35
+ });
36
+ function separator() {
37
+ return "──────────";
38
+ }
39
+ __name(separator, "separator");
40
+ async function getQQGroupName(ctx, guildId) {
41
+ try {
42
+ const bot = ctx.bots.find((bot2) => bot2.platform === "onebot");
43
+ if (bot) {
44
+ const guild = await bot.getGuild(guildId);
45
+ return guild?.name || `群#${guildId}`;
46
+ }
47
+ } catch (error) {
48
+ }
49
+ return `群#${guildId}`;
50
+ }
51
+ __name(getQQGroupName, "getQQGroupName");
52
+ function apply(ctx, config) {
53
+ ctx.model.extend("broadcast_groups", {
54
+ id: "unsigned",
55
+ name: "string",
56
+ created_at: "timestamp"
57
+ }, {
58
+ primary: "id",
59
+ autoInc: true,
60
+ unique: ["name"]
61
+ });
62
+ ctx.model.extend("broadcast_groups_bots", {
63
+ id: "unsigned",
64
+ group_id: "unsigned",
65
+ guildId: "string",
66
+ guildName: "string",
67
+ platform: "string",
68
+ created_at: "timestamp"
69
+ }, {
70
+ primary: "id",
71
+ autoInc: true,
72
+ foreign: {
73
+ group_id: ["broadcast_groups", "id"]
74
+ }
75
+ });
76
+ const broadcast = ctx.command("广播", { authority: 2 }).alias("bc").usage("广播管理插件");
77
+ ctx.command("广播/广播分组", { authority: 2 }).alias("bc/group").usage("广播分组管理");
78
+ ctx.command("广播/广播群", { authority: 2 }).alias("bc/bot").usage("广播群管理");
79
+ ctx.command("广播/广播分组/添加广播分组 <分组名:string>", { authority: 2 }).example("添加广播分组 攻略组").example("添加广播分组 活动组").action(async ({ session }, 分组名) => {
80
+ if (!分组名) return "请输入分组名";
81
+ try {
82
+ await ctx.database.create("broadcast_groups", {
83
+ name: 分组名,
84
+ created_at: /* @__PURE__ */ new Date()
85
+ });
86
+ return `广播分组 "${分组名}" 创建成功`;
87
+ } catch (error) {
88
+ if (error.code === 2067) {
89
+ return `分组名 "${分组名}" 已存在`;
90
+ }
91
+ return "创建分组时发生错误";
92
+ }
93
+ });
94
+ ctx.command("广播/广播分组/查看广播分组 [分组名:string]", { authority: 2 }).example("查看广播分组").example("查看广播分组 攻略组").action(async ({ session }, 分组名) => {
95
+ let groups;
96
+ if (分组名) {
97
+ groups = await ctx.database.get("broadcast_groups", { name: 分组名 });
98
+ } else {
99
+ groups = await ctx.database.get("broadcast_groups", {});
100
+ }
101
+ if (groups.length === 0) {
102
+ return 分组名 ? `未找到分组 "${分组名}"` : "暂无广播分组";
103
+ }
104
+ const results = [];
105
+ for (const group of groups) {
106
+ const bots = await ctx.database.get("broadcast_groups_bots", {
107
+ group_id: group.id
108
+ });
109
+ results.push(separator());
110
+ results.push(`【${group.name}】(共 ${bots.length} 个群)`);
111
+ if (bots.length > 0) {
112
+ bots.forEach((bot, index) => {
113
+ results.push(`${index + 1}. [#${bot.id}] ${bot.guildName} ${bot.guildId}`);
114
+ });
115
+ } else {
116
+ results.push("(暂无群)");
117
+ }
118
+ }
119
+ return results.join("\n");
120
+ });
121
+ ctx.command("广播/广播分组/删除广播分组 <分组名:string>", { authority: 2 }).example("删除广播分组 测试组").action(async ({ session }, 分组名) => {
122
+ if (!分组名) return "请输入分组名";
123
+ const group = await ctx.database.get("broadcast_groups", { name: 分组名 });
124
+ if (group.length === 0) {
125
+ return `未找到分组 "${分组名}"`;
126
+ }
127
+ await ctx.database.remove("broadcast_groups_bots", {
128
+ group_id: group[0].id
129
+ });
130
+ await ctx.database.remove("broadcast_groups", {
131
+ name: 分组名
132
+ });
133
+ return `广播分组 "${分组名}" 已删除`;
134
+ });
135
+ ctx.command("广播/广播分组/修改广播分组 <旧分组名:string> <新分组名:string>", { authority: 2 }).example("修改广播分组 旧分组 新分组").action(async ({ session }, 旧分组名, 新分组名) => {
136
+ if (!旧分组名 || !新分组名) return "请提供旧分组名和新分组名";
137
+ if (旧分组名 === 新分组名) {
138
+ return "新旧分组名相同";
139
+ }
140
+ const group = await ctx.database.get("broadcast_groups", { name: 旧分组名 });
141
+ if (group.length === 0) {
142
+ return `未找到分组 "${旧分组名}"`;
143
+ }
144
+ try {
145
+ await ctx.database.set("broadcast_groups", { name: 旧分组名 }, {
146
+ name: 新分组名
147
+ });
148
+ return `分组名已从 "${旧分组名}" 修改为 "${新分组名}"`;
149
+ } catch (error) {
150
+ if (error.code === 2067) {
151
+ return `分组名 "${新分组名}" 已存在`;
152
+ }
153
+ return "修改分组名时发生错误";
154
+ }
155
+ });
156
+ ctx.command("广播/广播群/添加广播群 <分组名:string> <群号:string>", { authority: 2 }).example("添加广播群 攻略组 123456789,987654321").example("添加广播群 活动组 111111111").action(async ({ session }, 分组名, 群号) => {
157
+ if (!分组名 || !群号) return "请输入分组名和群号";
158
+ const group = await ctx.database.get("broadcast_groups", { name: 分组名 });
159
+ if (group.length === 0) {
160
+ return `未找到分组 "${分组名}"`;
161
+ }
162
+ const 群号列表 = 群号.split(",").map((id) => id.trim()).filter((id) => id);
163
+ const 成功列表 = [];
164
+ const 失败列表 = [];
165
+ for (const guildId of 群号列表) {
166
+ try {
167
+ const exists = await ctx.database.get("broadcast_groups_bots", {
168
+ group_id: group[0].id,
169
+ guildId
170
+ });
171
+ if (exists.length > 0) {
172
+ 失败列表.push(`${guildId}(已存在于该分组)`);
173
+ continue;
174
+ }
175
+ const guildName = await getQQGroupName(ctx, guildId);
176
+ await ctx.database.create("broadcast_groups_bots", {
177
+ group_id: group[0].id,
178
+ guildId,
179
+ guildName,
180
+ platform: "onebot",
181
+ // 固定为onebot平台
182
+ created_at: /* @__PURE__ */ new Date()
183
+ });
184
+ 成功列表.push(guildId);
185
+ } catch (error) {
186
+ 失败列表.push(`${guildId}(添加失败)`);
187
+ }
188
+ }
189
+ let result = "";
190
+ if (成功列表.length > 0) {
191
+ result += `成功添加群:${成功列表.join(", ")}
192
+ `;
193
+ }
194
+ if (失败列表.length > 0) {
195
+ result += `添加失败:${失败列表.join(", ")}`;
196
+ }
197
+ return result || "未添加任何群";
198
+ });
199
+ ctx.command("广播/广播群/删除广播群 <群编号:string>", { authority: 2 }).example("删除广播群 1,3,5").example("删除广播群 2").action(async ({ session }, 群编号) => {
200
+ if (!群编号) return "请输入群编号";
201
+ const 编号列表 = 群编号.split(",").map((id) => parseInt(id.trim())).filter((id) => !isNaN(id));
202
+ if (编号列表.length === 0) return "请输入有效的群编号";
203
+ const 删除结果 = await ctx.database.remove("broadcast_groups_bots", {
204
+ id: { $in: 编号列表 }
205
+ });
206
+ return `已删除 ${删除结果} 个群`;
207
+ });
208
+ ctx.command("广播/广播群/修改广播群 <群编号:number> <群名:string>", { authority: 2 }).example("修改广播群 1 攻略一群").example("修改广播群 3 活动通知群").action(async ({ session }, 群编号, 群名) => {
209
+ if (!群编号 || !群名) return "请输入群编号和群名";
210
+ const bot = await ctx.database.get("broadcast_groups_bots", { id: 群编号 });
211
+ if (bot.length === 0) {
212
+ return `未找到群编号 ${群编号}`;
213
+ }
214
+ await ctx.database.set("broadcast_groups_bots", { id: 群编号 }, {
215
+ guildName: 群名
216
+ });
217
+ return `群 ${bot[0].guildId} 的群名已修改为 "${群名}"`;
218
+ });
219
+ ctx.command("广播/发送广播 <分组名:string> <广播内容:text>", { authority: 3 }).example("发送广播 攻略组,活动组 今日活动:晚上8点公会战").example("发送广播 测试组 这是一条测试广播").action(async ({ session }, 分组名, 广播内容) => {
220
+ if (!分组名 || !广播内容) return "请输入分组名和广播内容";
221
+ const 分组名列表 = 分组名.split(",").map((name2) => name2.trim()).filter((name2) => name2);
222
+ const 群列表 = [];
223
+ const 去重Map = /* @__PURE__ */ new Map();
224
+ for (const name2 of 分组名列表) {
225
+ const group = await ctx.database.get("broadcast_groups", { name: name2 });
226
+ if (group.length === 0) {
227
+ return `未找到分组 "${name2}"`;
228
+ }
229
+ const bots = await ctx.database.get("broadcast_groups_bots", {
230
+ group_id: group[0].id
231
+ });
232
+ for (const bot of bots) {
233
+ if (!去重Map.has(bot.guildId)) {
234
+ 去重Map.set(bot.guildId, bot);
235
+ 群列表.push(bot);
236
+ }
237
+ }
238
+ }
239
+ if (群列表.length === 0) {
240
+ return "所选分组中没有可发送的群";
241
+ }
242
+ const 群信息列表 = 群列表.map(
243
+ (bot, index) => `${index + 1}. [#${bot.id}] ${bot.guildName} ${bot.guildId}`
244
+ );
245
+ const 发送结果 = [`需要发送的群列表(共 ${群列表.length} 个):`, ...群信息列表];
246
+ await session?.send(发送结果.join("\n"));
247
+ const 广播结果 = await 发送广播到群组(ctx, session, 群列表, 广播内容, config.broadcastInterval, config.broadcastTimeout);
248
+ return 广播结果;
249
+ });
250
+ async function 发送广播到群组(ctx2, session, 群列表, 广播内容, 间隔, 超时时间) {
251
+ const 成功列表 = [];
252
+ const 失败列表 = [];
253
+ const 超时列表 = [];
254
+ const 超时Promise = new Promise((resolve) => {
255
+ setTimeout(() => {
256
+ resolve(true);
257
+ }, 超时时间 * 1e3);
258
+ });
259
+ const 开始时间 = Date.now();
260
+ try {
261
+ await Promise.race([
262
+ 发送广播任务(ctx2, 群列表, 广播内容, 间隔, 成功列表, 失败列表),
263
+ 超时Promise.then((isTimeout) => {
264
+ if (isTimeout) {
265
+ throw new Error(`广播发送超时(${超时时间}秒)`);
266
+ }
267
+ })
268
+ ]);
269
+ const 总耗时 = Math.round((Date.now() - 开始时间) / 1e3);
270
+ const 结果消息 = [
271
+ separator(),
272
+ "广播发送完成!",
273
+ `总耗时:${总耗时}秒`,
274
+ `发送结果:成功 ${成功列表.length} 个,失败 ${失败列表.length} 个`
275
+ ];
276
+ if (成功列表.length > 0) {
277
+ 结果消息.push("");
278
+ 结果消息.push("✅ 发送成功:");
279
+ 成功列表.forEach((bot, index) => {
280
+ 结果消息.push(`${index + 1}. [#${bot.guildId}] ${bot.guildName}`);
281
+ });
282
+ }
283
+ if (失败列表.length > 0) {
284
+ 结果消息.push("");
285
+ 结果消息.push("❌ 发送失败:");
286
+ 失败列表.forEach((bot, index) => {
287
+ 结果消息.push(`${index + 1}. [#${bot.guildId}] ${bot.guildName} - ${bot.error}`);
288
+ });
289
+ }
290
+ return 结果消息.join("\n");
291
+ } catch (error) {
292
+ if (error.message.includes("超时")) {
293
+ const 已发送数量 = 成功列表.length + 失败列表.length;
294
+ const 未发送数量 = 群列表.length - 已发送数量;
295
+ const 已发送群号 = /* @__PURE__ */ new Set([...成功列表.map((b) => b.guildId), ...失败列表.map((b) => b.guildId)]);
296
+ for (const bot of 群列表) {
297
+ if (!已发送群号.has(bot.guildId)) {
298
+ 超时列表.push({ guildId: bot.guildId, guildName: bot.guildName });
299
+ }
300
+ }
301
+ const 总耗时 = Math.round((Date.now() - 开始时间) / 1e3);
302
+ const 结果消息 = [
303
+ separator(),
304
+ `广播发送超时(${超时时间}秒),已终止!`,
305
+ `总耗时:${总耗时}秒`,
306
+ `发送结果:成功 ${成功列表.length} 个,失败 ${失败列表.length} 个,超时未发送 ${超时列表.length} 个`
307
+ ];
308
+ if (成功列表.length > 0) {
309
+ 结果消息.push("");
310
+ 结果消息.push("✅ 发送成功:");
311
+ 成功列表.forEach((bot, index) => {
312
+ 结果消息.push(`${index + 1}. [#${bot.guildId}] ${bot.guildName}`);
313
+ });
314
+ }
315
+ if (失败列表.length > 0) {
316
+ 结果消息.push("");
317
+ 结果消息.push("❌ 发送失败:");
318
+ 失败列表.forEach((bot, index) => {
319
+ 结果消息.push(`${index + 1}. [#${bot.guildId}] ${bot.guildName} - ${bot.error}`);
320
+ });
321
+ }
322
+ if (超时列表.length > 0) {
323
+ 结果消息.push("");
324
+ 结果消息.push("⏰ 超时未发送:");
325
+ 超时列表.forEach((bot, index) => {
326
+ 结果消息.push(`${index + 1}. [#${bot.guildId}] ${bot.guildName}`);
327
+ });
328
+ }
329
+ return 结果消息.join("\n");
330
+ }
331
+ return `广播发送过程中发生错误:${error.message}`;
332
+ }
333
+ }
334
+ __name(发送广播到群组, "发送广播到群组");
335
+ async function 发送广播任务(ctx2, 群列表, 广播内容, 间隔, 成功列表, 失败列表) {
336
+ for (let i = 0; i < 群列表.length; i++) {
337
+ const bot = 群列表[i];
338
+ try {
339
+ const botInstance = ctx2.bots.find((bot2) => bot2.platform === "onebot");
340
+ if (botInstance) {
341
+ await botInstance.sendMessage(bot.guildId, 广播内容);
342
+ } else {
343
+ throw new Error("机器人实例不存在");
344
+ }
345
+ console.log(`[广播发送成功] 群号: ${bot.guildId}, 群名: ${bot.guildName}`);
346
+ 成功列表.push({ guildId: bot.guildId, guildName: bot.guildName });
347
+ } catch (error) {
348
+ console.log(`[广播发送失败] 群号: ${bot.guildId}, 群名: ${bot.guildName}, 错误: ${error.message}`);
349
+ 失败列表.push({ guildId: bot.guildId, guildName: bot.guildName, error: error.message });
350
+ }
351
+ if (i < 群列表.length - 1) {
352
+ await new Promise((resolve) => setTimeout(resolve, 间隔));
353
+ }
354
+ }
355
+ }
356
+ __name(发送广播任务, "发送广播任务");
357
+ ctx.on("ready", () => {
358
+ ctx.logger("maple-broadcast").info("广播插件已加载");
359
+ ctx.logger("maple-broadcast").info(`广播发送间隔:${config.broadcastInterval}ms`);
360
+ ctx.logger("maple-broadcast").info(`广播发送超时时间:${config.broadcastTimeout}秒`);
361
+ });
362
+ }
363
+ __name(apply, "apply");
364
+ // Annotate the CommonJS export names for ESM import in node:
365
+ 0 && (module.exports = {
366
+ Config,
367
+ apply,
368
+ name,
369
+ using
370
+ });
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "koishi-plugin-maple-broadcast",
3
+ "description": "-",
4
+ "version": "0.0.1",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "dist"
10
+ ],
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "maple"
14
+ ],
15
+ "peerDependencies": {
16
+ "koishi": "^4.18.7"
17
+ }
18
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # koishi-plugin-maple-broadcast
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-maple-broadcast?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-maple-broadcast)
4
+
5
+ -