koishi-plugin-ll-schedule 1.3.0 → 1.4.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/index.js +110 -44
- package/package.json +4 -1
package/index.js
CHANGED
|
@@ -11,40 +11,81 @@ const { Schema, h } = require('koishi')
|
|
|
11
11
|
|
|
12
12
|
exports.name = 'll-schedule'
|
|
13
13
|
|
|
14
|
-
exports.usage =
|
|
14
|
+
exports.usage = `定时提醒插件:到时间自动向订阅了该任务的群发送提醒。每个任务就是一个**大文本框(日程)**,整张日程直接写进去,换行正常。
|
|
15
15
|
|
|
16
16
|
## 快速上手
|
|
17
17
|
|
|
18
|
-
1.
|
|
19
|
-
2. 在任意群 @机器人 发:\`订阅 任务名\` →
|
|
20
|
-
3.
|
|
18
|
+
1. 添加任务(键=任务名),在任务的 \`schedule\` 大文本框里直接写日程(格式见下)
|
|
19
|
+
2. 在任意群 @机器人 发:\`订阅 任务名\` → 该群加入此任务订阅列表
|
|
20
|
+
3. 到点自动发送
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## 日程怎么写
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
- **时间**:\`HH:MM\`,可填多个用逗号分隔,如 \`12:20, 18:20, 21:20\`(这些时间点都发同一段内容)
|
|
26
|
-
- **内容**:到点发送的话;支持 Markdown 语法和模板变量 \`{date}\` \`{time}\` \`{week}\` \`{task}\`
|
|
24
|
+
每个时间段 = 一行 **\`星期 时间\`** 开头,下面几行是该时间的内容(可多行 markdown,保留换行)。写到下一个 \`星期 时间\` 行,自动切到下一个时间段。不同时间段之间可空一行,更清晰。
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
- **星期**:\`周一\` \`周六\`...、\`每天\`、\`工作日\`(周一到周五)、\`周末\`(周六日)、\`1,3,5\`;留空 = 每天
|
|
27
|
+
- **时间**:\`HH:MM\`,可多个逗号分隔 \`12:20, 18:20, 21:20\`
|
|
28
|
+
- **内容**:到点发送的话,支持 Markdown 和模板变量 \`{date}\` \`{time}\` \`{week}\` \`{task}\`
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
→ 每天这三个点都发这句。
|
|
30
|
+
## 示例:任务「世界BOSS」——直接把下面整段复制到文本框
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
\`\`\`
|
|
33
|
+
周一 21:00
|
|
34
|
+
# 🐦 马上到世界BOSS啦
|
|
35
|
+
|
|
36
|
+
> 地点:仙幻岛 → 白青山脉
|
|
37
|
+
|
|
38
|
+
世界BOSS刷新!
|
|
39
|
+
|
|
40
|
+
周二 21:00
|
|
41
|
+
# 🐦 马上到世界BOSS啦
|
|
42
|
+
|
|
43
|
+
> 地点:仙幻岛 → 大漠
|
|
44
|
+
|
|
45
|
+
世界BOSS刷新!
|
|
46
|
+
|
|
47
|
+
周三 21:00
|
|
48
|
+
> 地点:仙幻岛 → 御龙林
|
|
49
|
+
|
|
50
|
+
世界BOSS刷新!
|
|
51
|
+
|
|
52
|
+
周四 21:00
|
|
53
|
+
> 地点:仙幻岛 → 水月平原
|
|
54
|
+
|
|
55
|
+
世界BOSS刷新!
|
|
56
|
+
|
|
57
|
+
周五 21:00
|
|
58
|
+
> 地点:白青山脉 → 大漠
|
|
59
|
+
|
|
60
|
+
世界BOSS刷新!
|
|
61
|
+
|
|
62
|
+
周六 15:00
|
|
63
|
+
> 地点:仙幻岛 → 白青山脉
|
|
64
|
+
|
|
65
|
+
世界BOSS刷新!
|
|
66
|
+
|
|
67
|
+
周六 21:00
|
|
68
|
+
> 地点:仙幻岛 → 御龙林
|
|
69
|
+
|
|
70
|
+
世界BOSS刷新!
|
|
71
|
+
|
|
72
|
+
周日 15:00
|
|
73
|
+
> 地点:仙幻岛 → 大漠
|
|
74
|
+
|
|
75
|
+
世界BOSS刷新!
|
|
76
|
+
|
|
77
|
+
周日 21:00
|
|
78
|
+
> 地点:仙幻岛 → 水月平原
|
|
79
|
+
|
|
80
|
+
世界BOSS刷新!
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
## 示例:任务「轻功」——每天三个时间点发同一句
|
|
34
84
|
|
|
35
|
-
任务「世界BOSS」,添加多行:
|
|
36
85
|
\`\`\`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
周三 21:00 世界BOSS刷新!仙幻岛 → 御龙林
|
|
40
|
-
周四 21:00 世界BOSS刷新!仙幻岛 → 水月平原
|
|
41
|
-
周五 21:00 世界BOSS刷新!白青山脉 → 大漠
|
|
42
|
-
周六 15:00 世界BOSS刷新!仙幻岛 → 白青山脉
|
|
43
|
-
周六 21:00 世界BOSS刷新!仙幻岛 → 御龙林
|
|
44
|
-
周日 15:00 世界BOSS刷新!仙幻岛 → 大漠
|
|
45
|
-
周日 21:00 世界BOSS刷新!仙幻岛 → 水月平原
|
|
86
|
+
每天 12:20, 18:20, 21:20
|
|
87
|
+
⏰ 轻功时间到了!
|
|
46
88
|
\`\`\`
|
|
47
|
-
每行对应一个时间段,内容各不相同。
|
|
48
89
|
|
|
49
90
|
## Markdown 卡片(官方QQ机器人)
|
|
50
91
|
|
|
@@ -128,10 +169,46 @@ function dayMatches(daysSet, day) {
|
|
|
128
169
|
return !daysSet || daysSet.has(day)
|
|
129
170
|
}
|
|
130
171
|
|
|
172
|
+
// 判断某 token 是否是合法的星期写法
|
|
173
|
+
const WEEK_TOKEN_RE = /^(?:工作日|周末|休息日|每天|每日|天天|(?:每)?(?:星期|周|礼拜)[一二三四五六日天]|[一二三四五六日天]|[1-7](?:[,,、\s]+[1-7])*|[1-7]\s*[-~]\s*[1-7])$/
|
|
174
|
+
|
|
175
|
+
// 解析"日程大文本框":每个时间段 = 一行"星期 时间"作为开头,之后(到下一个"星期 时间"行之前)的行都是该时间段的内容(多行 markdown,保留换行)
|
|
176
|
+
// 例:
|
|
177
|
+
// 周一 21:00
|
|
178
|
+
// # 马上到世界BOSS
|
|
179
|
+
// > 地点:仙幻岛 → 白青山脉
|
|
180
|
+
// 游戏底部有道地按钮
|
|
181
|
+
//
|
|
182
|
+
// 周六 15:00
|
|
183
|
+
// # 世界BOSS
|
|
184
|
+
// > 地点:仙幻岛 → 白青山脉
|
|
185
|
+
function parseScheduleBlocks(text) {
|
|
186
|
+
const lines = String(text || '').split(/\r?\n/)
|
|
187
|
+
const blocks = []
|
|
188
|
+
let cur = null
|
|
189
|
+
for (const raw of lines) {
|
|
190
|
+
const line = raw.replace(/\s+$/, '')
|
|
191
|
+
// 尝试识别块头行:星期token 开头 + 后面是时间串
|
|
192
|
+
const m = line.match(/^(\S+)\s+(\d{1,2}:\d{2}(?:[,,、;;\s]*\d{1,2}:\d{2})*)\s*$/)
|
|
193
|
+
if (m && WEEK_TOKEN_RE.test(m[1])) {
|
|
194
|
+
cur = { idx: blocks.length, wd: m[1], time: m[2], textLines: [] }
|
|
195
|
+
blocks.push(cur)
|
|
196
|
+
}
|
|
197
|
+
else if (cur) {
|
|
198
|
+
cur.textLines.push(line)
|
|
199
|
+
}
|
|
200
|
+
// 未识别且还没遇到任何块头 → 忽略(前面是说明/空行)
|
|
201
|
+
}
|
|
202
|
+
return blocks.map(b => ({ idx: b.idx, wd: b.wd, time: b.time, text: b.textLines.join('\n').trim() }))
|
|
203
|
+
}
|
|
204
|
+
|
|
131
205
|
// 把任务的时间段解析成统一行列表:[{ idx, wd(星期), time, text }]
|
|
132
|
-
//
|
|
206
|
+
// 兼容四种写法:schedule 大文本框(优先)、旧 slots 数组、旧 time/text(每天)、旧 slots(days)
|
|
133
207
|
function parseScheduleRows(task) {
|
|
134
208
|
if (!task) return []
|
|
209
|
+
if (typeof task.schedule === 'string' && task.schedule.trim()) {
|
|
210
|
+
return parseScheduleBlocks(task.schedule)
|
|
211
|
+
}
|
|
135
212
|
if (Array.isArray(task.slots) && task.slots.length) {
|
|
136
213
|
return task.slots
|
|
137
214
|
.map((s, i) => ({
|
|
@@ -202,26 +279,15 @@ function buildTextFromMd(md) {
|
|
|
202
279
|
.trim()
|
|
203
280
|
}
|
|
204
281
|
|
|
205
|
-
exports._test = { parseTimes, todayDay, parseDays, dayMatches, parseScheduleRows, computeFires, renderText, buildMd, buildTextFromMd }
|
|
282
|
+
exports._test = { parseTimes, todayDay, parseDays, dayMatches, parseScheduleBlocks, parseScheduleRows, computeFires, renderText, buildMd, buildTextFromMd }
|
|
206
283
|
|
|
207
284
|
// ===== 配置 Schema =====
|
|
208
285
|
|
|
209
|
-
const SlotItem = Schema.object({
|
|
210
|
-
wd: Schema.string()
|
|
211
|
-
.default('')
|
|
212
|
-
.description('星期:留空=每天;周一/周二...;工作日(周一到五)/周末(周六日);或 1,3,5 / 1-5'),
|
|
213
|
-
time: Schema.string()
|
|
214
|
-
.required()
|
|
215
|
-
.description('时间(24小时制 HH:MM),可多个用逗号分隔,如 12:20, 18:20, 21:20'),
|
|
216
|
-
text: Schema.string()
|
|
217
|
-
.default('')
|
|
218
|
-
.description('内容:到点发送的话,支持 Markdown 和模板变量 {date} {time} {week} {task}'),
|
|
219
|
-
})
|
|
220
|
-
|
|
221
286
|
const TaskItem = Schema.object({
|
|
222
|
-
|
|
223
|
-
.
|
|
224
|
-
.
|
|
287
|
+
schedule: Schema.string()
|
|
288
|
+
.role('textarea', { rows: [6, 16] })
|
|
289
|
+
.default('')
|
|
290
|
+
.description('📅 日程:多行文本框,直接写整张日程。每个时间段分两段:第一行写 星期 时间,下面几行写内容(可多行 markdown)。示例见 usage。'),
|
|
225
291
|
groups: Schema.array(Schema.string())
|
|
226
292
|
.default([])
|
|
227
293
|
.description('手动指定的目标群 ID(可留空;推荐用群内"订阅"命令自动添加,无需手动填 openid)'),
|
|
@@ -236,7 +302,7 @@ const TaskItem = Schema.object({
|
|
|
236
302
|
const Config = Schema.object({
|
|
237
303
|
tasks: Schema.dict(TaskItem)
|
|
238
304
|
.default({})
|
|
239
|
-
.description('📋 推送任务表格(键=任务名;每个任务 =
|
|
305
|
+
.description('📋 推送任务表格(键=任务名;每个任务 = 一个日程大文本框 schedule:星期/时间/内容)'),
|
|
240
306
|
subscribeAuthority: Schema.number()
|
|
241
307
|
.min(1).max(4).default(1)
|
|
242
308
|
.description('执行订阅命令所需的最低权限等级(Koishi 用户权限等级,与QQ群主/管理员身份无关;默认 1=所有人都能订阅。如需限制,先在控制台"用户管理"给指定账号提权,再调高此值)'),
|
|
@@ -621,11 +687,11 @@ function apply(ctx, config) {
|
|
|
621
687
|
if (!testTask.groups || !testTask.groups.length) {
|
|
622
688
|
testTask.groups = session.guildId ? [String(session.guildId)] : []
|
|
623
689
|
}
|
|
624
|
-
//
|
|
690
|
+
// 优先发"今天会命中的行",否则发第一条(保证测试总能发出内容)
|
|
625
691
|
const rows = parseScheduleRows(task)
|
|
626
692
|
const day = todayDay()
|
|
627
693
|
const hit = rows.find(r => dayMatches(parseDays(r.wd), day))
|
|
628
|
-
const sendText = hit ? (hit.text || '') : (
|
|
694
|
+
const sendText = hit ? (hit.text || '') : (rows.length ? (rows[0].text || '') : '')
|
|
629
695
|
await sendTask(name, testTask, { text: sendText, markdown: task.markdown, onlyBotSid: session.bot?.sid })
|
|
630
696
|
return `✅ 已发送任务 "${name}"`
|
|
631
697
|
})
|
|
@@ -813,7 +879,7 @@ function apply(ctx, config) {
|
|
|
813
879
|
const rows = parseScheduleRows(task)
|
|
814
880
|
const day = todayDay()
|
|
815
881
|
const hit = rows.find(r => dayMatches(parseDays(r.wd), day))
|
|
816
|
-
const sendText = hit ? (hit.text || '') : (
|
|
882
|
+
const sendText = hit ? (hit.text || '') : (rows.length ? (rows[0].text || '') : '')
|
|
817
883
|
await sendTask(name, testTask, { text: sendText, markdown: task.markdown, onlyBotSid: session.bot?.sid })
|
|
818
884
|
return `✅ 已发送任务 "${name}"`
|
|
819
885
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-ll-schedule",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Scheduled message sender with weekly slots (per-day time & content), QQ official markdown card support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -20,3 +20,6 @@
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|