koishi-plugin-ets2-tools-tmp 3.3.61 → 3.3.63

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.
@@ -1,14 +1,5 @@
1
1
  const changePointV1 = require("./changePointV1");
2
- const changePointV2 = require("./changePointV2");
3
2
 
4
3
  module.exports = async (ctx, cfg, session, target, changeType, quantity, reason) => {
5
- const { platformVersion } = cfg.mainSettings?.settings || {};
6
- const platform = (platformVersion || "v1").toLowerCase();
7
-
8
- switch (platform) {
9
- case "v2":
10
- return await changePointV2(ctx, cfg, session, target, changeType, quantity);
11
- default:
12
- return await changePointV1(ctx, cfg, session, target, changeType, quantity, reason);
13
- }
4
+ return await changePointV1(ctx, cfg, session, target, changeType, quantity, reason);
14
5
  };
@@ -1,10 +1,6 @@
1
1
  const queryPointV1 = require("./queryPointV1");
2
- const queryPointV2 = require("./queryPointV2");
3
2
 
4
3
  module.exports = async (ctx, cfg, session, targetQQ) => {
5
- const { platformVersion } = cfg.mainSettings?.settings || {};
6
- const platform = (platformVersion || "v1").toLowerCase();
7
-
8
4
  let queryQQ = targetQQ;
9
5
  if (!queryQQ) {
10
6
  queryQQ = session.userId;
@@ -29,10 +25,5 @@ module.exports = async (ctx, cfg, session, targetQQ) => {
29
25
  }
30
26
  }
31
27
 
32
- switch (platform) {
33
- case "v2":
34
- return await queryPointV2(ctx, cfg, queryQQ);
35
- default:
36
- return await queryPointV1(ctx, cfg, queryQQ);
37
- }
28
+ return await queryPointV1(ctx, cfg, queryQQ);
38
29
  };
@@ -1,20 +1,5 @@
1
1
  const resetPasswordV1 = require("./resetPasswordV1");
2
- const resetPasswordV2 = require("./resetPasswordV2");
3
2
 
4
3
  module.exports = async (ctx, cfg, session, targetTeamId, password) => {
5
- const { platformVersion } = cfg.mainSettings?.settings || {};
6
- const platform = (platformVersion || "v1").toLowerCase();
7
-
8
- switch (platform) {
9
- case "v2": {
10
- let actualPassword = password;
11
- if (targetTeamId && !password && !/^\d+$/.test(targetTeamId)) {
12
- actualPassword = targetTeamId;
13
- targetTeamId = undefined;
14
- }
15
- return await resetPasswordV2(ctx, cfg, session, actualPassword);
16
- }
17
- default:
18
- return await resetPasswordV1(ctx, cfg, session, targetTeamId, password);
19
- }
4
+ return await resetPasswordV1(ctx, cfg, session, targetTeamId, password);
20
5
  };
@@ -5,7 +5,6 @@ exports.ActivityService = void 0;
5
5
  const koishi_1 = require("koishi");
6
6
 
7
7
  const v1Methods = require('./tmpActivityService/tmpActivityServiceV1');
8
- const v2Methods = require('./tmpActivityService/tmpActivityServiceV2');
9
8
 
10
9
  class ActivityService {
11
10
  constructor(ctx, config) {
@@ -18,12 +17,7 @@ class ActivityService {
18
17
  this.timers = [];
19
18
  this.logger = this.initLogger();
20
19
 
21
- const platformVersion = (this.cfg.mainSettings?.platformVersion || "v1").toLowerCase();
22
- if (platformVersion === "v2") {
23
- Object.assign(this, v2Methods);
24
- } else {
25
- Object.assign(this, v1Methods);
26
- }
20
+ Object.assign(this, v1Methods);
27
21
  }
28
22
 
29
23
  initLogger() {
@@ -21,11 +21,44 @@ const userGroup = {
21
21
  'Add-On Team': '附加组件团队',
22
22
  'Game Moderator': '游戏管理员'
23
23
  };
24
+ /**
25
+ * 渲染倍率。1 = 原始像素(偏糊),2 = 视网膜屏清晰度。
26
+ * 代价:像素面积 ×4、单张渲染内存与耗时上升;配合 JPEG 编码后体积反而比 1 倍 PNG 更小。
27
+ */
28
+ const RENDER_SCALE = 2;
29
+ /**
30
+ * 遥测数据超过这个时长即视为过期,整块不显示(默认 2 分钟)
31
+ */
32
+ const TELEMETRY_MAX_AGE_MS = 2 * 60 * 1000;
33
+ /**
34
+ * 遥测记录距今的时间差(毫秒)。
35
+ * updateTime 是 UTC 字面量、updateTimeCn 是北京时间字面量,两者都能换算成绝对时间,
36
+ * 因此与机器人所在时区无关;两个字段都取不到时返回 null。
37
+ */
38
+ function telemetryRecordAge(player) {
39
+ const asUtc = (s) => {
40
+ const t = Date.parse(String(s).replace(' ', 'T') + 'Z');
41
+ return Number.isNaN(t) ? null : t;
42
+ };
43
+ const asCn = (s) => {
44
+ const t = Date.parse(String(s).replace(' ', 'T') + '+08:00');
45
+ return Number.isNaN(t) ? null : t;
46
+ };
47
+ let ts = player.updateTime ? asUtc(player.updateTime) : null;
48
+ if (ts === null && player.updateTimeCn) ts = asCn(player.updateTimeCn);
49
+ if (ts === null) return null;
50
+ return Date.now() - ts;
51
+ }
24
52
  /**
25
53
  * 查询玩家信息
26
54
  */
27
55
  module.exports = async (ctx, cfg, session, tmpId) => {
28
56
  const { vtcId } = cfg.tmpActivityService?.api || {};
57
+ // 车队平台配置(遥测 / 积分共用),提前取出供多处使用
58
+ const mainSettingValues = cfg.mainSettings?.settings || {};
59
+ const platformUrl = mainSettingValues.url;
60
+ const platformToken = mainSettingValues.token;
61
+ const logOutput = mainSettingValues.logOutput;
29
62
  if (!ctx.puppeteer) {
30
63
  return '未启用 puppeteer 服务';
31
64
  }
@@ -55,16 +88,74 @@ module.exports = async (ctx, cfg, session, tmpId) => {
55
88
  }
56
89
  tmpId = guildBindData.tmp_id;
57
90
  }
58
- // 查询玩家信息
59
- let playerInfo = await evmOpenApi.playerInfo(ctx.http, tmpId);
91
+ // 并行请求玩家信息与线上状态(互不依赖,避免串行等待)
92
+ const [playerInfo, playerMapInfo] = await Promise.all([
93
+ evmOpenApi.playerInfo(ctx.http, tmpId),
94
+ truckyAppApi.online(ctx.http, tmpId)
95
+ ]);
60
96
  if (playerInfo.error && playerInfo.code === 10001) {
61
97
  return '玩家不存在';
62
98
  }
63
99
  else if (playerInfo.error) {
64
100
  return '查询玩家信息失败,请重试';
65
101
  }
66
- // 查询线上信息
67
- let playerMapInfo = await truckyAppApi.online(ctx.http, tmpId);
102
+ // ===== 车队平台实时遥测(在线 + 本队成员) =====
103
+ // 触发条件:玩家当前在线,且其 vtcId 与 tmpActivityService.api.vtcId 一致
104
+ const isOwnVtcMember = vtcId !== undefined && vtcId !== null && vtcId !== '' &&
105
+ playerInfo.data.vtcId !== undefined && playerInfo.data.vtcId !== null &&
106
+ String(playerInfo.data.vtcId) === String(vtcId);
107
+ // 玩家离线时没有实时遥测可看,直接不发请求
108
+ const isPlayerOnline = !!(playerMapInfo && !playerMapInfo.error && playerMapInfo.data && playerMapInfo.data.online);
109
+ // 请求提前发起,与下方的积分查询 / 翻译 / Steam时长并行,避免串行等待
110
+ const telemetryPromise = (platformUrl && platformToken && isPlayerOnline && isOwnVtcMember)
111
+ ? (async () => {
112
+ const telemetryUrl = `https://${platformUrl}/api/telemetry/bot/live?tmpId=${tmpId}&token=${platformToken}`;
113
+ try {
114
+ if (logOutput) {
115
+ ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的V1.0实时遥测`);
116
+ ctx.logger.info(`[TMP_BOT] 请求V1.0遥测接口: ${telemetryUrl.replace(platformToken, "***")}`);
117
+ }
118
+ const telemetryResponse = await ctx.http.get(telemetryUrl, { timeout: 10000 });
119
+ const playerList = telemetryResponse?.data?.players;
120
+ if (logOutput) {
121
+ ctx.logger.info(`[TMP_BOT] V1.0遥测响应: code=${telemetryResponse?.code}, online=${telemetryResponse?.data?.online}, players=${Array.isArray(playerList) ? playerList.length : 0}`);
122
+ }
123
+ if (telemetryResponse && telemetryResponse.code === 0 && Array.isArray(playerList) && playerList.length > 0) {
124
+ // 优先取与查询 tmpId 一致的那条,避免接口返回多人的情况
125
+ const matched = playerList.find(p => p && String(p.tmpId) === String(tmpId)) || playerList[0];
126
+ // players 为空数组(无遥测记录)时不返回,渲染页会整块隐藏
127
+ if (!matched || typeof matched !== 'object') {
128
+ return null;
129
+ }
130
+ // 记录时间距今超过 TELEMETRY_MAX_AGE_MS 视为过期,同样不显示
131
+ const ageMs = telemetryRecordAge(matched);
132
+ if (ageMs === null || ageMs > TELEMETRY_MAX_AGE_MS) {
133
+ if (logOutput) {
134
+ ctx.logger.info(`[TMP_BOT] V1.0遥测记录已过期(${ageMs === null ? '无有效时间戳' : Math.round(ageMs / 1000) + ' 秒前'}),不显示遥测板块`);
135
+ }
136
+ return null;
137
+ }
138
+ return {
139
+ online: telemetryResponse.data.online === 1,
140
+ generatedAt: telemetryResponse.data.generatedAt || null,
141
+ player: matched
142
+ };
143
+ }
144
+ if (logOutput) {
145
+ ctx.logger.info(`[TMP_BOT] V1.0遥测接口未返回记录(players 为空),不显示遥测板块`);
146
+ }
147
+ return null;
148
+ } catch (error) {
149
+ ctx.logger.error(`遥测查询过程出错: ${error}`);
150
+ return null;
151
+ }
152
+ })()
153
+ : null;
154
+ // Steam游戏时长请求提前发起,与下方积分查询/翻译并行执行
155
+ const steamApiKey = cfg.steamApi?.key;
156
+ const steamGameTimePromise = (cfg.commands?.tmpQueryGameTime && steamApiKey)
157
+ ? evmOpenApi.steamGameTime(ctx.http, steamApiKey, playerInfo.data.steamId)
158
+ : null;
68
159
  // 拼接数据
69
160
  let data = {};
70
161
  data.showAvatar = cfg.tmpQuery?.showAvatar !== false;
@@ -94,8 +185,12 @@ module.exports = async (ctx, cfg, session, tmpId) => {
94
185
  if (data.isOnline) {
95
186
  data.onlineStatus = '在线';
96
187
  data.onlineServerName = playerMapInfo.data.serverDetails.name;
97
- data.onlineCountry = await baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.country);
98
- data.onlineCity = await baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.realName);
188
+ const [onlineCountry, onlineCity] = await Promise.all([
189
+ baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.country),
190
+ baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.realName)
191
+ ]);
192
+ data.onlineCountry = onlineCountry;
193
+ data.onlineCity = onlineCity;
99
194
  data.onlineX = playerMapInfo.data.x;
100
195
  data.onlineY = playerMapInfo.data.y;
101
196
  data.onlineMapType = playerMapInfo.data.serverDetails.id === 50 ? 'promods' : 'ets';
@@ -119,68 +214,52 @@ module.exports = async (ctx, cfg, session, tmpId) => {
119
214
  const isInAllowedGroup = allowedGroups.some(g => currentGroupId.includes(g));
120
215
 
121
216
  if (isInAllowedGroup && playerInfo.data.vtcId == vtcId) {
122
- const { url, token, logOutput, platformVersion } = cfg.mainSettings?.settings || {};
123
- const platform = (platformVersion || "v1").toLowerCase();
124
217
  try {
125
- if (platform === "v2") {
126
- const baseUrl = url;
127
- const userInfoUrl = `https://${baseUrl}/members/get?token=${token}&tmpId=${tmpId}`;
128
- if (logOutput) {
129
- ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的V2.0积分`);
130
- ctx.logger.info(`[TMP_BOT] 请求V2.0用户信息: ${userInfoUrl}`);
131
- }
132
- const userInfoResponse = await ctx.http.get(userInfoUrl);
133
- if (logOutput) {
134
- ctx.logger.info(`[TMP_BOT] V2.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
135
- }
136
- if (userInfoResponse.code === 200 && userInfoResponse.data) {
137
- data.rewardPoints = userInfoResponse.data.point || 0;
138
- }
139
- } else {
140
- if (logOutput) {
141
- ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的V1.0积分`);
142
- }
143
- const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&state=0&teamRole=`;
144
- if (logOutput) {
145
- ctx.logger.info(`[TMP_BOT] 请求V1.0用户信息: ${userInfoUrl}`);
146
- }
147
- const userInfoResponse = await ctx.http.post(userInfoUrl);
148
- if (logOutput) {
149
- ctx.logger.info(`[TMP_BOT] V1.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
150
- }
151
- const userList = userInfoResponse.page?.list || [];
152
- const userInfo = userList[0];
153
- data.rewardPoints = userInfo.rewardPoints || 0;
218
+ if (logOutput) {
219
+ ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的积分`);
154
220
  }
221
+ const userInfoUrl = `https://${platformUrl}/api/user/info/list?token=${platformToken}&page=0&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&state=0&teamRole=`;
222
+ if (logOutput) {
223
+ ctx.logger.info(`[TMP_BOT] 请求用户信息: ${userInfoUrl}`);
224
+ }
225
+ const userInfoResponse = await ctx.http.post(userInfoUrl);
226
+ if (logOutput) {
227
+ ctx.logger.info(`[TMP_BOT] 用户信息响应: ${JSON.stringify(userInfoResponse)}`);
228
+ }
229
+ const userList = userInfoResponse.page?.list || [];
230
+ const userInfo = userList[0];
231
+ data.rewardPoints = userInfo.rewardPoints || 0;
155
232
  } catch (error) {
156
233
  ctx.logger.error(`积分查询过程出错: ${error}`);
157
234
  }
158
235
  }
159
236
  }
160
- // 查询Steam游戏时长
237
+ // 等待V1遥测结果(请求已提前发起,与积分查询/Steam时长并行)
238
+ data.telemetry = telemetryPromise ? await telemetryPromise : null;
239
+ // 等待Steam游戏时长结果(请求已提前发起,与积分查询/翻译并行)
161
240
  data.ets2GameTime = null;
162
241
  data.atsGameTime = null;
163
- if (cfg.commands?.tmpQueryGameTime) {
164
- const steamApiKey = cfg.steamApi?.key;
165
- if (steamApiKey) {
166
- const steamId = playerInfo.data.steamId;
167
- const gameTimes = await evmOpenApi.steamGameTime(ctx.http, steamApiKey, steamId);
168
- data.ets2GameTime = gameTimes.ets2;
169
- data.atsGameTime = gameTimes.ats;
170
- }
242
+ if (steamGameTimePromise) {
243
+ const gameTimes = await steamGameTimePromise;
244
+ data.ets2GameTime = gameTimes.ets2;
245
+ data.atsGameTime = gameTimes.ats;
171
246
  }
172
247
  let page;
173
248
  try {
174
249
  page = await ctx.puppeteer.page();
175
- await page.setViewport({ width: 520, height: 1000 });
250
+ // deviceScaleFactor=RENDER_SCALE:按 RENDER_SCALE 倍像素渲染,客户端放大后依然清晰
251
+ await page.setViewport({ width: 520, height: 1000, deviceScaleFactor: RENDER_SCALE });
176
252
  await page.goto(`file:///${resolve(__dirname, '../../resource/query.html')}`);
177
253
  await page.evaluate(`init(${JSON.stringify(data)})`);
178
254
  await common.sleep(100);
179
255
  await page.waitForNetworkIdle();
180
256
  const element = await page.$("#container");
257
+ // 用 JPEG 编码:同一清晰度下体积远小于 PNG(卡片背景不透明,无需 alpha 通道)
181
258
  return (segment.image(await element.screenshot({
259
+ type: "jpeg",
260
+ quality: 90,
182
261
  encoding: "binary"
183
- }), "image/jpg"));
262
+ }), "image/jpeg"));
184
263
  }
185
264
  catch {
186
265
  return '渲染异常,请重试';
@@ -51,16 +51,22 @@ module.exports = async (ctx, cfg, session, tmpId) => {
51
51
  }
52
52
  tmpId = guildBindData.tmp_id;
53
53
  }
54
- // 查询玩家信息
55
- let playerInfo = await evmOpenApi.playerInfo(ctx.http, tmpId);
54
+ // 并行请求玩家信息与线上状态(互不依赖,避免串行等待)
55
+ const [playerInfo, playerMapInfo] = await Promise.all([
56
+ evmOpenApi.playerInfo(ctx.http, tmpId),
57
+ truckyAppApi.online(ctx.http, tmpId)
58
+ ]);
56
59
  if (playerInfo.error && playerInfo.code === 10001) {
57
60
  return '玩家不存在';
58
61
  }
59
62
  else if (playerInfo.error) {
60
63
  return '玩家信息查询失败,请重试';
61
64
  }
62
- // 查询线上信息
63
- let playerMapInfo = await truckyAppApi.online(ctx.http, tmpId);
65
+ // Steam游戏时长请求提前发起,与下方积分查询并行执行
66
+ const steamApiKey = cfg.steamApi?.key;
67
+ const steamGameTimePromise = (cfg.commands?.tmpQueryGameTime && steamApiKey)
68
+ ? evmOpenApi.steamGameTime(ctx.http, steamApiKey, playerInfo.data.steamId)
69
+ : null;
64
70
  // 拼接消息模板
65
71
  let message = '';
66
72
  if (cfg.tmpQuery?.showAvatar) {
@@ -89,40 +95,23 @@ module.exports = async (ctx, cfg, session, tmpId) => {
89
95
  const isInAllowedGroup = allowedGroups.some(g => currentGroupId.includes(g));
90
96
 
91
97
  if (isInAllowedGroup && playerInfo.data.vtcId == vtcId) {
92
- const { url, token, logOutput, platformVersion } = cfg.mainSettings?.settings || {};
93
- const platform = (platformVersion || "v1").toLowerCase();
98
+ const { url, token, logOutput } = cfg.mainSettings?.settings || {};
94
99
  try {
95
100
  let rewardPoints = 0;
96
- if (platform === "v2") {
97
- const baseUrl = url;
98
- const userInfoUrl = `https://${baseUrl}/members/get?token=${token}&tmpId=${tmpId}`;
99
- if (logOutput) {
100
- ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的V2.0积分`);
101
- ctx.logger.info(`[TMP_BOT] 请求V2.0用户信息: ${userInfoUrl}`);
102
- }
103
- const userInfoResponse = await ctx.http.get(userInfoUrl);
104
- if (logOutput) {
105
- ctx.logger.info(`[TMP_BOT] V2.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
106
- }
107
- if (userInfoResponse.code === 200 && userInfoResponse.data) {
108
- rewardPoints = userInfoResponse.data.point || 0;
109
- }
110
- } else {
111
- if (logOutput) {
112
- ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的V1.0积分`);
113
- }
114
- const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&state=0&teamRole=`;
115
- if (logOutput) {
116
- ctx.logger.info(`[TMP_BOT] 请求V1.0用户信息: ${userInfoUrl}`);
117
- }
118
- const userInfoResponse = await ctx.http.post(userInfoUrl);
119
- if (logOutput) {
120
- ctx.logger.info(`[TMP_BOT] V1.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
121
- }
122
- const userList = userInfoResponse.page?.list || [];
123
- const userInfo = userList[0];
124
- rewardPoints = userInfo.rewardPoints || 0;
101
+ if (logOutput) {
102
+ ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的积分`);
125
103
  }
104
+ const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&state=0&teamRole=`;
105
+ if (logOutput) {
106
+ ctx.logger.info(`[TMP_BOT] 请求用户信息: ${userInfoUrl}`);
107
+ }
108
+ const userInfoResponse = await ctx.http.post(userInfoUrl);
109
+ if (logOutput) {
110
+ ctx.logger.info(`[TMP_BOT] 用户信息响应: ${JSON.stringify(userInfoResponse)}`);
111
+ }
112
+ const userList = userInfoResponse.page?.list || [];
113
+ const userInfo = userList[0];
114
+ rewardPoints = userInfo.rewardPoints || 0;
126
115
  message += `\n⭐ 当前车队积分: ${rewardPoints}`;
127
116
  } catch (error) {
128
117
  ctx.logger.error(`积分查询过程出错: ${error}`);
@@ -171,30 +160,20 @@ module.exports = async (ctx, cfg, session, tmpId) => {
171
160
  if (playerMapInfo && !playerMapInfo.error) {
172
161
  message += '\n📶在线状态: ' + (playerMapInfo.data.online ? `在线🟢 (${playerMapInfo.data.serverDetails.name})` : '离线⚫');
173
162
  if (playerMapInfo.data.online) {
174
- message += '\n🌍线上位置: ';
175
- message += await baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.country);
176
- message += ' - ';
177
- message += await baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.realName);
163
+ const [onlineCountry, onlineCity] = await Promise.all([
164
+ baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.country),
165
+ baiduTranslate(ctx, cfg, playerMapInfo.data.location.poi.realName)
166
+ ]);
167
+ message += '\n🌍线上位置: ' + onlineCountry + ' - ' + onlineCity;
178
168
  }
179
169
  else if (playerInfo.data.lastOnlineTime) {
180
170
  message += '\n📶上次在线: ' + dayjs(playerInfo.data.lastOnlineTime).fromNow(false);
181
171
  }
182
172
  }
183
- const steamID = playerInfo.data.steamId;
184
-
185
- // 查询Steam游戏时长
186
- async function getSteamGameTimes(steamId) {
187
- const steamApiKey = cfg.steamApi?.key;
188
- if (!steamApiKey) {
189
- return { ets2: null, ats: null };
190
- }
191
- return evmOpenApi.steamGameTime(ctx.http, steamApiKey, steamId);
192
- }
193
-
194
- // 获取欧卡和美卡游戏时长
173
+ // 获取欧卡和美卡游戏时长(请求已提前发起,与积分查询/翻译并行)
195
174
  if (cfg.commands?.tmpQueryGameTime) {
196
- const gameTimes = await getSteamGameTimes(steamID);
197
-
175
+ const gameTimes = (await steamGameTimePromise) || { ets2: null, ats: null };
176
+
198
177
  // 添加游戏时长信息
199
178
  if (gameTimes.ets2) {
200
179
  message += '\n🎮欧卡游戏时长: ' + gameTimes.ets2;
package/lib/index.js CHANGED
@@ -21,7 +21,6 @@ const commands = {
21
21
  resetPassword: require('./command/ets-app/resetPassword/resetPassword'),
22
22
  queryPoint: require('./command/ets-app/queryPoint/queryPoint'),
23
23
  changePoint: require('./command/ets-app/changePoint/changePoint'),
24
- addMember: require('./command/ets-app/addMember/addMemberV2'),
25
24
  pointRanking: require('./command/ets-app/pointRanking/pointRanking'),
26
25
  tmpVtc: require('./command/tmpVtc'),
27
26
  tmpVtcOnline: require('./command/tmpVtcOnline/tmpVtcOnline'),
@@ -88,7 +87,6 @@ exports.Config = koishi_1.Schema.intersect([
88
87
  mainSettings: koishi_1.Schema.boolean().default(false).description('是否启用车队平台积分查询功能'),
89
88
  resetPassword: koishi_1.Schema.boolean().default(false).description('是否启用车队平台重置密码功能'),
90
89
  changePoint: koishi_1.Schema.boolean().default(false).description('是否启用车队平台积分修改功能'),
91
- addMember: koishi_1.Schema.boolean().default(false).description('是否启用车队平台新增成员功能'),
92
90
  tmpActivityService: koishi_1.Schema.boolean().default(false).description('是否启用车队活动查询'),
93
91
  tmpVersionCheck: koishi_1.Schema.boolean().default(false).description('是否启用TMP版本更新查询'),
94
92
  tmpQueryGameTime: koishi_1.Schema.boolean().default(true).description('是否启用游戏时长查询功能'),
@@ -136,18 +134,9 @@ exports.Config = koishi_1.Schema.intersect([
136
134
  mainSettings: koishi_1.Schema.object({
137
135
  settings: koishi_1.Schema.object({
138
136
  Name: koishi_1.Schema.string().description("车队名称"),
139
- platformVersion: koishi_1.Schema.union([
140
- koishi_1.Schema.const("v1").description("车队平台V1.0"),
141
- koishi_1.Schema.const("v2").description("车队平台V2.0")
142
- ]).default("v1").description("车队平台版本"),
143
137
  url: koishi_1.Schema.string().description("API服务器地址(不含协议)"),
144
138
  token: koishi_1.Schema.string().description("API认证令牌"),
145
- logOutput: koishi_1.Schema.boolean().description("是否输出日志").default(true),
146
- mailEnabled: koishi_1.Schema.boolean().description("【V2.0】重置密码后发送邮件(需启用 adapter-mail)").default(false),
147
- mailTo: koishi_1.Schema.string().description("【V2.0】重置密码通知邮箱(自定义邮箱地址)").default(""),
148
- mailSubject: koishi_1.Schema.string().description("【V2.0】重置密码邮件标题").default("重置密码通知"),
149
- mailTemplate: koishi_1.Schema.string().description("【V2.0】重置密码邮件内容模板,支持变量:{uid} {qq} {password} {psw}").default("我们已将您平台的密码进行重置,您的账号:{uid},新的密码为:{psw} 请妥善保管好您的密码,以防泄露"),
150
- mailFromName: koishi_1.Schema.string().description("【V2.0】重置密码邮件发件人名字(覆盖 adapter-mail 的 name)").default(""),
139
+ logOutput: koishi_1.Schema.boolean().description("是否输出日志").default(true)
151
140
  }).description("车队平台配置")
152
141
  }).description("车队平台配置"),
153
142
  resetPassword: koishi_1.Schema.object({
@@ -160,12 +149,6 @@ exports.Config = koishi_1.Schema.intersect([
160
149
  adminUsers: koishi_1.Schema.array(koishi_1.Schema.string()).description("管理员用户ID(拥有积分修改权限)").default([])
161
150
  })
162
151
  }).description("积分修改功能配置"),
163
- addMember: koishi_1.Schema.object({
164
- settings: koishi_1.Schema.object({
165
- adminUsers: koishi_1.Schema.array(koishi_1.Schema.string()).description("管理员用户ID(拥有新增成员权限)").default([]),
166
- teamNumberGenerateEnable: koishi_1.Schema.boolean().default(true).description("自动生成车队编号(关闭后需手动输入车队编号)")
167
- })
168
- }).description("新增成员功能配置"),
169
152
  tmpActivityService: koishi_1.Schema.object({
170
153
  api: koishi_1.Schema.object({
171
154
  vtcId: koishi_1.Schema.string().description("VTC ID(用于TMP API)").default("")
@@ -403,10 +386,9 @@ function registerBaseCommands(ctx, cfg) {
403
386
 
404
387
  if (cfg.commands?.resetPassword) {
405
388
  ctx.command(`重置密码 [targetTeamId:string] [password:string]`, "重置欧卡车队平台密码")
406
- .usage("V1.0使用teamId,V2.0使用QQ号;管理员可用 uid=12345;V2.0可指定新密码")
389
+ .usage("使用车队编号(teamId)指定目标;管理员可用 uid=12345")
407
390
  .example(`重置密码 - 重置自己的密码`)
408
391
  .example(`重置密码 789 - 管理员重置指定teamId的密码`)
409
- .example(`重置密码 123456 Abc123def4 - V2.0指定QQ与新密码`)
410
392
  .action(async ({ session }, targetTeamId, password) => await commands.resetPassword(ctx, cfg, session, targetTeamId, password));
411
393
  }
412
394
 
@@ -420,20 +402,12 @@ function registerBaseCommands(ctx, cfg) {
420
402
 
421
403
  if (cfg.commands?.changePoint) {
422
404
  ctx.command(`积分修改 <target:string> <changeType:string> <quantity:string> <reason:string>`, "修改欧卡车队平台积分")
423
- .usage("管理员专用。changeType: 增加 或 减少。target可输入车队编号(V1)或UID(V2)或@群成员。reason为备注原因(V1必填)")
405
+ .usage("管理员专用。changeType: 增加 或 减少。target可输入车队编号或@群成员。reason为备注原因")
424
406
  .example(`积分修改 @某人 增加 10 活动奖励 - 增加@某人10积分,备注活动奖励`)
425
407
  .example(`积分修改 5 减少 5 违规扣分 - 减少车队编号5的用户5积分,备注违规扣分`)
426
408
  .action(async ({ session }, target, changeType, quantity, reason) => await commands.changePoint(ctx, cfg, session, target, changeType, quantity, reason));
427
409
  }
428
410
 
429
- if (cfg.commands?.addMember) {
430
- ctx.command(`新增成员 <tmpId:string> <qq:string> [teamNumber:string]`, "新增车队平台成员")
431
- .usage("管理员专用。qq可输入QQ号或@群成员。若关闭自动生成车队编号则需手动输入teamNumber")
432
- .example(`新增成员 12345 79887143 - 自动生成车队编号`)
433
- .example(`新增成员 12345 @某人 5 - 手动指定车队编号为5`)
434
- .action(async ({ session }, tmpId, qq, teamNumber) => await commands.addMember(ctx, cfg, session, tmpId, qq, teamNumber));
435
- }
436
-
437
411
  ctx.command('规则查询')
438
412
  .action(async () => 'TruckersMP官方规则链接:https://truckersmp.com/knowledge-base/article/746');
439
413
  }
@@ -249,6 +249,27 @@
249
249
  // 轨迹的分段(断点判定)与抽稀已在 Node 侧完成,这里直接渲染
250
250
  render(data.segments || [], data.mapType);
251
251
  }
252
+ // 量出上下两张悬浮卡片(玩家卡片 / 底部统计栏)真实占用的高度,作为 fitBounds 的留白。
253
+ // 写死数字会随字号、名字长度、图例与「统计开始日期」提示的增减而失准,所以按真实布局测量。
254
+ function measureOverlayPadding(side, gap) {
255
+ side = side === undefined ? 60 : side;
256
+ gap = gap === undefined ? 14 : gap;
257
+ const box = document.getElementById('container').getBoundingClientRect();
258
+ const header = document.querySelector('.header');
259
+ const bar = document.querySelector('.status-bar');
260
+ let top = header ? Math.max(0, header.getBoundingClientRect().bottom - box.top) + gap : 0;
261
+ let bottom = bar ? Math.max(0, box.bottom - bar.getBoundingClientRect().top) + gap : 0;
262
+
263
+ // 兜底:万一下方留白过大,按比例压缩,至少给轨迹留 80px 高度
264
+ const maxY = box.height - 80;
265
+ if (top + bottom > maxY) {
266
+ const k = maxY / (top + bottom);
267
+ top = Math.round(top * k);
268
+ bottom = Math.round(bottom * k);
269
+ }
270
+ return { side: side, top: top, bottom: bottom };
271
+ }
272
+
252
273
  function render(lines, mapType) {
253
274
  const config = mapConfig[mapType];
254
275
 
@@ -324,11 +345,12 @@
324
345
  }).addTo(map);
325
346
  });
326
347
 
327
- // 自动适应(预留上下 UI 卡片区域;容器为正方形,尽量让正方形瓦片图占满)
348
+ // 自动适应(按上下两张悬浮卡片的实测高度预留留白,轨迹不会被卡片压住)
328
349
  if (allLatlngs.length > 0) {
350
+ const pad = measureOverlayPadding();
329
351
  map.fitBounds(L.latLngBounds(allLatlngs), {
330
- paddingTopLeft: [60, 95],
331
- paddingBottomRight: [60, 65]
352
+ paddingTopLeft: [pad.side, pad.top],
353
+ paddingBottomRight: [pad.side, pad.bottom]
332
354
  });
333
355
  }
334
356
  }