koishi-plugin-ll-schedule 1.0.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.
@@ -0,0 +1,16 @@
1
+ name: Publish to npm
2
+ on:
3
+ push:
4
+ branches: [master]
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - uses: actions/setup-node@v4
11
+ with:
12
+ node-version: '22'
13
+ registry-url: 'https://registry.npmjs.org'
14
+ - run: npm publish
15
+ env:
16
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/index.js ADDED
@@ -0,0 +1,138 @@
1
+ const { Schema, h } = require('koishi')
2
+
3
+ // 定时任务项
4
+ const TaskItem = Schema.object({
5
+ time: Schema.string()
6
+ .required()
7
+ .description('发送时间,HH:MM 格式(24小时制),如 08:30、21:00'),
8
+ groups: Schema.array(Schema.string())
9
+ .default([])
10
+ .description('目标群号(留空=所有群)'),
11
+ text: Schema.string()
12
+ .default('')
13
+ .description('文字内容(可留空)'),
14
+ image: Schema.string()
15
+ .default('')
16
+ .description('图片URL(可留空)'),
17
+ enabled: Schema.boolean()
18
+ .default(true)
19
+ .description('是否启用'),
20
+ })
21
+
22
+ const Config = Schema.object({
23
+ tasks: Schema.dict(TaskItem)
24
+ .default({})
25
+ .description('📋 定时任务表格(键=任务名)'),
26
+ })
27
+
28
+ function apply(ctx, config) {
29
+ // 记录当天已发送的任务,防止同一分钟重复发
30
+ let firedToday = {}
31
+
32
+ function nowHM() {
33
+ const d = new Date()
34
+ return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
35
+ }
36
+
37
+ function todayStr() {
38
+ const d = new Date()
39
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
40
+ }
41
+
42
+ async function checkAndSend() {
43
+ const today = todayStr()
44
+ // 跨天重置
45
+ if (firedToday.date !== today) {
46
+ firedToday = { date: today }
47
+ }
48
+
49
+ const hm = nowHM()
50
+ const tasks = config.tasks || {}
51
+
52
+ for (const [name, task] of Object.entries(tasks)) {
53
+ if (!task || task.enabled === false) continue
54
+ if (!task.time || task.time.trim() !== hm) continue
55
+ // 今天这个时间点已经发过这个任务 → 跳过
56
+ const fireKey = `${name}@${hm}`
57
+ if (firedToday[fireKey]) continue
58
+ firedToday[fireKey] = true
59
+
60
+ ctx.logger.info(`[schedule] 触发任务 "${name}" @ ${hm}`)
61
+ await sendTask(name, task)
62
+ }
63
+ }
64
+
65
+ async function sendTask(name, task) {
66
+ // 组装消息
67
+ const parts = []
68
+ if (task.text) parts.push(task.text)
69
+ if (task.image) {
70
+ if (parts.length) parts.push('\n')
71
+ parts.push(h.image(task.image))
72
+ }
73
+ if (!parts.length) {
74
+ ctx.logger.warn(`[schedule] 任务 "${name}" 没有文字也没有图片,跳过`)
75
+ return
76
+ }
77
+
78
+ const bots = ctx.bots || []
79
+ if (!bots.length) return
80
+
81
+ const groups = (task.groups && task.groups.length > 0) ? task.groups : null
82
+
83
+ for (const bot of bots) {
84
+ if (groups) {
85
+ for (const gid of groups) {
86
+ try { await bot.sendMessage(gid, parts) } catch (e) {
87
+ ctx.logger.error(`[schedule] 发送到群 ${gid} 失败: ${e.message}`)
88
+ }
89
+ }
90
+ } else {
91
+ ctx.logger.warn(`[schedule] 任务 "${name}" 未指定群号,跳过(请配置目标群)`)
92
+ }
93
+ }
94
+ }
95
+
96
+ // 每 20 秒检查一次(保证同一分钟内能触发)
97
+ const timer = setInterval(checkAndSend, 20000)
98
+ // 启动时立即检查一次
99
+ checkAndSend()
100
+
101
+ ctx.on('dispose', () => clearInterval(timer))
102
+
103
+ // ===== 命令 =====
104
+ ctx.command('schedule', '定时发送管理')
105
+ .action(() =>
106
+ '⚙️ 管理:插件设置页 → ll-schedule → tasks 表格\n' +
107
+ '命令:schedule.list 查看任务 | schedule.test <任务名> 立即测试发送'
108
+ )
109
+
110
+ ctx.command('schedule.list', '查看定时任务')
111
+ .action(() => {
112
+ const tasks = config.tasks || {}
113
+ const keys = Object.keys(tasks)
114
+ if (!keys.length) return '暂无定时任务\n请在插件设置页的 tasks 表格中添加'
115
+ return keys.map(k => {
116
+ const t = tasks[k]
117
+ const s = t.enabled === false ? '⛔' : '✅'
118
+ const content = [t.text && '文字', t.image && '图片'].filter(Boolean).join('+') || '(空)'
119
+ return `${s} ${k} @ ${t.time} → ${content} → 群:${(t.groups || []).join(',') || '未指定'}`
120
+ }).join('\n')
121
+ })
122
+
123
+ ctx.command('schedule.test <name>', '立即测试发送指定任务')
124
+ .action(async ({ session }, name) => {
125
+ const tasks = config.tasks || {}
126
+ const task = tasks[name]
127
+ if (!task) return `未找到任务 "${name}",可用 schedule.list 查看任务名`
128
+ // 测试时:任务没配群就发到当前群
129
+ const testTask = { ...task }
130
+ if (!testTask.groups || !testTask.groups.length) {
131
+ testTask.groups = session.guildId ? [String(session.guildId)] : []
132
+ }
133
+ await sendTask(name, testTask)
134
+ return `✅ 已发送任务 "${name}"`
135
+ })
136
+ }
137
+
138
+ module.exports = { Config, apply }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "koishi-plugin-ll-schedule",
3
+ "version": "1.0.0",
4
+ "description": "Scheduled message sender with text/image support",
5
+ "main": "index.js",
6
+ "license": "MIT",
7
+ "keywords": ["koishi", "koishi-plugin", "schedule", "timer", "定时"],
8
+ "peerDependencies": {
9
+ "koishi": "^4.18.0"
10
+ },
11
+ "koishi": {
12
+ "description": {
13
+ "zh": "定时发送插件。设置时间后自动发送文字/图片到指定群。",
14
+ "en": "Scheduled message sender. Auto-send text/image to groups at set times."
15
+ }
16
+ }
17
+ }