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.
Files changed (2) hide show
  1. package/index.js +110 -44
  2. 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. 在插件设置页的 \`tasks\` 里**添加任务**(键=任务名),在任务里 **添加时间段**:每行填 星期(留空=每天)/ 时间 / 内容
19
- 2. 在任意群 @机器人 发:\`订阅 任务名\` → 该群加入此任务的订阅列表
20
- 3. 到时间,插件自动把该时间段的内容发给所有订阅群
18
+ 1. 添加任务(键=任务名),在任务的 \`schedule\` 大文本框里直接写日程(格式见下)
19
+ 2. 在任意群 @机器人 发:\`订阅 任务名\` → 该群加入此任务订阅列表
20
+ 3. 到点自动发送
21
21
 
22
- ## 每个时间段三格子
22
+ ## 日程怎么写
23
23
 
24
- - **星期**:留空 = 每天;写 \`周一\` \`周六\`...、\`工作日\`(周一到周五)、\`周末\`(周六日)、或 \`1,3,5\` 数字
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
- 任务「轻功」,添加 1 行: 星期=空(每天) 时间=\`12:20, 18:20, 21:20\` 内容=\`⏰ 轻功时间到了!\`
31
- → 每天这三个点都发这句。
30
+ ## 示例:任务「世界BOSS」——直接把下面整段复制到文本框
32
31
 
33
- ## 示例:按天/按时段发不同内容(如世界BOSS)
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
- 周一 21:00 世界BOSS刷新!仙幻岛 → 白青山脉
38
- 周二 21:00 世界BOSS刷新!仙幻岛 → 大漠
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
- // 兼容三种写法:新 slots 数组(优先)、旧 time/text(每天)、旧 slots(days)
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
- slots: Schema.array(SlotItem)
223
- .default([])
224
- .description('📅 时间段列表:每行 = 星期 + 时间 + 内容,点 + 可添加多行;每行内容各自独立'),
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 || '') : (task.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 || '') : (task.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.0",
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
+