koishi-plugin-ets2-tools-tmp 0.0.5 → 1.0.0
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/command/ets-app/activity-reminder.js +419 -0
- package/lib/command/ets-app/resetPassword.js +89 -152
- package/lib/command/tmpQuery/tmpQueryText.js +2 -2
- package/lib/command/tmpTraffic/tmpTrafficText.js +5 -1
- package/lib/index.d.ts +21 -0
- package/lib/index.js +604 -9
- package/package.json +4 -3
- package/readme.md +134 -29
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
function apply(ctx, config) {
|
|
2
|
+
let todayActivities = [];
|
|
3
|
+
let todayTMPEvents = [];
|
|
4
|
+
const sentReminders = /* @__PURE__ */ new Set();
|
|
5
|
+
const timers = [];
|
|
6
|
+
const logger = {
|
|
7
|
+
debug: /* @__PURE__ */ __name((message, ...args) => {
|
|
8
|
+
if (config.debugMode) {
|
|
9
|
+
ctx.logger.debug(`[DEBUG] ${message}`, ...args);
|
|
10
|
+
}
|
|
11
|
+
}, "debug"),
|
|
12
|
+
info: /* @__PURE__ */ __name((message, ...args) => {
|
|
13
|
+
ctx.logger.info(message, ...args);
|
|
14
|
+
}, "info"),
|
|
15
|
+
warn: /* @__PURE__ */ __name((message, ...args) => {
|
|
16
|
+
ctx.logger.warn(message, ...args);
|
|
17
|
+
}, "warn"),
|
|
18
|
+
error: /* @__PURE__ */ __name((message, ...args) => {
|
|
19
|
+
ctx.logger.error(message, ...args);
|
|
20
|
+
}, "error"),
|
|
21
|
+
api: /* @__PURE__ */ __name((message, data) => {
|
|
22
|
+
if (config.logApiResponses) {
|
|
23
|
+
ctx.logger.info(`[API] ${message}`, data ? JSON.stringify(data, null, 2) : "");
|
|
24
|
+
}
|
|
25
|
+
}, "api"),
|
|
26
|
+
timing: /* @__PURE__ */ __name((message, data) => {
|
|
27
|
+
if (config.logTimingDetails) {
|
|
28
|
+
ctx.logger.info(`[TIMING] ${message}`, data || "");
|
|
29
|
+
}
|
|
30
|
+
}, "timing"),
|
|
31
|
+
matching: /* @__PURE__ */ __name((message, data) => {
|
|
32
|
+
if (config.logActivityMatching) {
|
|
33
|
+
ctx.logger.info(`[MATCHING] ${message}`, data || "");
|
|
34
|
+
}
|
|
35
|
+
}, "matching"),
|
|
36
|
+
message: /* @__PURE__ */ __name((message, data) => {
|
|
37
|
+
if (config.logMessageSending) {
|
|
38
|
+
ctx.logger.info(`[MESSAGE] ${message}`, data || "");
|
|
39
|
+
}
|
|
40
|
+
}, "message")
|
|
41
|
+
};
|
|
42
|
+
setupDailyTasks();
|
|
43
|
+
function getNextTime(hours, minutes) {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
const target = new Date(
|
|
46
|
+
now.getFullYear(),
|
|
47
|
+
now.getMonth(),
|
|
48
|
+
now.getDate(),
|
|
49
|
+
hours,
|
|
50
|
+
minutes,
|
|
51
|
+
0,
|
|
52
|
+
0
|
|
53
|
+
);
|
|
54
|
+
if (target.getTime() <= now.getTime()) {
|
|
55
|
+
target.setDate(target.getDate() + 1);
|
|
56
|
+
}
|
|
57
|
+
return target.getTime() - now.getTime();
|
|
58
|
+
}
|
|
59
|
+
__name(getNextTime, "getNextTime");
|
|
60
|
+
function resetDailyData() {
|
|
61
|
+
const previousActivityCount = todayActivities.length;
|
|
62
|
+
const previousTMPCount = todayTMPEvents.length;
|
|
63
|
+
const previousReminderCount = sentReminders.size;
|
|
64
|
+
todayActivities = [];
|
|
65
|
+
todayTMPEvents = [];
|
|
66
|
+
sentReminders.clear();
|
|
67
|
+
logger.debug(`每日数据已重置: 活动${previousActivityCount}→0, TMP${previousTMPCount}→0, 提醒${previousReminderCount}→0`);
|
|
68
|
+
updateActivityData();
|
|
69
|
+
}
|
|
70
|
+
__name(resetDailyData, "resetDailyData");
|
|
71
|
+
function setupDailyTasks() {
|
|
72
|
+
logger.timing("开始设置每日定时任务");
|
|
73
|
+
const resetHour = 2;
|
|
74
|
+
const resetMinute = 0;
|
|
75
|
+
const resetDelay = getNextTime(resetHour, resetMinute);
|
|
76
|
+
const resetTimer = setTimeout(() => {
|
|
77
|
+
logger.timing("执行每日数据重置");
|
|
78
|
+
resetDailyData();
|
|
79
|
+
const dailyResetTimer = setInterval(() => {
|
|
80
|
+
logger.timing("执行每日数据重置");
|
|
81
|
+
resetDailyData();
|
|
82
|
+
}, import_koishi.Time.day);
|
|
83
|
+
timers.push(dailyResetTimer);
|
|
84
|
+
}, resetDelay);
|
|
85
|
+
timers.push(resetTimer);
|
|
86
|
+
logger.timing(`设置数据重置定时器: ${resetHour}:${resetMinute.toString().padStart(2, "0")}, 延迟: ${resetDelay}ms`);
|
|
87
|
+
config.adminCheckTimes.forEach((timeStr, index) => {
|
|
88
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
89
|
+
const setupTimer = /* @__PURE__ */ __name(() => {
|
|
90
|
+
const delay = getNextTime(hours, minutes);
|
|
91
|
+
const timer = setTimeout(() => {
|
|
92
|
+
logger.timing(`执行定时检查任务 #${index + 1} (${timeStr})`);
|
|
93
|
+
updateActivityData();
|
|
94
|
+
const dailyTimer = setInterval(() => {
|
|
95
|
+
logger.timing(`执行每日检查任务 #${index + 1} (${timeStr})`);
|
|
96
|
+
updateActivityData();
|
|
97
|
+
}, import_koishi.Time.day);
|
|
98
|
+
timers.push(dailyTimer);
|
|
99
|
+
}, delay);
|
|
100
|
+
timers.push(timer);
|
|
101
|
+
logger.timing(`设置检查定时器 #${index + 1}: ${timeStr}, 延迟: ${delay}ms`);
|
|
102
|
+
}, "setupTimer");
|
|
103
|
+
setupTimer();
|
|
104
|
+
});
|
|
105
|
+
config.adminSendTimes.forEach((timeStr, index) => {
|
|
106
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
107
|
+
const setupTimer = /* @__PURE__ */ __name(() => {
|
|
108
|
+
const delay = getNextTime(hours, minutes);
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
logger.timing(`执行定时发送任务 #${index + 1} (${timeStr})`);
|
|
111
|
+
checkAndSendProfileReminders();
|
|
112
|
+
const dailyTimer = setInterval(() => {
|
|
113
|
+
logger.timing(`执行每日发送任务 #${index + 1} (${timeStr})`);
|
|
114
|
+
checkAndSendProfileReminders();
|
|
115
|
+
}, import_koishi.Time.day);
|
|
116
|
+
timers.push(dailyTimer);
|
|
117
|
+
}, delay);
|
|
118
|
+
timers.push(timer);
|
|
119
|
+
logger.timing(`设置发送定时器 #${index + 1}: ${timeStr}, 延迟: ${delay}ms`);
|
|
120
|
+
}, "setupTimer");
|
|
121
|
+
setupTimer();
|
|
122
|
+
});
|
|
123
|
+
const minuteTimer = setInterval(async () => {
|
|
124
|
+
await checkAndSendActivityReminders();
|
|
125
|
+
}, import_koishi.Time.minute);
|
|
126
|
+
timers.push(minuteTimer);
|
|
127
|
+
logger.timing("设置每分钟检查定时器");
|
|
128
|
+
logger.debug("启动时立即更新活动数据");
|
|
129
|
+
updateActivityData();
|
|
130
|
+
}
|
|
131
|
+
__name(setupDailyTasks, "setupDailyTasks");
|
|
132
|
+
async function updateActivityData() {
|
|
133
|
+
try {
|
|
134
|
+
logger.debug("开始更新活动数据");
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
await updateTodayActivities();
|
|
137
|
+
await updateTodayTMPEvents();
|
|
138
|
+
const duration = Date.now() - startTime;
|
|
139
|
+
logger.info(`活动数据更新完成,耗时: ${duration}ms`);
|
|
140
|
+
logger.debug(`今日活动数量: ${todayActivities.length}, TMP活动数量: ${todayTMPEvents.length}`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error("更新活动数据失败:", error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
__name(updateActivityData, "updateActivityData");
|
|
146
|
+
async function updateTodayActivities() {
|
|
147
|
+
try {
|
|
148
|
+
const protocol = config.adminUseHttps ? "https://" : "http://";
|
|
149
|
+
const fullUrl = `${protocol}${config.adminApiUrl}/api/activity/info/list?token=${config.adminApiToken}&page=1&limit=50&themeName=`;
|
|
150
|
+
logger.api(`请求车队平台API: ${fullUrl.replace(config.adminApiToken, "***")}`);
|
|
151
|
+
const startTime = Date.now();
|
|
152
|
+
const response = await ctx.http.get(fullUrl);
|
|
153
|
+
const duration = Date.now() - startTime;
|
|
154
|
+
logger.api(`车队平台API响应耗时: ${duration}ms, 状态码: ${response.code}`);
|
|
155
|
+
if (config.logApiResponses) {
|
|
156
|
+
logger.api("车队平台API响应详情:", {
|
|
157
|
+
code: response.code,
|
|
158
|
+
totalCount: response.data?.totalCount,
|
|
159
|
+
listCount: response.data?.list?.length
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (response.code === 0) {
|
|
163
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
164
|
+
const originalCount = response.data.list.length;
|
|
165
|
+
todayActivities = response.data.list.filter((activity) => {
|
|
166
|
+
const activityDate = activity.startTime.split(" ")[0];
|
|
167
|
+
return activityDate === today;
|
|
168
|
+
});
|
|
169
|
+
logger.info(`从车队平台找到 ${todayActivities.length}/${originalCount} 个今日活动`);
|
|
170
|
+
logger.debug("今日活动列表:", todayActivities.map((a) => ({
|
|
171
|
+
id: a.id,
|
|
172
|
+
name: a.themeName,
|
|
173
|
+
time: a.startTime,
|
|
174
|
+
hasProfile: !!a.profileFile
|
|
175
|
+
})));
|
|
176
|
+
} else {
|
|
177
|
+
logger.error(`车队平台API返回错误: ${response.msg} (代码: ${response.code})`);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.error("获取活动列表失败:", error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
__name(updateTodayActivities, "updateTodayActivities");
|
|
184
|
+
async function updateTodayTMPEvents() {
|
|
185
|
+
try {
|
|
186
|
+
const tmpApiUrl = `https://api.truckersmp.com/v2/vtc/${config.adminVtcId}/events/attending/`;
|
|
187
|
+
logger.api(`请求TMP API: ${tmpApiUrl}`);
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
const response = await ctx.http.get(tmpApiUrl);
|
|
190
|
+
const duration = Date.now() - startTime;
|
|
191
|
+
logger.api(`TMP API响应耗时: ${duration}ms, 错误状态: ${response.error}`);
|
|
192
|
+
if (!response.error) {
|
|
193
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
194
|
+
const originalCount = response.response.length;
|
|
195
|
+
todayTMPEvents = response.response.filter((event) => {
|
|
196
|
+
const eventDate = event.start_at.split(" ")[0];
|
|
197
|
+
return eventDate === today;
|
|
198
|
+
});
|
|
199
|
+
logger.info(`从TMP找到 ${todayTMPEvents.length}/${originalCount} 个今日活动`);
|
|
200
|
+
if (config.logApiResponses) {
|
|
201
|
+
logger.debug("TMP活动列表:", todayTMPEvents.map((e) => ({
|
|
202
|
+
id: e.id,
|
|
203
|
+
name: e.name,
|
|
204
|
+
time: e.start_at,
|
|
205
|
+
server: e.server.name
|
|
206
|
+
})));
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
logger.error("TMP API返回错误状态");
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.error("获取TMP活动失败:", error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
__name(updateTodayTMPEvents, "updateTodayTMPEvents");
|
|
216
|
+
async function checkAndSendProfileReminders() {
|
|
217
|
+
if (todayActivities.length === 0) {
|
|
218
|
+
logger.debug("今日没有活动,跳过档位检查");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
logger.debug(`开始检查 ${todayActivities.length} 个活动的档位状态`);
|
|
222
|
+
for (const activity of todayActivities) {
|
|
223
|
+
const hasProfile = !!activity.profileFile;
|
|
224
|
+
const message = hasProfile ? config.adminProfileUploadedMessage : config.adminProfileNotUploadedMessage;
|
|
225
|
+
const fullMessage = `活动 "${activity.themeName}" - ${message}`;
|
|
226
|
+
logger.message(`活动档位检查: "${activity.themeName}" - ${hasProfile ? "已上传" : "未上传"}`);
|
|
227
|
+
for (const groupId of config.adminGroups) {
|
|
228
|
+
try {
|
|
229
|
+
await sendToGroup(groupId, fullMessage, "管理群组");
|
|
230
|
+
logger.message(`已发送档位提醒到管理群组 ${groupId}: ${activity.themeName}`);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logger.error(`发送消息到管理群组 ${groupId} 失败:`, error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
__name(checkAndSendProfileReminders, "checkAndSendProfileReminders");
|
|
238
|
+
async function checkAndSendActivityReminders() {
|
|
239
|
+
const now = /* @__PURE__ */ new Date();
|
|
240
|
+
let remindersSent = 0;
|
|
241
|
+
logger.debug(`检查 ${todayActivities.length} 个活动的提醒时间`);
|
|
242
|
+
for (const activity of todayActivities) {
|
|
243
|
+
const activityStartTime = new Date(activity.startTime);
|
|
244
|
+
const timeDiff = activityStartTime.getTime() - now.getTime();
|
|
245
|
+
const minutesLeft = Math.floor(timeDiff / (1e3 * 60));
|
|
246
|
+
logger.debug(`活动 "${activity.themeName}" 剩余时间: ${minutesLeft} 分钟`);
|
|
247
|
+
for (const reminderTime of config.mainActivityReminderTimes) {
|
|
248
|
+
if (minutesLeft <= reminderTime && minutesLeft > reminderTime - 1) {
|
|
249
|
+
const reminderKey = `${activity.id}_${reminderTime}`;
|
|
250
|
+
if (!sentReminders.has(reminderKey)) {
|
|
251
|
+
logger.debug(`触发提醒: ${activity.themeName} - ${reminderTime} 分钟前`);
|
|
252
|
+
await sendActivityReminder(activity, minutesLeft);
|
|
253
|
+
sentReminders.add(reminderKey);
|
|
254
|
+
remindersSent++;
|
|
255
|
+
} else {
|
|
256
|
+
logger.debug(`提醒已发送过: ${activity.themeName} - ${reminderTime} 分钟前`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (remindersSent > 0) {
|
|
262
|
+
logger.debug(`本轮发送了 ${remindersSent} 个活动提醒`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
__name(checkAndSendActivityReminders, "checkAndSendActivityReminders");
|
|
266
|
+
async function sendActivityReminder(activity, minutesLeft) {
|
|
267
|
+
const tmpEvent = todayTMPEvents.find(
|
|
268
|
+
(event) => event.name.includes(activity.themeName) || activity.themeName.includes(event.name)
|
|
269
|
+
);
|
|
270
|
+
logger.matching(`活动匹配: "${activity.themeName}" - 找到TMP匹配: ${!!tmpEvent}`);
|
|
271
|
+
if (tmpEvent && config.logActivityMatching) {
|
|
272
|
+
logger.matching("TMP活动详情:", {
|
|
273
|
+
tmpName: tmpEvent.name,
|
|
274
|
+
activityName: activity.themeName,
|
|
275
|
+
server: tmpEvent.server.name,
|
|
276
|
+
departure: `${tmpEvent.departure.location} - ${tmpEvent.departure.city}`,
|
|
277
|
+
arrive: `${tmpEvent.arrive.location} - ${tmpEvent.arrive.city}`
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const replacements = {
|
|
281
|
+
name: activity.themeName,
|
|
282
|
+
distance: activity.distance.toString(),
|
|
283
|
+
timeLeft: minutesLeft.toString()
|
|
284
|
+
};
|
|
285
|
+
if (config.adminServerSource === "tmp" && tmpEvent) {
|
|
286
|
+
replacements.server = tmpEvent.server.name;
|
|
287
|
+
logger.debug(`使用TMP服务器信息: ${tmpEvent.server.name}`);
|
|
288
|
+
} else {
|
|
289
|
+
replacements.server = activity.serverName || "未知服务器";
|
|
290
|
+
logger.debug(`使用平台服务器信息: ${replacements.server}`);
|
|
291
|
+
}
|
|
292
|
+
if (config.adminStartPointSource === "tmp" && tmpEvent) {
|
|
293
|
+
replacements.startingPoint = `${tmpEvent.departure.location} - ${tmpEvent.departure.city}`;
|
|
294
|
+
logger.debug(`使用TMP起点信息: ${replacements.startingPoint}`);
|
|
295
|
+
} else {
|
|
296
|
+
replacements.startingPoint = activity.startingPoint;
|
|
297
|
+
logger.debug(`使用平台起点信息: ${replacements.startingPoint}`);
|
|
298
|
+
}
|
|
299
|
+
if (config.adminEndPointSource === "tmp" && tmpEvent) {
|
|
300
|
+
replacements.terminalPoint = `${tmpEvent.arrive.location} - ${tmpEvent.arrive.city}`;
|
|
301
|
+
logger.debug(`使用TMP终点信息: ${replacements.terminalPoint}`);
|
|
302
|
+
} else {
|
|
303
|
+
replacements.terminalPoint = activity.terminalPoint;
|
|
304
|
+
logger.debug(`使用平台终点信息: ${replacements.terminalPoint}`);
|
|
305
|
+
}
|
|
306
|
+
if (config.adminShowBanner && tmpEvent && tmpEvent.banner) {
|
|
307
|
+
replacements.banner = tmpEvent.banner;
|
|
308
|
+
logger.debug(`使用活动横幅: ${tmpEvent.banner}`);
|
|
309
|
+
} else {
|
|
310
|
+
replacements.banner = "无";
|
|
311
|
+
logger.debug("未使用活动横幅");
|
|
312
|
+
}
|
|
313
|
+
let message = config.mainActivityReminderMessage;
|
|
314
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
315
|
+
message = message.replace(new RegExp(`{${key}}`, "g"), value);
|
|
316
|
+
}
|
|
317
|
+
if (!config.adminShowBanner) {
|
|
318
|
+
message = message.replace(/活动横幅:.*?\n?/, "");
|
|
319
|
+
}
|
|
320
|
+
message = message.replace(/\\n/g, "\n");
|
|
321
|
+
const fullMessage = `@全体成员
|
|
322
|
+
${message}`;
|
|
323
|
+
logger.message(`准备发送活动提醒: ${activity.themeName} (${minutesLeft}分钟前)`);
|
|
324
|
+
logger.debug("完整消息内容:", fullMessage);
|
|
325
|
+
for (const groupId of config.mainGroups) {
|
|
326
|
+
try {
|
|
327
|
+
await sendToGroup(groupId, fullMessage, "主群组");
|
|
328
|
+
logger.message(`已发送活动提醒到主群组 ${groupId}: ${activity.themeName}`);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
logger.error(`发送活动提醒到主群组 ${groupId} 失败:`, error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
__name(sendActivityReminder, "sendActivityReminder");
|
|
335
|
+
async function sendToGroup(groupId, message, groupType) {
|
|
336
|
+
const availableBots = ctx.bots.filter((bot) => {
|
|
337
|
+
if (bot.platform === "mail") return false;
|
|
338
|
+
const unsupportedPlatforms = ["mail", "telegram", "discord"];
|
|
339
|
+
if (unsupportedPlatforms.includes(bot.platform)) return false;
|
|
340
|
+
return true;
|
|
341
|
+
});
|
|
342
|
+
if (availableBots.length === 0) {
|
|
343
|
+
throw new Error(`没有可用的聊天平台适配器,当前只有邮件适配器`);
|
|
344
|
+
}
|
|
345
|
+
let lastError = null;
|
|
346
|
+
logger.debug(`尝试通过 ${availableBots.length} 个适配器发送消息到${groupType} ${groupId}`);
|
|
347
|
+
for (const bot of availableBots) {
|
|
348
|
+
try {
|
|
349
|
+
await bot.sendMessage(groupId, message);
|
|
350
|
+
logger.debug(`已通过 ${bot.platform} 适配器发送消息到${groupType} ${groupId}`);
|
|
351
|
+
return;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
lastError = error;
|
|
354
|
+
logger.warn(`通过 ${bot.platform} 适配器发送消息到${groupType} ${groupId} 失败: ${error.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
throw lastError || new Error(`所有适配器都无法发送消息到${groupType} ${groupId}`);
|
|
358
|
+
}
|
|
359
|
+
__name(sendToGroup, "sendToGroup");
|
|
360
|
+
ctx.command("活动查询", "手动检查今日活动").action(async () => {
|
|
361
|
+
logger.debug("手动执行活动检查命令");
|
|
362
|
+
await updateActivityData();
|
|
363
|
+
const result = `检查完成!您的车队平台写入的今日活动: ${todayActivities.length} 个, 您的TMP今日参与了 ${todayTMPEvents.length} 个活动`;
|
|
364
|
+
logger.debug(result);
|
|
365
|
+
return result;
|
|
366
|
+
});
|
|
367
|
+
ctx.command("活动DEBUG", "查看插件调试信息").action(() => {
|
|
368
|
+
const state = {
|
|
369
|
+
todayActivities: todayActivities.length,
|
|
370
|
+
todayTMPEvents: todayTMPEvents.length,
|
|
371
|
+
sentReminders: sentReminders.size,
|
|
372
|
+
timers: timers.length,
|
|
373
|
+
config: {
|
|
374
|
+
debugMode: config.debugMode,
|
|
375
|
+
logApiResponses: config.logApiResponses,
|
|
376
|
+
logTimingDetails: config.logTimingDetails,
|
|
377
|
+
logActivityMatching: config.logActivityMatching,
|
|
378
|
+
logMessageSending: config.logMessageSending
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
let message = "插件调试信息:\n";
|
|
382
|
+
message += `今日活动: ${state.todayActivities} 个
|
|
383
|
+
`;
|
|
384
|
+
message += `TMP活动: ${state.todayTMPEvents} 个
|
|
385
|
+
`;
|
|
386
|
+
message += `已发送提醒: ${state.sentReminders} 个
|
|
387
|
+
`;
|
|
388
|
+
message += `活跃定时器: ${state.timers} 个
|
|
389
|
+
`;
|
|
390
|
+
message += `调试模式: ${state.config.debugMode ? "开启" : "关闭"}
|
|
391
|
+
`;
|
|
392
|
+
message += `日志选项: API=${state.config.logApiResponses}, 定时=${state.config.logTimingDetails}, 匹配=${state.config.logActivityMatching}, 消息=${state.config.logMessageSending}`;
|
|
393
|
+
return message;
|
|
394
|
+
});
|
|
395
|
+
ctx.command("重置数据", "手动重置今日数据").action(() => {
|
|
396
|
+
logger.debug("手动执行数据重置命令");
|
|
397
|
+
resetDailyData();
|
|
398
|
+
return "活动数据已重置";
|
|
399
|
+
});
|
|
400
|
+
ctx.on("dispose", () => {
|
|
401
|
+
logger.debug("插件卸载,开始清理资源");
|
|
402
|
+
todayActivities = [];
|
|
403
|
+
todayTMPEvents = [];
|
|
404
|
+
sentReminders.clear();
|
|
405
|
+
timers.forEach((timer) => {
|
|
406
|
+
clearTimeout(timer);
|
|
407
|
+
clearInterval(timer);
|
|
408
|
+
});
|
|
409
|
+
timers.length = 0;
|
|
410
|
+
logger.debug("资源清理完成");
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
__name(apply, "apply");
|
|
414
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
415
|
+
0 && (module.exports = {
|
|
416
|
+
Config,
|
|
417
|
+
apply,
|
|
418
|
+
name
|
|
419
|
+
});
|
|
@@ -3,172 +3,109 @@ module.exports = async (ctx, cfg, session, targetTeamId) => {
|
|
|
3
3
|
const { adminUsers } = cfg.resetPassword;
|
|
4
4
|
const currentUserQQ = session.userId;
|
|
5
5
|
const isAdmin = adminUsers.includes(currentUserQQ);
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const isPrivateChat = session.channelId === `private:${currentUserQQ}`;
|
|
7
|
+
|
|
8
|
+
// 日志工具函数
|
|
9
|
+
const log = (msg) => logOutput && ctx.logger.info(msg);
|
|
10
|
+
|
|
11
|
+
// 通用HTTP请求函数
|
|
12
|
+
const fetchData = async (apiUrl) => {
|
|
8
13
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (qq.startsWith("<at ")) {
|
|
13
|
-
if (qq.startsWith('<at ')) {
|
|
14
|
-
qq = qq.replace('<at ', '');
|
|
15
|
-
}
|
|
16
|
-
let id = '';
|
|
17
|
-
const idStart = qq.indexOf('id="');
|
|
18
|
-
if (idStart !== -1) {
|
|
19
|
-
const valueStart = idStart + 4;
|
|
20
|
-
const valueEnd = qq.indexOf('"', valueStart);
|
|
21
|
-
if (valueEnd !== -1) {
|
|
22
|
-
id = qq.substring(valueStart, valueEnd);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
queryQQ = id;
|
|
26
|
-
if (!/^\d+$/.test(queryQQ)) {
|
|
27
|
-
return "获取qq号错误,请使用车队编号重置";
|
|
28
|
-
}
|
|
29
|
-
if (logOutput) {
|
|
30
|
-
ctx.logger.info(`管理员 ${currentUserQQ} 请求重置 qq号 ${queryQQ} 的密码`);
|
|
31
|
-
}
|
|
32
|
-
const verifyUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=1&tmpId=&tmpName=&teamId=&qq=${queryQQ}&state=0&teamRole=`;
|
|
33
|
-
const verifyResponse = await ctx.http.post(verifyUrl);
|
|
34
|
-
if (verifyResponse.code !== 0 || !verifyResponse.page?.list?.length) {
|
|
35
|
-
return `未找到qq号为 ${queryQQ} 的用户信息`;
|
|
36
|
-
}
|
|
37
|
-
const userInfo = verifyResponse.page.list[0];
|
|
38
|
-
if (logOutput) {
|
|
39
|
-
ctx.logger.info(`目标用户信息: ${userInfo.tmpName} (QQ: ${userInfo.qq})`);
|
|
40
|
-
}
|
|
41
|
-
const teamId = userInfo.teamId;
|
|
42
|
-
const resetPasswordUrl = `https://${url}/api/user/info/resetPasswordWithTeamId?token=${token}&teamId=${teamId}`;
|
|
43
|
-
if (logOutput) {
|
|
44
|
-
ctx.logger.info(`请求重置密码: ${resetPasswordUrl}`);
|
|
45
|
-
}
|
|
46
|
-
const resetResponse = await ctx.http.post(resetPasswordUrl);
|
|
47
|
-
if (logOutput) {
|
|
48
|
-
ctx.logger.info(`重置密码响应: ${JSON.stringify(resetResponse)}`);
|
|
49
|
-
}
|
|
50
|
-
if (resetResponse.code === 0) {
|
|
51
|
-
if (targetTeamId) {
|
|
52
|
-
return `管理员操作:车队编号 ${teamId} 的密码重置成功!新密码已发送到用户邮箱。`;
|
|
53
|
-
} else {
|
|
54
|
-
return "密码重置成功!新密码已发送到您的邮箱,请查收。";
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
return `密码重置失败: ${resetResponse.msg || "未知错误"}`;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
let teamId = qq;
|
|
61
|
-
if (logOutput) {
|
|
62
|
-
ctx.logger.info(`管理员 ${currentUserQQ} 请求重置 车队编号 ${teamId} 的密码`);
|
|
63
|
-
}
|
|
64
|
-
const verifyUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=1&tmpId=&tmpName=&teamId=${teamId}&qq=&state=0&teamRole=`;
|
|
65
|
-
const verifyResponse = await ctx.http.post(verifyUrl);
|
|
66
|
-
if (verifyResponse.code !== 0 || !verifyResponse.page?.list?.length) {
|
|
67
|
-
return `未找到车队编号为 ${teamId} 的用户信息`;
|
|
68
|
-
}
|
|
69
|
-
const userInfo = verifyResponse.page.list[0];
|
|
70
|
-
if (logOutput) {
|
|
71
|
-
ctx.logger.info(`目标用户信息: ${userInfo.tmpName} (QQ: ${userInfo.qq})`);
|
|
72
|
-
}
|
|
73
|
-
const resetPasswordUrl = `https://${url}/api/user/info/resetPasswordWithTeamId?token=${token}&teamId=${teamId}`;
|
|
74
|
-
if (logOutput) {
|
|
75
|
-
ctx.logger.info(`请求重置密码: ${resetPasswordUrl}`);
|
|
76
|
-
}
|
|
77
|
-
const resetResponse = await ctx.http.post(resetPasswordUrl);
|
|
78
|
-
if (logOutput) {
|
|
79
|
-
ctx.logger.info(`重置密码响应: ${JSON.stringify(resetResponse)}`);
|
|
80
|
-
}
|
|
81
|
-
if (resetResponse.code === 0) {
|
|
82
|
-
if (targetTeamId) {
|
|
83
|
-
return `管理员操作:车队编号 ${teamId} 的密码重置成功!新密码已发送到用户邮箱。`;
|
|
84
|
-
} else {
|
|
85
|
-
return "密码重置成功!新密码已发送到您的邮箱,请查收。";
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
return `密码重置失败: ${resetResponse.msg || "未知错误"}`;
|
|
89
|
-
}
|
|
14
|
+
const response = await ctx.http.post(apiUrl);
|
|
15
|
+
log(`请求响应: ${JSON.stringify(response)}`);
|
|
16
|
+
return response;
|
|
90
17
|
} catch (error) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return `请求失败: ${error.response.status} ${error.response.statusText}`;
|
|
94
|
-
} else if (error.code) {
|
|
95
|
-
return `网络错误: ${error.code}`;
|
|
96
|
-
} else {
|
|
97
|
-
return "系统错误,请稍后重试";
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
let teamId;
|
|
103
|
-
if (!targetTeamId) {
|
|
104
|
-
if (logOutput) {
|
|
105
|
-
ctx.logger.info(`开始处理用户 ${currentUserQQ} 的密码重置请求`);
|
|
106
|
-
}
|
|
107
|
-
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=7&tmpId=&tmpName=&teamId=&qq=${currentUserQQ}&state=0&teamRole=`;
|
|
108
|
-
if (logOutput) {
|
|
109
|
-
ctx.logger.info(`请求用户信息: ${userInfoUrl}`);
|
|
110
|
-
}
|
|
111
|
-
const userInfoResponse = await ctx.http.post(userInfoUrl);
|
|
112
|
-
if (logOutput) {
|
|
113
|
-
ctx.logger.info(`用户信息响应: ${JSON.stringify(userInfoResponse)}`);
|
|
114
|
-
}
|
|
115
|
-
if (userInfoResponse.code !== 0) {
|
|
116
|
-
return `获取用户信息失败: ${userInfoResponse.msg || "未知错误"}`;
|
|
117
|
-
}
|
|
118
|
-
const userList = userInfoResponse.page?.list || [];
|
|
119
|
-
if (userList.length === 0) {
|
|
120
|
-
return "未找到与该QQ号关联的用户信息";
|
|
121
|
-
}
|
|
122
|
-
teamId = userList[0].teamId;
|
|
123
|
-
if (!teamId) {
|
|
124
|
-
return "在平台中未找到您的信息,请联系管理员重置密码";
|
|
125
|
-
}
|
|
126
|
-
if (logOutput) {
|
|
127
|
-
ctx.logger.info(`找到用户 车队编号: ${teamId}`);
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
if (!isAdmin) {
|
|
131
|
-
return "您没有权限重置其他成员的密码,请联系管理员";
|
|
132
|
-
}
|
|
133
|
-
teamId = targetTeamId;
|
|
134
|
-
if (logOutput) {
|
|
135
|
-
ctx.logger.info(`管理员 ${currentUserQQ} 请求重置 车队编号 ${teamId} 的密码`);
|
|
136
|
-
}
|
|
137
|
-
const verifyUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=1&tmpId=&tmpName=&teamId=${teamId}&qq=&state=0&teamRole=`;
|
|
138
|
-
const verifyResponse = await ctx.http.post(verifyUrl);
|
|
139
|
-
if (verifyResponse.code !== 0 || !verifyResponse.page?.list?.length) {
|
|
140
|
-
return `未找到车队编号为 ${teamId} 的用户信息`;
|
|
141
|
-
}
|
|
142
|
-
const userInfo = verifyResponse.page.list[0];
|
|
143
|
-
if (logOutput) {
|
|
144
|
-
ctx.logger.info(`目标用户信息: ${userInfo.tmpName} (QQ: ${userInfo.qq})`);
|
|
145
|
-
}
|
|
18
|
+
log(`请求错误: ${error}`);
|
|
19
|
+
throw error;
|
|
146
20
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 获取用户信息
|
|
24
|
+
const getUserInfo = async (params) => {
|
|
25
|
+
const query = new URLSearchParams({ token, page: 0, limit: 1, ...params }).toString();
|
|
26
|
+
const userInfoUrl = `https://${url}/api/user/info/list?${query}`;
|
|
27
|
+
log(`获取用户信息: ${userInfoUrl}`);
|
|
28
|
+
const response = await fetchData(userInfoUrl);
|
|
29
|
+
|
|
30
|
+
if (response.code !== 0 || !response.page?.list?.length) {
|
|
31
|
+
throw new Error(`未找到用户信息`);
|
|
150
32
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
33
|
+
return response.page.list[0];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 重置密码
|
|
37
|
+
const resetPassword = async (teamId) => {
|
|
38
|
+
const resetUrl = `https://${url}/api/user/info/resetPasswordWithTeamId?token=${token}&teamId=${teamId}`;
|
|
39
|
+
log(`重置密码请求: ${resetUrl}`);
|
|
40
|
+
const response = await fetchData(resetUrl);
|
|
41
|
+
|
|
42
|
+
if (response.code !== 0) {
|
|
43
|
+
throw new Error(response.msg || "未知错误");
|
|
154
44
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
45
|
+
return teamId;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
let teamId, targetQQ;
|
|
50
|
+
|
|
51
|
+
// 处理@提及的QQ号
|
|
52
|
+
if (targetTeamId?.startsWith("<at ")) {
|
|
53
|
+
if (!isAdmin) return "您没有权限重置其他用户的密码,请联系管理员";
|
|
54
|
+
|
|
55
|
+
// 提取QQ号
|
|
56
|
+
const idStart = targetTeamId.indexOf('id="');
|
|
57
|
+
if (idStart === -1) return "获取QQ号错误,请使用车队编号重置";
|
|
58
|
+
|
|
59
|
+
targetQQ = targetTeamId.substring(idStart + 4, targetTeamId.indexOf('"', idStart + 4));
|
|
60
|
+
if (!/^\d+$/.test(targetQQ)) return "获取QQ号错误,请使用车队编号重置";
|
|
61
|
+
|
|
62
|
+
log(`管理员 ${currentUserQQ} 重置QQ: ${targetQQ} 密码`);
|
|
63
|
+
const userInfo = await getUserInfo({ qq: targetQQ });
|
|
64
|
+
teamId = userInfo.teamId;
|
|
65
|
+
}
|
|
66
|
+
// 处理车队编号或私聊场景
|
|
67
|
+
else {
|
|
68
|
+
if (isPrivateChat) {
|
|
69
|
+
// 私聊场景
|
|
70
|
+
if (!targetTeamId) {
|
|
71
|
+
// 无目标编号时用当前用户QQ查询
|
|
72
|
+
log(`用户 ${currentUserQQ} 发起密码重置`);
|
|
73
|
+
const userInfo = await getUserInfo({ qq: currentUserQQ });
|
|
74
|
+
teamId = userInfo.teamId;
|
|
75
|
+
if (!teamId) return "未找到您的信息,请联系管理员";
|
|
76
|
+
} else {
|
|
77
|
+
// 有目标编号时验证权限
|
|
78
|
+
const userInfo = await getUserInfo({ teamId: targetTeamId });
|
|
79
|
+
if (!isAdmin && userInfo.qq !== currentUserQQ) {
|
|
80
|
+
return "您没有权限重置其他成员的密码,请联系管理员";
|
|
81
|
+
}
|
|
82
|
+
teamId = targetTeamId;
|
|
83
|
+
}
|
|
158
84
|
} else {
|
|
159
|
-
|
|
85
|
+
// 非私聊场景(仅管理员可操作)
|
|
86
|
+
if (!isAdmin) return "您没有权限重置其他用户的密码,请联系管理员重置,或私聊机器人重置";
|
|
87
|
+
|
|
88
|
+
teamId = targetTeamId;
|
|
89
|
+
log(`管理员 ${currentUserQQ} 重置车队: ${teamId} 密码`);
|
|
90
|
+
await getUserInfo({ teamId }); // 验证车队存在性
|
|
160
91
|
}
|
|
161
|
-
} else {
|
|
162
|
-
return `密码重置失败: ${resetResponse.msg || "未知错误"}`;
|
|
163
92
|
}
|
|
93
|
+
|
|
94
|
+
// 执行密码重置
|
|
95
|
+
const resetTeamId = await resetPassword(teamId);
|
|
96
|
+
const isAdminOp = isAdmin && (targetTeamId || targetQQ);
|
|
97
|
+
return isAdminOp
|
|
98
|
+
? `管理员操作:车队编号 ${resetTeamId} 的密码重置成功!新密码已发送到用户邮箱。`
|
|
99
|
+
: "密码重置成功!新密码已发送到您的邮箱,请查收。";
|
|
100
|
+
|
|
164
101
|
} catch (error) {
|
|
165
|
-
ctx.logger.error(
|
|
102
|
+
ctx.logger.error(`密码重置错误: ${error.message}`);
|
|
166
103
|
if (error.response) {
|
|
167
104
|
return `请求失败: ${error.response.status} ${error.response.statusText}`;
|
|
168
105
|
} else if (error.code) {
|
|
169
106
|
return `网络错误: ${error.code}`;
|
|
170
107
|
} else {
|
|
171
|
-
return "系统错误,请稍后重试";
|
|
108
|
+
return error.message.includes("未找到") ? error.message : "系统错误,请稍后重试";
|
|
172
109
|
}
|
|
173
110
|
}
|
|
174
111
|
};
|