koishi-plugin-ll-schedule 1.0.2 → 1.0.4
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/index.js +289 -50
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// koishi-plugin-ll-schedule
|
|
4
4
|
// 定时发送插件:设置时间自动向指定群发送文字/图片
|
|
5
|
-
// v1.0.
|
|
6
|
-
// 官机的群 ID 是 openid(不是数字群号),本插件通过"学习"机器人收到的群消息来记录群,
|
|
7
|
-
// 用 schedule.groups 命令查看已知群并填入 tasks.groups;留空则发送给所有已知群。
|
|
5
|
+
// v1.0.4:群内订阅(发关键词即可让本群加入推送列表,官机自动用 openid,无需手动填群号)
|
|
8
6
|
|
|
9
7
|
const { Schema, h } = require('koishi')
|
|
10
8
|
|
|
@@ -12,30 +10,34 @@ exports.name = 'll-schedule'
|
|
|
12
10
|
|
|
13
11
|
exports.usage = `定时发送插件:设置时间自动向指定群发送文字/图片。
|
|
14
12
|
|
|
15
|
-
##
|
|
13
|
+
## 快速上手(推荐用法)
|
|
14
|
+
|
|
15
|
+
1. 在插件设置页的 \`tasks\` 表格中添加任务:**键=任务名**,设置 \`time\`(发送时间)和 \`text\`/\`image\`(推送内容)
|
|
16
|
+
2. 在任意群里 @机器人 发:\`订阅 任务名\` → 这个群就加入了该任务的推送列表(自动记录,重启不丢)
|
|
17
|
+
3. 到点后插件自动把内容推送到所有订阅了该任务的群
|
|
18
|
+
|
|
19
|
+
## 群内命令
|
|
20
|
+
|
|
21
|
+
- \`订阅 [任务名]\`:当前群订阅指定任务的推送(不填任务名时,只有一个启用任务则自动选它)
|
|
22
|
+
- \`取消订阅 [任务名]\`:当前群取消订阅
|
|
23
|
+
- \`订阅列表\`:查看所有任务的订阅群
|
|
24
|
+
- \`schedule.list\`:查看任务列表
|
|
25
|
+
- \`schedule.test <任务名>\`:立即测试发送
|
|
26
|
+
- \`lls.*\`:以上命令的备用入口(lls.sub / lls.unsub / lls.sublist / lls.list / lls.test)
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
订阅/取消订阅的权限等级可在设置页 \`subscribeAuthority\` 调整(默认 2=群管理)。
|
|
29
|
+
|
|
30
|
+
## 任务配置
|
|
18
31
|
|
|
19
32
|
- \`time\`:发送时间,HH:MM 格式(24小时制)
|
|
20
|
-
- \`groups
|
|
33
|
+
- \`groups\`:手动指定的目标群列表(**推荐留空**,用群内"订阅"命令添加更省事;留空且无订阅 = 发给所有已知群)
|
|
21
34
|
- \`text\`:文字内容(可留空)
|
|
22
35
|
- \`image\`:图片 URL(可留空)
|
|
23
36
|
- \`enabled\`:是否启用
|
|
24
37
|
|
|
25
38
|
## 官机(官方QQ机器人)说明
|
|
26
39
|
|
|
27
|
-
官方平台没有"群号",只有 openid
|
|
28
|
-
|
|
29
|
-
- 命令 \`schedule.groups\` 查看所有已知群(显示群名和 openid)
|
|
30
|
-
- 把 openid 填进任务的 \`groups\` 里即可
|
|
31
|
-
- 注意:官机主动发群消息有平台频率限制,且机器人需在群里
|
|
32
|
-
|
|
33
|
-
## 命令
|
|
34
|
-
|
|
35
|
-
- \`schedule\` 帮助
|
|
36
|
-
- \`schedule.list\` 查看任务
|
|
37
|
-
- \`schedule.groups\` 查看机器人已知的群(官机显示 openid)
|
|
38
|
-
- \`schedule.test <任务名>\` 立即测试发送
|
|
40
|
+
官方平台没有"群号",只有 openid——用群内"订阅"命令即可,插件会自动记录当前群的 openid,无需手动填写。手动填 \`groups\` 时可用 \`schedule.groups\` 查看已知群的 openid。官机主动发群消息有平台频率限制,且机器人需在群里。
|
|
39
41
|
`
|
|
40
42
|
|
|
41
43
|
const TaskItem = Schema.object({
|
|
@@ -44,7 +46,7 @@ const TaskItem = Schema.object({
|
|
|
44
46
|
.description('发送时间,HH:MM 格式(24小时制),如 08:30、21:00'),
|
|
45
47
|
groups: Schema.array(Schema.string())
|
|
46
48
|
.default([])
|
|
47
|
-
.description('
|
|
49
|
+
.description('手动指定的目标群 ID(可留空;推荐用群内"订阅"命令自动添加,无需手动填 openid)'),
|
|
48
50
|
text: Schema.string()
|
|
49
51
|
.default('')
|
|
50
52
|
.description('文字内容(可留空)'),
|
|
@@ -59,10 +61,19 @@ const TaskItem = Schema.object({
|
|
|
59
61
|
const Config = Schema.object({
|
|
60
62
|
tasks: Schema.dict(TaskItem)
|
|
61
63
|
.default({})
|
|
62
|
-
.description('📋
|
|
64
|
+
.description('📋 定时任务表格(键=任务名,设置时间和推送内容)'),
|
|
65
|
+
subscribeKeyword: Schema.string()
|
|
66
|
+
.default('订阅')
|
|
67
|
+
.description('群内订阅推送的关键词(如"订阅 任务名",只输关键词则订阅唯一启用的任务)'),
|
|
68
|
+
unsubscribeKeyword: Schema.string()
|
|
69
|
+
.default('取消订阅')
|
|
70
|
+
.description('群内取消订阅的关键词(如"取消订阅 任务名")'),
|
|
71
|
+
subscribeAuthority: Schema.number()
|
|
72
|
+
.min(1).max(4).default(2)
|
|
73
|
+
.description('订阅/取消订阅所需权限等级(1=所有人 2=群管理 3=群主 4=管理员)'),
|
|
63
74
|
sendAllWhenEmpty: Schema.boolean()
|
|
64
75
|
.default(true)
|
|
65
|
-
.description('
|
|
76
|
+
.description('任务未指定群也没有群订阅时,发送给该机器人所有已知群'),
|
|
66
77
|
verbose: Schema.boolean()
|
|
67
78
|
.default(false)
|
|
68
79
|
.description('调试:控制台打印每次发送详情'),
|
|
@@ -102,6 +113,64 @@ function apply(ctx, config) {
|
|
|
102
113
|
platform: 'string',
|
|
103
114
|
learnedAt: 'unsigned',
|
|
104
115
|
}, { primary: ['botSid', 'guildId'] })
|
|
116
|
+
// 群订阅表:taskName + botSid + guildId(群内"订阅"命令注册的推送目标)
|
|
117
|
+
ctx.model.extend('ll_schedule_subs', {
|
|
118
|
+
taskName: 'string',
|
|
119
|
+
botSid: 'string',
|
|
120
|
+
guildId: 'string',
|
|
121
|
+
createdAt: 'unsigned',
|
|
122
|
+
}, { primary: ['taskName', 'botSid', 'guildId'] })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ===== 群订阅(群内发关键词注册推送目标) =====
|
|
126
|
+
// subs: Map<taskName, Map<`${botSid}:${guildId}`, { botSid, guildId, createdAt }>>
|
|
127
|
+
const subs = new Map()
|
|
128
|
+
|
|
129
|
+
function getTaskSubs(taskName) {
|
|
130
|
+
let taskSubs = subs.get(taskName)
|
|
131
|
+
if (!taskSubs) {
|
|
132
|
+
taskSubs = new Map()
|
|
133
|
+
subs.set(taskName, taskSubs)
|
|
134
|
+
}
|
|
135
|
+
return taskSubs
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function loadSubs() {
|
|
139
|
+
if (!ctx.database) return
|
|
140
|
+
try {
|
|
141
|
+
const rows = await ctx.database.get('ll_schedule_subs', {})
|
|
142
|
+
for (const row of rows) {
|
|
143
|
+
getTaskSubs(row.taskName).set(`${row.botSid}:${row.guildId}`, {
|
|
144
|
+
botSid: row.botSid,
|
|
145
|
+
guildId: row.guildId,
|
|
146
|
+
createdAt: row.createdAt,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
if (rows.length) logger.info(`[schedule] 已从数据库加载 ${rows.length} 条群订阅`)
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
logger.warn('[schedule] 加载群订阅失败:', e)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function persistSub(taskName, entry) {
|
|
157
|
+
if (!ctx.database) return
|
|
158
|
+
try {
|
|
159
|
+
await ctx.database.upsert('ll_schedule_subs', [{ ...entry, taskName }], ['taskName', 'botSid', 'guildId'])
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
logger.warn('[schedule] 保存群订阅失败:', e)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function removeSub(taskName, botSid, guildId) {
|
|
167
|
+
if (!ctx.database) return
|
|
168
|
+
try {
|
|
169
|
+
await ctx.database.remove('ll_schedule_subs', { taskName, botSid, guildId })
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
logger.warn('[schedule] 删除群订阅失败:', e)
|
|
173
|
+
}
|
|
105
174
|
}
|
|
106
175
|
|
|
107
176
|
async function loadKnownGroups() {
|
|
@@ -145,6 +214,7 @@ function apply(ctx, config) {
|
|
|
145
214
|
learnedAt: Date.now(),
|
|
146
215
|
}
|
|
147
216
|
knownGroups.set(key, entry)
|
|
217
|
+
logger.info(`[schedule] 学习到新群 ${bot.sid} (${bot.platform}) → ${session.guildId}`)
|
|
148
218
|
persistGroup(entry)
|
|
149
219
|
}
|
|
150
220
|
|
|
@@ -223,15 +293,21 @@ function apply(ctx, config) {
|
|
|
223
293
|
if (!bots.length) return
|
|
224
294
|
|
|
225
295
|
const groups = (task.groups && task.groups.length > 0) ? task.groups : null
|
|
296
|
+
const taskSubs = subs.get(name)
|
|
226
297
|
|
|
227
298
|
for (const bot of bots) {
|
|
228
|
-
|
|
229
|
-
|
|
299
|
+
// 该任务下、该机器人订阅的群(群内"订阅"命令注册的)
|
|
300
|
+
const subTargets = taskSubs
|
|
301
|
+
? [...taskSubs.values()].filter(s => s.botSid === bot.sid).map(s => s.guildId)
|
|
302
|
+
: []
|
|
303
|
+
|
|
304
|
+
if (groups || subTargets.length) {
|
|
305
|
+
for (const rawTarget of groups || []) {
|
|
230
306
|
const target = String(rawTarget).trim()
|
|
231
307
|
if (!target) continue
|
|
232
308
|
// 官机:群 ID 是 openid;数字群号在官机上无法直接使用
|
|
233
309
|
if (bot.platform === 'qq' && /^\d+$/.test(target)) {
|
|
234
|
-
logger.warn(`[schedule] 任务 "${name}":官方QQ机器人不能用数字群号 "${target}"
|
|
310
|
+
logger.warn(`[schedule] 任务 "${name}":官方QQ机器人不能用数字群号 "${target}" 发送,请用群内"订阅"命令或 schedule.groups 查看 openid`)
|
|
235
311
|
continue
|
|
236
312
|
}
|
|
237
313
|
try {
|
|
@@ -242,9 +318,18 @@ function apply(ctx, config) {
|
|
|
242
318
|
logger.error(`[schedule] 发送到 ${bot.sid}:${target} 失败: ${e.message}`)
|
|
243
319
|
}
|
|
244
320
|
}
|
|
321
|
+
for (const target of subTargets) {
|
|
322
|
+
try {
|
|
323
|
+
await bot.sendMessage(target, parts)
|
|
324
|
+
if (config.verbose) logger.info(`[schedule] 已发送任务 "${name}" 到订阅群 ${bot.sid}:${target}`)
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
logger.error(`[schedule] 发送到订阅群 ${bot.sid}:${target} 失败: ${e.message}`)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
245
330
|
}
|
|
246
331
|
else if (config.sendAllWhenEmpty) {
|
|
247
|
-
//
|
|
332
|
+
// 未指定群也没有订阅 → 发送给该 bot 所有已知群
|
|
248
333
|
const targets = [...knownGroups.values()].filter(g => g.botSid === bot.sid)
|
|
249
334
|
if (!targets.length) {
|
|
250
335
|
logger.warn(`[schedule] 任务 "${name}" 未指定群且机器人 ${bot.sid} 暂无已知群(先让机器人收过该群消息,或用 schedule.groups 查看)`)
|
|
@@ -272,44 +357,195 @@ function apply(ctx, config) {
|
|
|
272
357
|
ctx.on('dispose', () => clearInterval(timer))
|
|
273
358
|
|
|
274
359
|
// ===== 命令 =====
|
|
360
|
+
async function listKnownGroups() {
|
|
361
|
+
if (!knownGroups.size) return '暂无已知群。机器人收到过消息的群才会被记录(官机需要先把机器人拉进群并让它收消息)'
|
|
362
|
+
const lines = []
|
|
363
|
+
for (const g of knownGroups.values()) {
|
|
364
|
+
const bot = ctx.bots.find(b => b.sid === g.botSid)
|
|
365
|
+
const name = bot ? await resolveGroupName(bot, g.guildId) : ''
|
|
366
|
+
const platformTag = g.platform === 'qq' ? '[官机]' : `[${g.platform}]`
|
|
367
|
+
lines.push(`${platformTag} ${name || '未知群名'} ${g.guildId}`)
|
|
368
|
+
}
|
|
369
|
+
return '📋 机器人已知群:\n' + lines.join('\n') + '\n(官机的群 ID 是 openid,填入任务 groups 即可)'
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function formatTaskList() {
|
|
373
|
+
const tasks = config.tasks || {}
|
|
374
|
+
const keys = Object.keys(tasks)
|
|
375
|
+
if (!keys.length) return '暂无定时任务\n请在插件设置页的 tasks 表格中添加'
|
|
376
|
+
return keys.map(k => {
|
|
377
|
+
const t = tasks[k]
|
|
378
|
+
const s = t.enabled === false ? '⛔' : '✅'
|
|
379
|
+
const content = [t.text && '文字', t.image && '图片'].filter(Boolean).join('+') || '(空)'
|
|
380
|
+
const staticGroups = (t.groups || []).join(',')
|
|
381
|
+
const subCount = (subs.get(k) || new Map()).size
|
|
382
|
+
const target = staticGroups ? staticGroups : (subCount ? `订阅${subCount}群` : '所有已知群')
|
|
383
|
+
return `${s} ${k} @ ${t.time} → ${content} → 群:${target}${subCount && staticGroups ? ` +订阅${subCount}群` : ''}`
|
|
384
|
+
}).join('\n')
|
|
385
|
+
}
|
|
386
|
+
|
|
275
387
|
ctx.command('schedule', '定时发送管理')
|
|
276
|
-
.action(() =>
|
|
277
|
-
'
|
|
278
|
-
'
|
|
279
|
-
|
|
388
|
+
.action(() => {
|
|
389
|
+
logger.info('[schedule] 命令被调用: schedule')
|
|
390
|
+
return '⚙️ 管理:插件设置页 → ll-schedule → tasks 表格设置时间和内容\n' +
|
|
391
|
+
`群内发"${config.subscribeKeyword} 任务名"即可让本群订阅推送\n` +
|
|
392
|
+
`命令:${config.subscribeKeyword} | ${config.unsubscribeKeyword} | 订阅列表 | schedule.list | schedule.groups | schedule.test <任务名>`
|
|
393
|
+
})
|
|
280
394
|
|
|
281
395
|
ctx.command('schedule.list', '查看定时任务')
|
|
282
396
|
.action(() => {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!keys.length) return '暂无定时任务\n请在插件设置页的 tasks 表格中添加'
|
|
286
|
-
return keys.map(k => {
|
|
287
|
-
const t = tasks[k]
|
|
288
|
-
const s = t.enabled === false ? '⛔' : '✅'
|
|
289
|
-
const content = [t.text && '文字', t.image && '图片'].filter(Boolean).join('+') || '(空)'
|
|
290
|
-
return `${s} ${k} @ ${t.time} → ${content} → 群:${(t.groups || []).join(',') || '所有已知群'}`
|
|
291
|
-
}).join('\n')
|
|
397
|
+
logger.info('[schedule] 命令被调用: schedule.list')
|
|
398
|
+
return formatTaskList()
|
|
292
399
|
})
|
|
293
400
|
|
|
294
401
|
ctx.command('schedule.groups', '查看机器人已知的群(官机显示 openid)')
|
|
295
402
|
.action(async () => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
for (const g of knownGroups.values()) {
|
|
299
|
-
const bot = ctx.bots.find(b => b.sid === g.botSid)
|
|
300
|
-
const name = bot ? await resolveGroupName(bot, g.guildId) : ''
|
|
301
|
-
const platformTag = g.platform === 'qq' ? '[官机]' : `[${g.platform}]`
|
|
302
|
-
lines.push(`${platformTag} ${name || '未知群名'} ${g.guildId}`)
|
|
303
|
-
}
|
|
304
|
-
return '📋 机器人已知群:\n' + lines.join('\n') + '\n(官机的群 ID 是 openid,填入任务 groups 即可)'
|
|
403
|
+
logger.info('[schedule] 命令被调用: schedule.groups')
|
|
404
|
+
return await listKnownGroups()
|
|
305
405
|
})
|
|
306
406
|
|
|
307
407
|
ctx.command('schedule.test <name>', '立即测试发送指定任务')
|
|
308
408
|
.action(async ({ session }, name) => {
|
|
409
|
+
logger.info(`[schedule] 命令被调用: schedule.test name=${name}`)
|
|
309
410
|
const tasks = config.tasks || {}
|
|
310
411
|
const task = tasks[name]
|
|
311
412
|
if (!task) return `未找到任务 "${name}",可用 schedule.list 查看任务名`
|
|
312
|
-
//
|
|
413
|
+
// 测试时:任务没配群也没订阅就发到当前群
|
|
414
|
+
const testTask = { ...task }
|
|
415
|
+
if (!testTask.groups || !testTask.groups.length) {
|
|
416
|
+
testTask.groups = session.guildId ? [String(session.guildId)] : []
|
|
417
|
+
}
|
|
418
|
+
await sendTask(name, testTask, session.bot?.sid)
|
|
419
|
+
return `✅ 已发送任务 "${name}"`
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
// ===== 群内订阅命令 =====
|
|
423
|
+
// 解析订阅命令的任务名:明确指定 → 用它;不填且只有一个启用任务 → 用它;否则返回提示
|
|
424
|
+
function resolveSubscribeTaskName(taskName) {
|
|
425
|
+
const tasks = config.tasks || {}
|
|
426
|
+
if (taskName) {
|
|
427
|
+
if (!tasks[taskName]) return { name: null, hint: `未找到任务 "${taskName}",可用"订阅列表"或 lls.list 查看任务名` }
|
|
428
|
+
return { name: taskName, hint: null }
|
|
429
|
+
}
|
|
430
|
+
const names = Object.keys(tasks).filter(k => tasks[k] && tasks[k].enabled !== false)
|
|
431
|
+
if (names.length === 1) return { name: names[0], hint: null }
|
|
432
|
+
if (names.length === 0) return { name: null, hint: '暂无启用的任务,请先在插件设置页的 tasks 表格中添加' }
|
|
433
|
+
return { name: null, hint: `有多个任务,请指定任务名:${names.join(' / ')}` }
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function subscribeCurrentGroup(session, taskName) {
|
|
437
|
+
logger.info(`[schedule] 命令被调用: 订阅 taskName=${taskName || '(自动)'}`)
|
|
438
|
+
if (!session.guildId || !session.bot) return '❌ 订阅只能在群聊里使用'
|
|
439
|
+
const { name, hint } = resolveSubscribeTaskName(taskName)
|
|
440
|
+
if (!name) return hint
|
|
441
|
+
const task = config.tasks[name]
|
|
442
|
+
const key = `${session.bot.sid}:${session.guildId}`
|
|
443
|
+
const taskSubs = getTaskSubs(name)
|
|
444
|
+
if (taskSubs.has(key)) return `ℹ️ 本群已在任务 "${name}" 的推送列表里`
|
|
445
|
+
const entry = { botSid: session.bot.sid, guildId: session.guildId, createdAt: Date.now() }
|
|
446
|
+
taskSubs.set(key, entry)
|
|
447
|
+
persistSub(name, entry)
|
|
448
|
+
logger.info(`[schedule] 群 ${session.guildId} 已订阅任务 "${name}"`)
|
|
449
|
+
return `✅ 本群已加入任务 "${name}" 的推送列表\n时间:${task.time}\n内容:${task.text || '(图片)'}`
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function unsubscribeCurrentGroup(session, taskName) {
|
|
453
|
+
logger.info(`[schedule] 命令被调用: 取消订阅 taskName=${taskName || '(自动)'}`)
|
|
454
|
+
if (!session.guildId || !session.bot) return '❌ 取消订阅只能在群聊里使用'
|
|
455
|
+
const { name, hint } = resolveSubscribeTaskName(taskName)
|
|
456
|
+
if (!name) return hint
|
|
457
|
+
const taskSubs = subs.get(name)
|
|
458
|
+
const key = `${session.bot.sid}:${session.guildId}`
|
|
459
|
+
if (!taskSubs || !taskSubs.has(key)) return `ℹ️ 本群不在任务 "${name}" 的推送列表里`
|
|
460
|
+
taskSubs.delete(key)
|
|
461
|
+
removeSub(name, session.bot.sid, session.guildId)
|
|
462
|
+
logger.info(`[schedule] 群 ${session.guildId} 已取消订阅任务 "${name}"`)
|
|
463
|
+
return `✅ 本群已取消订阅任务 "${name}"`
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
ctx.command(config.subscribeKeyword + ' [taskName:text]', '让当前群订阅指定任务的推送')
|
|
467
|
+
.authority(config.subscribeAuthority)
|
|
468
|
+
.action(async ({ session }, taskName) => {
|
|
469
|
+
return await subscribeCurrentGroup(session, taskName)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
ctx.command('lls.sub [taskName:text]', '订阅推送(备用入口)')
|
|
473
|
+
.authority(config.subscribeAuthority)
|
|
474
|
+
.action(async ({ session }, taskName) => {
|
|
475
|
+
return await subscribeCurrentGroup(session, taskName)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
ctx.command(config.unsubscribeKeyword + ' [taskName:text]', '让当前群取消订阅指定任务的推送')
|
|
479
|
+
.authority(config.subscribeAuthority)
|
|
480
|
+
.action(async ({ session }, taskName) => {
|
|
481
|
+
return await unsubscribeCurrentGroup(session, taskName)
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
ctx.command('lls.unsub [taskName:text]', '取消订阅(备用入口)')
|
|
485
|
+
.authority(config.subscribeAuthority)
|
|
486
|
+
.action(async ({ session }, taskName) => {
|
|
487
|
+
return await unsubscribeCurrentGroup(session, taskName)
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
ctx.command('订阅列表', '查看所有任务的订阅群')
|
|
491
|
+
.action(async () => {
|
|
492
|
+
logger.info('[schedule] 命令被调用: 订阅列表')
|
|
493
|
+
if (!subs.size) return '暂无任何群订阅。群内发"' + config.subscribeKeyword + ' 任务名"即可订阅'
|
|
494
|
+
const lines = []
|
|
495
|
+
for (const [taskName, taskSubs] of subs) {
|
|
496
|
+
const entries = [...taskSubs.values()]
|
|
497
|
+
lines.push(`【${taskName}】${entries.length} 个群`)
|
|
498
|
+
for (const s of entries) {
|
|
499
|
+
const bot = ctx.bots.find(b => b.sid === s.botSid)
|
|
500
|
+
const name = bot ? await resolveGroupName(bot, s.guildId) : ''
|
|
501
|
+
lines.push(` · ${name || '未知群名'} (${s.guildId})`)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return '📋 订阅列表:\n' + lines.join('\n')
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
ctx.command('lls.sublist', '查看订阅列表(备用入口)')
|
|
508
|
+
.action(async () => {
|
|
509
|
+
logger.info('[schedule] 命令被调用: lls.sublist')
|
|
510
|
+
if (!subs.size) return '暂无任何群订阅。群内发"' + config.subscribeKeyword + ' 任务名"即可订阅'
|
|
511
|
+
const lines = []
|
|
512
|
+
for (const [taskName, taskSubs] of subs) {
|
|
513
|
+
const entries = [...taskSubs.values()]
|
|
514
|
+
lines.push(`【${taskName}】${entries.length} 个群`)
|
|
515
|
+
for (const s of entries) {
|
|
516
|
+
const bot = ctx.bots.find(b => b.sid === s.botSid)
|
|
517
|
+
const name = bot ? await resolveGroupName(bot, s.guildId) : ''
|
|
518
|
+
lines.push(` · ${name || '未知群名'} (${s.guildId})`)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return '📋 订阅列表:\n' + lines.join('\n')
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
// 备用命令入口(避免与其他插件的 schedule 命令冲突)
|
|
525
|
+
ctx.command('lls', 'll-schedule 管理(备用入口)')
|
|
526
|
+
.action(() => {
|
|
527
|
+
logger.info('[schedule] 命令被调用: lls')
|
|
528
|
+
return `⚙️ 备用入口:lls.list / lls.groups / lls.test <任务名> / lls.sub / lls.unsub / lls.sublist`
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
ctx.command('lls.list', '查看定时任务(备用入口)')
|
|
532
|
+
.action(() => {
|
|
533
|
+
logger.info('[schedule] 命令被调用: lls.list')
|
|
534
|
+
return formatTaskList()
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
ctx.command('lls.groups', '查看机器人已知的群(备用入口,官机显示 openid)')
|
|
538
|
+
.action(async () => {
|
|
539
|
+
logger.info('[schedule] 命令被调用: lls.groups')
|
|
540
|
+
return await listKnownGroups()
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
ctx.command('lls.test <name>', '立即测试发送指定任务(备用入口)')
|
|
544
|
+
.action(async ({ session }, name) => {
|
|
545
|
+
logger.info(`[schedule] 命令被调用: lls.test name=${name}`)
|
|
546
|
+
const tasks = config.tasks || {}
|
|
547
|
+
const task = tasks[name]
|
|
548
|
+
if (!task) return `未找到任务 "${name}",可用 lls.list 查看任务名`
|
|
313
549
|
const testTask = { ...task }
|
|
314
550
|
if (!testTask.groups || !testTask.groups.length) {
|
|
315
551
|
testTask.groups = session.guildId ? [String(session.guildId)] : []
|
|
@@ -318,8 +554,11 @@ function apply(ctx, config) {
|
|
|
318
554
|
return `✅ 已发送任务 "${name}"`
|
|
319
555
|
})
|
|
320
556
|
|
|
321
|
-
|
|
322
|
-
|
|
557
|
+
logger.info('[schedule] 命令已注册: schedule / schedule.list / schedule.groups / schedule.test / ' +
|
|
558
|
+
`"${config.subscribeKeyword}" / "${config.unsubscribeKeyword}" / 订阅列表 / lls.*`)
|
|
559
|
+
|
|
560
|
+
// 启动时:加载已知群 + 订阅 + 立即检查一次
|
|
561
|
+
Promise.all([loadKnownGroups(), loadSubs()]).then(() => checkAndSend())
|
|
323
562
|
}
|
|
324
563
|
|
|
325
564
|
exports.apply = apply
|