koishi-plugin-hhs-huatu 1.3.13 → 1.3.14
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.js +105 -350
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1352,7 +1352,6 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
1352
1352
|
]).description("点数模式").default("periodic"),
|
|
1353
1353
|
pointsDefault: import_koishi2.Schema.number().description("会员默认初始点数").default(200),
|
|
1354
1354
|
pointsRefreshCycleDays: import_koishi2.Schema.number().description("刷新周期(天数),默认每30天刷新一次").default(30).min(1).max(365),
|
|
1355
|
-
pointsRefreshCheckTime: import_koishi2.Schema.string().description("每天检查并执行刷新的时刻(HH:MM,24小时制,本地时区)。实际刷新发生在「周期到期后的第一个该时刻」。").default("00:05"),
|
|
1356
1355
|
pointsRefreshAmount: import_koishi2.Schema.number().description("每次刷新恢复的点数").default(200),
|
|
1357
1356
|
pointsRefreshIncludeNonMember: import_koishi2.Schema.boolean().description("刷新范围是否包括非会员").default(false)
|
|
1358
1357
|
}).description("点数控制设置"),
|
|
@@ -1934,7 +1933,6 @@ __name(formatOpusQuota, "formatOpusQuota");
|
|
|
1934
1933
|
// src/membershipSystem.ts
|
|
1935
1934
|
var MIN_TIER = 1;
|
|
1936
1935
|
var MAX_TIER = 5;
|
|
1937
|
-
var POINTS_REFRESH_TOLERANCE = 1e3;
|
|
1938
1936
|
var MembershipSystem = class {
|
|
1939
1937
|
constructor(ctx, config) {
|
|
1940
1938
|
this.ctx = ctx;
|
|
@@ -1944,15 +1942,13 @@ var MembershipSystem = class {
|
|
|
1944
1942
|
this.loadUserDataFromDB();
|
|
1945
1943
|
this.setupCleanupTask();
|
|
1946
1944
|
this.setupReminderTask();
|
|
1947
|
-
|
|
1948
|
-
ctx.accept(["membershipEnabled", "memberCleanupEnabled", "memberCleanupTime", "memberExpiryReminderEnabled", "memberReminderTime", "memberReminderHours", "pointsEnabled", "pointsMode", "pointsRefreshCycleDays", "pointsRefreshCheckTime"], () => {
|
|
1945
|
+
ctx.accept(["membershipEnabled", "memberCleanupEnabled", "memberCleanupTime", "memberExpiryReminderEnabled", "memberReminderTime", "memberReminderHours"], () => {
|
|
1949
1946
|
ctx.logger.info("会员系统配置已更新,重新安排定时任务");
|
|
1950
1947
|
if (this.config.membershipEnabled && Object.keys(this.userData).length === 0) {
|
|
1951
1948
|
this.loadUserDataFromDB().catch((err) => this.ctx.logger.error("动态加载用户数据失败", err));
|
|
1952
1949
|
}
|
|
1953
1950
|
this.setupCleanupTask();
|
|
1954
1951
|
this.setupReminderTask();
|
|
1955
|
-
this.setupPointsRefreshTask();
|
|
1956
1952
|
});
|
|
1957
1953
|
}
|
|
1958
1954
|
static {
|
|
@@ -1965,14 +1961,9 @@ var MembershipSystem = class {
|
|
|
1965
1961
|
// 定时任务取消函数
|
|
1966
1962
|
cleanupTimerDispose = null;
|
|
1967
1963
|
reminderTimerDispose = null;
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
pointsRefreshAnchor = 0;
|
|
1972
|
-
// 上次点数刷新的**实际**执行时刻(事实记录):手动刷新、停机补刷时可能与锚点不同。
|
|
1973
|
-
pointsRefreshLastActualAt = 0;
|
|
1974
|
-
// 库中记录仍是旧语义(lastUsed = 实际刷新时刻),加载后需要回写一次
|
|
1975
|
-
pointsRefreshStateNeedsMigration = false;
|
|
1964
|
+
// 上一版本的全局刷新时间,仅用于迁移旧数据
|
|
1965
|
+
legacyPointsRefreshTime = 0;
|
|
1966
|
+
pointsRefreshLocks = /* @__PURE__ */ new Map();
|
|
1976
1967
|
// 确保导入文件夹存在
|
|
1977
1968
|
ensureImportDir() {
|
|
1978
1969
|
try {
|
|
@@ -2000,6 +1991,8 @@ var MembershipSystem = class {
|
|
|
2000
1991
|
dailyLimit: "unsigned",
|
|
2001
1992
|
lastDrawTime: "unsigned",
|
|
2002
1993
|
points: "integer",
|
|
1994
|
+
pointsRefreshAt: "unsigned",
|
|
1995
|
+
pointsLastRefreshedAt: "unsigned",
|
|
2003
1996
|
nai5DailyUsage: "unsigned",
|
|
2004
1997
|
nai5Bucket: "unsigned",
|
|
2005
1998
|
nai5BucketDay: "unsigned",
|
|
@@ -2030,10 +2023,7 @@ var MembershipSystem = class {
|
|
|
2030
2023
|
let loadedCount = 0;
|
|
2031
2024
|
for (const row of rows) {
|
|
2032
2025
|
if (row.visitorId === "_system_points_refresh") {
|
|
2033
|
-
|
|
2034
|
-
this.pointsRefreshAnchor = actualAt > 0 ? this.alignToCheck(actualAt - POINTS_REFRESH_TOLERANCE) : 0;
|
|
2035
|
-
this.pointsRefreshLastActualAt = row.lastDrawTime || actualAt || 0;
|
|
2036
|
-
this.pointsRefreshStateNeedsMigration = (row.lastUsed || 0) !== this.pointsRefreshAnchor || (row.lastDrawTime || 0) !== this.pointsRefreshLastActualAt;
|
|
2026
|
+
this.legacyPointsRefreshTime = row.lastUsed || 0;
|
|
2037
2027
|
continue;
|
|
2038
2028
|
}
|
|
2039
2029
|
loadedCount++;
|
|
@@ -2045,6 +2035,8 @@ var MembershipSystem = class {
|
|
|
2045
2035
|
dailyLimit: row.dailyLimit,
|
|
2046
2036
|
lastDrawTime: row.lastDrawTime,
|
|
2047
2037
|
points: row.points,
|
|
2038
|
+
pointsRefreshAt: row.pointsRefreshAt || 0,
|
|
2039
|
+
pointsLastRefreshedAt: row.pointsLastRefreshedAt || 0,
|
|
2048
2040
|
nai5DailyUsage: row.nai5DailyUsage || 0,
|
|
2049
2041
|
nai5Bucket: row.nai5Bucket || 0,
|
|
2050
2042
|
nai5BucketDay: row.nai5BucketDay || 0,
|
|
@@ -2054,19 +2046,7 @@ var MembershipSystem = class {
|
|
|
2054
2046
|
this.ctx.logger.info(`会员系统数据从数据库加载成功,共 ${loadedCount} 条记录`);
|
|
2055
2047
|
await this.loadCardsFromDB();
|
|
2056
2048
|
await this.migrateLegacyMembers();
|
|
2057
|
-
|
|
2058
|
-
await this.checkAndCatchUpPointsRefresh();
|
|
2059
|
-
} catch (err) {
|
|
2060
|
-
this.ctx.logger.error("点数刷新补刷失败", err);
|
|
2061
|
-
}
|
|
2062
|
-
if (this.config.pointsEnabled && this.config.pointsMode === "periodic") {
|
|
2063
|
-
if (this.pointsRefreshStateNeedsMigration) {
|
|
2064
|
-
await this.savePointsRefreshState();
|
|
2065
|
-
this.pointsRefreshStateNeedsMigration = false;
|
|
2066
|
-
this.ctx.logger.info(`点数刷新状态已迁移为周期锚点制:锚点 ${this.formatRefreshAnchor()},上次实际刷新 ${this.formatLastRefreshTime()}`);
|
|
2067
|
-
}
|
|
2068
|
-
this.ctx.logger.info(`点数刷新计划:周期锚点 ${this.formatRefreshAnchor()},上次实际刷新 ${this.formatLastRefreshTime()},下次刷新 ${this.formatNextRefreshTime()}(${this.getDaysUntilNextRefresh()} 天后)`);
|
|
2069
|
-
}
|
|
2049
|
+
await this.migratePointsRefreshPlans();
|
|
2070
2050
|
} catch (err) {
|
|
2071
2051
|
this.ctx.logger.error("从数据库加载会员系统数据失败", err);
|
|
2072
2052
|
}
|
|
@@ -2115,7 +2095,24 @@ var MembershipSystem = class {
|
|
|
2115
2095
|
this.ctx.logger.info(`会员等级迁移完成:已为 ${migrated} 位存量会员生成 Lv1 会员卡`);
|
|
2116
2096
|
}
|
|
2117
2097
|
}
|
|
2118
|
-
|
|
2098
|
+
/**
|
|
2099
|
+
* 将旧版全局刷新时间迁移为用户级刷新计划。
|
|
2100
|
+
* 迁移只补字段,不改变当前点数、会员卡或使用记录。
|
|
2101
|
+
*/
|
|
2102
|
+
async migratePointsRefreshPlans() {
|
|
2103
|
+
if (!this.legacyPointsRefreshTime || this.config.pointsMode !== "periodic") return;
|
|
2104
|
+
const cycleMs = (this.config.pointsRefreshCycleDays || 30) * 24 * 60 * 60 * 1e3;
|
|
2105
|
+
let changed = false;
|
|
2106
|
+
for (const userId in this.userData) {
|
|
2107
|
+
const user = this.userData[userId];
|
|
2108
|
+
const tier = this.computeActiveTier(user.cards || []);
|
|
2109
|
+
const isInScope = tier > 0 || this.config.pointsRefreshIncludeNonMember;
|
|
2110
|
+
if (!isInScope || user.pointsRefreshAt) continue;
|
|
2111
|
+
user.pointsRefreshAt = this.legacyPointsRefreshTime + cycleMs;
|
|
2112
|
+
changed = true;
|
|
2113
|
+
}
|
|
2114
|
+
if (changed) await this.saveUserData();
|
|
2115
|
+
}
|
|
2119
2116
|
async syncCardsToDB(userId) {
|
|
2120
2117
|
try {
|
|
2121
2118
|
const user = this.userData[userId];
|
|
@@ -2155,25 +2152,9 @@ var MembershipSystem = class {
|
|
|
2155
2152
|
this.ctx.logger.error("批量保存会员卡数据失败", err);
|
|
2156
2153
|
}
|
|
2157
2154
|
}
|
|
2158
|
-
|
|
2159
|
-
async
|
|
2160
|
-
|
|
2161
|
-
await this.ctx.database.upsert("hhs_huatu_user", [{
|
|
2162
|
-
visitorId: "_system_points_refresh",
|
|
2163
|
-
isMember: false,
|
|
2164
|
-
membershipExpiry: 0,
|
|
2165
|
-
dailyUsage: 0,
|
|
2166
|
-
// lastUsed 在该行语义 = 周期锚点(对齐到检查点)
|
|
2167
|
-
lastUsed: this.pointsRefreshAnchor,
|
|
2168
|
-
dailyLimit: 0,
|
|
2169
|
-
// lastDrawTime 在该行语义 = 上次实际刷新时刻
|
|
2170
|
-
lastDrawTime: this.pointsRefreshLastActualAt,
|
|
2171
|
-
points: 0,
|
|
2172
|
-
nai5DailyUsage: 0
|
|
2173
|
-
}], ["visitorId"]);
|
|
2174
|
-
} catch (err) {
|
|
2175
|
-
this.ctx.logger.error("保存点数刷新状态失败", err);
|
|
2176
|
-
}
|
|
2155
|
+
/** 旧版全局刷新记录仅保留用于读取迁移,不再写入。 */
|
|
2156
|
+
async saveLastPointsRefreshTime() {
|
|
2157
|
+
return;
|
|
2177
2158
|
}
|
|
2178
2159
|
// 将单个用户数据同步到数据库
|
|
2179
2160
|
async syncUserToDB(userId) {
|
|
@@ -2189,6 +2170,8 @@ var MembershipSystem = class {
|
|
|
2189
2170
|
dailyLimit: user.dailyLimit,
|
|
2190
2171
|
lastDrawTime: user.lastDrawTime || 0,
|
|
2191
2172
|
points: user.points || 0,
|
|
2173
|
+
pointsRefreshAt: user.pointsRefreshAt || 0,
|
|
2174
|
+
pointsLastRefreshedAt: user.pointsLastRefreshedAt || 0,
|
|
2192
2175
|
nai5DailyUsage: user.nai5DailyUsage || 0,
|
|
2193
2176
|
nai5Bucket: user.nai5Bucket || 0,
|
|
2194
2177
|
nai5BucketDay: user.nai5BucketDay || 0,
|
|
@@ -2214,6 +2197,8 @@ var MembershipSystem = class {
|
|
|
2214
2197
|
dailyLimit: user.dailyLimit,
|
|
2215
2198
|
lastDrawTime: user.lastDrawTime || 0,
|
|
2216
2199
|
points: user.points || 0,
|
|
2200
|
+
pointsRefreshAt: user.pointsRefreshAt || 0,
|
|
2201
|
+
pointsLastRefreshedAt: user.pointsLastRefreshedAt || 0,
|
|
2217
2202
|
nai5DailyUsage: user.nai5DailyUsage || 0,
|
|
2218
2203
|
nai5Bucket: user.nai5Bucket || 0,
|
|
2219
2204
|
nai5BucketDay: user.nai5BucketDay || 0,
|
|
@@ -2385,6 +2370,7 @@ var MembershipSystem = class {
|
|
|
2385
2370
|
if (this.config.pointsMode === "periodic") {
|
|
2386
2371
|
const refreshAmount = this.getTierBenefit(safeTier).pointsRefresh;
|
|
2387
2372
|
user.points = refreshAmount;
|
|
2373
|
+
user.pointsRefreshAt = now + (this.config.pointsRefreshCycleDays || 30) * 24 * 60 * 60 * 1e3;
|
|
2388
2374
|
this.ctx.logger.info(`[会员系统] 用户 ${userId} 首次成为 Lv${safeTier} 会员,点数已初始化为 ${refreshAmount}`);
|
|
2389
2375
|
} else if (!user.points) {
|
|
2390
2376
|
user.points = this.config.pointsDefault || 200;
|
|
@@ -2510,6 +2496,7 @@ var MembershipSystem = class {
|
|
|
2510
2496
|
async deductPoints(userId, amount) {
|
|
2511
2497
|
if (!this.config.pointsEnabled || amount <= 0) return 0;
|
|
2512
2498
|
this.ensureUserData(userId);
|
|
2499
|
+
await this.ensurePointsFresh(userId);
|
|
2513
2500
|
const user = this.userData[userId];
|
|
2514
2501
|
const currentPoints = user.points || 0;
|
|
2515
2502
|
if (currentPoints < amount) {
|
|
@@ -2525,6 +2512,7 @@ var MembershipSystem = class {
|
|
|
2525
2512
|
async refundPoints(userId, amount) {
|
|
2526
2513
|
if (!this.config.pointsEnabled || amount <= 0) return;
|
|
2527
2514
|
this.ensureUserData(userId);
|
|
2515
|
+
await this.ensurePointsFresh(userId);
|
|
2528
2516
|
const user = this.userData[userId];
|
|
2529
2517
|
user.points = (user.points || 0) + amount;
|
|
2530
2518
|
await this.syncUserToDB(userId);
|
|
@@ -2547,6 +2535,7 @@ var MembershipSystem = class {
|
|
|
2547
2535
|
let updatedCount = 0;
|
|
2548
2536
|
const now = Date.now();
|
|
2549
2537
|
for (const userId in this.userData) {
|
|
2538
|
+
await this.ensurePointsFresh(userId);
|
|
2550
2539
|
const user = this.userData[userId];
|
|
2551
2540
|
if (membersOnly && (!user.isMember || user.membershipExpiry < now)) {
|
|
2552
2541
|
continue;
|
|
@@ -2565,245 +2554,70 @@ var MembershipSystem = class {
|
|
|
2565
2554
|
const message = updatedCount > 0 ? `✅ 成功为 ${updatedCount} 位用户${action} ${Math.abs(amount)} 点数` : `⚠️ 没有符合条件的用户`;
|
|
2566
2555
|
return { count: updatedCount, message };
|
|
2567
2556
|
}
|
|
2568
|
-
/**
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2557
|
+
/** 确保用户在读取或扣除点数前完成到期的惰性刷新。 */
|
|
2558
|
+
async ensurePointsFresh(userId) {
|
|
2559
|
+
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") return;
|
|
2560
|
+
this.ensureUserData(userId);
|
|
2561
|
+
const running = this.pointsRefreshLocks.get(userId);
|
|
2562
|
+
if (running) return running;
|
|
2563
|
+
const task = this.refreshUserPointsIfDue(userId).finally(() => {
|
|
2564
|
+
if (this.pointsRefreshLocks.get(userId) === task) this.pointsRefreshLocks.delete(userId);
|
|
2565
|
+
});
|
|
2566
|
+
this.pointsRefreshLocks.set(userId, task);
|
|
2567
|
+
return task;
|
|
2568
|
+
}
|
|
2569
|
+
async refreshUserPointsIfDue(userId) {
|
|
2570
|
+
const user = this.userData[userId];
|
|
2571
|
+
const now = Date.now();
|
|
2572
|
+
const cycleMs = (this.config.pointsRefreshCycleDays || 30) * 24 * 60 * 60 * 1e3;
|
|
2573
|
+
const tier = this.getActiveTier(userId);
|
|
2574
|
+
const inScope = tier > 0 || this.config.pointsRefreshIncludeNonMember;
|
|
2575
|
+
if (!user.pointsRefreshAt) {
|
|
2576
|
+
if (!inScope) return;
|
|
2577
|
+
user.pointsRefreshAt = now + cycleMs;
|
|
2578
|
+
await this.syncUserToDB(userId);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
if (user.pointsRefreshAt > now || !inScope) return;
|
|
2582
|
+
const plannedAt = user.pointsRefreshAt;
|
|
2583
|
+
user.points = this.getTierBenefit(tier > 0 ? tier : 1).pointsRefresh;
|
|
2584
|
+
user.pointsLastRefreshedAt = now;
|
|
2585
|
+
do {
|
|
2586
|
+
user.pointsRefreshAt += cycleMs;
|
|
2587
|
+
} while (user.pointsRefreshAt <= now);
|
|
2588
|
+
await this.syncUserToDB(userId);
|
|
2589
|
+
this.ctx.logger.info(`[会员系统] 用户 ${userId} 惰性刷新点数:${plannedAt} → ${user.pointsRefreshAt}`);
|
|
2590
|
+
}
|
|
2591
|
+
/** 手动刷新点数,不改变用户自动刷新计划。 */
|
|
2574
2592
|
async refreshPoints(tierFilter) {
|
|
2575
2593
|
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") {
|
|
2576
2594
|
return { count: 0, message: "点数刷新未启用或不是周期模式" };
|
|
2577
2595
|
}
|
|
2578
2596
|
let refreshedCount = 0;
|
|
2579
|
-
const now = Date.now();
|
|
2580
2597
|
for (const userId in this.userData) {
|
|
2581
|
-
const
|
|
2582
|
-
|
|
2583
|
-
if (tier > 1 && this.config.tierEnabled === false) tier = 1;
|
|
2584
|
-
const isActive = tier > 0;
|
|
2585
|
-
if (!this.config.pointsRefreshIncludeNonMember && !isActive) {
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2598
|
+
const tier = this.getActiveTier(userId);
|
|
2599
|
+
if (tier <= 0 && !this.config.pointsRefreshIncludeNonMember) continue;
|
|
2588
2600
|
if (tierFilter !== void 0 && tier !== tierFilter) continue;
|
|
2601
|
+
await this.ensurePointsFresh(userId);
|
|
2602
|
+
const user = this.userData[userId];
|
|
2589
2603
|
user.points = this.getTierBenefit(tier > 0 ? tier : 1).pointsRefresh;
|
|
2604
|
+
await this.syncUserToDB(userId);
|
|
2590
2605
|
refreshedCount++;
|
|
2591
2606
|
}
|
|
2592
|
-
this.pointsRefreshLastActualAt = now;
|
|
2593
|
-
await this.savePointsRefreshState();
|
|
2594
|
-
if (refreshedCount > 0) {
|
|
2595
|
-
await this.saveUserData();
|
|
2596
|
-
}
|
|
2597
2607
|
const scope = tierFilter !== void 0 ? `Lv${tierFilter} 会员` : "用户";
|
|
2598
|
-
const message = refreshedCount > 0 ? `✅
|
|
2608
|
+
const message = refreshedCount > 0 ? `✅ 已手动刷新 ${refreshedCount} 位${scope}的点数,自动刷新计划未改变` : "⚠️ 没有符合条件的用户需要刷新点数";
|
|
2599
2609
|
this.ctx.logger.info(message);
|
|
2600
2610
|
return { count: refreshedCount, message };
|
|
2601
2611
|
}
|
|
2602
|
-
|
|
2603
|
-
/** 周期毫秒数 */
|
|
2604
|
-
getPointsRefreshCycleMs() {
|
|
2605
|
-
return (this.config.pointsRefreshCycleDays || 30) * 24 * 60 * 60 * 1e3;
|
|
2606
|
-
}
|
|
2607
|
-
/** 每日检查时刻(HH:MM,默认 00:05);配置非法时回落 00:05 */
|
|
2608
|
-
getCheckTimeParts() {
|
|
2609
|
-
const raw = String(this.config.pointsRefreshCheckTime || "00:05").trim();
|
|
2610
|
-
const m = raw.match(/^(\d{1,2}):(\d{1,2})$/);
|
|
2611
|
-
if (!m) return { hour: 0, minute: 5 };
|
|
2612
|
-
const hour = Number(m[1]);
|
|
2613
|
-
const minute = Number(m[2]);
|
|
2614
|
-
if (!Number.isInteger(hour) || !Number.isInteger(minute) || hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
|
2615
|
-
return { hour: 0, minute: 5 };
|
|
2616
|
-
}
|
|
2617
|
-
return { hour, minute };
|
|
2618
|
-
}
|
|
2619
|
-
/** 指定日期(本地时区)的检查点时刻 */
|
|
2620
|
-
checkPointOfDay(timestamp) {
|
|
2621
|
-
const { hour, minute } = this.getCheckTimeParts();
|
|
2622
|
-
const d = new Date(timestamp);
|
|
2623
|
-
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), hour, minute, 0, 0).getTime();
|
|
2624
|
-
}
|
|
2625
|
-
/** 首个 ≥ timestamp 的检查点 */
|
|
2626
|
-
alignToCheck(timestamp) {
|
|
2627
|
-
const check = this.checkPointOfDay(timestamp);
|
|
2628
|
-
return check < timestamp ? check + 24 * 60 * 60 * 1e3 : check;
|
|
2629
|
-
}
|
|
2630
|
-
/** 最近一个 ≤ timestamp 的检查点 */
|
|
2631
|
-
lastCheckPointAtOrBefore(timestamp) {
|
|
2632
|
-
const check = this.checkPointOfDay(timestamp);
|
|
2633
|
-
return check > timestamp ? check - 24 * 60 * 60 * 1e3 : check;
|
|
2634
|
-
}
|
|
2635
|
-
/**
|
|
2636
|
-
* 下次刷新的**计划**时刻(毫秒);0 表示尚未初始化(首次运行,将在下一个检查点刷新)。
|
|
2637
|
-
* 计划时刻可能已经过去(例如停机漏检、或运维把锚点设到了过去),此时由 isPointsRefreshOverdue() 反映。
|
|
2638
|
-
*/
|
|
2639
|
-
getPointsRefreshPlan() {
|
|
2640
|
-
if (this.pointsRefreshAnchor === 0) return 0;
|
|
2641
|
-
return this.alignToCheck(this.pointsRefreshAnchor + this.getPointsRefreshCycleMs());
|
|
2642
|
-
}
|
|
2643
|
-
/**
|
|
2644
|
-
* 下次刷新**实际**会发生的时刻(毫秒时间戳);-1 表示未启用周期刷新。
|
|
2645
|
-
* 展示(会员 -u / 会员调试 -s)与执行判定都以这里为准:
|
|
2646
|
-
* - 尚未初始化(锚点 0)→ 下一个检查点(首次刷新)
|
|
2647
|
-
* - 计划时刻还没到 → 计划时刻
|
|
2648
|
-
* - 计划已过(停机漏检 / 手动把锚点设到过去)→ 下一个检查点(届时会补刷)
|
|
2649
|
-
*/
|
|
2650
|
-
getNextRefreshTime() {
|
|
2612
|
+
getNextPointsRefreshAt(userId) {
|
|
2651
2613
|
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") return -1;
|
|
2652
|
-
|
|
2653
|
-
const plan = this.getPointsRefreshPlan();
|
|
2654
|
-
if (plan === 0) return this.alignToCheck(now);
|
|
2655
|
-
return plan <= now + POINTS_REFRESH_TOLERANCE ? this.alignToCheck(now + POINTS_REFRESH_TOLERANCE) : plan;
|
|
2656
|
-
}
|
|
2657
|
-
/** 是否已逾期(计划刷新时刻已过但尚未刷新):仅在停机漏检或手动把锚点设到过去时成立 */
|
|
2658
|
-
isPointsRefreshOverdue() {
|
|
2659
|
-
const plan = this.getPointsRefreshPlan();
|
|
2660
|
-
if (plan === 0) return false;
|
|
2661
|
-
return Date.now() + POINTS_REFRESH_TOLERANCE >= plan;
|
|
2662
|
-
}
|
|
2663
|
-
// 把时间戳格式化为本地时区的 YYYY-MM-DD HH:mm(不依赖 locale,输出稳定)
|
|
2664
|
-
formatTimeValue(timestamp) {
|
|
2665
|
-
const d = new Date(timestamp);
|
|
2666
|
-
const p = /* @__PURE__ */ __name((n) => String(n).padStart(2, "0"), "p");
|
|
2667
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
2668
|
-
}
|
|
2669
|
-
/** 下次刷新时刻的展示文本(如 2026-11-05 00:05);未启用周期刷新时返回空串 */
|
|
2670
|
-
formatNextRefreshTime() {
|
|
2671
|
-
const t = this.getNextRefreshTime();
|
|
2672
|
-
return t < 0 ? "" : this.formatTimeValue(t);
|
|
2673
|
-
}
|
|
2674
|
-
/** 上次**实际**刷新时刻的展示文本;从未刷新过时返回「尚未记录」 */
|
|
2675
|
-
formatLastRefreshTime() {
|
|
2676
|
-
return this.pointsRefreshLastActualAt === 0 ? "尚未记录" : this.formatTimeValue(this.pointsRefreshLastActualAt);
|
|
2677
|
-
}
|
|
2678
|
-
/** 周期锚点的展示文本;尚未初始化时返回「尚未初始化」 */
|
|
2679
|
-
formatRefreshAnchor() {
|
|
2680
|
-
return this.pointsRefreshAnchor === 0 ? "尚未初始化" : this.formatTimeValue(this.pointsRefreshAnchor);
|
|
2681
|
-
}
|
|
2682
|
-
/** 下次刷新**计划**时刻的展示文本(可能已过去);尚未初始化时返回「尚未初始化」 */
|
|
2683
|
-
formatRefreshPlanTime() {
|
|
2684
|
-
const plan = this.getPointsRefreshPlan();
|
|
2685
|
-
return plan === 0 ? "尚未初始化" : this.formatTimeValue(plan);
|
|
2686
|
-
}
|
|
2687
|
-
/** 距离下次刷新的天数(用于「N 天后」文案) */
|
|
2688
|
-
getDaysUntilNextRefresh() {
|
|
2689
|
-
const next = this.getNextRefreshTime();
|
|
2690
|
-
if (next < 0) return -1;
|
|
2691
|
-
return Math.max(0, Math.ceil((next - Date.now()) / (24 * 60 * 60 * 1e3)));
|
|
2692
|
-
}
|
|
2693
|
-
/**
|
|
2694
|
-
* 刷新完成后推进周期锚点:锚点 = 本次刷新所属的检查点(取本次刷新时刻附近的检查点),
|
|
2695
|
-
* 而不是"实际执行时刻"。因此:
|
|
2696
|
-
* - 正常在检查点触发 → 锚点就是该检查点,周期保持对齐;
|
|
2697
|
-
* - 停机补刷(白天启动)→ 锚点仍是当天的检查点,周期不会漂移到白天;
|
|
2698
|
-
* - 定时器早/晚几十毫秒触发 → 仍归到同一个检查点,不会退/进一天。
|
|
2699
|
-
*/
|
|
2700
|
-
advanceAnchorAfterRefresh() {
|
|
2701
|
-
this.pointsRefreshAnchor = this.lastCheckPointAtOrBefore(Date.now() + POINTS_REFRESH_TOLERANCE);
|
|
2702
|
-
}
|
|
2703
|
-
/**
|
|
2704
|
-
* 停机漏检补刷(S1):启动加载完成后若计划时刻已过(说明实例在检查点离线),立即补刷一次,
|
|
2705
|
-
* 并把锚点推进到"本次补刷所属的检查点"。返回是否执行了补刷。
|
|
2706
|
-
*/
|
|
2707
|
-
async checkAndCatchUpPointsRefresh() {
|
|
2708
|
-
if (!this.config.membershipEnabled || !this.config.pointsEnabled || this.config.pointsMode !== "periodic") return false;
|
|
2709
|
-
if (!this.isPointsRefreshOverdue()) return false;
|
|
2710
|
-
const plan = this.getPointsRefreshPlan();
|
|
2711
|
-
this.ctx.logger.info(`检测到点数刷新计划已逾期(计划 ${this.formatTimeValue(plan)},当前 ${this.formatTimeValue(Date.now())}),正在补刷...`);
|
|
2712
|
-
const result = await this.refreshPoints();
|
|
2713
|
-
this.advanceAnchorAfterRefresh();
|
|
2714
|
-
await this.savePointsRefreshState();
|
|
2715
|
-
this.ctx.logger.info(`点数刷新补刷完成:${result.message};周期锚点推进至 ${this.formatTimeValue(this.pointsRefreshAnchor)},下次刷新 ${this.formatTimeValue(this.getNextRefreshTime())}`);
|
|
2716
|
-
return true;
|
|
2717
|
-
}
|
|
2718
|
-
// ========== 点数刷新:运维接口(查看 / 设置锚点与计划时刻,均不触发刷新) ==========
|
|
2719
|
-
/** 周期锚点时间戳(0 = 尚未初始化) */
|
|
2720
|
-
getPointsRefreshAnchor() {
|
|
2721
|
-
return this.pointsRefreshAnchor;
|
|
2722
|
-
}
|
|
2723
|
-
/** 下次刷新的计划时刻(可能已过);0 = 尚未初始化 */
|
|
2724
|
-
getPointsRefreshPlanTime() {
|
|
2725
|
-
return this.getPointsRefreshPlan();
|
|
2726
|
-
}
|
|
2727
|
-
/** 上次实际刷新时刻 */
|
|
2728
|
-
getPointsRefreshLastActualAt() {
|
|
2729
|
-
return this.pointsRefreshLastActualAt;
|
|
2730
|
-
}
|
|
2731
|
-
/**
|
|
2732
|
-
* 解析运维输入的时间:
|
|
2733
|
-
* - `now`:当前时刻
|
|
2734
|
-
* - `+Nd` / `+Nh` / `+Nm`:相对当前时刻 N 天/小时/分钟
|
|
2735
|
-
* - `YYYY-MM-DD`:该日期(dateOnly=true,调用方按"当天的检查点"解释)
|
|
2736
|
-
* - `YYYY-MM-DD HH:mm` / `YYYY-MM-DDTHH:mm`:精确到分
|
|
2737
|
-
* 解析失败返回 null。
|
|
2738
|
-
*/
|
|
2739
|
-
parseRefreshTimeInput(input) {
|
|
2740
|
-
const s = String(input ?? "").trim();
|
|
2741
|
-
if (!s) return null;
|
|
2742
|
-
const now = Date.now();
|
|
2743
|
-
if (s === "now" || s === "现在") return { timestamp: now, dateOnly: false };
|
|
2744
|
-
const rel = s.match(/^\+(\d+)([dhm])$/i);
|
|
2745
|
-
if (rel) {
|
|
2746
|
-
const n = Number(rel[1]);
|
|
2747
|
-
const unit = rel[2].toLowerCase();
|
|
2748
|
-
const unitMs = unit === "d" ? 864e5 : unit === "h" ? 36e5 : 6e4;
|
|
2749
|
-
return { timestamp: now + n * unitMs, dateOnly: false };
|
|
2750
|
-
}
|
|
2751
|
-
const abs = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{1,2}))?$/);
|
|
2752
|
-
if (abs) {
|
|
2753
|
-
const year = Number(abs[1]);
|
|
2754
|
-
const month = Number(abs[2]);
|
|
2755
|
-
const day = Number(abs[3]);
|
|
2756
|
-
const hour = abs[4] ? Number(abs[4]) : 0;
|
|
2757
|
-
const minute = abs[5] ? Number(abs[5]) : 0;
|
|
2758
|
-
if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) return null;
|
|
2759
|
-
const d = new Date(year, month - 1, day, hour, minute, 0, 0);
|
|
2760
|
-
if (d.getFullYear() !== year || d.getMonth() !== month - 1 || d.getDate() !== day) return null;
|
|
2761
|
-
return { timestamp: d.getTime(), dateOnly: !abs[4] };
|
|
2762
|
-
}
|
|
2763
|
-
return null;
|
|
2614
|
+
return this.userData[userId]?.pointsRefreshAt || 0;
|
|
2764
2615
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
async setPointsRefreshAnchor(input) {
|
|
2771
|
-
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") return "❌ 未启用周期刷新,无法设置周期锚点";
|
|
2772
|
-
const parsed = this.parseRefreshTimeInput(input);
|
|
2773
|
-
if (!parsed) return `❌ 时间格式无法识别:${input}(支持 now、+5d、2026-11-01、2026-11-01 00:05)`;
|
|
2774
|
-
const anchor = parsed.dateOnly ? this.checkPointOfDay(parsed.timestamp) : this.lastCheckPointAtOrBefore(parsed.timestamp);
|
|
2775
|
-
this.pointsRefreshAnchor = anchor;
|
|
2776
|
-
await this.savePointsRefreshState();
|
|
2777
|
-
this.ctx.logger.info(`[会员系统] 周期锚点被设置为 ${this.formatTimeValue(anchor)}(输入:${input})`);
|
|
2778
|
-
const next = this.getNextRefreshTime();
|
|
2779
|
-
return `✅ 周期锚点已设为 ${this.formatTimeValue(anchor)}(已对齐检查点)
|
|
2780
|
-
下次刷新:${this.formatTimeValue(next)}(${this.getDaysUntilNextRefresh()}天后)${this.isPointsRefreshOverdue() ? "\n⚠️ 该计划已逾期,将在下一个检查点自动补刷(也可用 -f 立即刷新)" : ""}`;
|
|
2781
|
-
}
|
|
2782
|
-
/** 直接指定下次刷新时刻(不触发刷新):取"首个 ≥ 该时刻的检查点",锚点由它反推 */
|
|
2783
|
-
async setPointsRefreshNextAt(input) {
|
|
2784
|
-
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") return "❌ 未启用周期刷新,无法设置下次刷新时刻";
|
|
2785
|
-
const parsed = this.parseRefreshTimeInput(input);
|
|
2786
|
-
if (!parsed) return `❌ 时间格式无法识别:${input}(支持 now、+5d、2026-11-01、2026-11-01 00:05)`;
|
|
2787
|
-
const nextAt = parsed.dateOnly ? this.checkPointOfDay(parsed.timestamp) : this.alignToCheck(parsed.timestamp);
|
|
2788
|
-
const anchor = nextAt - this.getPointsRefreshCycleMs();
|
|
2789
|
-
if (anchor <= 0) return "❌ 目标时刻过于久远,无法反推周期锚点";
|
|
2790
|
-
this.pointsRefreshAnchor = anchor;
|
|
2791
|
-
await this.savePointsRefreshState();
|
|
2792
|
-
this.ctx.logger.info(`[会员系统] 下次刷新时刻被设置为 ${this.formatTimeValue(nextAt)}(输入:${input})`);
|
|
2793
|
-
return `✅ 下次刷新已设为 ${this.formatTimeValue(nextAt)}(周期锚点反推为 ${this.formatTimeValue(anchor)})
|
|
2794
|
-
本次设置不会立即刷新点数,如需立即刷新请加 -f`;
|
|
2795
|
-
}
|
|
2796
|
-
/** 重开一个周期:以当前检查点为锚点(不触发刷新) */
|
|
2797
|
-
async resetPointsRefreshCycle() {
|
|
2798
|
-
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") return "❌ 未启用周期刷新,无法重置周期";
|
|
2799
|
-
const anchor = this.lastCheckPointAtOrBefore(Date.now() + POINTS_REFRESH_TOLERANCE);
|
|
2800
|
-
this.pointsRefreshAnchor = anchor;
|
|
2801
|
-
await this.savePointsRefreshState();
|
|
2802
|
-
this.ctx.logger.info(`[会员系统] 周期已重置,锚点 ${this.formatTimeValue(anchor)}`);
|
|
2803
|
-
const next = this.getNextRefreshTime();
|
|
2804
|
-
return `✅ 刷新周期已重置,周期锚点:${this.formatTimeValue(anchor)}
|
|
2805
|
-
下次刷新:${this.formatTimeValue(next)}(${this.getDaysUntilNextRefresh()}天后)
|
|
2806
|
-
本次操作不会立即刷新点数,如需立即刷新请加 -f`;
|
|
2616
|
+
getDaysUntilNextRefresh(userId) {
|
|
2617
|
+
if (!this.config.pointsEnabled || this.config.pointsMode !== "periodic") return -1;
|
|
2618
|
+
const at = userId ? this.getNextPointsRefreshAt(userId) : 0;
|
|
2619
|
+
if (!at) return 0;
|
|
2620
|
+
return Math.max(0, Math.ceil((at - Date.now()) / (24 * 60 * 60 * 1e3)));
|
|
2807
2621
|
}
|
|
2808
2622
|
/**
|
|
2809
2623
|
* 从 JSON 文件导入数据到数据库。
|
|
@@ -3050,39 +2864,7 @@ var MembershipSystem = class {
|
|
|
3050
2864
|
scheduleReminder();
|
|
3051
2865
|
}
|
|
3052
2866
|
}
|
|
3053
|
-
//
|
|
3054
|
-
setupPointsRefreshTask() {
|
|
3055
|
-
if (this.pointsRefreshTimerDispose) {
|
|
3056
|
-
this.pointsRefreshTimerDispose();
|
|
3057
|
-
this.pointsRefreshTimerDispose = null;
|
|
3058
|
-
}
|
|
3059
|
-
if (this.config.membershipEnabled && this.config.pointsEnabled && this.config.pointsMode === "periodic") {
|
|
3060
|
-
const cycleDays = this.config.pointsRefreshCycleDays || 30;
|
|
3061
|
-
const { hour, minute } = this.getCheckTimeParts();
|
|
3062
|
-
const checkTime = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
3063
|
-
const scheduleDailyCheck = /* @__PURE__ */ __name(() => {
|
|
3064
|
-
const delay = this.getDelayUntilTime(checkTime);
|
|
3065
|
-
this.ctx.logger.info(`点数刷新检查任务已安排,将在 ${new Date(Date.now() + delay).toLocaleString()} 检查(检查时刻 ${checkTime},刷新周期 ${cycleDays} 天)`);
|
|
3066
|
-
this.pointsRefreshTimerDispose = this.ctx.setTimeout(async () => {
|
|
3067
|
-
const firstRun = this.pointsRefreshAnchor === 0;
|
|
3068
|
-
if (firstRun || this.isPointsRefreshOverdue()) {
|
|
3069
|
-
this.ctx.logger.info(firstRun ? "点数刷新尚未初始化,正在执行首次刷新..." : `已达到刷新周期 (${cycleDays} 天),正在执行点数刷新...`);
|
|
3070
|
-
await this.refreshPoints();
|
|
3071
|
-
this.advanceAnchorAfterRefresh();
|
|
3072
|
-
await this.savePointsRefreshState();
|
|
3073
|
-
this.ctx.logger.info(`本轮刷新完成,周期锚点 ${this.formatTimeValue(this.pointsRefreshAnchor)},下次刷新 ${this.formatTimeValue(this.getNextRefreshTime())}`);
|
|
3074
|
-
} else {
|
|
3075
|
-
const remainDays = this.getDaysUntilNextRefresh();
|
|
3076
|
-
if (this.config.debugLog) {
|
|
3077
|
-
this.ctx.logger.info(`点数刷新检查:距离下次刷新还有 ${remainDays} 天(${this.formatTimeValue(this.getNextRefreshTime())})`);
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
scheduleDailyCheck();
|
|
3081
|
-
}, delay);
|
|
3082
|
-
}, "scheduleDailyCheck");
|
|
3083
|
-
scheduleDailyCheck();
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
2867
|
+
// 点数刷新采用按用户惰性执行,不注册全局定时任务。
|
|
3086
2868
|
// 给所有有效会员卡增加天数(按卡粒度,每张有效卡独立延长)。
|
|
3087
2869
|
// @param tierFilter 指定生效等级时仅操作该等级会员;缺省操作全部会员
|
|
3088
2870
|
async addDaysToAllMembers(days, tierFilter) {
|
|
@@ -3124,6 +2906,8 @@ var MembershipSystem = class {
|
|
|
3124
2906
|
lastUsed: Date.now(),
|
|
3125
2907
|
dailyLimit: this.config.nonMemberDailyLimit,
|
|
3126
2908
|
points: 0,
|
|
2909
|
+
pointsRefreshAt: 0,
|
|
2910
|
+
pointsLastRefreshedAt: 0,
|
|
3127
2911
|
nai5DailyUsage: 0,
|
|
3128
2912
|
nai5Bucket: 0,
|
|
3129
2913
|
nai5BucketDay: 0,
|
|
@@ -5482,6 +5266,7 @@ function registerMember(ctx, config, runtime) {
|
|
|
5482
5266
|
return "您没有权限设置会员状态";
|
|
5483
5267
|
}
|
|
5484
5268
|
membershipSystem.checkAndResetDailyUsage(targetId);
|
|
5269
|
+
await membershipSystem.ensurePointsFresh(targetId);
|
|
5485
5270
|
if (options2.refreshPoints) {
|
|
5486
5271
|
if (!config.pointsEnabled) {
|
|
5487
5272
|
return "点数控制未启用";
|
|
@@ -5609,10 +5394,12 @@ nai5/nai5c 今日免费:${benefit.nai5DailyLimit} 次,已用 ${nai5Used} 次
|
|
|
5609
5394
|
pointsInfo = `
|
|
5610
5395
|
点数余额:${points}`;
|
|
5611
5396
|
if (config.pointsMode === "periodic") {
|
|
5612
|
-
const
|
|
5613
|
-
|
|
5397
|
+
const refreshAt = membershipSystem.getNextPointsRefreshAt(targetId);
|
|
5398
|
+
const remainDaysRefresh = membershipSystem.getDaysUntilNextRefresh(targetId);
|
|
5399
|
+
if (refreshAt > 0) {
|
|
5400
|
+
const refreshDate = new Date(refreshAt).toLocaleString();
|
|
5614
5401
|
pointsInfo += `
|
|
5615
|
-
下次点数刷新时间:${
|
|
5402
|
+
下次点数刷新时间:${refreshDate}(约${remainDaysRefresh}天后,Lv${activeTier} 档刷新为 ${benefit.pointsRefresh} 点)`;
|
|
5616
5403
|
}
|
|
5617
5404
|
}
|
|
5618
5405
|
}
|
|
@@ -5625,10 +5412,11 @@ nai5/nai5c 今日免费:${benefit.nai5DailyLimit} 次,已用 ${nai5Used} 次
|
|
|
5625
5412
|
pointsInfo = `
|
|
5626
5413
|
点数余额:${user.points || 0}`;
|
|
5627
5414
|
if (config.pointsMode === "periodic" && config.pointsRefreshIncludeNonMember) {
|
|
5628
|
-
const
|
|
5629
|
-
if (
|
|
5415
|
+
const refreshAt = membershipSystem.getNextPointsRefreshAt(targetId);
|
|
5416
|
+
if (refreshAt > 0) {
|
|
5417
|
+
const refreshDate = new Date(refreshAt).toLocaleString();
|
|
5630
5418
|
pointsInfo += `
|
|
5631
|
-
下次点数刷新时间:${membershipSystem.
|
|
5419
|
+
下次点数刷新时间:${refreshDate}(约${membershipSystem.getDaysUntilNextRefresh(targetId)}天后)`;
|
|
5632
5420
|
}
|
|
5633
5421
|
}
|
|
5634
5422
|
}
|
|
@@ -5647,7 +5435,7 @@ nai5/nai5c 今日免费:${benefit.nai5DailyLimit} 次,已用 ${nai5Used} 次
|
|
|
5647
5435
|
}
|
|
5648
5436
|
});
|
|
5649
5437
|
if (config.memberDebugCommandEnabled) {
|
|
5650
|
-
ctx.command("novelai.member-debug", "会员系统调试指令", { authority: config.memberDebugCommandAuthLv }).alias("会员调试").option("cleanup", "-c 立即执行会员信息清理").option("remind", "-r 立即执行会员到期提醒").option("status", "-s 查看定时任务状态").option("resetUsage", "-u <userId:string> 重置指定用户的使用次数").option("addDaysAll", "-a <days:number> 给会员增加天数(配合 -t 仅操作指定等级)").option("tier", "-t <tier:number> 指定生效等级(Lv1-Lv5),缺省对所有会员操作").option("refreshPoints", "-f
|
|
5438
|
+
ctx.command("novelai.member-debug", "会员系统调试指令", { authority: config.memberDebugCommandAuthLv }).alias("会员调试").option("cleanup", "-c 立即执行会员信息清理").option("remind", "-r 立即执行会员到期提醒").option("status", "-s 查看定时任务状态").option("resetUsage", "-u <userId:string> 重置指定用户的使用次数").option("addDaysAll", "-a <days:number> 给会员增加天数(配合 -t 仅操作指定等级)").option("tier", "-t <tier:number> 指定生效等级(Lv1-Lv5),缺省对所有会员操作").option("refreshPoints", "-f 立即手动刷新点数(不改变自动刷新计划,配合 -t 仅刷新指定等级)").option("addPoints", "--add-points <amount:number> 给会员加点数(配合 -t 仅操作指定等级)").option("subPoints", "--sub-points <amount:number> 给会员减点数(配合 -t 仅操作指定等级)").option("setPoints", "--set-points <value:string> 设置指定用户点数,格式: 用户ID:点数").option("addBucket", "--add-bucket [days:number] 给会员赠送免费次数(发福利:每人按各自档位每日额度 × 天数到账,受 7 天上限约束;配合 -t 指定等级、--bucket-user 指定单人)").option("bucketUser", "--bucket-user <userId:string> 配合 --add-bucket 仅给指定用户赠送").option("setBucket", "--set-bucket <value:string> 设置指定用户免费次数余额,格式: 用户ID:次数").option("importData", "--import 从 JSON 文件导入数据").action(async ({ session, options: options2 }) => {
|
|
5651
5439
|
if (!config.membershipEnabled) {
|
|
5652
5440
|
return "会员系统未启用";
|
|
5653
5441
|
}
|
|
@@ -5819,24 +5607,16 @@ nai5 已使用:${user.nai5DailyUsage || 0} 次` : "";
|
|
|
5819
5607
|
`;
|
|
5820
5608
|
statusMsg += `✅ 点数控制:已启用
|
|
5821
5609
|
`;
|
|
5822
|
-
statusMsg += ` 点数模式:${config.pointsMode === "periodic" ? "
|
|
5610
|
+
statusMsg += ` 点数模式:${config.pointsMode === "periodic" ? "按周期惰性刷新" : "永久点数"}
|
|
5823
5611
|
`;
|
|
5824
5612
|
if (config.pointsMode === "periodic") {
|
|
5825
5613
|
statusMsg += ` 刷新周期:${config.pointsRefreshCycleDays || 30} 天
|
|
5826
|
-
`;
|
|
5827
|
-
statusMsg += ` 刷新检查时刻:${config.pointsRefreshCheckTime || "00:05"}
|
|
5828
5614
|
`;
|
|
5829
5615
|
statusMsg += ` 刷新点数:${config.pointsRefreshAmount || 200}
|
|
5830
5616
|
`;
|
|
5831
|
-
statusMsg += `
|
|
5617
|
+
statusMsg += ` 刷新触发:用户发生点数相关操作时按用户惰性执行
|
|
5832
5618
|
`;
|
|
5833
|
-
statusMsg += `
|
|
5834
|
-
`;
|
|
5835
|
-
statusMsg += ` 上次实际刷新:${membershipSystem.formatLastRefreshTime()}
|
|
5836
|
-
`;
|
|
5837
|
-
const nextDays = membershipSystem.getDaysUntilNextRefresh();
|
|
5838
|
-
const planLabel = membershipSystem.formatRefreshPlanTime();
|
|
5839
|
-
statusMsg += ` 下次刷新:${membershipSystem.formatNextRefreshTime()}(${nextDays}天后,计划 ${planLabel})${membershipSystem.isPointsRefreshOverdue() ? " ⚠️ 已逾期,将在下个检查点补刷" : ""}
|
|
5619
|
+
statusMsg += ` 刷新范围:${config.pointsRefreshIncludeNonMember ? "所有用户" : "仅会员"}
|
|
5840
5620
|
`;
|
|
5841
5621
|
}
|
|
5842
5622
|
statusMsg += ` 默认点数:${config.pointsDefault || 200}
|
|
@@ -5867,27 +5647,6 @@ nai5 已使用:${user.nai5DailyUsage || 0} 次` : "";
|
|
|
5867
5647
|
await membershipSystem.checkAndRemindExpiringMembers();
|
|
5868
5648
|
return "✅ 提醒完成!请查看控制台日志获取详细信息。";
|
|
5869
5649
|
}
|
|
5870
|
-
if (options2.setAnchor !== void 0) {
|
|
5871
|
-
if (!config.pointsEnabled || config.pointsMode !== "periodic") {
|
|
5872
|
-
return "❌ 未启用周期刷新,无法设置周期锚点";
|
|
5873
|
-
}
|
|
5874
|
-
const result = await membershipSystem.setPointsRefreshAnchor(options2.setAnchor);
|
|
5875
|
-
return result;
|
|
5876
|
-
}
|
|
5877
|
-
if (options2.setNextAt !== void 0) {
|
|
5878
|
-
if (!config.pointsEnabled || config.pointsMode !== "periodic") {
|
|
5879
|
-
return "❌ 未启用周期刷新,无法设置下次刷新时刻";
|
|
5880
|
-
}
|
|
5881
|
-
const result = await membershipSystem.setPointsRefreshNextAt(options2.setNextAt);
|
|
5882
|
-
return result;
|
|
5883
|
-
}
|
|
5884
|
-
if (options2.resetCycle) {
|
|
5885
|
-
if (!config.pointsEnabled || config.pointsMode !== "periodic") {
|
|
5886
|
-
return "❌ 未启用周期刷新,无法重置周期";
|
|
5887
|
-
}
|
|
5888
|
-
const result = await membershipSystem.resetPointsRefreshCycle();
|
|
5889
|
-
return result;
|
|
5890
|
-
}
|
|
5891
5650
|
if (options2.refreshPoints) {
|
|
5892
5651
|
if (!config.pointsEnabled) {
|
|
5893
5652
|
return "❌ 点数控制未启用";
|
|
@@ -5897,12 +5656,8 @@ nai5 已使用:${user.nai5DailyUsage || 0} 次` : "";
|
|
|
5897
5656
|
}
|
|
5898
5657
|
const refreshScope = debugTierFilter !== void 0 ? `Lv${debugTierFilter} 会员` : "所有会员";
|
|
5899
5658
|
await session.send(`正在为${refreshScope}执行点数刷新...`);
|
|
5900
|
-
const overdueBefore = membershipSystem.isPointsRefreshOverdue();
|
|
5901
5659
|
const result = await membershipSystem.refreshPoints(debugTierFilter);
|
|
5902
|
-
|
|
5903
|
-
ℹ️ 本次刷新不改变周期锚点(${membershipSystem.formatRefreshAnchor()}),下次刷新仍为 ${membershipSystem.formatNextRefreshTime()};如需立即重开周期请用 --reset-cycle` : `
|
|
5904
|
-
ℹ️ 周期不受影响,下次刷新仍为 ${membershipSystem.formatNextRefreshTime()}(${membershipSystem.getDaysUntilNextRefresh()}天后)`;
|
|
5905
|
-
return result.message + note;
|
|
5660
|
+
return result.message;
|
|
5906
5661
|
}
|
|
5907
5662
|
if (options2.addPoints !== void 0) {
|
|
5908
5663
|
if (!config.pointsEnabled) {
|