koishi-plugin-aka-60s-api 0.2.1 → 0.2.2

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 CHANGED
File without changes
package/lib/index.js CHANGED
@@ -34,29 +34,29 @@ var Config = import_koishi.Schema.intersect([
34
34
  apiBaseUrl: import_koishi.Schema.string().default("http://172.0.0.1:4399").description("60s 服务 URL(不含 /v2 路径)"),
35
35
  cooldownTime: import_koishi.Schema.number().default(30).min(5).max(300).description("冷却时间(秒)"),
36
36
  enableLog: import_koishi.Schema.boolean().default(true).description("启用日志记录"),
37
- scheduleWhitelist: import_koishi.Schema.array(String).default([]).description("定时发送群组白名单频道ID列表(为空=不发送)")
37
+ scheduleWhitelist: import_koishi.Schema.array(String).default([]).description("定时发送群组白名单频道ID列表(格式: platform:channelId,如 onebot:123456)")
38
38
  }).description("基础设置"),
39
39
  import_koishi.Schema.object({
40
40
  enableSchedule: import_koishi.Schema.boolean().default(false).description("启用定时发送新闻"),
41
- scheduleTime: import_koishi.Schema.string().default("08:00 / 1d").description("定时发送时间 (格式: HH:MM / 1d)"),
41
+ scheduleTime: import_koishi.Schema.string().default("08:00").description("定时发送时间 (格式: HH:MM,每天固定时间)"),
42
42
  useForward: import_koishi.Schema.boolean().default(false).description("是否使用合并转发(仅QQ平台效果最佳)")
43
43
  }).description("每日新闻"),
44
44
  import_koishi.Schema.object({
45
45
  enableAiNewsSchedule: import_koishi.Schema.boolean().default(false).description("启用AI快报定时发送(仅当天)"),
46
- aiNewsScheduleTime: import_koishi.Schema.string().default("22:00 / 1d").description("AI快报定时发送时间 (格式: HH:MM / 1d)"),
46
+ aiNewsScheduleTime: import_koishi.Schema.string().default("22:00").description("AI快报定时发送时间 (格式: HH:MM,每天固定时间)"),
47
47
  aiUseForward: import_koishi.Schema.boolean().default(false).description("AI快报是否使用合并转发(仅QQ平台效果最佳)")
48
48
  }).description("AI快报"),
49
49
  import_koishi.Schema.object({
50
50
  enableMoyuSchedule: import_koishi.Schema.boolean().default(false).description("启用摸鱼日报定时发送"),
51
- moyuScheduleTime: import_koishi.Schema.string().default("10:00 / 1d").description("摸鱼日报定时发送时间 (格式: HH:MM / 1d)")
51
+ moyuScheduleTime: import_koishi.Schema.string().default("10:00").description("摸鱼日报定时发送时间 (格式: HH:MM,每天固定时间)")
52
52
  }).description("摸鱼日报"),
53
53
  import_koishi.Schema.object({
54
54
  enableGoldSchedule: import_koishi.Schema.boolean().default(false).description("启用今日金价定时发送"),
55
- goldScheduleTime: import_koishi.Schema.string().default("09:00 / 1d").description("今日金价定时发送时间 (格式: HH:MM / 1d)")
55
+ goldScheduleTime: import_koishi.Schema.string().default("09:00").description("今日金价定时发送时间 (格式: HH:MM,每天固定时间)")
56
56
  }).description("今日金价"),
57
57
  import_koishi.Schema.object({
58
58
  enableFuelSchedule: import_koishi.Schema.boolean().default(false).description("启用今日油价定时发送"),
59
- fuelScheduleTime: import_koishi.Schema.string().default("09:30 / 1d").description("今日油价定时发送时间 (格式: HH:MM / 1d)"),
59
+ fuelScheduleTime: import_koishi.Schema.string().default("09:30").description("今日油价定时发送时间 (格式: HH:MM,每天固定时间)"),
60
60
  fuelDefaultRegion: import_koishi.Schema.string().default("上海").description("今日油价默认地区")
61
61
  }).description("今日油价")
62
62
  ]);
@@ -65,27 +65,65 @@ function apply(ctx, config) {
65
65
  const normalizedApiBaseUrl = (config.apiBaseUrl || "http://172.0.0.1:4399").replace(/\/$/, "");
66
66
  const buildApiUrl = /* @__PURE__ */ __name((path) => `${normalizedApiBaseUrl}${path}`, "buildApiUrl");
67
67
  const cooldowns = /* @__PURE__ */ new Map();
68
- let scheduleInterval = null;
69
- let aiNewsScheduleInterval = null;
70
- let moyuScheduleInterval = null;
71
- let goldScheduleInterval = null;
72
- let fuelScheduleInterval = null;
68
+ let scheduleTimeout = null;
69
+ let aiNewsScheduleTimeout = null;
70
+ let moyuScheduleTimeout = null;
71
+ let goldScheduleTimeout = null;
72
+ let fuelScheduleTimeout = null;
73
73
  async function resolveGroupScheduleChannels(whitelist, tag) {
74
74
  try {
75
75
  if (!whitelist.length) {
76
76
  logInfo("60s API: 未配置定时发送白名单", { tag });
77
77
  return [];
78
78
  }
79
+ const normalize = /* @__PURE__ */ __name((raw) => {
80
+ const value = String(raw || "").trim();
81
+ const withoutAt = value.includes("@") ? value.split("@")[0] : value;
82
+ const [platform, id] = withoutAt.includes(":") ? withoutAt.split(":", 2) : ["", withoutAt];
83
+ return {
84
+ raw: value,
85
+ withoutAt,
86
+ platform,
87
+ id
88
+ };
89
+ }, "normalize");
90
+ const whitelistNormalized = whitelist.map(normalize).filter((item) => item.withoutAt);
91
+ const whitelistKeys = /* @__PURE__ */ new Set();
92
+ whitelistNormalized.forEach((item) => {
93
+ whitelistKeys.add(item.raw);
94
+ whitelistKeys.add(item.withoutAt);
95
+ if (item.id) whitelistKeys.add(item.id);
96
+ });
79
97
  const assigned = await ctx.database.getAssignedChannels(["id", "platform", "guildId"]);
80
- const groupChannels = assigned.filter((channel) => !!channel.guildId).map((channel) => `${channel.platform}:${channel.id}`);
98
+ logInfo("60s API: 获取到的频道列表", {
99
+ tag,
100
+ count: assigned.length,
101
+ channels: assigned.map((c) => ({ platform: c.platform, id: c.id, guildId: c.guildId }))
102
+ });
103
+ const groupChannels = assigned.filter((channel) => !!channel.guildId).map((channel) => ({
104
+ platform: channel.platform,
105
+ id: channel.id,
106
+ key: `${channel.platform}:${channel.id}`
107
+ }));
81
108
  if (!groupChannels.length) {
82
109
  logInfo("60s API: 没有可发送的群组频道", { tag });
83
110
  return [];
84
111
  }
85
- const whitelistSet = new Set(whitelist);
86
- const targets = groupChannels.filter((channelId) => whitelistSet.has(channelId));
112
+ logInfo("60s API: 群组频道列表", { tag, channels: groupChannels.map((c) => c.key) });
113
+ logInfo("60s API: 白名单配置(原始)", { tag, whitelist });
114
+ logInfo("60s API: 白名单配置(规范化)", {
115
+ tag,
116
+ whitelist: whitelistNormalized.map((w) => ({ raw: w.raw, withoutAt: w.withoutAt, id: w.id }))
117
+ });
118
+ const targets = groupChannels.filter((c) => whitelistKeys.has(c.key) || whitelistKeys.has(c.id)).map((c) => c.key);
87
119
  if (!targets.length) {
88
- logInfo("60s API: 白名单未命中任何已加入群组频道", { tag });
120
+ logInfo("60s API: 白名单未命中任何已加入群组频道", {
121
+ tag,
122
+ groupChannels: groupChannels.map((c) => c.key),
123
+ whitelist
124
+ });
125
+ } else {
126
+ logInfo("60s API: 白名单匹配成功", { tag, targets });
89
127
  }
90
128
  return targets;
91
129
  } catch (error) {
@@ -253,7 +291,10 @@ function apply(ctx, config) {
253
291
  __name(getFuelPrice, "getFuelPrice");
254
292
  async function sendNewsToChannels() {
255
293
  const targetChannels = await resolveGroupScheduleChannels(config.scheduleWhitelist, "news");
256
- if (targetChannels.length === 0) return;
294
+ if (targetChannels.length === 0) {
295
+ logInfo("60s API: 没有目标频道,跳过新闻发送");
296
+ return;
297
+ }
257
298
  try {
258
299
  const imageBuffer = await get60sNewsImage();
259
300
  const imageMessage = import_koishi.h.image(imageBuffer, "image/png");
@@ -329,7 +370,10 @@ ${newsItem.link}`).join("\n\n");
329
370
  __name(fetchAiNewsByDates, "fetchAiNewsByDates");
330
371
  async function sendAiNewsToChannels() {
331
372
  const targetChannels = await resolveGroupScheduleChannels(config.scheduleWhitelist, "ai-news");
332
- if (targetChannels.length === 0) return;
373
+ if (targetChannels.length === 0) {
374
+ logInfo("60s API: 没有目标频道,跳过AI快报发送");
375
+ return;
376
+ }
333
377
  try {
334
378
  const response = await getAiNews({ encoding: "json" });
335
379
  if (response.code !== 200 || !response.data) {
@@ -371,7 +415,10 @@ ${newsItem.link}`).join("\n\n");
371
415
  __name(formatMoyuText, "formatMoyuText");
372
416
  async function sendMoyuToChannels() {
373
417
  const targetChannels = await resolveGroupScheduleChannels(config.scheduleWhitelist, "moyu");
374
- if (targetChannels.length === 0) return;
418
+ if (targetChannels.length === 0) {
419
+ logInfo("60s API: 没有目标频道,跳过摸鱼日报发送");
420
+ return;
421
+ }
375
422
  try {
376
423
  const response = await getMoyuDaily("json");
377
424
  if (response.code !== 200 || !response.data) {
@@ -432,7 +479,10 @@ ${newsItem.link}`).join("\n\n");
432
479
  __name(formatFuelText, "formatFuelText");
433
480
  async function sendGoldToChannels() {
434
481
  const targetChannels = await resolveGroupScheduleChannels(config.scheduleWhitelist, "gold");
435
- if (targetChannels.length === 0) return;
482
+ if (targetChannels.length === 0) {
483
+ logInfo("60s API: 没有目标频道,跳过金价发送");
484
+ return;
485
+ }
436
486
  try {
437
487
  const response = await getGoldPrice("json");
438
488
  if (response.code !== 200 || !response.data) {
@@ -455,7 +505,10 @@ ${newsItem.link}`).join("\n\n");
455
505
  __name(sendGoldToChannels, "sendGoldToChannels");
456
506
  async function sendFuelToChannels() {
457
507
  const targetChannels = await resolveGroupScheduleChannels(config.scheduleWhitelist, "fuel");
458
- if (targetChannels.length === 0) return;
508
+ if (targetChannels.length === 0) {
509
+ logInfo("60s API: 没有目标频道,跳过油价发送");
510
+ return;
511
+ }
459
512
  try {
460
513
  const response = await getFuelPrice({ region: config.fuelDefaultRegion, encoding: "json" });
461
514
  if (response.code !== 200 || !response.data) {
@@ -476,49 +529,40 @@ ${newsItem.link}`).join("\n\n");
476
529
  }
477
530
  }
478
531
  __name(sendFuelToChannels, "sendFuelToChannels");
479
- function parseScheduleTime(timeStr) {
480
- if (timeStr.includes("/")) {
481
- const [timePart] = timeStr.split(" / ");
482
- const [hours, minutes] = timePart.split(":").map(Number);
483
- const now = /* @__PURE__ */ new Date();
484
- const targetTime = /* @__PURE__ */ new Date();
485
- targetTime.setHours(hours, minutes, 0, 0);
486
- if (targetTime <= now) {
487
- targetTime.setDate(targetTime.getDate() + 1);
488
- }
489
- return targetTime.getTime() - now.getTime();
490
- } else if (timeStr.includes("h")) {
491
- const hours = parseInt(timeStr.replace("h", ""));
492
- return hours * 60 * 60 * 1e3;
493
- } else if (timeStr.includes("m")) {
494
- const minutes = parseInt(timeStr.replace("m", ""));
495
- return minutes * 60 * 1e3;
496
- } else {
497
- return 60 * 60 * 1e3;
532
+ function getMsUntilNextTime(timeStr) {
533
+ const [hours, minutes] = timeStr.split(":").map(Number);
534
+ const now = /* @__PURE__ */ new Date();
535
+ const target = /* @__PURE__ */ new Date();
536
+ target.setHours(hours, minutes, 0, 0);
537
+ if (target <= now) {
538
+ target.setDate(target.getDate() + 1);
498
539
  }
540
+ return target.getTime() - now.getTime();
499
541
  }
500
- __name(parseScheduleTime, "parseScheduleTime");
542
+ __name(getMsUntilNextTime, "getMsUntilNextTime");
501
543
  function setupSchedule() {
502
544
  if (!config.enableSchedule) {
503
- logInfo("60s API: 定时发送功能已禁用");
545
+ logInfo("60s API: 定时发送新闻功能已禁用");
504
546
  return;
505
547
  }
506
- if (scheduleInterval) {
507
- clearInterval(scheduleInterval);
508
- scheduleInterval = null;
548
+ if (scheduleTimeout) {
549
+ clearTimeout(scheduleTimeout);
550
+ scheduleTimeout = null;
509
551
  }
510
552
  try {
511
- const interval = parseScheduleTime(config.scheduleTime);
512
- scheduleInterval = setInterval(async () => {
513
- await sendNewsToChannels();
514
- }, interval);
515
- logInfo("60s API: 定时任务设置成功", {
553
+ const msUntilNext = getMsUntilNextTime(config.scheduleTime);
554
+ logInfo("60s API: 新闻定时任务已设置", {
516
555
  scheduleTime: config.scheduleTime,
517
- interval,
518
- channels: config.scheduleWhitelist
556
+ msUntilNext,
557
+ nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
558
+ whitelist: config.scheduleWhitelist
519
559
  });
560
+ scheduleTimeout = setTimeout(async () => {
561
+ await sendNewsToChannels();
562
+ setupSchedule();
563
+ }, msUntilNext);
520
564
  } catch (error) {
521
- logError("60s API: 设置定时任务失败", error);
565
+ logError("60s API: 设置新闻定时任务失败", error);
522
566
  }
523
567
  }
524
568
  __name(setupSchedule, "setupSchedule");
@@ -527,20 +571,22 @@ ${newsItem.link}`).join("\n\n");
527
571
  logInfo("60s API: AI快报定时发送功能已禁用");
528
572
  return;
529
573
  }
530
- if (aiNewsScheduleInterval) {
531
- clearInterval(aiNewsScheduleInterval);
532
- aiNewsScheduleInterval = null;
574
+ if (aiNewsScheduleTimeout) {
575
+ clearTimeout(aiNewsScheduleTimeout);
576
+ aiNewsScheduleTimeout = null;
533
577
  }
534
578
  try {
535
- const interval = parseScheduleTime(config.aiNewsScheduleTime);
536
- aiNewsScheduleInterval = setInterval(async () => {
537
- await sendAiNewsToChannels();
538
- }, interval);
539
- logInfo("60s API: AI快报定时任务设置成功", {
579
+ const msUntilNext = getMsUntilNextTime(config.aiNewsScheduleTime);
580
+ logInfo("60s API: AI快报定时任务已设置", {
540
581
  scheduleTime: config.aiNewsScheduleTime,
541
- interval,
542
- channels: config.scheduleWhitelist
582
+ msUntilNext,
583
+ nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
584
+ whitelist: config.scheduleWhitelist
543
585
  });
586
+ aiNewsScheduleTimeout = setTimeout(async () => {
587
+ await sendAiNewsToChannels();
588
+ setupAiNewsSchedule();
589
+ }, msUntilNext);
544
590
  } catch (error) {
545
591
  logError("60s API: 设置AI快报定时任务失败", error);
546
592
  }
@@ -551,20 +597,22 @@ ${newsItem.link}`).join("\n\n");
551
597
  logInfo("60s API: 摸鱼日报定时发送功能已禁用");
552
598
  return;
553
599
  }
554
- if (moyuScheduleInterval) {
555
- clearInterval(moyuScheduleInterval);
556
- moyuScheduleInterval = null;
600
+ if (moyuScheduleTimeout) {
601
+ clearTimeout(moyuScheduleTimeout);
602
+ moyuScheduleTimeout = null;
557
603
  }
558
604
  try {
559
- const interval = parseScheduleTime(config.moyuScheduleTime);
560
- moyuScheduleInterval = setInterval(async () => {
561
- await sendMoyuToChannels();
562
- }, interval);
563
- logInfo("60s API: 摸鱼日报定时任务设置成功", {
605
+ const msUntilNext = getMsUntilNextTime(config.moyuScheduleTime);
606
+ logInfo("60s API: 摸鱼日报定时任务已设置", {
564
607
  scheduleTime: config.moyuScheduleTime,
565
- interval,
566
- channels: config.scheduleWhitelist
608
+ msUntilNext,
609
+ nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
610
+ whitelist: config.scheduleWhitelist
567
611
  });
612
+ moyuScheduleTimeout = setTimeout(async () => {
613
+ await sendMoyuToChannels();
614
+ setupMoyuSchedule();
615
+ }, msUntilNext);
568
616
  } catch (error) {
569
617
  logError("60s API: 设置摸鱼日报定时任务失败", error);
570
618
  }
@@ -575,20 +623,22 @@ ${newsItem.link}`).join("\n\n");
575
623
  logInfo("60s API: 今日金价定时发送功能已禁用");
576
624
  return;
577
625
  }
578
- if (goldScheduleInterval) {
579
- clearInterval(goldScheduleInterval);
580
- goldScheduleInterval = null;
626
+ if (goldScheduleTimeout) {
627
+ clearTimeout(goldScheduleTimeout);
628
+ goldScheduleTimeout = null;
581
629
  }
582
630
  try {
583
- const interval = parseScheduleTime(config.goldScheduleTime);
584
- goldScheduleInterval = setInterval(async () => {
585
- await sendGoldToChannels();
586
- }, interval);
587
- logInfo("60s API: 今日金价定时任务设置成功", {
631
+ const msUntilNext = getMsUntilNextTime(config.goldScheduleTime);
632
+ logInfo("60s API: 今日金价定时任务已设置", {
588
633
  scheduleTime: config.goldScheduleTime,
589
- interval,
590
- channels: config.scheduleWhitelist
634
+ msUntilNext,
635
+ nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
636
+ whitelist: config.scheduleWhitelist
591
637
  });
638
+ goldScheduleTimeout = setTimeout(async () => {
639
+ await sendGoldToChannels();
640
+ setupGoldSchedule();
641
+ }, msUntilNext);
592
642
  } catch (error) {
593
643
  logError("60s API: 设置今日金价定时任务失败", error);
594
644
  }
@@ -599,20 +649,22 @@ ${newsItem.link}`).join("\n\n");
599
649
  logInfo("60s API: 今日油价定时发送功能已禁用");
600
650
  return;
601
651
  }
602
- if (fuelScheduleInterval) {
603
- clearInterval(fuelScheduleInterval);
604
- fuelScheduleInterval = null;
652
+ if (fuelScheduleTimeout) {
653
+ clearTimeout(fuelScheduleTimeout);
654
+ fuelScheduleTimeout = null;
605
655
  }
606
656
  try {
607
- const interval = parseScheduleTime(config.fuelScheduleTime);
608
- fuelScheduleInterval = setInterval(async () => {
609
- await sendFuelToChannels();
610
- }, interval);
611
- logInfo("60s API: 今日油价定时任务设置成功", {
657
+ const msUntilNext = getMsUntilNextTime(config.fuelScheduleTime);
658
+ logInfo("60s API: 今日油价定时任务已设置", {
612
659
  scheduleTime: config.fuelScheduleTime,
613
- interval,
614
- channels: config.scheduleWhitelist
660
+ msUntilNext,
661
+ nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
662
+ whitelist: config.scheduleWhitelist
615
663
  });
664
+ fuelScheduleTimeout = setTimeout(async () => {
665
+ await sendFuelToChannels();
666
+ setupFuelSchedule();
667
+ }, msUntilNext);
616
668
  } catch (error) {
617
669
  logError("60s API: 设置今日油价定时任务失败", error);
618
670
  }
@@ -987,33 +1039,33 @@ ${shortDetail}
987
1039
  }
988
1040
  });
989
1041
  ctx.on("ready", async () => {
990
- await setupSchedule();
991
- await setupAiNewsSchedule();
992
- await setupMoyuSchedule();
993
- await setupGoldSchedule();
994
- await setupFuelSchedule();
1042
+ setupSchedule();
1043
+ setupAiNewsSchedule();
1044
+ setupMoyuSchedule();
1045
+ setupGoldSchedule();
1046
+ setupFuelSchedule();
995
1047
  });
996
1048
  ctx.on("dispose", () => {
997
1049
  cooldowns.clear();
998
- if (scheduleInterval) {
999
- clearInterval(scheduleInterval);
1000
- scheduleInterval = null;
1050
+ if (scheduleTimeout) {
1051
+ clearTimeout(scheduleTimeout);
1052
+ scheduleTimeout = null;
1001
1053
  }
1002
- if (aiNewsScheduleInterval) {
1003
- clearInterval(aiNewsScheduleInterval);
1004
- aiNewsScheduleInterval = null;
1054
+ if (aiNewsScheduleTimeout) {
1055
+ clearTimeout(aiNewsScheduleTimeout);
1056
+ aiNewsScheduleTimeout = null;
1005
1057
  }
1006
- if (moyuScheduleInterval) {
1007
- clearInterval(moyuScheduleInterval);
1008
- moyuScheduleInterval = null;
1058
+ if (moyuScheduleTimeout) {
1059
+ clearTimeout(moyuScheduleTimeout);
1060
+ moyuScheduleTimeout = null;
1009
1061
  }
1010
- if (goldScheduleInterval) {
1011
- clearInterval(goldScheduleInterval);
1012
- goldScheduleInterval = null;
1062
+ if (goldScheduleTimeout) {
1063
+ clearTimeout(goldScheduleTimeout);
1064
+ goldScheduleTimeout = null;
1013
1065
  }
1014
- if (fuelScheduleInterval) {
1015
- clearInterval(fuelScheduleInterval);
1016
- fuelScheduleInterval = null;
1066
+ if (fuelScheduleTimeout) {
1067
+ clearTimeout(fuelScheduleTimeout);
1068
+ fuelScheduleTimeout = null;
1017
1069
  }
1018
1070
  });
1019
1071
  }