mslxdff 0.1.68 → 0.1.69

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.68",
3
+ "version": "0.1.69",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,6 +6,7 @@ import { loadToken, refreshToken } from "../../state.js";
6
6
  import { stopDaemon, pidFile, logFile } from "../../daemon.js";
7
7
  import { logDir, eventsFile, callsFile, errorsFile, recentEvents } from "../../logs.js";
8
8
  import { fmtEvent } from "../format.js";
9
+ import { fmtShanghaiYMDHMS, fmtShanghaiHMS } from "../../time.js";
9
10
  import { printHelp } from "../help.js";
10
11
  import { printStatus } from "../status.js";
11
12
  import { loadPlugins, resolvePluginDirs } from "../../plugins.js";
@@ -178,7 +179,7 @@ export async function handleFree(args) {
178
179
  const { fetchV2exFree } = await import("../../free-watcher.js");
179
180
  const show = async () => {
180
181
  const hits = await fetchV2exFree({ timeoutMs: 6000 });
181
- const ts = new Date().toISOString().replace("T", " ").slice(0, 19);
182
+ const ts = fmtShanghaiYMDHMS(new Date());
182
183
  console.log(`[V2EX] free check @ ${ts} — ${hits.length} hit(s)`);
183
184
  if (!hits.length) {
184
185
  console.log("(暂无命中 — 关键词:白嫖|限免|免费额度|注册送|羊毛,来源:/api/topics/latest.json + hot.json)");
@@ -192,7 +193,7 @@ export async function handleFree(args) {
192
193
  }
193
194
  console.log("V2EX 白嫖雷达 watch 模式 — 每 5 分钟拉一次 Ctrl+C 退出");
194
195
  const run = async () => {
195
- try { await show(); } catch (err) { console.error(`[${new Date().toISOString().slice(11,19)}] 拉取失败: ${err?.message || err}`); }
196
+ try { await show(); } catch (err) { console.error(`[${fmtShanghaiHMS(new Date())}] 拉取失败: ${err?.message || err}`); }
196
197
  console.log("---");
197
198
  };
198
199
  await run();
@@ -0,0 +1,47 @@
1
+ import { loadTimezone, loadTimezoneState, saveTimezone, clearTimezone, getTimezoneEnv, isValidTimezone, DEFAULT_TZ } from "../../state/schemas/timezone.js";
2
+ import { getTimezone } from "../../time.js";
3
+
4
+ export async function handleTimezone(args) {
5
+ if (!(args.includes("-timezone") || args.includes("--timezone") || args.includes("-tz") || args.includes("--tz") || args.includes("-time") || args.includes("--time"))) return false;
6
+ const idx = args.findIndex((x) => ["-timezone","--timezone","-tz","--tz","-time","--time"].includes(x));
7
+ const sub = args[idx + 1];
8
+ const rest = args.slice(idx + 2);
9
+ const env = getTimezoneEnv();
10
+ const current = loadTimezoneState();
11
+ const effective = getTimezone();
12
+
13
+ if (!sub || sub === "status" || sub === "list" || sub === "show") {
14
+ console.log(`timezone: ${effective} ${env ? `(env ${env} 覆盖)` : ""}`.trim());
15
+ console.log(` state: ${current} ${current === DEFAULT_TZ ? "(默认 Asia/Shanghai)" : ""}`);
16
+ if (env) console.log(` env : ${env} (MSLXDFF_TZ 覆盖 state)`);
17
+ else console.log(` env : (未设 MSLXDFF_TZ)`);
18
+ console.log(`\n可用示例: Asia/Shanghai, UTC, America/New_York, Europe/London, Asia/Tokyo`);
19
+ console.log(`用法:`);
20
+ console.log(` mslxdff -timezone set Asia/Shanghai 设为上海时间(默认)`);
21
+ console.log(` mslxdff -timezone set UTC 设为 UTC`);
22
+ console.log(` mslxdff -timezone clear 恢复默认 (${DEFAULT_TZ})`);
23
+ console.log(` MSLXDFF_TZ=UTC mslxdff -status 临时用 UTC(env 覆盖,不落盘)`);
24
+ process.exit(0);
25
+ }
26
+ if (sub === "clear" || sub === "reset") {
27
+ clearTimezone();
28
+ console.log(`timezone 已清除,恢复默认: ${DEFAULT_TZ}`);
29
+ process.exit(0);
30
+ }
31
+ let target = "";
32
+ if (sub === "set") target = rest[0];
33
+ else target = sub;
34
+ if (!target) {
35
+ console.error("usage: mslxdff -timezone set <Timezone> e.g. Asia/Shanghai, UTC");
36
+ console.error(" mslxdff -timezone <Timezone> 直接设置");
37
+ process.exit(1);
38
+ }
39
+ if (!isValidTimezone(target)) {
40
+ console.error(`无效时区: ${target}`);
41
+ console.error(`示例: Asia/Shanghai, UTC, America/New_York`);
42
+ process.exit(1);
43
+ }
44
+ saveTimezone(target);
45
+ console.log(`timezone 已设为: ${target}(已写入 state.json,${env ? "但当前 env MSLXDFF_TZ 仍覆盖,需 unset 后生效" : "立即生效"})`);
46
+ process.exit(0);
47
+ }
package/src/cli/index.js CHANGED
@@ -12,6 +12,8 @@ export async function run(args = process.argv.slice(2)) {
12
12
  if (await handleUpdate(args, VERSION)) return;
13
13
  if (await handleRefreshToken(args)) return;
14
14
  if (await handleShowToken(args)) return;
15
+ const { handleTimezone } = await import("./commands/timezone.js");
16
+ if (await handleTimezone(args)) return;
15
17
 
16
18
  const { handleStop, handleRestart } = await import("./commands/daemon.js");
17
19
  if (await handleStop(args)) return;
package/src/logs.js CHANGED
@@ -3,6 +3,7 @@ import { appendFile, stat, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import os from "node:os";
5
5
  import { defaultStateFile } from "./state.js";
6
+ import { fmtShanghaiYMDHMS } from "./time.js";
6
7
 
7
8
  const MAX_CALLS = 500;
8
9
  const MAX_ERRORS = 200;
@@ -73,7 +74,7 @@ function shouldSync(file) {
73
74
 
74
75
  function appendLine(file, entry) {
75
76
  ensureDir(dirname(file));
76
- const line = JSON.stringify({ ts: new Date().toISOString(), ...entry }) + "\n";
77
+ const line = JSON.stringify({ ts: fmtShanghaiYMDHMS(new Date()), ...entry }) + "\n";
77
78
  if (shouldSync(file)) {
78
79
  appendFileSync(file, line);
79
80
  trimIfOversized(file);
@@ -1,5 +1,6 @@
1
1
  import { appendFileSync, mkdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { fmtShanghaiYMDHMS } from "../../time.js";
3
4
 
4
5
  function defaultDirs() {
5
6
  const dirs = new Set();
@@ -22,7 +23,7 @@ export function appendRotationLog({ uid, model, totalMs, balanceHit, error, cloc
22
23
  const useFs = fsOverride || { appendFileSync, mkdirSync, readFileSync, writeFileSync, statSync, join };
23
24
  const useDirs = dirsOverride || defaultDirs();
24
25
  try {
25
- const line = `${new Date(clock()).toISOString()} uid=${uid} model=${model || "-"} totalMs=${totalMs} balanceHit=${balanceHit ? 1 : 0}${error ? ` error=${String(error).slice(0, 120)}` : ""}\n`;
26
+ const line = `${fmtShanghaiYMDHMS(new Date(clock()))} uid=${uid} model=${model || "-"} totalMs=${totalMs} balanceHit=${balanceHit ? 1 : 0}${error ? ` error=${String(error).slice(0, 120)}` : ""}\n`;
26
27
  for (const dir of useDirs) {
27
28
  try {
28
29
  useFs.mkdirSync(dir, { recursive: true });
@@ -1,11 +1,12 @@
1
1
  import { clientIp, json, readBody, errMsg } from "./helpers.js";
2
+ import { fmtShanghaiYMDHMS } from "../time.js";
2
3
 
3
4
  export async function joinHandler({ req, res, groups, token, bans }) {
4
5
  if (!groups) return json(res, 501, { error: "Groups service not configured" });
5
6
  const ip = clientIp(req);
6
7
  const banned = bans?.isBanned(ip);
7
8
  if (banned) {
8
- return json(res, 403, { error: `banned until ${new Date(banned.until).toISOString()}` });
9
+ return json(res, 403, { error: `banned until ${fmtShanghaiYMDHMS(banned.until)}` });
9
10
  }
10
11
  let body;
11
12
  try {
@@ -55,3 +55,4 @@ export {
55
55
  } from "./schemas/model.js";
56
56
  export { loadPeers, savePeers, loadPeerErrors, savePeerErrors, loadPeerStats, savePeerStats } from "./schemas/peer.js";
57
57
  export { loadGroups, loadGroupsJoined, saveGroupsJoined, loadBans, saveBans, saveGroups } from "./schemas/group.js";
58
+ export { loadTimezone, loadTimezoneState, saveTimezone, clearTimezone, getTimezoneEnv, DEFAULT_TZ, isValidTimezone } from "./schemas/timezone.js";
@@ -0,0 +1,70 @@
1
+ import { readState, writeStateImmediate, defaultStateFile } from "../store.js";
2
+
3
+ const DEFAULT_TZ = "Asia/Shanghai";
4
+ const ENV_KEYS = ["MSLXDFF_TZ", "MSLXDFF_TIMEZONE", "TZ"];
5
+
6
+ function isValidTimezone(tz) {
7
+ try {
8
+ new Intl.DateTimeFormat("en-GB", { timeZone: tz });
9
+ return true;
10
+ } catch { return false; }
11
+ }
12
+
13
+ export function getTimezoneEnv() {
14
+ for (const k of ENV_KEYS) {
15
+ const v = String(process.env[k] || "").trim();
16
+ if (v && isValidTimezone(v)) return v;
17
+ }
18
+ return "";
19
+ }
20
+
21
+ export function loadTimezone({ file } = {}) {
22
+ const env = getTimezoneEnv();
23
+ if (env) return env;
24
+ try {
25
+ const f = file || defaultStateFile();
26
+ const s = readState(f);
27
+ const v = String(s?.timezone || s?.tz || "").trim();
28
+ if (v && isValidTimezone(v)) return v;
29
+ } catch {}
30
+ return DEFAULT_TZ;
31
+ }
32
+
33
+ export function loadTimezoneState({ file } = {}) {
34
+ try {
35
+ const f = file || defaultStateFile();
36
+ const s = readState(f);
37
+ const v = String(s?.timezone || s?.tz || "").trim();
38
+ if (v && isValidTimezone(v)) return v;
39
+ } catch {}
40
+ return DEFAULT_TZ;
41
+ }
42
+
43
+ export function saveTimezone(tz, { file } = {}) {
44
+ const v = String(tz || "").trim();
45
+ if (!v) throw new Error("timezone 不能为空");
46
+ if (!isValidTimezone(v)) throw new Error(`无效时区: ${v}(示例: Asia/Shanghai, UTC, America/New_York)`);
47
+ const f = file || defaultStateFile();
48
+ const patch = { timezone: v };
49
+ // 兼容旧字段 tz
50
+ const cur = readState(f);
51
+ if (cur?.tz !== undefined) patch.tz = undefined;
52
+ return writeStateImmediate(f, patch);
53
+ }
54
+
55
+ export function clearTimezone({ file } = {}) {
56
+ const f = file || defaultStateFile();
57
+ const cur = readState(f);
58
+ const patch = {};
59
+ if (cur?.timezone !== undefined) patch.timezone = undefined;
60
+ if (cur?.tz !== undefined) patch.tz = undefined;
61
+ if (!Object.keys(patch).length) return cur;
62
+ // 通过写 undefined 触发 merge 覆盖?用直接删后写回
63
+ const next = { ...cur };
64
+ delete next.timezone;
65
+ delete next.tz;
66
+ writeStateImmediate(f, next);
67
+ return next;
68
+ }
69
+
70
+ export { DEFAULT_TZ, isValidTimezone };
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { defaultStateFile, tokenFile, generateToken, readState, writeStateImmediate, getEntry } from "../store.js";
4
+ import { fmtShanghaiYMDHMS } from "../../time.js";
4
5
 
5
6
  function syncTokenFile(token, file) {
6
7
  try {
@@ -29,13 +30,13 @@ export async function loadToken({ file = defaultStateFile() } = {}) {
29
30
  } catch {}
30
31
  }
31
32
  const tok = generateToken();
32
- const saved = writeStateImmediate(file, { token: tok, createdAt: new Date().toISOString() }).token;
33
+ const saved = writeStateImmediate(file, { token: tok, createdAt: fmtShanghaiYMDHMS(new Date()) }).token;
33
34
  syncTokenFile(saved, file);
34
35
  return { token: saved, created: true };
35
36
  }
36
37
 
37
38
  export async function refreshToken({ file = defaultStateFile() } = {}) {
38
- const tok = writeStateImmediate(file, { token: generateToken(), createdAt: new Date().toISOString() }).token;
39
+ const tok = writeStateImmediate(file, { token: generateToken(), createdAt: fmtShanghaiYMDHMS(new Date()) }).token;
39
40
  syncTokenFile(tok, file);
40
41
  return tok;
41
42
  }
package/src/time.js CHANGED
@@ -1,23 +1,50 @@
1
- const TZ = "Asia/Shanghai";
1
+ import { loadTimezone } from "./state/schemas/timezone.js";
2
2
 
3
- function shanghaiParts(d) {
3
+ const DEFAULT_TZ = "Asia/Shanghai";
4
+
5
+ function getTimezone() {
6
+ try { return loadTimezone() || DEFAULT_TZ; } catch { return DEFAULT_TZ; }
7
+ }
8
+
9
+ function tzParts(d, tz) {
10
+ const zone = tz || getTimezone();
4
11
  const date = d instanceof Date ? d : new Date(d);
5
- const fmt = new Intl.DateTimeFormat("en-GB", {
6
- timeZone: TZ,
7
- year: "numeric",
8
- month: "2-digit",
9
- day: "2-digit",
10
- hour: "2-digit",
11
- minute: "2-digit",
12
- second: "2-digit",
13
- hour12: false,
14
- });
15
- const parts = fmt.formatToParts(date);
16
- const m = {};
17
- for (const p of parts) m[p.type] = p.value;
18
- return m; // {year, month, day, hour, minute, second}
12
+ try {
13
+ const fmt = new Intl.DateTimeFormat("en-GB", {
14
+ timeZone: zone,
15
+ year: "numeric",
16
+ month: "2-digit",
17
+ day: "2-digit",
18
+ hour: "2-digit",
19
+ minute: "2-digit",
20
+ second: "2-digit",
21
+ hour12: false,
22
+ });
23
+ const parts = fmt.formatToParts(date);
24
+ const m = {};
25
+ for (const p of parts) m[p.type] = p.value;
26
+ return m; // {year, month, day, hour, minute, second}
27
+ } catch {
28
+ // 回退上海
29
+ const fmt = new Intl.DateTimeFormat("en-GB", {
30
+ timeZone: DEFAULT_TZ,
31
+ year: "numeric",
32
+ month: "2-digit",
33
+ day: "2-digit",
34
+ hour: "2-digit",
35
+ minute: "2-digit",
36
+ second: "2-digit",
37
+ hour12: false,
38
+ });
39
+ const parts = fmt.formatToParts(date);
40
+ const m = {};
41
+ for (const p of parts) m[p.type] = p.value;
42
+ return m;
43
+ }
19
44
  }
20
45
 
46
+ function shanghaiParts(d) { return tzParts(d, getTimezone()); }
47
+
21
48
  // "MM-DD HH:mm:ss" e.g. "08-27 15:07:14"
22
49
  export function fmtShanghai(isoOrTs) {
23
50
  if (isoOrTs == null) return "-";
@@ -71,3 +98,9 @@ export function nowShanghaiYMDHM() {
71
98
  export function fmtTsShanghai(iso) {
72
99
  return fmtShanghai(iso);
73
100
  }
101
+
102
+ export { getTimezone };
103
+ // 通用别名(实际已可配置,不再仅限上海)
104
+ export const fmtYMDHMS = fmtShanghaiYMDHMS;
105
+ export const fmtYMDHM = fmtShanghaiYMDHM;
106
+ export const fmtHMS = fmtShanghaiHMS;