koishi-plugin-aka-60s-api 0.2.8 → 0.2.10

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
@@ -6,6 +6,7 @@ export interface Config {
6
6
  cooldownTime: number;
7
7
  enableLog: boolean;
8
8
  scheduleWhitelist: string[];
9
+ scheduleCooldown: number;
9
10
  enableSchedule: boolean;
10
11
  scheduleTime: string;
11
12
  useForward: boolean;
package/lib/index.js CHANGED
@@ -32,31 +32,32 @@ var inject = ["database"];
32
32
  var Config = import_koishi.Schema.intersect([
33
33
  import_koishi.Schema.object({
34
34
  apiBaseUrl: import_koishi.Schema.string().default("http://172.0.0.1:4399").description("60s 服务 URL(不含 /v2 路径)"),
35
- cooldownTime: import_koishi.Schema.number().default(30).min(5).max(300).description("冷却时间(秒)"),
35
+ cooldownTime: import_koishi.Schema.number().default(30).min(5).max(300).description("命令冷却时间(秒),用户手动触发命令的间隔限制"),
36
+ scheduleCooldown: import_koishi.Schema.number().default(86400).min(0).description("定时任务防重发冷却时间(秒),0表示不限制。默认86400秒=24小时"),
36
37
  enableLog: import_koishi.Schema.boolean().default(true).description("启用日志记录"),
37
38
  scheduleWhitelist: import_koishi.Schema.array(String).default([]).description("定时发送群组白名单频道ID列表(格式: platform:channelId,如 onebot:123456)")
38
39
  }).description("基础设置"),
39
40
  import_koishi.Schema.object({
40
41
  enableSchedule: import_koishi.Schema.boolean().default(false).description("启用定时发送新闻"),
41
- scheduleTime: import_koishi.Schema.string().default("08:00").description("定时发送时间 (格式: HH:MM,每天固定时间)"),
42
+ scheduleTime: import_koishi.Schema.string().default("08:00").description("定时发送时间 (格式: HH:MM 或 HH:MM / Nd,如 08:00 或 08:00 / 1d,表示每天或每N天的固定时间)"),
42
43
  useForward: import_koishi.Schema.boolean().default(false).description("是否使用合并转发(仅QQ平台效果最佳)")
43
44
  }).description("每日新闻"),
44
45
  import_koishi.Schema.object({
45
46
  enableAiNewsSchedule: import_koishi.Schema.boolean().default(false).description("启用AI快报定时发送(仅当天)"),
46
- aiNewsScheduleTime: import_koishi.Schema.string().default("22:00").description("AI快报定时发送时间 (格式: HH:MM,每天固定时间)"),
47
+ aiNewsScheduleTime: import_koishi.Schema.string().default("22:00").description("AI快报定时发送时间 (格式: HH:MM 或 HH:MM / Nd,如 22:00 或 22:00 / 1d)"),
47
48
  aiUseForward: import_koishi.Schema.boolean().default(false).description("AI快报是否使用合并转发(仅QQ平台效果最佳)")
48
49
  }).description("AI快报"),
49
50
  import_koishi.Schema.object({
50
51
  enableMoyuSchedule: import_koishi.Schema.boolean().default(false).description("启用摸鱼日报定时发送"),
51
- moyuScheduleTime: import_koishi.Schema.string().default("10:00").description("摸鱼日报定时发送时间 (格式: HH:MM,每天固定时间)")
52
+ moyuScheduleTime: import_koishi.Schema.string().default("10:00").description("摸鱼日报定时发送时间 (格式: HH:MM 或 HH:MM / Nd,如 10:00 或 10:00 / 1d)")
52
53
  }).description("摸鱼日报"),
53
54
  import_koishi.Schema.object({
54
55
  enableGoldSchedule: import_koishi.Schema.boolean().default(false).description("启用今日金价定时发送"),
55
- goldScheduleTime: import_koishi.Schema.string().default("09:00").description("今日金价定时发送时间 (格式: HH:MM,每天固定时间)")
56
+ goldScheduleTime: import_koishi.Schema.string().default("09:00").description("今日金价定时发送时间 (格式: HH:MM 或 HH:MM / Nd,如 09:00 或 09:00 / 1d)")
56
57
  }).description("今日金价"),
57
58
  import_koishi.Schema.object({
58
59
  enableFuelSchedule: import_koishi.Schema.boolean().default(false).description("启用今日油价定时发送"),
59
- fuelScheduleTime: import_koishi.Schema.string().default("09:30").description("今日油价定时发送时间 (格式: HH:MM,每天固定时间)"),
60
+ fuelScheduleTime: import_koishi.Schema.string().default("09:30").description("今日油价定时发送时间 (格式: HH:MM 或 HH:MM / Nd,如 09:30 或 09:30 / 1d)"),
60
61
  fuelDefaultRegion: import_koishi.Schema.string().default("上海").description("今日油价默认地区")
61
62
  }).description("今日油价")
62
63
  ]);
@@ -70,13 +71,34 @@ function apply(ctx, config) {
70
71
  let moyuScheduleTimeout = null;
71
72
  let goldScheduleTimeout = null;
72
73
  let fuelScheduleTimeout = null;
73
- const sentToday = {
74
- news: "",
75
- aiNews: "",
76
- moyu: "",
77
- gold: "",
78
- fuel: ""
74
+ const lastSentTime = {
75
+ news: 0,
76
+ aiNews: 0,
77
+ moyu: 0,
78
+ gold: 0,
79
+ fuel: 0
79
80
  };
81
+ function isInCooldown(type) {
82
+ if (config.scheduleCooldown <= 0) {
83
+ return false;
84
+ }
85
+ const now = Date.now();
86
+ const lastTime = lastSentTime[type];
87
+ const cooldownMs = config.scheduleCooldown * 1e3;
88
+ return now - lastTime < cooldownMs;
89
+ }
90
+ __name(isInCooldown, "isInCooldown");
91
+ function getRemainingCooldown(type) {
92
+ if (config.scheduleCooldown <= 0) {
93
+ return 0;
94
+ }
95
+ const now = Date.now();
96
+ const lastTime = lastSentTime[type];
97
+ const cooldownMs = config.scheduleCooldown * 1e3;
98
+ const remaining = Math.ceil((lastTime + cooldownMs - now) / 1e3);
99
+ return Math.max(0, remaining);
100
+ }
101
+ __name(getRemainingCooldown, "getRemainingCooldown");
80
102
  function getTodayString() {
81
103
  const now = /* @__PURE__ */ new Date();
82
104
  const year = now.getFullYear();
@@ -455,7 +477,8 @@ ${newsItem.link}`).join("\n\n");
455
477
  }
456
478
  __name(sendMoyuToChannels, "sendMoyuToChannels");
457
479
  function formatGoldText(data) {
458
- const metals = data.metals.filter((item) => item.name.includes("金价") || item.name.includes("伦敦金")).map((item) => {
480
+ const targetKeywords = ["黄金", "伦敦金", "白银", "钯金"];
481
+ const metals = data.metals.filter((item) => targetKeywords.some((keyword) => item.name.includes(keyword))).map((item) => {
459
482
  return `${item.name}: ${item.today_price}${item.unit}`;
460
483
  });
461
484
  return [
@@ -529,17 +552,21 @@ ${newsItem.link}`).join("\n\n");
529
552
  }
530
553
  __name(sendFuelToChannels, "sendFuelToChannels");
531
554
  function getMsUntilNextTime(timeStr) {
532
- const timePattern = /^([0-1]?\d|2[0-3]):([0-5]\d)$/;
533
- if (!timePattern.test(timeStr.trim())) {
555
+ const trimmedTimeStr = timeStr.trim();
556
+ const fullPattern = /^([0-1]?\d|2[0-3]):([0-5]\d)\s*(?:\/\s*(\d+)\s*d)?$/;
557
+ const match = trimmedTimeStr.match(fullPattern);
558
+ if (!match) {
534
559
  logError("60s API: 时间格式无效,跳过今天", { timeStr });
535
560
  return null;
536
561
  }
537
- const [hours, minutes] = timeStr.split(":").map(Number);
562
+ const hours = parseInt(match[1], 10);
563
+ const minutes = parseInt(match[2], 10);
564
+ const daysInterval = match[3] ? parseInt(match[3], 10) : 1;
538
565
  const now = /* @__PURE__ */ new Date();
539
566
  const target = /* @__PURE__ */ new Date();
540
567
  target.setHours(hours, minutes, 0, 0);
541
568
  if (target <= now) {
542
- target.setDate(target.getDate() + 1);
569
+ target.setDate(target.getDate() + daysInterval);
543
570
  }
544
571
  const msUntilNext = target.getTime() - now.getTime();
545
572
  if (!isFinite(msUntilNext) || msUntilNext <= 0) {
@@ -550,7 +577,14 @@ ${newsItem.link}`).join("\n\n");
550
577
  }
551
578
  __name(getMsUntilNextTime, "getMsUntilNextTime");
552
579
  function isTimePassedToday(timeStr) {
553
- const [hours, minutes] = timeStr.split(":").map(Number);
580
+ const trimmedTimeStr = timeStr.trim();
581
+ const fullPattern = /^([0-1]?\d|2[0-3]):([0-5]\d)\s*(?:\/\s*(\d+)\s*d)?$/;
582
+ const match = trimmedTimeStr.match(fullPattern);
583
+ if (!match) {
584
+ return false;
585
+ }
586
+ const hours = parseInt(match[1], 10);
587
+ const minutes = parseInt(match[2], 10);
554
588
  const now = /* @__PURE__ */ new Date();
555
589
  const target = /* @__PURE__ */ new Date();
556
590
  target.setHours(hours, minutes, 0, 0);
@@ -568,20 +602,23 @@ ${newsItem.link}`).join("\n\n");
568
602
  }
569
603
  try {
570
604
  const today = getTodayString();
571
- if (sentToday.news === today) {
572
- logInfo("60s API: 今日新闻已发送过,设置为明天任务", { today });
605
+ const remainingCooldown = getRemainingCooldown("news");
606
+ if (isInCooldown("news")) {
607
+ logInfo("60s API: 新闻发送冷却中,跳过本次发送", {
608
+ today,
609
+ remainingCooldownSec: remainingCooldown,
610
+ scheduleCooldown: config.scheduleCooldown
611
+ });
573
612
  const msUntilNext2 = getMsUntilNextTime(config.scheduleTime);
574
613
  if (msUntilNext2 === null) {
575
614
  scheduleTimeout = setTimeout(() => {
576
- sentToday.news = "";
577
615
  setupSchedule();
578
616
  }, 24 * 60 * 60 * 1e3);
579
617
  return;
580
618
  }
581
619
  scheduleTimeout = setTimeout(() => {
582
- sentToday.news = "";
583
620
  setupSchedule();
584
- }, msUntilNext2 + 24 * 60 * 60 * 1e3);
621
+ }, msUntilNext2);
585
622
  return;
586
623
  }
587
624
  const msUntilNext = getMsUntilNextTime(config.scheduleTime);
@@ -597,17 +634,18 @@ ${newsItem.link}`).join("\n\n");
597
634
  msUntilNext,
598
635
  nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
599
636
  whitelist: config.scheduleWhitelist,
600
- today,
601
- alreadySent: sentToday.news === today
637
+ scheduleCooldown: config.scheduleCooldown
602
638
  });
603
639
  scheduleTimeout = setTimeout(async () => {
604
- if (sentToday.news === getTodayString()) {
605
- logInfo("60s API: 今日新闻已在本次任务等待期间发送,跳过");
640
+ if (isInCooldown("news")) {
641
+ logInfo("60s API: 新闻发送冷却中,跳过本次发送", {
642
+ remainingCooldownSec: getRemainingCooldown("news")
643
+ });
606
644
  setupSchedule();
607
645
  return;
608
646
  }
609
647
  await sendNewsToChannels();
610
- sentToday.news = getTodayString();
648
+ lastSentTime.news = Date.now();
611
649
  setupSchedule();
612
650
  }, msUntilNext);
613
651
  } catch (error) {
@@ -626,20 +664,23 @@ ${newsItem.link}`).join("\n\n");
626
664
  }
627
665
  try {
628
666
  const today = getTodayString();
629
- if (sentToday.aiNews === today) {
630
- logInfo("60s API: 今日AI快报已发送过,设置为明天任务", { today });
667
+ const remainingCooldown = getRemainingCooldown("aiNews");
668
+ if (isInCooldown("aiNews")) {
669
+ logInfo("60s API: AI快报发送冷却中,跳过本次发送", {
670
+ today,
671
+ remainingCooldownSec: remainingCooldown,
672
+ scheduleCooldown: config.scheduleCooldown
673
+ });
631
674
  const msUntilNext2 = getMsUntilNextTime(config.aiNewsScheduleTime);
632
675
  if (msUntilNext2 === null) {
633
676
  aiNewsScheduleTimeout = setTimeout(() => {
634
- sentToday.aiNews = "";
635
677
  setupAiNewsSchedule();
636
678
  }, 24 * 60 * 60 * 1e3);
637
679
  return;
638
680
  }
639
681
  aiNewsScheduleTimeout = setTimeout(() => {
640
- sentToday.aiNews = "";
641
682
  setupAiNewsSchedule();
642
- }, msUntilNext2 + 24 * 60 * 60 * 1e3);
683
+ }, msUntilNext2);
643
684
  return;
644
685
  }
645
686
  const msUntilNext = getMsUntilNextTime(config.aiNewsScheduleTime);
@@ -654,16 +695,19 @@ ${newsItem.link}`).join("\n\n");
654
695
  scheduleTime: config.aiNewsScheduleTime,
655
696
  msUntilNext,
656
697
  nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
657
- whitelist: config.scheduleWhitelist
698
+ whitelist: config.scheduleWhitelist,
699
+ scheduleCooldown: config.scheduleCooldown
658
700
  });
659
701
  aiNewsScheduleTimeout = setTimeout(async () => {
660
- if (sentToday.aiNews === getTodayString()) {
661
- logInfo("60s API: 今日AI快报已在本次任务等待期间发送,跳过");
702
+ if (isInCooldown("aiNews")) {
703
+ logInfo("60s API: AI快报发送冷却中,跳过本次发送", {
704
+ remainingCooldownSec: getRemainingCooldown("aiNews")
705
+ });
662
706
  setupAiNewsSchedule();
663
707
  return;
664
708
  }
665
709
  await sendAiNewsToChannels();
666
- sentToday.aiNews = getTodayString();
710
+ lastSentTime.aiNews = Date.now();
667
711
  setupAiNewsSchedule();
668
712
  }, msUntilNext);
669
713
  } catch (error) {
@@ -682,20 +726,23 @@ ${newsItem.link}`).join("\n\n");
682
726
  }
683
727
  try {
684
728
  const today = getTodayString();
685
- if (sentToday.moyu === today) {
686
- logInfo("60s API: 今日摸鱼日报已发送过,设置为明天任务", { today });
729
+ const remainingCooldown = getRemainingCooldown("moyu");
730
+ if (isInCooldown("moyu")) {
731
+ logInfo("60s API: 摸鱼日报发送冷却中,跳过本次发送", {
732
+ today,
733
+ remainingCooldownSec: remainingCooldown,
734
+ scheduleCooldown: config.scheduleCooldown
735
+ });
687
736
  const msUntilNext2 = getMsUntilNextTime(config.moyuScheduleTime);
688
737
  if (msUntilNext2 === null) {
689
738
  moyuScheduleTimeout = setTimeout(() => {
690
- sentToday.moyu = "";
691
739
  setupMoyuSchedule();
692
740
  }, 24 * 60 * 60 * 1e3);
693
741
  return;
694
742
  }
695
743
  moyuScheduleTimeout = setTimeout(() => {
696
- sentToday.moyu = "";
697
744
  setupMoyuSchedule();
698
- }, msUntilNext2 + 24 * 60 * 60 * 1e3);
745
+ }, msUntilNext2);
699
746
  return;
700
747
  }
701
748
  const msUntilNext = getMsUntilNextTime(config.moyuScheduleTime);
@@ -710,16 +757,19 @@ ${newsItem.link}`).join("\n\n");
710
757
  scheduleTime: config.moyuScheduleTime,
711
758
  msUntilNext,
712
759
  nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
713
- whitelist: config.scheduleWhitelist
760
+ whitelist: config.scheduleWhitelist,
761
+ scheduleCooldown: config.scheduleCooldown
714
762
  });
715
763
  moyuScheduleTimeout = setTimeout(async () => {
716
- if (sentToday.moyu === getTodayString()) {
717
- logInfo("60s API: 今日摸鱼日报已在本次任务等待期间发送,跳过");
764
+ if (isInCooldown("moyu")) {
765
+ logInfo("60s API: 摸鱼日报发送冷却中,跳过本次发送", {
766
+ remainingCooldownSec: getRemainingCooldown("moyu")
767
+ });
718
768
  setupMoyuSchedule();
719
769
  return;
720
770
  }
721
771
  await sendMoyuToChannels();
722
- sentToday.moyu = getTodayString();
772
+ lastSentTime.moyu = Date.now();
723
773
  setupMoyuSchedule();
724
774
  }, msUntilNext);
725
775
  } catch (error) {
@@ -738,20 +788,23 @@ ${newsItem.link}`).join("\n\n");
738
788
  }
739
789
  try {
740
790
  const today = getTodayString();
741
- if (sentToday.gold === today) {
742
- logInfo("60s API: 今日金价已发送过,设置为明天任务", { today });
791
+ const remainingCooldown = getRemainingCooldown("gold");
792
+ if (isInCooldown("gold")) {
793
+ logInfo("60s API: 今日金价发送冷却中,跳过本次发送", {
794
+ today,
795
+ remainingCooldownSec: remainingCooldown,
796
+ scheduleCooldown: config.scheduleCooldown
797
+ });
743
798
  const msUntilNext2 = getMsUntilNextTime(config.goldScheduleTime);
744
799
  if (msUntilNext2 === null) {
745
800
  goldScheduleTimeout = setTimeout(() => {
746
- sentToday.gold = "";
747
801
  setupGoldSchedule();
748
802
  }, 24 * 60 * 60 * 1e3);
749
803
  return;
750
804
  }
751
805
  goldScheduleTimeout = setTimeout(() => {
752
- sentToday.gold = "";
753
806
  setupGoldSchedule();
754
- }, msUntilNext2 + 24 * 60 * 60 * 1e3);
807
+ }, msUntilNext2);
755
808
  return;
756
809
  }
757
810
  const msUntilNext = getMsUntilNextTime(config.goldScheduleTime);
@@ -766,16 +819,19 @@ ${newsItem.link}`).join("\n\n");
766
819
  scheduleTime: config.goldScheduleTime,
767
820
  msUntilNext,
768
821
  nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
769
- whitelist: config.scheduleWhitelist
822
+ whitelist: config.scheduleWhitelist,
823
+ scheduleCooldown: config.scheduleCooldown
770
824
  });
771
825
  goldScheduleTimeout = setTimeout(async () => {
772
- if (sentToday.gold === getTodayString()) {
773
- logInfo("60s API: 今日金价已在本次任务等待期间发送,跳过");
826
+ if (isInCooldown("gold")) {
827
+ logInfo("60s API: 今日金价发送冷却中,跳过本次发送", {
828
+ remainingCooldownSec: getRemainingCooldown("gold")
829
+ });
774
830
  setupGoldSchedule();
775
831
  return;
776
832
  }
777
833
  await sendGoldToChannels();
778
- sentToday.gold = getTodayString();
834
+ lastSentTime.gold = Date.now();
779
835
  setupGoldSchedule();
780
836
  }, msUntilNext);
781
837
  } catch (error) {
@@ -794,20 +850,23 @@ ${newsItem.link}`).join("\n\n");
794
850
  }
795
851
  try {
796
852
  const today = getTodayString();
797
- if (sentToday.fuel === today) {
798
- logInfo("60s API: 今日油价已发送过,设置为明天任务", { today });
853
+ const remainingCooldown = getRemainingCooldown("fuel");
854
+ if (isInCooldown("fuel")) {
855
+ logInfo("60s API: 今日油价发送冷却中,跳过本次发送", {
856
+ today,
857
+ remainingCooldownSec: remainingCooldown,
858
+ scheduleCooldown: config.scheduleCooldown
859
+ });
799
860
  const msUntilNext2 = getMsUntilNextTime(config.fuelScheduleTime);
800
861
  if (msUntilNext2 === null) {
801
862
  fuelScheduleTimeout = setTimeout(() => {
802
- sentToday.fuel = "";
803
863
  setupFuelSchedule();
804
864
  }, 24 * 60 * 60 * 1e3);
805
865
  return;
806
866
  }
807
867
  fuelScheduleTimeout = setTimeout(() => {
808
- sentToday.fuel = "";
809
868
  setupFuelSchedule();
810
- }, msUntilNext2 + 24 * 60 * 60 * 1e3);
869
+ }, msUntilNext2);
811
870
  return;
812
871
  }
813
872
  const msUntilNext = getMsUntilNextTime(config.fuelScheduleTime);
@@ -822,16 +881,19 @@ ${newsItem.link}`).join("\n\n");
822
881
  scheduleTime: config.fuelScheduleTime,
823
882
  msUntilNext,
824
883
  nextRun: new Date(Date.now() + msUntilNext).toLocaleString(),
825
- whitelist: config.scheduleWhitelist
884
+ whitelist: config.scheduleWhitelist,
885
+ scheduleCooldown: config.scheduleCooldown
826
886
  });
827
887
  fuelScheduleTimeout = setTimeout(async () => {
828
- if (sentToday.fuel === getTodayString()) {
829
- logInfo("60s API: 今日油价已在本次任务等待期间发送,跳过");
888
+ if (isInCooldown("fuel")) {
889
+ logInfo("60s API: 今日油价发送冷却中,跳过本次发送", {
890
+ remainingCooldownSec: getRemainingCooldown("fuel")
891
+ });
830
892
  setupFuelSchedule();
831
893
  return;
832
894
  }
833
895
  await sendFuelToChannels();
834
- sentToday.fuel = getTodayString();
896
+ lastSentTime.fuel = Date.now();
835
897
  setupFuelSchedule();
836
898
  }, msUntilNext);
837
899
  } catch (error) {
@@ -1225,11 +1287,11 @@ ${shortDetail}
1225
1287
  clearTimeout(fuelScheduleTimeout);
1226
1288
  fuelScheduleTimeout = null;
1227
1289
  }
1228
- sentToday.news = "";
1229
- sentToday.aiNews = "";
1230
- sentToday.moyu = "";
1231
- sentToday.gold = "";
1232
- sentToday.fuel = "";
1290
+ lastSentTime.news = 0;
1291
+ lastSentTime.aiNews = 0;
1292
+ lastSentTime.moyu = 0;
1293
+ lastSentTime.gold = 0;
1294
+ lastSentTime.fuel = 0;
1233
1295
  });
1234
1296
  }
1235
1297
  __name(apply, "apply");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-aka-60s-api",
3
3
  "description": "调用60s API转发信息的工具 - 个人用",
4
- "version": "0.2.8",
4
+ "version": "0.2.10",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [