balance-dsh 1.2.0 → 1.2.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/client.js +63 -6
- package/lib/index.js +72 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -294,19 +294,76 @@ window.__ModuleLoader__.load({
|
|
|
294
294
|
|
|
295
295
|
// ─── 高峰/空闲时段(纯客户端判断,无需服务端) ────────────────────────
|
|
296
296
|
// V4 峰谷定价(2026-08-17 生效):高峰价 = 空闲价 × 2。
|
|
297
|
-
// 官方高峰窗口(北京时间)为
|
|
297
|
+
// 官方高峰窗口(北京时间)为 工作日 09:00-12:00、14:00-18:00;
|
|
298
|
+
// 周末与法定节假日全天空闲,但「调休上班的周末」按工作日算(2026-09-20 DeepSeek 明确)。
|
|
298
299
|
const PEAK_WINDOWS = [
|
|
299
300
|
{ start: 9, end: 12 },
|
|
300
301
|
{ start: 14, end: 18 },
|
|
301
302
|
];
|
|
302
303
|
const PEAK_WEEKDAYS = [1, 2, 3, 4, 5]; // 1=周一 … 5=周五,0/6=周末
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
// 中国大陆法定节假日与调休(北京时区)。**必须与 lib/index.js 的 CN_CALENDAR 完全一致**
|
|
305
|
+
// (服务端负责算钱、客户端只负责显示时段标签)。来源:国办发明电〔2025〕7号,每年需补录。
|
|
306
|
+
const CN_CALENDAR = {
|
|
307
|
+
// 元旦:1/1-1/3 放假,1/4(周日) 上班
|
|
308
|
+
"2026-01-01": "holiday", "2026-01-02": "holiday", "2026-01-03": "holiday", "2026-01-04": "workday",
|
|
309
|
+
// 春节:2/15-2/23 放假,2/14(周六)、2/28(周六) 上班
|
|
310
|
+
"2026-02-14": "workday",
|
|
311
|
+
"2026-02-15": "holiday", "2026-02-16": "holiday", "2026-02-17": "holiday", "2026-02-18": "holiday",
|
|
312
|
+
"2026-02-19": "holiday", "2026-02-20": "holiday", "2026-02-21": "holiday", "2026-02-22": "holiday",
|
|
313
|
+
"2026-02-23": "holiday", "2026-02-28": "workday",
|
|
314
|
+
// 清明节:4/4-4/6 放假(无调休)
|
|
315
|
+
"2026-04-04": "holiday", "2026-04-05": "holiday", "2026-04-06": "holiday",
|
|
316
|
+
// 劳动节:5/1-5/5 放假,5/9(周六) 上班
|
|
317
|
+
"2026-05-01": "holiday", "2026-05-02": "holiday", "2026-05-03": "holiday", "2026-05-04": "holiday",
|
|
318
|
+
"2026-05-05": "holiday", "2026-05-09": "workday",
|
|
319
|
+
// 端午节:6/19-6/21 放假(无调休)
|
|
320
|
+
"2026-06-19": "holiday", "2026-06-20": "holiday", "2026-06-21": "holiday",
|
|
321
|
+
// 国庆节调休:9/20(周日) 上班
|
|
322
|
+
"2026-09-20": "workday",
|
|
323
|
+
// 中秋节:9/25-9/27 放假(无调休)
|
|
324
|
+
"2026-09-25": "holiday", "2026-09-26": "holiday", "2026-09-27": "holiday",
|
|
325
|
+
// 国庆节:10/1-10/7 放假,10/10(周六) 上班
|
|
326
|
+
"2026-10-01": "holiday", "2026-10-02": "holiday", "2026-10-03": "holiday", "2026-10-04": "holiday",
|
|
327
|
+
"2026-10-05": "holiday", "2026-10-06": "holiday", "2026-10-07": "holiday", "2026-10-10": "workday",
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// 节假日自动补录:与 lib/index.js 同源、同逻辑(客户端只用来显示时段标签)。
|
|
331
|
+
// 拉取失败静默 —— 拿不到就继续用上面的内置表。
|
|
332
|
+
const CN_CALENDAR_SOURCE = "https://cdn.jsdelivr.net/gh/NateScarlet/holiday-cn@master";
|
|
333
|
+
const cnCalendarYears = new Set();
|
|
334
|
+
async function ensureCnCalendar(year) {
|
|
335
|
+
if (cnCalendarYears.has(year)) return;
|
|
336
|
+
cnCalendarYears.add(year); // 先占位,避免并发重复请求
|
|
337
|
+
try {
|
|
338
|
+
const res = await fetch(CN_CALENDAR_SOURCE + "/" + year + ".json", { signal: AbortSignal.timeout(8000) });
|
|
339
|
+
if (!res.ok) return;
|
|
340
|
+
const data = await res.json();
|
|
341
|
+
if (!Array.isArray(data?.days)) return;
|
|
342
|
+
for (const item of data.days) {
|
|
343
|
+
if (item && typeof item.date === "string" && typeof item.isOffDay === "boolean") {
|
|
344
|
+
CN_CALENDAR[item.date] = item.isOffDay ? "holiday" : "workday";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// 网络不可用:保留内置表
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
{
|
|
352
|
+
const y = new Date(Date.now() + 8 * 60 * 60 * 1000).getUTCFullYear();
|
|
353
|
+
void ensureCnCalendar(y - 1);
|
|
354
|
+
void ensureCnCalendar(y);
|
|
355
|
+
void ensureCnCalendar(y + 1);
|
|
356
|
+
}
|
|
357
|
+
function currentPeriod(atMs) {
|
|
358
|
+
const d = new Date((atMs === undefined ? Date.now() : atMs) + 8 * 60 * 60 * 1000); // 北京时间
|
|
359
|
+
const kind = CN_CALENDAR[
|
|
360
|
+
d.getUTCFullYear() + "-" + String(d.getUTCMonth() + 1).padStart(2, "0") + "-" + String(d.getUTCDate()).padStart(2, "0")
|
|
361
|
+
];
|
|
362
|
+
if (kind === "holiday") return "offpeak"; // 法定节假日:全天按空闲
|
|
305
363
|
const hour = d.getUTCHours();
|
|
306
364
|
const weekday = d.getUTCDay(); // 0=周日 … 6=周六
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
: "offpeak";
|
|
365
|
+
const onDuty = PEAK_WEEKDAYS.includes(weekday) || kind === "workday"; // 调休上班的周末按工作日算
|
|
366
|
+
return onDuty && PEAK_WINDOWS.some((w) => hour >= w.start && hour < w.end) ? "peak" : "offpeak";
|
|
310
367
|
}
|
|
311
368
|
/** 按当前时段调整价格:高峰 ×2、空闲原价。注意:若服务端已配 config.peak:true
|
|
312
369
|
* (强制高峰价翻倍),此处会再乘一次,两种方式不要同时使用。 */
|
package/lib/index.js
CHANGED
|
@@ -79,7 +79,8 @@ function pricingEraAt(time) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// 高峰/空闲时段(V4 峰谷定价,2026-08-17 生效):高峰价 = 空闲价 × 2。
|
|
82
|
-
// 官方高峰窗口(北京时间)为
|
|
82
|
+
// 官方高峰窗口(北京时间)为 工作日 09:00-12:00、14:00-18:00;
|
|
83
|
+
// 周末与法定节假日全天空闲,但**调休上班的周末**按工作日算(2026-09-20 DeepSeek 明确此规则)。
|
|
83
84
|
// 可用 config.peakWindows 覆盖小时窗口(如 [{start:9,end:12},{start:14,end:18}]),
|
|
84
85
|
// 可用 config.peakWeekdays 覆盖周几(1=周一 … 5=周五,默认 [1,2,3,4,5],0/6=周末)。
|
|
85
86
|
const DEFAULT_PEAK_WINDOWS = [
|
|
@@ -88,7 +89,63 @@ const DEFAULT_PEAK_WINDOWS = [
|
|
|
88
89
|
];
|
|
89
90
|
const DEFAULT_PEAK_WEEKDAYS = [1, 2, 3, 4, 5];
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
// 中国大陆法定节假日与调休(日期为北京时区)。来源:国务院办公厅《关于2026年部分节假日安排的通知》
|
|
93
|
+
// (国办发明电〔2025〕7号)。holiday = 法定节假日(全天按空闲计费);workday = 调休上班的周末(按工作日峰谷计费)。
|
|
94
|
+
// ⚠️ 国务院每年 11 月前后公布次年安排,届时需补录。lib/client.js 有一份同样的表(用于时段标签),两处必须同步。
|
|
95
|
+
const CN_CALENDAR = {
|
|
96
|
+
// 元旦:1/1-1/3 放假,1/4(周日) 上班
|
|
97
|
+
"2026-01-01": "holiday", "2026-01-02": "holiday", "2026-01-03": "holiday", "2026-01-04": "workday",
|
|
98
|
+
// 春节:2/15-2/23 放假,2/14(周六)、2/28(周六) 上班
|
|
99
|
+
"2026-02-14": "workday",
|
|
100
|
+
"2026-02-15": "holiday", "2026-02-16": "holiday", "2026-02-17": "holiday", "2026-02-18": "holiday",
|
|
101
|
+
"2026-02-19": "holiday", "2026-02-20": "holiday", "2026-02-21": "holiday", "2026-02-22": "holiday",
|
|
102
|
+
"2026-02-23": "holiday", "2026-02-28": "workday",
|
|
103
|
+
// 清明节:4/4-4/6 放假(无调休)
|
|
104
|
+
"2026-04-04": "holiday", "2026-04-05": "holiday", "2026-04-06": "holiday",
|
|
105
|
+
// 劳动节:5/1-5/5 放假,5/9(周六) 上班
|
|
106
|
+
"2026-05-01": "holiday", "2026-05-02": "holiday", "2026-05-03": "holiday", "2026-05-04": "holiday",
|
|
107
|
+
"2026-05-05": "holiday", "2026-05-09": "workday",
|
|
108
|
+
// 端午节:6/19-6/21 放假(无调休)
|
|
109
|
+
"2026-06-19": "holiday", "2026-06-20": "holiday", "2026-06-21": "holiday",
|
|
110
|
+
// 国庆节调休:9/20(周日) 上班
|
|
111
|
+
"2026-09-20": "workday",
|
|
112
|
+
// 中秋节:9/25-9/27 放假(无调休)
|
|
113
|
+
"2026-09-25": "holiday", "2026-09-26": "holiday", "2026-09-27": "holiday",
|
|
114
|
+
// 国庆节:10/1-10/7 放假,10/10(周六) 上班
|
|
115
|
+
"2026-10-01": "holiday", "2026-10-02": "holiday", "2026-10-03": "holiday", "2026-10-04": "holiday",
|
|
116
|
+
"2026-10-05": "holiday", "2026-10-06": "holiday", "2026-10-07": "holiday", "2026-10-10": "workday",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// 节假日自动补录:内置表仅作**离线兜底**,运行时从社区维护的源拉取(其 papers 字段直接引用国务院公告)。
|
|
120
|
+
// 源不可用/无网络/被 CSP 拦截时静默失败,继续用内置表;拉取结果按日期覆盖同键条目。
|
|
121
|
+
const CN_CALENDAR_SOURCE = "https://cdn.jsdelivr.net/gh/NateScarlet/holiday-cn@master";
|
|
122
|
+
const cnCalendarYears = new Set();
|
|
123
|
+
|
|
124
|
+
/** 拉取指定年份的节假日安排并合并进 CN_CALENDAR(幂等;失败静默,不阻塞调用方)。 */
|
|
125
|
+
async function ensureCnCalendar(year) {
|
|
126
|
+
if (cnCalendarYears.has(year)) return;
|
|
127
|
+
cnCalendarYears.add(year); // 先占位,避免并发重复请求
|
|
128
|
+
try {
|
|
129
|
+
const res = await fetch(CN_CALENDAR_SOURCE + "/" + year + ".json", { signal: AbortSignal.timeout(8000) });
|
|
130
|
+
if (!res.ok) return;
|
|
131
|
+
const data = await res.json();
|
|
132
|
+
if (!Array.isArray(data?.days)) return;
|
|
133
|
+
for (const item of data.days) {
|
|
134
|
+
if (item && typeof item.date === "string" && typeof item.isOffDay === "boolean") {
|
|
135
|
+
CN_CALENDAR[item.date] = item.isOffDay ? "holiday" : "workday";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// 网络不可用:保留内置表(该年调休会退化为「周末全天空闲」)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 北京时区日期键 YYYY-MM-DD(传入已 +8h 偏移的 Date)。 */
|
|
144
|
+
function cnDateKey(d) {
|
|
145
|
+
return d.getUTCFullYear() + "-" + String(d.getUTCMonth() + 1).padStart(2, "0") + "-" + String(d.getUTCDate()).padStart(2, "0");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 判断某时间戳(毫秒)是否处于高峰时段(北京时间;含周末、法定节假日与调休上班判断)。 */
|
|
92
149
|
function isPeakAt(timeMs, config) {
|
|
93
150
|
const windows = (Array.isArray(config?.peakWindows) && config.peakWindows.length > 0
|
|
94
151
|
? config.peakWindows
|
|
@@ -98,9 +155,12 @@ function isPeakAt(timeMs, config) {
|
|
|
98
155
|
? config.peakWeekdays.map(Number)
|
|
99
156
|
: DEFAULT_PEAK_WEEKDAYS;
|
|
100
157
|
const d = new Date(Number(timeMs) + 8 * 60 * 60 * 1000); // 北京时间
|
|
158
|
+
const kind = CN_CALENDAR[cnDateKey(d)];
|
|
159
|
+
if (kind === "holiday") return false; // 法定节假日:全天按空闲计费
|
|
101
160
|
const hour = d.getUTCHours();
|
|
102
161
|
const weekday = d.getUTCDay(); // 0=周日 … 6=周六
|
|
103
|
-
|
|
162
|
+
const onDuty = weekdays.includes(weekday) || kind === "workday"; // 调休上班的周末按工作日算
|
|
163
|
+
return onDuty && windows.some((w) => hour >= w.start && hour < w.end);
|
|
104
164
|
}
|
|
105
165
|
|
|
106
166
|
/** 把模型字符串规整到定价表键;未识别返回 ""(用 Flash 价兜底)。
|
|
@@ -736,6 +796,15 @@ export function apply(ctx, config = {}) {
|
|
|
736
796
|
// 时统一清理,保证重复 apply 不冲突。
|
|
737
797
|
const disposers = [];
|
|
738
798
|
|
|
799
|
+
// 后台补录节假日数据(当年 ±1 年,覆盖 90 天统计窗口与跨年场景)。
|
|
800
|
+
// 故意不 await:拉取失败不能影响插件启动;isPeakAt 保持同步,未拉到时继续用内置表。
|
|
801
|
+
{
|
|
802
|
+
const y = new Date(Date.now() + 8 * 60 * 60 * 1000).getUTCFullYear();
|
|
803
|
+
void ensureCnCalendar(y - 1);
|
|
804
|
+
void ensureCnCalendar(y);
|
|
805
|
+
void ensureCnCalendar(y + 1);
|
|
806
|
+
}
|
|
807
|
+
|
|
739
808
|
// 1) /balance 命令:保留命令行查询入口
|
|
740
809
|
ctx.inject(["commands"], (commandCtx) => {
|
|
741
810
|
commandCtx.commands.register({
|