koishi-plugin-maple-schedule 0.0.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/lib/index.d.ts +28 -0
- package/lib/index.js +445 -0
- package/package.json +18 -0
- package/readme.md +5 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "maple-schedule";
|
|
3
|
+
export declare const using: readonly ["database"];
|
|
4
|
+
interface ScheduleTask {
|
|
5
|
+
id: number;
|
|
6
|
+
platform: string;
|
|
7
|
+
botId: string;
|
|
8
|
+
channelId: string | null;
|
|
9
|
+
userId: string;
|
|
10
|
+
content: string;
|
|
11
|
+
nextExecutionTime: Date;
|
|
12
|
+
intervalMs: number | null;
|
|
13
|
+
}
|
|
14
|
+
declare module 'koishi' {
|
|
15
|
+
interface Tables {
|
|
16
|
+
'maple-schedule-tasks': ScheduleTask;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export interface Config {
|
|
20
|
+
maxContentLength: number;
|
|
21
|
+
maxPreviewLength: number;
|
|
22
|
+
sendInterval: number;
|
|
23
|
+
scanInterval: number;
|
|
24
|
+
timeTolerance: number;
|
|
25
|
+
}
|
|
26
|
+
export declare const Config: Schema<Config>;
|
|
27
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
28
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Config: () => Config,
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
name: () => name,
|
|
26
|
+
using: () => using
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
var import_koishi = require("koishi");
|
|
30
|
+
var name = "maple-schedule";
|
|
31
|
+
var using = ["database"];
|
|
32
|
+
var Config = import_koishi.Schema.object({
|
|
33
|
+
maxContentLength: import_koishi.Schema.number().default(500).min(50).max(2e3).description("任务内容最大长度(字)(50-2000)"),
|
|
34
|
+
maxPreviewLength: import_koishi.Schema.number().default(8).min(3).max(50).description("定时列表最大预览长度(字)(3-50)"),
|
|
35
|
+
sendInterval: import_koishi.Schema.number().default(1e3).min(100).max(1e4).description("任务发送间隔(毫秒)(100-10000)"),
|
|
36
|
+
scanInterval: import_koishi.Schema.number().default(1).min(1).max(86400).description("检查器扫描时间(秒)(1-86400)"),
|
|
37
|
+
timeTolerance: import_koishi.Schema.number().default(30).min(1).max(3600).description("定时时间容错(秒)(1-3600)")
|
|
38
|
+
});
|
|
39
|
+
function parseTimeToMs(timeStr) {
|
|
40
|
+
if (!timeStr) return null;
|
|
41
|
+
const regex = /^(\d+)([smhd])$/i;
|
|
42
|
+
const match = timeStr.match(regex);
|
|
43
|
+
if (!match) return null;
|
|
44
|
+
const value = parseInt(match[1]);
|
|
45
|
+
const unit = match[2].toLowerCase();
|
|
46
|
+
switch (unit) {
|
|
47
|
+
case "s":
|
|
48
|
+
return value * 1e3;
|
|
49
|
+
case "m":
|
|
50
|
+
return value * 60 * 1e3;
|
|
51
|
+
case "h":
|
|
52
|
+
return value * 60 * 60 * 1e3;
|
|
53
|
+
case "d":
|
|
54
|
+
return value * 24 * 60 * 60 * 1e3;
|
|
55
|
+
default:
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
__name(parseTimeToMs, "parseTimeToMs");
|
|
60
|
+
function parseCombinedTimeToMs(timeStr) {
|
|
61
|
+
const regex = /(\d+)([smhd])/gi;
|
|
62
|
+
let totalMs = 0;
|
|
63
|
+
let match;
|
|
64
|
+
while ((match = regex.exec(timeStr)) !== null) {
|
|
65
|
+
const value = parseInt(match[1]);
|
|
66
|
+
const unit = match[2].toLowerCase();
|
|
67
|
+
switch (unit) {
|
|
68
|
+
case "s":
|
|
69
|
+
totalMs += value * 1e3;
|
|
70
|
+
break;
|
|
71
|
+
case "m":
|
|
72
|
+
totalMs += value * 60 * 1e3;
|
|
73
|
+
break;
|
|
74
|
+
case "h":
|
|
75
|
+
totalMs += value * 60 * 60 * 1e3;
|
|
76
|
+
break;
|
|
77
|
+
case "d":
|
|
78
|
+
totalMs += value * 24 * 60 * 60 * 1e3;
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return totalMs > 0 ? totalMs : null;
|
|
85
|
+
}
|
|
86
|
+
__name(parseCombinedTimeToMs, "parseCombinedTimeToMs");
|
|
87
|
+
function parseAbsoluteTime(timeStr, baseDate = /* @__PURE__ */ new Date()) {
|
|
88
|
+
try {
|
|
89
|
+
const result = new Date(baseDate);
|
|
90
|
+
if (/^\d{1,2}:\d{2}$/.test(timeStr)) {
|
|
91
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
92
|
+
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
result.setHours(hours, minutes, 0, 0);
|
|
96
|
+
if (result <= baseDate) {
|
|
97
|
+
result.setDate(result.getDate() + 1);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
if (/^\d{1,2}\.\d{1,2},\d{1,2}:\d{2}$/.test(timeStr)) {
|
|
102
|
+
const [datePart, timePart] = timeStr.split(",");
|
|
103
|
+
const [month, day] = datePart.split(".").map(Number);
|
|
104
|
+
const [hours, minutes] = timePart.split(":").map(Number);
|
|
105
|
+
if (month < 1 || month > 12 || day < 1 || day > 31 || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
result.setMonth(month - 1, day);
|
|
109
|
+
result.setHours(hours, minutes, 0, 0);
|
|
110
|
+
if (result <= baseDate) {
|
|
111
|
+
result.setFullYear(result.getFullYear() + 1);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
__name(parseAbsoluteTime, "parseAbsoluteTime");
|
|
121
|
+
function formatDateTime(date) {
|
|
122
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
123
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
124
|
+
const hours = date.getHours().toString().padStart(2, "0");
|
|
125
|
+
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
126
|
+
const seconds = date.getSeconds().toString().padStart(2, "0");
|
|
127
|
+
return `${month}/${day} ${hours}:${minutes}:${seconds}`;
|
|
128
|
+
}
|
|
129
|
+
__name(formatDateTime, "formatDateTime");
|
|
130
|
+
function formatInterval(intervalMs) {
|
|
131
|
+
const days = Math.floor(intervalMs / (24 * 60 * 60 * 1e3));
|
|
132
|
+
const hours = Math.floor(intervalMs % (24 * 60 * 60 * 1e3) / (60 * 60 * 1e3));
|
|
133
|
+
const minutes = Math.floor(intervalMs % (60 * 60 * 1e3) / (60 * 1e3));
|
|
134
|
+
const seconds = Math.floor(intervalMs % (60 * 1e3) / 1e3);
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (days > 0) parts.push(`${days}天`);
|
|
137
|
+
if (hours > 0) parts.push(`${hours}小时`);
|
|
138
|
+
if (minutes > 0) parts.push(`${minutes}分钟`);
|
|
139
|
+
if (seconds > 0) parts.push(`${seconds}秒`);
|
|
140
|
+
return parts.join("");
|
|
141
|
+
}
|
|
142
|
+
__name(formatInterval, "formatInterval");
|
|
143
|
+
function calculateNextExecutionTime(timeExpression, baseDate = /* @__PURE__ */ new Date()) {
|
|
144
|
+
try {
|
|
145
|
+
const hasInterval = timeExpression.includes("/");
|
|
146
|
+
if (hasInterval) {
|
|
147
|
+
const [initialPart, intervalPart] = timeExpression.split("/");
|
|
148
|
+
let initialTime;
|
|
149
|
+
const initialMs = parseTimeToMs(initialPart) || parseCombinedTimeToMs(initialPart);
|
|
150
|
+
if (initialMs) {
|
|
151
|
+
initialTime = new Date(baseDate.getTime() + initialMs);
|
|
152
|
+
} else {
|
|
153
|
+
initialTime = parseAbsoluteTime(initialPart, baseDate);
|
|
154
|
+
if (!initialTime) return null;
|
|
155
|
+
}
|
|
156
|
+
const intervalMs = parseTimeToMs(intervalPart) || parseCombinedTimeToMs(intervalPart);
|
|
157
|
+
if (!intervalMs) return null;
|
|
158
|
+
let nextTime = initialTime;
|
|
159
|
+
while (nextTime <= baseDate) {
|
|
160
|
+
nextTime = new Date(nextTime.getTime() + intervalMs);
|
|
161
|
+
}
|
|
162
|
+
return { nextTime, intervalMs };
|
|
163
|
+
} else {
|
|
164
|
+
let nextTime;
|
|
165
|
+
const ms = parseTimeToMs(timeExpression) || parseCombinedTimeToMs(timeExpression);
|
|
166
|
+
if (ms) {
|
|
167
|
+
nextTime = new Date(baseDate.getTime() + ms);
|
|
168
|
+
} else {
|
|
169
|
+
nextTime = parseAbsoluteTime(timeExpression, baseDate);
|
|
170
|
+
if (!nextTime) return null;
|
|
171
|
+
}
|
|
172
|
+
return { nextTime, intervalMs: null };
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
__name(calculateNextExecutionTime, "calculateNextExecutionTime");
|
|
179
|
+
function getContentPreview(content, maxLength) {
|
|
180
|
+
if (content.length <= maxLength) {
|
|
181
|
+
return content;
|
|
182
|
+
}
|
|
183
|
+
return content.substring(0, maxLength) + "...";
|
|
184
|
+
}
|
|
185
|
+
__name(getContentPreview, "getContentPreview");
|
|
186
|
+
function apply(ctx, config) {
|
|
187
|
+
ctx.logger("maple-schedule").info("定时任务插件加载中...");
|
|
188
|
+
ctx.model.extend("maple-schedule-tasks", {
|
|
189
|
+
id: "unsigned",
|
|
190
|
+
platform: "string",
|
|
191
|
+
botId: "string",
|
|
192
|
+
channelId: "string",
|
|
193
|
+
userId: "string",
|
|
194
|
+
content: "text",
|
|
195
|
+
nextExecutionTime: "timestamp",
|
|
196
|
+
intervalMs: "integer"
|
|
197
|
+
}, {
|
|
198
|
+
primary: "id",
|
|
199
|
+
autoInc: true
|
|
200
|
+
});
|
|
201
|
+
let timer;
|
|
202
|
+
let isChecking = false;
|
|
203
|
+
async function checkAndExecuteTasks() {
|
|
204
|
+
if (isChecking) {
|
|
205
|
+
ctx.logger("maple-schedule").debug("任务扫描正在进行中,跳过本次扫描");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
isChecking = true;
|
|
209
|
+
try {
|
|
210
|
+
const now = /* @__PURE__ */ new Date();
|
|
211
|
+
const expiredTasks = await ctx.database.get("maple-schedule-tasks", {
|
|
212
|
+
nextExecutionTime: { $lte: now }
|
|
213
|
+
});
|
|
214
|
+
if (expiredTasks.length === 0) {
|
|
215
|
+
ctx.logger("maple-schedule").debug("没有找到到期任务");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
expiredTasks.sort((a, b) => a.nextExecutionTime.getTime() - b.nextExecutionTime.getTime());
|
|
219
|
+
ctx.logger("maple-schedule").info(`找到 ${expiredTasks.length} 个到期任务,开始依次发送...`);
|
|
220
|
+
const toleranceMs = config.timeTolerance * 1e3;
|
|
221
|
+
ctx.logger("maple-schedule").debug(`当前时间容错: ${config.timeTolerance} 秒 (${toleranceMs} 毫秒)`);
|
|
222
|
+
for (let i = 0; i < expiredTasks.length; i++) {
|
|
223
|
+
const task = expiredTasks[i];
|
|
224
|
+
try {
|
|
225
|
+
const taskTime = task.nextExecutionTime;
|
|
226
|
+
const shouldHaveTime = formatDateTime(taskTime);
|
|
227
|
+
const timeDiff = now.getTime() - taskTime.getTime();
|
|
228
|
+
const isOverdue = timeDiff > toleranceMs;
|
|
229
|
+
if (isOverdue) {
|
|
230
|
+
ctx.logger("maple-schedule").info(`执行任务 #${task.id},本应在 ${shouldHaveTime} 执行`);
|
|
231
|
+
} else {
|
|
232
|
+
ctx.logger("maple-schedule").info(`执行任务 #${task.id},按时在 ${shouldHaveTime} 执行`);
|
|
233
|
+
}
|
|
234
|
+
const botId = `${task.platform}:${task.botId}`;
|
|
235
|
+
const bot = ctx.bots.find(
|
|
236
|
+
(bot2) => bot2.sid === botId || bot2.platform === task.platform && bot2.selfId === task.botId
|
|
237
|
+
);
|
|
238
|
+
if (!bot) {
|
|
239
|
+
ctx.logger("maple-schedule").warn(`机器人 ${botId} 不存在,跳过任务 #${task.id}`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
let messageContent = task.content;
|
|
243
|
+
if (isOverdue) {
|
|
244
|
+
messageContent = `过期任务: 本应在 ${shouldHaveTime} 执行
|
|
245
|
+
${task.content}`;
|
|
246
|
+
}
|
|
247
|
+
if (task.channelId) {
|
|
248
|
+
await bot.sendMessage(task.channelId, messageContent);
|
|
249
|
+
ctx.logger("maple-schedule").info(`任务 #${task.id} 已在群聊 ${task.channelId} 执行`);
|
|
250
|
+
} else {
|
|
251
|
+
await bot.sendPrivateMessage(task.userId, messageContent);
|
|
252
|
+
ctx.logger("maple-schedule").info(`任务 #${task.id} 已向用户 ${task.userId} 执行`);
|
|
253
|
+
}
|
|
254
|
+
if (task.intervalMs) {
|
|
255
|
+
let nextTime = new Date(task.nextExecutionTime);
|
|
256
|
+
let skippedCount = 0;
|
|
257
|
+
while (nextTime <= now) {
|
|
258
|
+
nextTime = new Date(nextTime.getTime() + task.intervalMs);
|
|
259
|
+
skippedCount++;
|
|
260
|
+
}
|
|
261
|
+
if (skippedCount > 1) {
|
|
262
|
+
ctx.logger("maple-schedule").info(`任务 #${task.id} 跳过了 ${skippedCount - 1} 次已过期循环`);
|
|
263
|
+
}
|
|
264
|
+
await ctx.database.set("maple-schedule-tasks", { id: task.id }, {
|
|
265
|
+
nextExecutionTime: nextTime
|
|
266
|
+
});
|
|
267
|
+
ctx.logger("maple-schedule").info(`任务 #${task.id} 下次执行时间更新为: ${formatDateTime(nextTime)}`);
|
|
268
|
+
} else {
|
|
269
|
+
await ctx.database.remove("maple-schedule-tasks", { id: task.id });
|
|
270
|
+
ctx.logger("maple-schedule").info(`一次性任务 #${task.id} 已执行并删除`);
|
|
271
|
+
}
|
|
272
|
+
if (i < expiredTasks.length - 1 && config.sendInterval > 0) {
|
|
273
|
+
ctx.logger("maple-schedule").info(`等待 ${config.sendInterval} 毫秒后发送下一个任务...`);
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, config.sendInterval));
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
ctx.logger("maple-schedule").error(`执行定时任务 #${task.id} 时出错:`, error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (expiredTasks.length > 0) {
|
|
281
|
+
ctx.logger("maple-schedule").info(`已处理完 ${expiredTasks.length} 个到期任务`);
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
ctx.logger("maple-schedule").error("检查定时任务时出错:", error);
|
|
285
|
+
} finally {
|
|
286
|
+
isChecking = false;
|
|
287
|
+
ctx.logger("maple-schedule").debug("任务扫描已完成,互斥锁已释放");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
__name(checkAndExecuteTasks, "checkAndExecuteTasks");
|
|
291
|
+
ctx.command("定时", "定时任务相关指令").action(({ session }) => {
|
|
292
|
+
return session.execute("help 定时");
|
|
293
|
+
});
|
|
294
|
+
ctx.command("定时/定时任务 <timeExpression:string> <content:text>", "创建定时任务").alias("定时任务").example("定时任务 1m 该喝水啦!").example("定时任务 10:00 起床时间到!").action(async ({ session }, timeExpression, content) => {
|
|
295
|
+
try {
|
|
296
|
+
if (!content || content.trim().length === 0) {
|
|
297
|
+
return "任务内容不能为空!\n格式:定时任务 时间 任务内容\n示例:定时任务 1m 该喝水啦!";
|
|
298
|
+
}
|
|
299
|
+
const trimmedContent = content.trim();
|
|
300
|
+
if (trimmedContent.length > config.maxContentLength) {
|
|
301
|
+
return `任务内容太长了!最多只能输入${config.maxContentLength}个字(当前: ${trimmedContent.length})。`;
|
|
302
|
+
}
|
|
303
|
+
if (!timeExpression || timeExpression.trim().length === 0) {
|
|
304
|
+
return "时间表达式不能为空!\n格式:定时任务 时间 任务内容\n示例:定时任务 1m 该喝水啦!";
|
|
305
|
+
}
|
|
306
|
+
const result = calculateNextExecutionTime(timeExpression.trim());
|
|
307
|
+
if (!result) {
|
|
308
|
+
return "时间表达式格式错误!\n支持的格式:\n1m (1分钟后)\n2h30m (2小时30分钟后)\n10:00 (今天10点)\n12.23,23:00 (12月23日23点)\n1m/10s (1分钟后每隔10秒)\n10:00/1d (从今天起每天10点)";
|
|
309
|
+
}
|
|
310
|
+
const newTask = await ctx.database.create("maple-schedule-tasks", {
|
|
311
|
+
platform: session.platform,
|
|
312
|
+
botId: session.bot.selfId,
|
|
313
|
+
channelId: session.channelId,
|
|
314
|
+
userId: session.userId,
|
|
315
|
+
content: trimmedContent,
|
|
316
|
+
nextExecutionTime: result.nextTime,
|
|
317
|
+
intervalMs: result.intervalMs
|
|
318
|
+
});
|
|
319
|
+
const timeDisplay = formatDateTime(result.nextTime);
|
|
320
|
+
const taskType = result.intervalMs ? "循环任务" : "一次性任务";
|
|
321
|
+
let intervalDisplay = "";
|
|
322
|
+
if (result.intervalMs) {
|
|
323
|
+
intervalDisplay = `
|
|
324
|
+
循环间隔: 每${formatInterval(result.intervalMs)}`;
|
|
325
|
+
}
|
|
326
|
+
ctx.logger("maple-schedule").info(`用户 ${session.userId} 创建了定时任务 #${newTask.id}`);
|
|
327
|
+
ctx.logger("maple-schedule").info(`任务 #${newTask.id} 类型: ${taskType}, 执行时间: ${timeDisplay}`);
|
|
328
|
+
return `定时任务创建成功!
|
|
329
|
+
任务ID: #${newTask.id}
|
|
330
|
+
任务类型: ${taskType}
|
|
331
|
+
执行时间: ${timeDisplay}${intervalDisplay}
|
|
332
|
+
任务内容: ${trimmedContent}`;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
ctx.logger("maple-schedule").error("创建定时任务时出错:", error);
|
|
335
|
+
return "创建定时任务时出错了,请稍后再试。";
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
ctx.command("定时/定时列表 [page:number]", "查看当前聊天中的所有定时任务").alias("定时列表").example("定时列表").example("定时列表 2").action(async ({ session }, page = 1) => {
|
|
339
|
+
try {
|
|
340
|
+
if (page < 1) {
|
|
341
|
+
return "页码必须是正整数!";
|
|
342
|
+
}
|
|
343
|
+
const query = {
|
|
344
|
+
platform: session.platform,
|
|
345
|
+
botId: session.bot.selfId
|
|
346
|
+
};
|
|
347
|
+
if (session.channelId) {
|
|
348
|
+
query.channelId = session.channelId;
|
|
349
|
+
} else {
|
|
350
|
+
query.userId = session.userId;
|
|
351
|
+
query.channelId = null;
|
|
352
|
+
}
|
|
353
|
+
const tasks = await ctx.database.get("maple-schedule-tasks", query);
|
|
354
|
+
if (tasks.length === 0) {
|
|
355
|
+
return '当前聊天中没有定时任务。\n使用"定时任务"指令创建一个吧!';
|
|
356
|
+
}
|
|
357
|
+
tasks.sort((a, b) => a.nextExecutionTime.getTime() - b.nextExecutionTime.getTime());
|
|
358
|
+
const pageSize = 10;
|
|
359
|
+
const totalPages = Math.ceil(tasks.length / pageSize);
|
|
360
|
+
if (page > totalPages) {
|
|
361
|
+
return `只有 ${totalPages} 页定时任务,请输入 1-${totalPages} 之间的页码。`;
|
|
362
|
+
}
|
|
363
|
+
const startIndex = (page - 1) * pageSize;
|
|
364
|
+
const endIndex = Math.min(startIndex + pageSize, tasks.length);
|
|
365
|
+
const currentPageTasks = tasks.slice(startIndex, endIndex);
|
|
366
|
+
let output = `【定时任务列表】第 ${page}/${totalPages} 页(共 ${tasks.length} 条)
|
|
367
|
+
|
|
368
|
+
`;
|
|
369
|
+
currentPageTasks.forEach((task, index) => {
|
|
370
|
+
const displayIndex = startIndex + index + 1;
|
|
371
|
+
const timeDisplay = formatDateTime(task.nextExecutionTime);
|
|
372
|
+
const contentPreview = getContentPreview(task.content, config.maxPreviewLength);
|
|
373
|
+
let intervalInfo = "";
|
|
374
|
+
if (task.intervalMs) {
|
|
375
|
+
intervalInfo = `(每${formatInterval(task.intervalMs)}) `;
|
|
376
|
+
}
|
|
377
|
+
output += `${displayIndex}. #${task.id} ${timeDisplay}${intervalInfo}${contentPreview}
|
|
378
|
+
`;
|
|
379
|
+
});
|
|
380
|
+
output += "\n──────────\n";
|
|
381
|
+
if (page > 1) {
|
|
382
|
+
output += `输入"定时列表 ${page - 1}"查看上一页
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
if (page < totalPages) {
|
|
386
|
+
output += `输入"定时列表 ${page + 1}"查看下一页
|
|
387
|
+
`;
|
|
388
|
+
}
|
|
389
|
+
output += `输入"删除定时 编号"删除任务`;
|
|
390
|
+
return output.trim();
|
|
391
|
+
} catch (error) {
|
|
392
|
+
ctx.logger("maple-schedule").error("查看定时列表时出错:", error);
|
|
393
|
+
return "查看定时列表时出错了,请稍后再试。";
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
ctx.command("定时/删除定时 <id:number>", "删除指定编号的定时任务").alias("删除定时").example("删除定时 1").action(async ({ session }, id) => {
|
|
397
|
+
try {
|
|
398
|
+
if (!id) {
|
|
399
|
+
return '请提供任务编号!\n格式:删除定时 编号\n可以发送"定时列表"查看所有任务编号。';
|
|
400
|
+
}
|
|
401
|
+
const tasks = await ctx.database.get("maple-schedule-tasks", { id });
|
|
402
|
+
if (tasks.length === 0) {
|
|
403
|
+
return `没有找到ID为 #${id} 的定时任务。
|
|
404
|
+
可以发送"定时列表"查看所有任务编号。`;
|
|
405
|
+
}
|
|
406
|
+
const task = tasks[0];
|
|
407
|
+
const timeDisplay = formatDateTime(task.nextExecutionTime);
|
|
408
|
+
const contentPreview = task.content.length > 50 ? task.content.substring(0, 50) + "..." : task.content;
|
|
409
|
+
ctx.logger("maple-schedule").info(`用户 ${session.userId} 删除了定时任务 #${id}`);
|
|
410
|
+
await ctx.database.remove("maple-schedule-tasks", { id });
|
|
411
|
+
return `已成功删除定时任务 #${id}
|
|
412
|
+
执行时间: ${timeDisplay}
|
|
413
|
+
任务内容: ${contentPreview}`;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
ctx.logger("maple-schedule").error("删除定时任务时出错:", error);
|
|
416
|
+
return "删除定时任务时出错了,请稍后再试。";
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
ctx.on("ready", async () => {
|
|
420
|
+
ctx.logger("maple-schedule").info("定时任务插件已加载完成");
|
|
421
|
+
ctx.logger("maple-schedule").info(`任务内容最大长度: ${config.maxContentLength} 字`);
|
|
422
|
+
ctx.logger("maple-schedule").info(`定时列表最大预览长度: ${config.maxPreviewLength} 字`);
|
|
423
|
+
ctx.logger("maple-schedule").info(`任务发送间隔: ${config.sendInterval} 毫秒`);
|
|
424
|
+
ctx.logger("maple-schedule").info(`检查器扫描时间: ${config.scanInterval} 秒`);
|
|
425
|
+
ctx.logger("maple-schedule").info(`定时时间容错: ${config.timeTolerance} 秒`);
|
|
426
|
+
const scanIntervalMs = config.scanInterval * 1e3;
|
|
427
|
+
timer = setInterval(checkAndExecuteTasks, scanIntervalMs);
|
|
428
|
+
ctx.logger("maple-schedule").info(`定时任务检查器已启动,扫描间隔: ${config.scanInterval} 秒`);
|
|
429
|
+
await checkAndExecuteTasks();
|
|
430
|
+
});
|
|
431
|
+
ctx.on("dispose", () => {
|
|
432
|
+
if (timer) {
|
|
433
|
+
clearInterval(timer);
|
|
434
|
+
ctx.logger("maple-schedule").info("定时任务检查器已停止");
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
__name(apply, "apply");
|
|
439
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
440
|
+
0 && (module.exports = {
|
|
441
|
+
Config,
|
|
442
|
+
apply,
|
|
443
|
+
name,
|
|
444
|
+
using
|
|
445
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-maple-schedule",
|
|
3
|
+
"description": "-",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"maple"
|
|
14
|
+
],
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"koishi": "^4.18.7"
|
|
17
|
+
}
|
|
18
|
+
}
|