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
package/lib/index.js
CHANGED
|
@@ -16,6 +16,30 @@ const tmpMileageRanking = require('./command/tmpMileageRanking');
|
|
|
16
16
|
const resetPassword = require('./command/ets-app/resetPassword');
|
|
17
17
|
const queryPoint = require('./command/ets-app/queryPoint');
|
|
18
18
|
const tmpVtc = require('./command/tmpVtc');
|
|
19
|
+
var __defProp = Object.defineProperty;
|
|
20
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
21
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
22
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
23
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
24
|
+
var __export = (target, all) => {
|
|
25
|
+
for (var name2 in all)
|
|
26
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
27
|
+
};
|
|
28
|
+
var __copyProps = (to, from, except, desc) => {
|
|
29
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
30
|
+
for (let key of __getOwnPropNames(from))
|
|
31
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
32
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
33
|
+
}
|
|
34
|
+
return to;
|
|
35
|
+
};
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
var src_exports = {};
|
|
38
|
+
__export(src_exports, {
|
|
39
|
+
Config: () => Config,
|
|
40
|
+
apply: () => apply,
|
|
41
|
+
name: () => name
|
|
42
|
+
});
|
|
19
43
|
|
|
20
44
|
exports.name = 'tmp-bot';
|
|
21
45
|
exports.inject = {
|
|
@@ -28,7 +52,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
28
52
|
baiduTranslateAppId: koishi_1.Schema.string().description('百度翻译APP ID'),
|
|
29
53
|
baiduTranslateKey: koishi_1.Schema.string().description('百度翻译秘钥'),
|
|
30
54
|
baiduTranslateCacheEnable: koishi_1.Schema.boolean().default(false).description('启用百度翻译缓存')
|
|
31
|
-
}).description('
|
|
55
|
+
}).description('指令基本配置'),
|
|
32
56
|
koishi_1.Schema.object({
|
|
33
57
|
queryShowAvatarEnable: koishi_1.Schema.boolean().default(false).description('查询指令展示头像,部分玩家的擦边头像可能导致封号'),
|
|
34
58
|
tmpTrafficType: koishi_1.Schema.union([
|
|
@@ -39,7 +63,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
39
63
|
koishi_1.Schema.const(1).description('文字'),
|
|
40
64
|
koishi_1.Schema.const(2).description('图片')
|
|
41
65
|
]).default(1).description('玩家信息展示方式'),
|
|
42
|
-
}).description('
|
|
66
|
+
}).description('指令名称配置'),
|
|
43
67
|
koishi_1.Schema.object({
|
|
44
68
|
mainSettings: koishi_1.Schema.object({
|
|
45
69
|
url: koishi_1.Schema.string().description("API服务器地址").required(),
|
|
@@ -50,12 +74,66 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
50
74
|
koishi_1.Schema.object({
|
|
51
75
|
resetPassword: koishi_1.Schema.object({
|
|
52
76
|
adminUsers: koishi_1.Schema.array(koishi_1.Schema.string()).description("管理员用户ID(拥有重置任意teamId权限)").default([])
|
|
53
|
-
}).description("
|
|
77
|
+
}).description("重置密码指令设置")
|
|
54
78
|
}),
|
|
79
|
+
koishi_1.Schema.object({
|
|
80
|
+
activityQuertEnable: koishi_1.Schema.boolean().default(false).description('是否启用活动查询'),
|
|
81
|
+
}).description("活动查询 - 开关"),
|
|
82
|
+
koishi_1.Schema.object({
|
|
83
|
+
adminUseHttps: koishi_1.Schema.boolean().description("使用HTTPS协议").default(true),
|
|
84
|
+
adminApiUrl: koishi_1.Schema.string().required().description("车队平台URL(不包含协议)").default(""),
|
|
85
|
+
adminApiToken: koishi_1.Schema.string().required().description("车队平台TOKEN").default(""),
|
|
86
|
+
adminVtcId: koishi_1.Schema.string().required().description("VTC ID(用于TMP API)").default("")
|
|
87
|
+
}).description("活动查询 - API配置"),
|
|
88
|
+
koishi_1.Schema.object({
|
|
89
|
+
adminCheckTimes: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("活动检查时间(HH:mm格式)").default(["08:00", "12:00", "14:00", "20:00"]),
|
|
90
|
+
adminSendTimes: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("信息发送时间(HH:mm格式)").default(["08:05", "12:05", "14:05", "20:05"]),
|
|
91
|
+
adminGroups: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("管理群组ID列表").default([])
|
|
92
|
+
}).description("活动查询 - 管理群配置"),
|
|
93
|
+
koishi_1.Schema.object({
|
|
94
|
+
adminServerSource: koishi_1.Schema.union([
|
|
95
|
+
koishi_1.Schema.const("platform").description("车队平台API"),
|
|
96
|
+
koishi_1.Schema.const("tmp").description("TMP API")
|
|
97
|
+
]).description("服务器信息来源").default("tmp"),
|
|
98
|
+
adminStartPointSource: koishi_1.Schema.union([
|
|
99
|
+
koishi_1.Schema.const("platform").description("车队平台API"),
|
|
100
|
+
koishi_1.Schema.const("tmp").description("TMP API")
|
|
101
|
+
]).description("起点信息来源").default("tmp"),
|
|
102
|
+
adminEndPointSource: koishi_1.Schema.union([
|
|
103
|
+
koishi_1.Schema.const("platform").description("车队平台API"),
|
|
104
|
+
koishi_1.Schema.const("tmp").description("TMP API")
|
|
105
|
+
]).description("终点信息来源").default("tmp"),
|
|
106
|
+
adminShowBanner: koishi_1.Schema.boolean().description("是否显示活动横幅").default(false)
|
|
107
|
+
}).description("活动查询 - 数据源配置"),
|
|
108
|
+
koishi_1.Schema.object({
|
|
109
|
+
adminProfileUploadedMessage: koishi_1.Schema.string().description("活动档已上传时的消息").default("今日活动档已做/上传"),
|
|
110
|
+
adminProfileNotUploadedMessage: koishi_1.Schema.string().description("活动档未上传时的消息").default("今日活动档还没做,请负责的管理注意!")
|
|
111
|
+
}).description("活动查询 - 管理群消息配置"),
|
|
112
|
+
koishi_1.Schema.object({
|
|
113
|
+
mainGroups: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("主群群号列表").default([]),
|
|
114
|
+
mainActivityReminderMessage: koishi_1.Schema.string().description("活动提醒消息模板,支持变量:{name}, {server}, {startingPoint}, {terminalPoint}, {distance}, {banner}, {timeLeft}").default("活动 {name} 还有 {timeLeft} 分钟就要开始啦!\n服务器: {server}\n起点: {startingPoint}\n终点: {terminalPoint}\n距离: {distance}KM"),
|
|
115
|
+
mainActivityReminderTimes: koishi_1.Schema.array(koishi_1.Schema.number()).role("table").description("活动开始前提醒时间(分钟)").default([60, 30, 15])
|
|
116
|
+
}).description("活动查询 - 主群配置"),
|
|
117
|
+
koishi_1.Schema.object({
|
|
118
|
+
debugMode: koishi_1.Schema.boolean().description("启用调试模式(输出详细日志)").default(false),
|
|
119
|
+
logApiResponses: koishi_1.Schema.boolean().description("记录API响应详情").default(false),
|
|
120
|
+
logTimingDetails: koishi_1.Schema.boolean().description("记录定时任务执行详情").default(false),
|
|
121
|
+
logActivityMatching: koishi_1.Schema.boolean().description("记录活动匹配过程").default(false),
|
|
122
|
+
logMessageSending: koishi_1.Schema.boolean().description("记录消息发送详情").default(false)
|
|
123
|
+
}).description("活动查询 - 开发者选项")
|
|
55
124
|
]);
|
|
125
|
+
|
|
56
126
|
function apply(ctx, cfg) {
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
try {
|
|
128
|
+
model(ctx);
|
|
129
|
+
if (cfg.debugMode) {
|
|
130
|
+
ctx.logger.debug("[TMP-BOT] 数据库模型初始化成功");
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
ctx.logger.error("[TMP-BOT] 数据库模型初始化失败:", error.message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
59
137
|
// 注册指令
|
|
60
138
|
ctx.command('查询 <tmpId>').action(async ({ session }, tmpId) => await tmpQuery(ctx, cfg, session, tmpId));
|
|
61
139
|
ctx.command('美卡服务器').action(async () => await tmpServer(ctx, cfg, 'ATS'));
|
|
@@ -67,9 +145,526 @@ function apply(ctx, cfg) {
|
|
|
67
145
|
ctx.command('地图dlc价格').action(async ({ session }) => await tmpDlcMap(ctx, session));
|
|
68
146
|
ctx.command('里程排行榜').action(async ({ session }) => await tmpMileageRanking(ctx, session, MileageRankingType.total));
|
|
69
147
|
ctx.command('今日里程排行榜').action(async ({ session }) => await tmpMileageRanking(ctx, session, MileageRankingType.today));
|
|
70
|
-
|
|
71
148
|
ctx.command('vtc查询 <vtcid>').action(async ({ session }, vtcid) => await tmpVtc(ctx, cfg, session, vtcid));
|
|
72
|
-
ctx.command(`重置密码 [targetTeamId:string]`, "重置欧卡车队平台密码")
|
|
73
|
-
|
|
74
|
-
|
|
149
|
+
ctx.command(`重置密码 [targetTeamId:string]`, "重置欧卡车队平台密码")
|
|
150
|
+
.usage("重置自己的密码,或管理员重置指定teamId的密码")
|
|
151
|
+
.example(`重置密码new - 重置自己的密码`)
|
|
152
|
+
.example(`重置密码new 123 - 管理员重置指定teamId的密码`)
|
|
153
|
+
.action(async ({ session }, targetTeamId) => await resetPassword(ctx, cfg, session, targetTeamId));
|
|
154
|
+
ctx.command(`查询积分 [targetQQ:string]`, "查询欧卡车队平台积分")
|
|
155
|
+
.usage("查询自己或指定QQ号的积分,在群聊中可@他人查询")
|
|
156
|
+
.example(`查询积分 - 查询自己的积分`)
|
|
157
|
+
.example(`查询积分 123456 - 查询指定QQ号的积分`)
|
|
158
|
+
.action(async ({ session }, targetQQ) => await queryPoint(ctx, cfg, session, targetQQ));
|
|
159
|
+
ctx.command('规则查询').action(async ({ session }) => {
|
|
160
|
+
return 'TruckersMP官方规则链接:https://truckersmp.com/knowledge-base/article/746';
|
|
161
|
+
});
|
|
162
|
+
if (cfg.activityQuertEnable) {
|
|
163
|
+
let todayActivities = [];
|
|
164
|
+
let todayTMPEvents = [];
|
|
165
|
+
const sentReminders = /* @__PURE__ */ new Set();
|
|
166
|
+
const timers = [];
|
|
167
|
+
const logger = {
|
|
168
|
+
debug: /* @__PURE__ */ __name((message, ...args) => {
|
|
169
|
+
if (cfg.debugMode) {
|
|
170
|
+
ctx.logger.debug(`[TMP-BOT DEBUG] ${message}`, ...args);
|
|
171
|
+
}
|
|
172
|
+
}, "debug"),
|
|
173
|
+
info: /* @__PURE__ */ __name((message, ...args) => {
|
|
174
|
+
ctx.logger.info(`[TMP-BOT] ${message}`, ...args);
|
|
175
|
+
}, "info"),
|
|
176
|
+
warn: /* @__PURE__ */ __name((message, ...args) => {
|
|
177
|
+
ctx.logger.warn(`[TMP-BOT WARN] ${message}`, ...args);
|
|
178
|
+
}, "warn"),
|
|
179
|
+
error: /* @__PURE__ */ __name((message, ...args) => {
|
|
180
|
+
ctx.logger.error(`[TMP-BOT ERROR] ${message}`, ...args);
|
|
181
|
+
}, "error"),
|
|
182
|
+
api: /* @__PURE__ */ __name((message, data) => {
|
|
183
|
+
if (cfg.logApiResponses) {
|
|
184
|
+
ctx.logger.info(`[TMP-BOT API] ${message}`, data ? JSON.stringify(data, null, 2) : "");
|
|
185
|
+
}
|
|
186
|
+
}, "api"),
|
|
187
|
+
timing: /* @__PURE__ */ __name((message, data) => {
|
|
188
|
+
if (cfg.logTimingDetails) {
|
|
189
|
+
ctx.logger.info(`[TMP-BOT TIMING] ${message}`, data || "");
|
|
190
|
+
}
|
|
191
|
+
}, "timing"),
|
|
192
|
+
matching: /* @__PURE__ */ __name((message, data) => {
|
|
193
|
+
if (cfg.logActivityMatching) {
|
|
194
|
+
ctx.logger.info(`[TMP-BOT MATCHING] ${message}`, data || "");
|
|
195
|
+
}
|
|
196
|
+
}, "matching"),
|
|
197
|
+
message: /* @__PURE__ */ __name((message, data) => {
|
|
198
|
+
if (cfg.logMessageSending) {
|
|
199
|
+
ctx.logger.info(`[TMP-BOT MESSAGE] ${message}`, data || "");
|
|
200
|
+
}
|
|
201
|
+
}, "message")
|
|
202
|
+
};
|
|
203
|
+
setupDailyTasks();
|
|
204
|
+
|
|
205
|
+
// 获取下次执行时间(修复函数命名)
|
|
206
|
+
function getNextTime(hours, minutes) {
|
|
207
|
+
const now = /* @__PURE__ */ new Date();
|
|
208
|
+
const target = new Date(
|
|
209
|
+
now.getFullYear(),
|
|
210
|
+
now.getMonth(),
|
|
211
|
+
now.getDate(),
|
|
212
|
+
hours,
|
|
213
|
+
minutes,
|
|
214
|
+
0,
|
|
215
|
+
0
|
|
216
|
+
);
|
|
217
|
+
if (target.getTime() <= now.getTime()) {
|
|
218
|
+
target.setDate(target.getDate() + 1);
|
|
219
|
+
}
|
|
220
|
+
return target.getTime() - now.getTime();
|
|
221
|
+
}
|
|
222
|
+
__name(getNextTime, "getNextTime");
|
|
223
|
+
|
|
224
|
+
// 重置每日数据
|
|
225
|
+
function resetDailyData() {
|
|
226
|
+
const previousActivityCount = todayActivities.length;
|
|
227
|
+
const previousTMPCount = todayTMPEvents.length;
|
|
228
|
+
const previousReminderCount = sentReminders.size;
|
|
229
|
+
todayActivities = [];
|
|
230
|
+
todayTMPEvents = [];
|
|
231
|
+
sentReminders.clear();
|
|
232
|
+
logger.debug(`每日数据已重置: 活动${previousActivityCount}→0, TMP${previousTMPCount}→0, 提醒${previousReminderCount}→0`);
|
|
233
|
+
updateActivityData();
|
|
234
|
+
}
|
|
235
|
+
__name(resetDailyData, "resetDailyData");
|
|
236
|
+
|
|
237
|
+
// 设置每日定时任务
|
|
238
|
+
function setupDailyTasks() {
|
|
239
|
+
logger.timing("开始设置每日定时任务");
|
|
240
|
+
|
|
241
|
+
// 每日2:00重置数据
|
|
242
|
+
const resetHour = 2;
|
|
243
|
+
const resetMinute = 0;
|
|
244
|
+
const resetDelay = getNextTime(resetHour, resetMinute);
|
|
245
|
+
const resetTimer = setTimeout(() => {
|
|
246
|
+
logger.timing("执行每日数据重置");
|
|
247
|
+
resetDailyData();
|
|
248
|
+
const dailyResetTimer = setInterval(() => {
|
|
249
|
+
logger.timing("执行每日数据重置");
|
|
250
|
+
resetDailyData();
|
|
251
|
+
}, koishi_1.Time.day);
|
|
252
|
+
timers.push(dailyResetTimer);
|
|
253
|
+
}, resetDelay);
|
|
254
|
+
timers.push(resetTimer);
|
|
255
|
+
logger.timing(`设置数据重置定时器: ${resetHour}:${resetMinute.toString().padStart(2, "0")}, 延迟: ${resetDelay}ms`);
|
|
256
|
+
|
|
257
|
+
// 活动检查定时任务
|
|
258
|
+
cfg.adminCheckTimes.forEach((timeStr, index) => {
|
|
259
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
260
|
+
const setupTimer = /* @__PURE__ */ __name(() => {
|
|
261
|
+
const delay = getNextTime(hours, minutes);
|
|
262
|
+
const timer = setTimeout(() => {
|
|
263
|
+
logger.timing(`执行定时检查任务 #${index + 1} (${timeStr})`);
|
|
264
|
+
updateActivityData();
|
|
265
|
+
const dailyTimer = setInterval(() => {
|
|
266
|
+
logger.timing(`执行每日检查任务 #${index + 1} (${timeStr})`);
|
|
267
|
+
updateActivityData();
|
|
268
|
+
}, koishi_1.Time.day);
|
|
269
|
+
timers.push(dailyTimer);
|
|
270
|
+
}, delay);
|
|
271
|
+
timers.push(timer);
|
|
272
|
+
logger.timing(`设置检查定时器 #${index + 1}: ${timeStr}, 延迟: ${delay}ms`);
|
|
273
|
+
}, "setupTimer");
|
|
274
|
+
setupTimer();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// 管理群消息发送定时任务
|
|
278
|
+
cfg.adminSendTimes.forEach((timeStr, index) => {
|
|
279
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
280
|
+
const setupTimer = /* @__PURE__ */ __name(() => {
|
|
281
|
+
const delay = getNextTime(hours, minutes);
|
|
282
|
+
const timer = setTimeout(() => {
|
|
283
|
+
logger.timing(`执行定时发送任务 #${index + 1} (${timeStr})`);
|
|
284
|
+
checkAndSendProfileReminders();
|
|
285
|
+
const dailyTimer = setInterval(() => {
|
|
286
|
+
logger.timing(`执行每日发送任务 #${index + 1} (${timeStr})`);
|
|
287
|
+
checkAndSendProfileReminders();
|
|
288
|
+
}, koishi_1.Time.day);
|
|
289
|
+
timers.push(dailyTimer);
|
|
290
|
+
}, delay);
|
|
291
|
+
timers.push(timer);
|
|
292
|
+
logger.timing(`设置发送定时器 #${index + 1}: ${timeStr}, 延迟: ${delay}ms`);
|
|
293
|
+
}, "setupTimer");
|
|
294
|
+
setupTimer();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// 每分钟检查活动提醒
|
|
298
|
+
const minuteTimer = setInterval(async () => {
|
|
299
|
+
await checkAndSendActivityReminders();
|
|
300
|
+
}, koishi_1.Time.minute);
|
|
301
|
+
timers.push(minuteTimer);
|
|
302
|
+
logger.timing("设置每分钟检查定时器");
|
|
303
|
+
|
|
304
|
+
// 启动时立即更新活动数据
|
|
305
|
+
logger.debug("启动时立即更新活动数据");
|
|
306
|
+
updateActivityData();
|
|
307
|
+
}
|
|
308
|
+
__name(setupDailyTasks, "setupDailyTasks");
|
|
309
|
+
|
|
310
|
+
// 更新活动数据(主函数)
|
|
311
|
+
async function updateActivityData() {
|
|
312
|
+
try {
|
|
313
|
+
logger.debug("开始更新活动数据");
|
|
314
|
+
const startTime = Date.now();
|
|
315
|
+
await updateTodayActivities();
|
|
316
|
+
await updateTodayTMPEvents();
|
|
317
|
+
const duration = Date.now() - startTime;
|
|
318
|
+
logger.info(`活动数据更新完成,耗时: ${duration}ms`);
|
|
319
|
+
logger.debug(`今日活动数量: ${todayActivities.length}, TMP活动数量: ${todayTMPEvents.length}`);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
logger.error("更新活动数据失败:", error.message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
__name(updateActivityData, "updateActivityData");
|
|
325
|
+
|
|
326
|
+
// 从车队平台获取今日活动
|
|
327
|
+
async function updateTodayActivities() {
|
|
328
|
+
try {
|
|
329
|
+
const protocol = cfg.adminUseHttps ? "https://" : "http://";
|
|
330
|
+
const fullUrl = `${protocol}${cfg.adminApiUrl}/api/activity/info/list?token=${cfg.adminApiToken}&page=1&limit=50&themeName=`;
|
|
331
|
+
logger.api(`请求车队平台API: ${fullUrl.replace(cfg.adminApiToken, "***")}`);
|
|
332
|
+
|
|
333
|
+
const startTime = Date.now();
|
|
334
|
+
// 添加10秒超时配置
|
|
335
|
+
const response = await ctx.http.get(fullUrl, { timeout: 10000 });
|
|
336
|
+
const duration = Date.now() - startTime;
|
|
337
|
+
logger.api(`车队平台API响应耗时: ${duration}ms, 状态码: ${response.code}`);
|
|
338
|
+
|
|
339
|
+
if (cfg.logApiResponses) {
|
|
340
|
+
logger.api("车队平台API响应详情:", {
|
|
341
|
+
code: response.code,
|
|
342
|
+
totalCount: response.data?.totalCount,
|
|
343
|
+
listCount: response.data?.list?.length
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (response.code === 0 && response.data?.list) {
|
|
348
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
349
|
+
const originalCount = response.data.list.length;
|
|
350
|
+
todayActivities = response.data.list.filter((activity) => {
|
|
351
|
+
const activityDate = activity.startTime?.split(" ")[0];
|
|
352
|
+
return activityDate === today;
|
|
353
|
+
});
|
|
354
|
+
logger.info(`从车队平台找到 ${todayActivities.length}/${originalCount} 个今日活动`);
|
|
355
|
+
logger.debug("今日活动列表:", todayActivities.map((a) => ({
|
|
356
|
+
id: a.id,
|
|
357
|
+
name: a.themeName,
|
|
358
|
+
time: a.startTime,
|
|
359
|
+
hasProfile: !!a.profileFile
|
|
360
|
+
})));
|
|
361
|
+
} else {
|
|
362
|
+
logger.error(`车队平台API返回错误: ${response.msg || '未知错误'} (代码: ${response.code || '无'})`);
|
|
363
|
+
todayActivities = []; // 接口错误时清空数据,避免使用旧数据
|
|
364
|
+
}
|
|
365
|
+
} catch (error) {
|
|
366
|
+
logger.error("获取车队平台活动列表失败:", error.message);
|
|
367
|
+
todayActivities = [];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
__name(updateTodayActivities, "updateTodayActivities");
|
|
371
|
+
|
|
372
|
+
// 从TMP API获取今日活动
|
|
373
|
+
async function updateTodayTMPEvents() {
|
|
374
|
+
try {
|
|
375
|
+
if (!cfg.adminVtcId) {
|
|
376
|
+
logger.warn("TMP API请求失败:未配置adminVtcId");
|
|
377
|
+
todayTMPEvents = [];
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const tmpApiUrl = `https://api.truckersmp.com/v2/vtc/${cfg.adminVtcId}/events/attending/`;
|
|
381
|
+
logger.api(`请求TMP API: ${tmpApiUrl}`);
|
|
382
|
+
|
|
383
|
+
const startTime = Date.now();
|
|
384
|
+
// 添加10秒超时配置
|
|
385
|
+
const response = await ctx.http.get(tmpApiUrl, { timeout: 10000 });
|
|
386
|
+
const duration = Date.now() - startTime;
|
|
387
|
+
logger.api(`TMP API响应耗时: ${duration}ms, 错误状态: ${response.error}`);
|
|
388
|
+
|
|
389
|
+
if (!response.error && Array.isArray(response.response)) {
|
|
390
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
391
|
+
const originalCount = response.response.length;
|
|
392
|
+
todayTMPEvents = response.response.filter((event) => {
|
|
393
|
+
const eventDate = event.start_at?.split(" ")[0];
|
|
394
|
+
return eventDate === today;
|
|
395
|
+
});
|
|
396
|
+
logger.info(`从TMP找到 ${todayTMPEvents.length}/${originalCount} 个今日活动`);
|
|
397
|
+
if (cfg.logApiResponses) {
|
|
398
|
+
logger.debug("TMP活动列表:", todayTMPEvents.map((e) => ({
|
|
399
|
+
id: e.id,
|
|
400
|
+
name: e.name,
|
|
401
|
+
time: e.start_at,
|
|
402
|
+
server: e.server?.name
|
|
403
|
+
})));
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
logger.error(`TMP API返回错误: ${response.message || '未知错误'}`);
|
|
407
|
+
todayTMPEvents = [];
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
logger.error("获取TMP活动失败:", error.message);
|
|
411
|
+
todayTMPEvents = [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
__name(updateTodayTMPEvents, "updateTodayTMPEvents");
|
|
415
|
+
|
|
416
|
+
// 检查并发送活动档提醒(管理群)
|
|
417
|
+
async function checkAndSendProfileReminders() {
|
|
418
|
+
if (todayActivities.length === 0) {
|
|
419
|
+
logger.debug("今日没有活动,跳过档位检查");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
logger.debug(`开始检查 ${todayActivities.length} 个活动的档位状态`);
|
|
423
|
+
|
|
424
|
+
for (const activity of todayActivities) {
|
|
425
|
+
const hasProfile = !!activity.profileFile;
|
|
426
|
+
const message = hasProfile ? cfg.adminProfileUploadedMessage : cfg.adminProfileNotUploadedMessage;
|
|
427
|
+
const fullMessage = `活动 "${activity.themeName || '未知活动'}" - ${message}`;
|
|
428
|
+
logger.message(`活动档位检查: "${activity.themeName || '未知活动'}" - ${hasProfile ? "已上传" : "未上传"}`);
|
|
429
|
+
|
|
430
|
+
for (const groupId of cfg.adminGroups) {
|
|
431
|
+
try {
|
|
432
|
+
await sendToGroup(groupId, fullMessage, "管理群组");
|
|
433
|
+
logger.message(`已发送档位提醒到管理群组 ${groupId}: ${activity.themeName || '未知活动'}`);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
logger.error(`发送消息到管理群组 ${groupId} 失败:`, error.message);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
__name(checkAndSendProfileReminders, "checkAndSendProfileReminders");
|
|
441
|
+
|
|
442
|
+
// 检查并发送活动提醒(主群)
|
|
443
|
+
async function checkAndSendActivityReminders() {
|
|
444
|
+
const now = /* @__PURE__ */ new Date();
|
|
445
|
+
let remindersSent = 0;
|
|
446
|
+
logger.debug(`检查 ${todayActivities.length} 个活动的提醒时间`);
|
|
447
|
+
|
|
448
|
+
for (const activity of todayActivities) {
|
|
449
|
+
try {
|
|
450
|
+
const activityStartTime = new Date(activity.startTime);
|
|
451
|
+
if (isNaN(activityStartTime.getTime())) {
|
|
452
|
+
logger.warn(`活动 "${activity.themeName}" 开始时间格式错误,跳过提醒`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const timeDiff = activityStartTime.getTime() - now.getTime();
|
|
457
|
+
const minutesLeft = Math.floor(timeDiff / (1e3 * 60));
|
|
458
|
+
logger.debug(`活动 "${activity.themeName}" 剩余时间: ${minutesLeft} 分钟`);
|
|
459
|
+
|
|
460
|
+
// 只处理未来的活动
|
|
461
|
+
if (minutesLeft < 0) continue;
|
|
462
|
+
|
|
463
|
+
for (const reminderTime of cfg.mainActivityReminderTimes) {
|
|
464
|
+
// 当剩余时间落在 [reminderTime-1, reminderTime] 区间时触发提醒
|
|
465
|
+
if (minutesLeft <= reminderTime && minutesLeft > reminderTime - 1) {
|
|
466
|
+
const reminderKey = `${activity.id}_${reminderTime}`;
|
|
467
|
+
if (!sentReminders.has(reminderKey)) {
|
|
468
|
+
logger.debug(`触发提醒: ${activity.themeName} - ${reminderTime} 分钟前`);
|
|
469
|
+
await sendActivityReminder(activity, minutesLeft);
|
|
470
|
+
sentReminders.add(reminderKey);
|
|
471
|
+
remindersSent++;
|
|
472
|
+
} else {
|
|
473
|
+
logger.debug(`提醒已发送过: ${activity.themeName} - ${reminderTime} 分钟前`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
logger.error(`处理活动 "${activity.themeName}" 提醒失败:`, error.message);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (remindersSent > 0) {
|
|
483
|
+
logger.debug(`本轮发送了 ${remindersSent} 个活动提醒`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
__name(checkAndSendActivityReminders, "checkAndSendActivityReminders");
|
|
487
|
+
|
|
488
|
+
// 发送活动提醒详情
|
|
489
|
+
async function sendActivityReminder(activity, minutesLeft) {
|
|
490
|
+
try {
|
|
491
|
+
// 匹配TMP活动
|
|
492
|
+
const tmpEvent = todayTMPEvents.find(
|
|
493
|
+
(event) => event.name.includes(activity.themeName) || activity.themeName.includes(event.name)
|
|
494
|
+
);
|
|
495
|
+
logger.matching(`活动匹配: "${activity.themeName}" - 找到TMP匹配: ${!!tmpEvent}`);
|
|
496
|
+
|
|
497
|
+
if (tmpEvent && cfg.logActivityMatching) {
|
|
498
|
+
logger.matching("TMP活动详情:", {
|
|
499
|
+
tmpName: tmpEvent.name,
|
|
500
|
+
activityName: activity.themeName,
|
|
501
|
+
server: tmpEvent.server?.name,
|
|
502
|
+
departure: `${tmpEvent.departure?.location} - ${tmpEvent.departure?.city}`,
|
|
503
|
+
arrive: `${tmpEvent.arrive?.location} - ${tmpEvent.arrive?.city}`
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// 替换消息模板变量
|
|
508
|
+
const replacements = {
|
|
509
|
+
name: activity.themeName || '未知活动',
|
|
510
|
+
distance: activity.distance?.toString() || '未知',
|
|
511
|
+
timeLeft: minutesLeft.toString()
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// 服务器信息
|
|
515
|
+
if (cfg.adminServerSource === "tmp" && tmpEvent) {
|
|
516
|
+
replacements.server = tmpEvent.server?.name || '未知服务器';
|
|
517
|
+
} else {
|
|
518
|
+
replacements.server = activity.serverName || '未知服务器';
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// 起点信息
|
|
522
|
+
if (cfg.adminStartPointSource === "tmp" && tmpEvent) {
|
|
523
|
+
replacements.startingPoint = `${tmpEvent.departure?.location || ''} - ${tmpEvent.departure?.city || ''}`.trim() || '未知起点';
|
|
524
|
+
} else {
|
|
525
|
+
replacements.startingPoint = activity.startingPoint || '未知起点';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 终点信息
|
|
529
|
+
if (cfg.adminEndPointSource === "tmp" && tmpEvent) {
|
|
530
|
+
replacements.terminalPoint = `${tmpEvent.arrive?.location || ''} - ${tmpEvent.arrive?.city || ''}`.trim() || '未知终点';
|
|
531
|
+
} else {
|
|
532
|
+
replacements.terminalPoint = activity.terminalPoint || '未知终点';
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// 活动横幅
|
|
536
|
+
if (cfg.adminShowBanner && tmpEvent && tmpEvent.banner) {
|
|
537
|
+
replacements.banner = tmpEvent.banner;
|
|
538
|
+
} else {
|
|
539
|
+
replacements.banner = "无";
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 替换模板变量
|
|
543
|
+
let message = cfg.mainActivityReminderMessage;
|
|
544
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
545
|
+
message = message.replace(new RegExp(`{${key}}`, "g"), value);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 不显示横幅时移除相关内容
|
|
549
|
+
if (!cfg.adminShowBanner) {
|
|
550
|
+
message = message.replace(/活动横幅:.*?\n?/, "");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 处理换行符
|
|
554
|
+
message = message.replace(/\\n/g, "\n").trim();
|
|
555
|
+
const fullMessage = `@全体成员\n${message}`;
|
|
556
|
+
|
|
557
|
+
logger.message(`准备发送活动提醒: ${activity.themeName} (${minutesLeft}分钟前)`);
|
|
558
|
+
logger.debug("完整消息内容:", fullMessage);
|
|
559
|
+
|
|
560
|
+
// 发送到所有主群
|
|
561
|
+
for (const groupId of cfg.mainGroups) {
|
|
562
|
+
try {
|
|
563
|
+
await sendToGroup(groupId, fullMessage, "主群组");
|
|
564
|
+
logger.message(`已发送活动提醒到主群组 ${groupId}: ${activity.themeName}`);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logger.error(`发送活动提醒到主群组 ${groupId} 失败:`, error.message);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} catch (error) {
|
|
570
|
+
logger.error(`发送活动提醒失败:`, error.message);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
__name(sendActivityReminder, "sendActivityReminder");
|
|
574
|
+
|
|
575
|
+
// 发送消息到指定群组
|
|
576
|
+
async function sendToGroup(groupId, message, groupType) {
|
|
577
|
+
// 过滤不支持的平台
|
|
578
|
+
const availableBots = ctx.bots.filter((bot) => {
|
|
579
|
+
const unsupportedPlatforms = ["mail", "telegram", "discord"];
|
|
580
|
+
return !unsupportedPlatforms.includes(bot.platform);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (availableBots.length === 0) {
|
|
584
|
+
throw new Error(`没有可用的聊天平台适配器(当前不支持邮件/电报/Discord)`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
let lastError = null;
|
|
588
|
+
logger.debug(`尝试通过 ${availableBots.length} 个适配器发送消息到${groupType} ${groupId}`);
|
|
589
|
+
|
|
590
|
+
// 尝试所有可用机器人发送
|
|
591
|
+
for (const bot of availableBots) {
|
|
592
|
+
try {
|
|
593
|
+
await bot.sendMessage(groupId, message);
|
|
594
|
+
logger.debug(`已通过 ${bot.platform} 适配器发送消息到${groupType} ${groupId}`);
|
|
595
|
+
return; // 发送成功则退出循环
|
|
596
|
+
} catch (error) {
|
|
597
|
+
lastError = error;
|
|
598
|
+
logger.warn(`通过 ${bot.platform} 适配器发送消息失败: ${error.message}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
throw lastError || new Error(`所有适配器都无法发送消息到${groupType} ${groupId}`);
|
|
603
|
+
}
|
|
604
|
+
__name(sendToGroup, "sendToGroup");
|
|
605
|
+
|
|
606
|
+
// 手动检查今日活动命令
|
|
607
|
+
ctx.command("活动查询", "手动检查今日活动").action(async () => {
|
|
608
|
+
logger.debug("手动执行活动检查命令");
|
|
609
|
+
await updateActivityData();
|
|
610
|
+
const result = `检查完成!\n车队平台今日活动: ${todayActivities.length} 个\nTMP今日参与活动: ${todayTMPEvents.length} 个`;
|
|
611
|
+
logger.debug(result);
|
|
612
|
+
return result;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// 查看调试信息命令
|
|
616
|
+
ctx.command("活动DEBUG", "查看插件调试信息").action(() => {
|
|
617
|
+
const state = {
|
|
618
|
+
todayActivities: todayActivities.length,
|
|
619
|
+
todayTMPEvents: todayTMPEvents.length,
|
|
620
|
+
sentReminders: sentReminders.size,
|
|
621
|
+
timers: timers.length,
|
|
622
|
+
config: {
|
|
623
|
+
debugMode: cfg.debugMode,
|
|
624
|
+
logApiResponses: cfg.logApiResponses,
|
|
625
|
+
logTimingDetails: cfg.logTimingDetails,
|
|
626
|
+
logActivityMatching: cfg.logActivityMatching,
|
|
627
|
+
logMessageSending: cfg.logMessageSending
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
let message = "📊 TMP-BOT 插件调试信息:\n";
|
|
632
|
+
message += `• 今日活动: ${state.todayActivities} 个\n`;
|
|
633
|
+
message += `• TMP活动: ${state.todayTMPEvents} 个\n`;
|
|
634
|
+
message += `• 已发送提醒: ${state.sentReminders} 个\n`;
|
|
635
|
+
message += `• 活跃定时器: ${state.timers} 个\n`;
|
|
636
|
+
message += `• 调试模式: ${state.config.debugMode ? "✅ 开启" : "❌ 关闭"}\n`;
|
|
637
|
+
message += `• 日志选项: API=${state.config.logApiResponses ? "✅" : "❌"}, 定时=${state.config.logTimingDetails ? "✅" : "❌"}, 匹配=${state.config.logActivityMatching ? "✅" : "❌"}, 消息=${state.config.logMessageSending ? "✅" : "❌"}`;
|
|
638
|
+
|
|
639
|
+
return message;
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// 手动重置数据命令
|
|
643
|
+
ctx.command("重置数据", "手动重置今日活动数据").action(() => {
|
|
644
|
+
logger.debug("手动执行数据重置命令");
|
|
645
|
+
resetDailyData();
|
|
646
|
+
return "✅ 今日活动数据已重置完成!";
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// 插件卸载时清理资源
|
|
650
|
+
ctx.on("dispose", () => {
|
|
651
|
+
logger.debug("插件卸载,开始清理资源");
|
|
652
|
+
todayActivities = [];
|
|
653
|
+
todayTMPEvents = [];
|
|
654
|
+
sentReminders.clear();
|
|
655
|
+
// 清理所有定时器
|
|
656
|
+
timers.forEach((timer) => {
|
|
657
|
+
clearTimeout(timer);
|
|
658
|
+
clearInterval(timer);
|
|
659
|
+
});
|
|
660
|
+
timers.length = 0;
|
|
661
|
+
logger.debug("资源清理完成");
|
|
662
|
+
});
|
|
663
|
+
}
|
|
75
664
|
}
|
|
665
|
+
__name(apply, "apply");
|
|
666
|
+
0 && (module.exports = {
|
|
667
|
+
Config,
|
|
668
|
+
apply,
|
|
669
|
+
name
|
|
670
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-ets2-tools-tmp",
|
|
3
|
-
"description": "欧卡2
|
|
4
|
-
"version": "0.0
|
|
3
|
+
"description": "欧卡2 TMP在线查询、车队平台查询及活动提醒",
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"opwop <slhp1013@qq.com>",
|
|
7
7
|
"bot_actions <168329908@qq.com>"
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"plugin",
|
|
22
22
|
"ETS2",
|
|
23
23
|
"欧卡2",
|
|
24
|
-
"欧洲卡车模拟"
|
|
24
|
+
"欧洲卡车模拟",
|
|
25
|
+
"车队平台"
|
|
25
26
|
],
|
|
26
27
|
"koishi": {
|
|
27
28
|
"service": {
|