gacha-calendar-core 0.10.4 → 0.10.6
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/core.mjs +69 -11
- package/package.json +1 -1
package/core.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 用途:缓存里记录"这份数据是哪版插件产出的"。更新插件后首次启动,据此**强制**刷新一次
|
|
5
5
|
// (不看自动刷新开关)——因为有些改动(悬停格式、来源地址、样式、解析器)不刷新就看不到效果。
|
|
6
6
|
// 写入时机见 engine-api.js 的 refresh();判定见 60-helpers.js 的 autoRefreshPlan()。
|
|
7
|
-
const PLUGIN_VERSION = "0.10.
|
|
7
|
+
const PLUGIN_VERSION = "0.10.6";
|
|
8
8
|
//#endregion
|
|
9
9
|
|
|
10
10
|
//#region config
|
|
@@ -849,13 +849,34 @@ export function createEngine(env) {
|
|
|
849
849
|
.trim();
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
// 「版本更新后」这类**只有日期没有时分**的写法,默认按当日该时刻折算。
|
|
853
|
+
// 为什么需要一个默认值:源站常见 `2026/09/28 4.6版本更新后`(日期 + 版本标签的混合体),
|
|
854
|
+
// 日期是明确的,缺的只是时分;不补的话 startTs 会是 null,外显只能回落源站原文
|
|
855
|
+
// (星铁活动列一度显示成 `2026/09/28 4.6版本更新后 ~ 11-10 15:00`,与其它游戏的
|
|
856
|
+
// `09-28 04:00 ~ 11-10 15:00` 口径不一致)。
|
|
857
|
+
// 取 04:00 是因为国内二游版本更新普遍落在凌晨维护窗口(该表内 04:00 出现最多),
|
|
858
|
+
// 拿它当锚点比"不补"更接近真实,也不会让"活动是否已开始"的判定偏移一天。
|
|
859
|
+
const VERSION_UPDATE_ANCHOR = { h: 4, mi: 0 };
|
|
860
|
+
|
|
852
861
|
// 解析单个时间 → {ts, text};无法解析返回 {ts:null, text:null}
|
|
853
862
|
function parseTime(s) {
|
|
854
863
|
const m = String(s).match(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?/);
|
|
855
|
-
if (
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
864
|
+
if (m) {
|
|
865
|
+
const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]), h = Number(m[4]), mi = Number(m[5]);
|
|
866
|
+
const text = `${String(mo).padStart(2, "0")}-${String(d).padStart(2, "0")} ${String(h).padStart(2, "0")}:${String(mi).padStart(2, "0")}`;
|
|
867
|
+
return { ts: new Date(y, mo - 1, d, h, mi).getTime(), text };
|
|
868
|
+
}
|
|
869
|
+
// 回退:`YYYY/MM/DD <版本更新后>`(日期 + 版本标签,缺时分)→ 按锚点时刻补全。
|
|
870
|
+
// 注意只认**带日期前缀**的这种;纯标签(`4.6版本更新后`)保持 null —— 那种确实
|
|
871
|
+
// 给不出日期(下一版本何时更新是未知的),硬造时间会错得更离谱。
|
|
872
|
+
const v = String(s).match(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})\s*(?:\d+(?:\.\d+)?\s*版本更新后|版本更新后)/);
|
|
873
|
+
if (v) {
|
|
874
|
+
const y = Number(v[1]), mo = Number(v[2]), d = Number(v[3]);
|
|
875
|
+
const { h, mi } = VERSION_UPDATE_ANCHOR;
|
|
876
|
+
const text = `${String(mo).padStart(2, "0")}-${String(d).padStart(2, "0")} ${String(h).padStart(2, "0")}:${String(mi).padStart(2, "0")}`;
|
|
877
|
+
return { ts: new Date(y, mo - 1, d, h, mi).getTime(), text };
|
|
878
|
+
}
|
|
879
|
+
return { ts: null, text: null };
|
|
859
880
|
}
|
|
860
881
|
|
|
861
882
|
// 时间段 → startTs/endTs + 统一文本 mm-dd hh:mm ~ mm-dd hh:mm(无法解析的一侧保留原文)
|
|
@@ -1191,6 +1212,21 @@ export function createEngine(env) {
|
|
|
1191
1212
|
return !!x && x.startTs != null && x.endTs != null && (x.endTs - x.startTs) > EVENT_MAX_WINDOW_DAYS * 864e5;
|
|
1192
1213
|
}
|
|
1193
1214
|
|
|
1215
|
+
// 永久/常驻活动判定:源站把「结束时间」写成 `永久`(星铁「星际碰碰好搭档!」等)。
|
|
1216
|
+
// 这类行 endTs 为 null,**过去被静默丢弃**——不是判定为"非当期",而是连痕都没留下,
|
|
1217
|
+
// 表现为"源站表里有、面板悬停里没有"(用户点名要修)。
|
|
1218
|
+
// 现在显式识别:外显仍不选它(永久活动没有"当期"语义、也就没有倒计时),
|
|
1219
|
+
// 但悬停里以一行计数如实交代,不再无声消失。
|
|
1220
|
+
//
|
|
1221
|
+
// ⚠️ **必须同时满足 endTs == null**:星铁表里大量正常限时活动的**类别**叫
|
|
1222
|
+
// 「版本活动 常驻活动」「常驻活动 联动活动」(wiki 的命名习惯),但它们都有明确结束时间。
|
|
1223
|
+
// 只按名字判会把 39 条限时活动全误判成永久(实测踩到)。有结束时间 = 限时,不做特例。
|
|
1224
|
+
function isPermanentEvent(x) {
|
|
1225
|
+
if (!x || x.endTs != null) return false;
|
|
1226
|
+
if (/永久/.test(`${x.cat || ""} ${x.tags || ""}`)) return true;
|
|
1227
|
+
return /永久\s*$/.test(String(x.raw || ""));
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1194
1230
|
// 活动列表统一排序(③):结束时间升序(越快结束越靠前);结束时间无/未知/未抓到的排最后;
|
|
1195
1231
|
// 同结束时间再按开始时间升序。同时剔除常驻/长期玩法与空名行(①)。
|
|
1196
1232
|
// 外显取排序后的第一条,悬停按同序逐行展示 —— 两处共用本函数保证一致。
|
|
@@ -1235,15 +1271,27 @@ export function createEngine(env) {
|
|
|
1235
1271
|
})[0] || null;
|
|
1236
1272
|
}
|
|
1237
1273
|
|
|
1274
|
+
// 永久/常驻活动的计数行。抽成函数是为了**措辞只有一处**,并让守卫能直接断言文案
|
|
1275
|
+
// (曾经的写法是 `常驻/永久活动 N 项(无结束时间,不参与倒计时)`,用户要求改为
|
|
1276
|
+
// `以及常驻活动 N 项`)。
|
|
1277
|
+
function permanentLine(count) {
|
|
1278
|
+
return count > 0 ? `以及常驻活动 ${count} 项` : "";
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1238
1281
|
// 活动列悬停(鸣潮式多行):按传入顺序(调用方已 sortEventItems)每条一行;
|
|
1239
1282
|
// 各行窗口完全相同 → 时间只在末尾写一遍;缺起止的行原样显示该行原文;跨年窗口两端带年份(②)。
|
|
1240
1283
|
// 兜底:只有 0/1 条时返回 "",由 UI 退回原有"活动名 + 时间"单条展示 ——
|
|
1241
1284
|
// 公告类单条源(蔚蓝国服/日服、1999、异环等)因此完全不受影响,也不会出现空行或半截区间。
|
|
1242
|
-
|
|
1285
|
+
// permanentCount:永久/常驻活动的条数(不参与排序,只在末尾补一行计数,避免"源站有、面板没有")。
|
|
1286
|
+
function buildEventHover(items, permanentCount = 0) {
|
|
1243
1287
|
const list = (Array.isArray(items) ? items : [])
|
|
1244
1288
|
.filter((x) => x && typeof x.name === "string" && x.name.trim() !== "")
|
|
1245
1289
|
.filter((x) => !isLongTermEvent(x));
|
|
1246
|
-
|
|
1290
|
+
// 只有 1 条当期活动时,本函数仍返回 ""(由 UI 单条展示);但若还有永久活动,
|
|
1291
|
+
// 就必须把那一行计数带上,否则永久活动又变成看不见。
|
|
1292
|
+
if (list.length < 2) {
|
|
1293
|
+
return permanentLine(permanentCount);
|
|
1294
|
+
}
|
|
1247
1295
|
const allTimed = list.every((x) => x.startTs != null && x.endTs != null);
|
|
1248
1296
|
const same = allTimed && new Set(list.map((x) => `${x.startTs}~${x.endTs}`)).size === 1;
|
|
1249
1297
|
const lines = list.map((x) => {
|
|
@@ -1255,6 +1303,7 @@ export function createEngine(env) {
|
|
|
1255
1303
|
return raw ? `${x.name} ${raw}` : x.name;
|
|
1256
1304
|
});
|
|
1257
1305
|
if (same) lines.push(fmtWindow(list[0].startTs, list[0].endTs));
|
|
1306
|
+
if (permanentCount > 0) lines.push(permanentLine(permanentCount));
|
|
1258
1307
|
return lines.join("\n");
|
|
1259
1308
|
}
|
|
1260
1309
|
|
|
@@ -2084,9 +2133,15 @@ export function createEngine(env) {
|
|
|
2084
2133
|
name = name.replace(/^文件:[^\s]*?\.(?:png|jpe?g|gif|webp|svg)\s*/i, "").trim();
|
|
2085
2134
|
if (!name) continue;
|
|
2086
2135
|
const range = parseRange(time);
|
|
2087
|
-
// 起始可为 null("版本更新后"
|
|
2088
|
-
|
|
2089
|
-
|
|
2136
|
+
// 起始可为 null("版本更新后"),结束时间必须有效——**除非这行是永久活动**。
|
|
2137
|
+
// 原来这里一律 `endTs == null → continue`,把「…~永久」的行在采集阶段就丢了,
|
|
2138
|
+
// 下游连"它存在过"都看不到(表现为源站表里有、面板悬停里没有)。
|
|
2139
|
+
// 永久活动(结束写作「永久」)保留下来,由 genericEventPayload 单独计数;
|
|
2140
|
+
// 其它无结束时间的行仍按旧规则丢弃(起止都拿不到,画不出来)。
|
|
2141
|
+
const cat0 = catIdx >= 0 ? (tds[catIdx] || "") : "";
|
|
2142
|
+
const isPermanent = range.endTs == null && (/永久/.test(cat0) || /永久\s*$/.test(String(range.raw || time)));
|
|
2143
|
+
if (range.endTs == null && !isPermanent) continue;
|
|
2144
|
+
items.push({ banner: name, cat: cat0, ...range, isMain: true });
|
|
2090
2145
|
}
|
|
2091
2146
|
}
|
|
2092
2147
|
return items;
|
|
@@ -2132,6 +2187,9 @@ export function createEngine(env) {
|
|
|
2132
2187
|
if (items.length === 0) throw new Error("bwiki-event-no-table");
|
|
2133
2188
|
const snapshot = items.map((it) => ({ name: it.banner, cat: it.cat || "", startTs: it.startTs, endTs: it.endTs, raw: it.rawOriginal || it.raw }));
|
|
2134
2189
|
const now = nowMs();
|
|
2190
|
+
// 永久/常驻活动单独计一项:它们 endTs 为 null,本就不该混进"当期"排序,
|
|
2191
|
+
// 但也不能像以前那样无声丢掉(见 isPermanentEvent 注释)。
|
|
2192
|
+
const permanent = snapshot.filter((it) => isPermanentEvent(it));
|
|
2135
2193
|
const active = sortEventItems(snapshot.filter((it) => it.endTs != null && it.endTs >= now && (it.startTs == null || it.startTs <= now)));
|
|
2136
2194
|
if (active.length === 0) return null;
|
|
2137
2195
|
// 外显:类别优先(剧情/叙事、限时高难),同级内结束时间升序;悬停仍按 endTs 升序全量
|
|
@@ -2140,7 +2198,7 @@ export function createEngine(env) {
|
|
|
2140
2198
|
event: primary.name,
|
|
2141
2199
|
eventDates: primary.startTs != null && primary.endTs != null ? fmtWindow(primary.startTs, primary.endTs) : (primary.raw || ""),
|
|
2142
2200
|
eventDatesRaw: primary.raw || "",
|
|
2143
|
-
eventHover: buildEventHover(active)
|
|
2201
|
+
eventHover: buildEventHover(active, permanent.length)
|
|
2144
2202
|
};
|
|
2145
2203
|
}
|
|
2146
2204
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gacha-calendar-core",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"description": "二游活动/卡池排期抓取核心:11 款游戏的来源注册表 + 解析器 + 刷新编排 + 缓存降级。零依赖、零宿主环境依赖,任何 JS 运行时(浏览器扩展 background / Tauri / JavaScriptCore / ArkTS 嵌入式引擎)都能跑。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./core.mjs",
|