koishi-plugin-ll-schedule 1.4.2 → 1.5.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/index.js +83 -3
- package/package.json +3 -1
package/index.js
CHANGED
|
@@ -24,6 +24,9 @@ exports.usage = `定时提醒:每个任务一个大文本框(schedule)。每
|
|
|
24
24
|
内容支持 Markdown 卡片、模板变量 {date} {time} {week} {task}
|
|
25
25
|
|
|
26
26
|
群命令:订阅 任务名 / 取消订阅 任务名 / 订阅列表 / schedule.list / schedule.groups / schedule.test 任务名 / schedule.testcur 任务名(只发当前群) / 我的ID
|
|
27
|
+
|
|
28
|
+
推送接口:POST /ll-schedule/push {"secret":"密钥","task":"任务名","place1":"xxx","delay":0}
|
|
29
|
+
|
|
27
30
|
`
|
|
28
31
|
|
|
29
32
|
// ===== 纯函数(可单测) =====
|
|
@@ -173,6 +176,25 @@ function renderText(text, ctx) {
|
|
|
173
176
|
.replace(/\{task\}/g, ctx.task)
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
// 接口推送默认模板(任务没配 pushTemplate 时用)
|
|
180
|
+
const DEFAULT_PUSH_TEMPLATE = [
|
|
181
|
+
'# 🎉 {task}开始啦!',
|
|
182
|
+
'',
|
|
183
|
+
'> 地点1:{place1}',
|
|
184
|
+
'> 地点2:{place2}',
|
|
185
|
+
'{time}',
|
|
186
|
+
].join('\n')
|
|
187
|
+
|
|
188
|
+
// 渲染接口推送模板:body 里除 secret/task/delay 外的字段都可作为变量 {字段名} 填入
|
|
189
|
+
// 然后走 renderText 处理内置变量 {date} {time} {week} {task}
|
|
190
|
+
function renderPushTemplate(tpl, data, builtin) {
|
|
191
|
+
let out = String(tpl || DEFAULT_PUSH_TEMPLATE)
|
|
192
|
+
for (const [k, v] of Object.entries(data || {})) {
|
|
193
|
+
out = out.split('{' + k + '}').join(v === undefined || v === null ? '' : String(v))
|
|
194
|
+
}
|
|
195
|
+
return renderText(out, builtin)
|
|
196
|
+
}
|
|
197
|
+
|
|
176
198
|
// 组装 markdown:文本 + 图片
|
|
177
199
|
function buildMd(text, image) {
|
|
178
200
|
let md = String(text || '').trim()
|
|
@@ -195,7 +217,7 @@ function buildTextFromMd(md) {
|
|
|
195
217
|
.trim()
|
|
196
218
|
}
|
|
197
219
|
|
|
198
|
-
exports._test = { parseTimes, todayDay, parseDays, dayMatches, parseScheduleBlocks, parseScheduleRows, computeFires, renderText, buildMd, buildTextFromMd }
|
|
220
|
+
exports._test = { parseTimes, todayDay, parseDays, dayMatches, parseScheduleBlocks, parseScheduleRows, computeFires, renderText, renderPushTemplate, buildMd, buildTextFromMd }
|
|
199
221
|
|
|
200
222
|
// ===== 配置 Schema =====
|
|
201
223
|
|
|
@@ -210,6 +232,10 @@ const TaskItem = Schema.object({
|
|
|
210
232
|
markdown: Schema.boolean()
|
|
211
233
|
.default(false)
|
|
212
234
|
.description('用 QQ官方 Markdown 卡片发送(仅官方QQ机器人生效,失败自动回退纯文本;其他平台始终纯文本)'),
|
|
235
|
+
pushTemplate: Schema.string()
|
|
236
|
+
.role('textarea', { rows: [3, 8] })
|
|
237
|
+
.default('')
|
|
238
|
+
.description('接口推送模板(可选):HTTP 接口推送时套用的卡片模板。变量 {place1} {place2} 等 = 推送方传入的字段;留空用默认模板。多行 markdown,例见 usage'),
|
|
213
239
|
enabled: Schema.boolean()
|
|
214
240
|
.default(true)
|
|
215
241
|
.description('是否启用'),
|
|
@@ -219,6 +245,9 @@ const Config = Schema.object({
|
|
|
219
245
|
tasks: Schema.dict(TaskItem)
|
|
220
246
|
.default({})
|
|
221
247
|
.description('📋 推送任务表格(键=任务名;每个任务 = 一个日程大文本框 schedule:星期/时间/内容)'),
|
|
248
|
+
pushSecret: Schema.string()
|
|
249
|
+
.default('')
|
|
250
|
+
.description('🔑 HTTP 推送接口密钥:外部系统调用 POST /ll-schedule/push 时须携带相同 secret(留空则接口拒绝所有推送)'),
|
|
222
251
|
subscribeAuthority: Schema.number()
|
|
223
252
|
.min(1).max(4).default(1)
|
|
224
253
|
.description('执行订阅命令所需的最低权限等级(Koishi 用户权限等级,与QQ群主/管理员身份无关;默认 1=所有人都能订阅。如需限制,先在控制台"用户管理"给指定账号提权,再调高此值)'),
|
|
@@ -235,8 +264,8 @@ const Config = Schema.object({
|
|
|
235
264
|
|
|
236
265
|
exports.Config = Config
|
|
237
266
|
|
|
238
|
-
//
|
|
239
|
-
exports.inject = { required: ['database'] }
|
|
267
|
+
// 需要数据库 + HTTP 服务(router):推送接口依赖 router,没有 router 时自动跳过接口功能
|
|
268
|
+
exports.inject = { required: ['database'], optional: ['router'] }
|
|
240
269
|
|
|
241
270
|
function apply(ctx, config) {
|
|
242
271
|
const logger = ctx.logger('ll-schedule')
|
|
@@ -517,6 +546,57 @@ function apply(ctx, config) {
|
|
|
517
546
|
}
|
|
518
547
|
}
|
|
519
548
|
|
|
549
|
+
// ===== HTTP 推送接口:外部系统 POST 数据 → 转为卡片发给该任务订阅群 =====
|
|
550
|
+
if (ctx.router) {
|
|
551
|
+
ctx.router.post('/ll-schedule/push', async (ctx) => {
|
|
552
|
+
const respond = (status, body) => { ctx.status = status; ctx.body = body }
|
|
553
|
+
let body = null
|
|
554
|
+
try {
|
|
555
|
+
body = (typeof ctx.request.body === 'string') ? JSON.parse(ctx.request.body) : (ctx.request.body || {})
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
return respond(400, { ok: false, error: 'invalid json body' })
|
|
559
|
+
}
|
|
560
|
+
// 密钥校验(未配置密钥则一律拒绝)
|
|
561
|
+
if (!config.pushSecret) return respond(403, { ok: false, error: 'pushSecret not configured' })
|
|
562
|
+
if (body.secret !== config.pushSecret) return respond(403, { ok: false, error: 'invalid secret' })
|
|
563
|
+
|
|
564
|
+
const taskName = String(body.task || '')
|
|
565
|
+
const task = (config.tasks || {})[taskName]
|
|
566
|
+
if (!taskName || !task) return respond(404, { ok: false, error: `task "${taskName}" not found` })
|
|
567
|
+
|
|
568
|
+
// 除 secret/task/delay 外的字段都作为模板变量(place1/place2/time/...)
|
|
569
|
+
const data = { ...body }
|
|
570
|
+
delete data.secret
|
|
571
|
+
delete data.task
|
|
572
|
+
delete data.delay
|
|
573
|
+
const now = new Date()
|
|
574
|
+
const rendered = renderPushTemplate(task.pushTemplate, data, {
|
|
575
|
+
date: todayStr(now),
|
|
576
|
+
time: nowHM(now),
|
|
577
|
+
week: '星期' + WEEK_CN[todayDay(now)],
|
|
578
|
+
task: taskName,
|
|
579
|
+
})
|
|
580
|
+
if (!rendered.trim()) return respond(400, { ok: false, error: 'empty content' })
|
|
581
|
+
|
|
582
|
+
const delay = Math.max(0, parseInt(body.delay, 10) || 0)
|
|
583
|
+
const push = async () => {
|
|
584
|
+
// 只发给订阅群(含手动 groups),不发全量已知群
|
|
585
|
+
await sendTask(taskName, task, { text: rendered, markdown: task.markdown })
|
|
586
|
+
}
|
|
587
|
+
if (delay > 0) {
|
|
588
|
+
setTimeout(push, delay * 60 * 1000)
|
|
589
|
+
logger.info(`[schedule] 接口已接收任务 "${taskName}",${delay} 分钟后推送`)
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
await push()
|
|
593
|
+
logger.info(`[schedule] 接口已推送任务 "${taskName}"`)
|
|
594
|
+
}
|
|
595
|
+
return respond(200, { ok: true, delay, task: taskName })
|
|
596
|
+
})
|
|
597
|
+
logger.info('[schedule] HTTP 推送接口已注册: POST /ll-schedule/push')
|
|
598
|
+
}
|
|
599
|
+
|
|
520
600
|
async function checkAndSend() {
|
|
521
601
|
const now = new Date()
|
|
522
602
|
const today = todayStr(now)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-ll-schedule",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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": [
|
|
@@ -25,3 +25,5 @@
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
|
|
29
|
+
|