mslxdff 0.1.68 → 0.1.70
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 +1 -1
- package/src/cli/commands/system.js +3 -2
- package/src/cli/commands/timezone.js +47 -0
- package/src/cli/index.js +2 -0
- package/src/logs.js +2 -1
- package/src/providers/workbuddy/rotation-log.js +2 -1
- package/src/routes/groups.js +2 -1
- package/src/runtime/bootstrap.js +6 -6
- package/src/state/facade.js +1 -0
- package/src/state/schemas/timezone.js +70 -0
- package/src/state/schemas/token.js +3 -2
- package/src/time.js +49 -16
package/package.json
CHANGED
|
@@ -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()
|
|
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()
|
|
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()
|
|
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())
|
|
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 });
|
package/src/routes/groups.js
CHANGED
|
@@ -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 ${
|
|
9
|
+
return json(res, 403, { error: `banned until ${fmtShanghaiYMDHMS(banned.until)}` });
|
|
9
10
|
}
|
|
10
11
|
let body;
|
|
11
12
|
try {
|
package/src/runtime/bootstrap.js
CHANGED
|
@@ -14,9 +14,9 @@ import { logDir, appendCall, appendError, appendEvent } from "../logs.js";
|
|
|
14
14
|
import { loadPlugins, runHook, resolvePluginDirs } from "../plugins.js";
|
|
15
15
|
import { createOpenCodeProvider } from "../providers/opencode.js";
|
|
16
16
|
import { loadProviderKeys, loadProviderAuths, loadProviderConfigs } from "../state.js";
|
|
17
|
-
import { effectiveHost, refreshIntervalMs, modelCooldownMs, slowCooldownMs, peerCooldownMs, peerHeatMs, maxHopsValue, groupSyncIntervalMs, autoUpdateIntervalMs, banWindowMs, banThreshold } from "
|
|
18
|
-
import { fmtEvent } from "
|
|
19
|
-
import { errMsg, npmCmd, run } from "
|
|
17
|
+
import { effectiveHost, refreshIntervalMs, modelCooldownMs, slowCooldownMs, peerCooldownMs, peerHeatMs, maxHopsValue, groupSyncIntervalMs, autoUpdateIntervalMs, banWindowMs, banThreshold } from "../cli/policy.js";
|
|
18
|
+
import { fmtEvent } from "../cli/format.js";
|
|
19
|
+
import { errMsg, npmCmd, run } from "../cli/util.js";
|
|
20
20
|
import { loadGroupsJoined } from "../state.js";
|
|
21
21
|
import { writePid } from "../daemon.js";
|
|
22
22
|
import { resolvePort } from "../server.js";
|
|
@@ -230,7 +230,7 @@ export async function startDaemonMain(VERSION) {
|
|
|
230
230
|
} catch {}
|
|
231
231
|
|
|
232
232
|
// group sync
|
|
233
|
-
const { syncAllJoinedGroups } = await import("
|
|
233
|
+
const { syncAllJoinedGroups } = await import("../cli/group-helpers.js");
|
|
234
234
|
syncAllJoinedGroups({ peers, groups })
|
|
235
235
|
.then((results) => {
|
|
236
236
|
for (const r of results) {
|
|
@@ -379,7 +379,7 @@ export async function startDaemonMain(VERSION) {
|
|
|
379
379
|
emitAutoUpdate("auto-update-noop", { current: VERSION, latest });
|
|
380
380
|
return;
|
|
381
381
|
}
|
|
382
|
-
const { compareSemver } = await import("
|
|
382
|
+
const { compareSemver } = await import("../cli/policy.js");
|
|
383
383
|
if (compareSemver(latest, VERSION) <= 0) {
|
|
384
384
|
emitAutoUpdate("auto-update-noop", { current: VERSION, latest, reason: "not newer" });
|
|
385
385
|
return;
|
|
@@ -397,7 +397,7 @@ export async function startDaemonMain(VERSION) {
|
|
|
397
397
|
emitAutoUpdate("auto-update-restarting", { current: VERSION, latest });
|
|
398
398
|
const { stopDaemon, startDaemon } = await import("../daemon.js");
|
|
399
399
|
try { stopDaemon(); } catch (e) { emitAutoUpdate("auto-update-stop-failed", { error: errMsg(e) }); }
|
|
400
|
-
const { waitForHealth } = await import("
|
|
400
|
+
const { waitForHealth } = await import("../cli/policy.js");
|
|
401
401
|
const newPid = startDaemon([]);
|
|
402
402
|
await waitForHealth(resolvePort(), 8000);
|
|
403
403
|
console.log(`auto-update: restarted as v${latest} (pid ${newPid})`);
|
package/src/state/facade.js
CHANGED
|
@@ -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()
|
|
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()
|
|
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
|
-
|
|
1
|
+
import { loadTimezone } from "./state/schemas/timezone.js";
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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;
|