koishi-plugin-ets2-tools-tmp 3.3.62 → 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.
- package/lib/command/ets-app/changePoint/changePoint.js +1 -10
- package/lib/command/ets-app/queryPoint/queryPoint.js +1 -10
- package/lib/command/ets-app/resetPassword/resetPassword.js +1 -16
- package/lib/command/tmpActivityService.js +1 -7
- package/lib/command/tmpQuery/tmpQueryImg.js +106 -33
- package/lib/command/tmpQuery/tmpQueryText.js +14 -31
- package/lib/index.js +3 -29
- package/lib/resource/footprint.html +25 -3
- package/lib/resource/query.html +817 -151
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -66,6 +99,58 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
66
99
|
else if (playerInfo.error) {
|
|
67
100
|
return '查询玩家信息失败,请重试';
|
|
68
101
|
}
|
|
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;
|
|
69
154
|
// Steam游戏时长请求提前发起,与下方积分查询/翻译并行执行
|
|
70
155
|
const steamApiKey = cfg.steamApi?.key;
|
|
71
156
|
const steamGameTimePromise = (cfg.commands?.tmpQueryGameTime && steamApiKey)
|
|
@@ -129,44 +214,28 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
129
214
|
const isInAllowedGroup = allowedGroups.some(g => currentGroupId.includes(g));
|
|
130
215
|
|
|
131
216
|
if (isInAllowedGroup && playerInfo.data.vtcId == vtcId) {
|
|
132
|
-
const { url, token, logOutput, platformVersion } = cfg.mainSettings?.settings || {};
|
|
133
|
-
const platform = (platformVersion || "v1").toLowerCase();
|
|
134
217
|
try {
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
if (userInfoResponse.code === 200 && userInfoResponse.data) {
|
|
147
|
-
data.rewardPoints = userInfoResponse.data.point || 0;
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
if (logOutput) {
|
|
151
|
-
ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的V1.0积分`);
|
|
152
|
-
}
|
|
153
|
-
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&state=0&teamRole=`;
|
|
154
|
-
if (logOutput) {
|
|
155
|
-
ctx.logger.info(`[TMP_BOT] 请求V1.0用户信息: ${userInfoUrl}`);
|
|
156
|
-
}
|
|
157
|
-
const userInfoResponse = await ctx.http.post(userInfoUrl);
|
|
158
|
-
if (logOutput) {
|
|
159
|
-
ctx.logger.info(`[TMP_BOT] V1.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
|
|
160
|
-
}
|
|
161
|
-
const userList = userInfoResponse.page?.list || [];
|
|
162
|
-
const userInfo = userList[0];
|
|
163
|
-
data.rewardPoints = userInfo.rewardPoints || 0;
|
|
218
|
+
if (logOutput) {
|
|
219
|
+
ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的积分`);
|
|
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)}`);
|
|
164
228
|
}
|
|
229
|
+
const userList = userInfoResponse.page?.list || [];
|
|
230
|
+
const userInfo = userList[0];
|
|
231
|
+
data.rewardPoints = userInfo.rewardPoints || 0;
|
|
165
232
|
} catch (error) {
|
|
166
233
|
ctx.logger.error(`积分查询过程出错: ${error}`);
|
|
167
234
|
}
|
|
168
235
|
}
|
|
169
236
|
}
|
|
237
|
+
// 等待V1遥测结果(请求已提前发起,与积分查询/Steam时长并行)
|
|
238
|
+
data.telemetry = telemetryPromise ? await telemetryPromise : null;
|
|
170
239
|
// 等待Steam游戏时长结果(请求已提前发起,与积分查询/翻译并行)
|
|
171
240
|
data.ets2GameTime = null;
|
|
172
241
|
data.atsGameTime = null;
|
|
@@ -178,15 +247,19 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
178
247
|
let page;
|
|
179
248
|
try {
|
|
180
249
|
page = await ctx.puppeteer.page();
|
|
181
|
-
|
|
250
|
+
// deviceScaleFactor=RENDER_SCALE:按 RENDER_SCALE 倍像素渲染,客户端放大后依然清晰
|
|
251
|
+
await page.setViewport({ width: 520, height: 1000, deviceScaleFactor: RENDER_SCALE });
|
|
182
252
|
await page.goto(`file:///${resolve(__dirname, '../../resource/query.html')}`);
|
|
183
253
|
await page.evaluate(`init(${JSON.stringify(data)})`);
|
|
184
254
|
await common.sleep(100);
|
|
185
255
|
await page.waitForNetworkIdle();
|
|
186
256
|
const element = await page.$("#container");
|
|
257
|
+
// 用 JPEG 编码:同一清晰度下体积远小于 PNG(卡片背景不透明,无需 alpha 通道)
|
|
187
258
|
return (segment.image(await element.screenshot({
|
|
259
|
+
type: "jpeg",
|
|
260
|
+
quality: 90,
|
|
188
261
|
encoding: "binary"
|
|
189
|
-
}), "image/
|
|
262
|
+
}), "image/jpeg"));
|
|
190
263
|
}
|
|
191
264
|
catch {
|
|
192
265
|
return '渲染异常,请重试';
|
|
@@ -95,40 +95,23 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
95
95
|
const isInAllowedGroup = allowedGroups.some(g => currentGroupId.includes(g));
|
|
96
96
|
|
|
97
97
|
if (isInAllowedGroup && playerInfo.data.vtcId == vtcId) {
|
|
98
|
-
const { url, token, logOutput
|
|
99
|
-
const platform = (platformVersion || "v1").toLowerCase();
|
|
98
|
+
const { url, token, logOutput } = cfg.mainSettings?.settings || {};
|
|
100
99
|
try {
|
|
101
100
|
let rewardPoints = 0;
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
const userInfoUrl = `https://${baseUrl}/members/get?token=${token}&tmpId=${tmpId}`;
|
|
105
|
-
if (logOutput) {
|
|
106
|
-
ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的V2.0积分`);
|
|
107
|
-
ctx.logger.info(`[TMP_BOT] 请求V2.0用户信息: ${userInfoUrl}`);
|
|
108
|
-
}
|
|
109
|
-
const userInfoResponse = await ctx.http.get(userInfoUrl);
|
|
110
|
-
if (logOutput) {
|
|
111
|
-
ctx.logger.info(`[TMP_BOT] V2.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
|
|
112
|
-
}
|
|
113
|
-
if (userInfoResponse.code === 200 && userInfoResponse.data) {
|
|
114
|
-
rewardPoints = userInfoResponse.data.point || 0;
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
if (logOutput) {
|
|
118
|
-
ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的V1.0积分`);
|
|
119
|
-
}
|
|
120
|
-
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=0&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&state=0&teamRole=`;
|
|
121
|
-
if (logOutput) {
|
|
122
|
-
ctx.logger.info(`[TMP_BOT] 请求V1.0用户信息: ${userInfoUrl}`);
|
|
123
|
-
}
|
|
124
|
-
const userInfoResponse = await ctx.http.post(userInfoUrl);
|
|
125
|
-
if (logOutput) {
|
|
126
|
-
ctx.logger.info(`[TMP_BOT] V1.0用户信息响应: ${JSON.stringify(userInfoResponse)}`);
|
|
127
|
-
}
|
|
128
|
-
const userList = userInfoResponse.page?.list || [];
|
|
129
|
-
const userInfo = userList[0];
|
|
130
|
-
rewardPoints = userInfo.rewardPoints || 0;
|
|
101
|
+
if (logOutput) {
|
|
102
|
+
ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的积分`);
|
|
131
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;
|
|
132
115
|
message += `\n⭐ 当前车队积分: ${rewardPoints}`;
|
|
133
116
|
} catch (error) {
|
|
134
117
|
ctx.logger.error(`积分查询过程出错: ${error}`);
|
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("
|
|
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
|
|
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
|
-
//
|
|
348
|
+
// 自动适应(按上下两张悬浮卡片的实测高度预留留白,轨迹不会被卡片压住)
|
|
328
349
|
if (allLatlngs.length > 0) {
|
|
350
|
+
const pad = measureOverlayPadding();
|
|
329
351
|
map.fitBounds(L.latLngBounds(allLatlngs), {
|
|
330
|
-
paddingTopLeft: [
|
|
331
|
-
paddingBottomRight: [
|
|
352
|
+
paddingTopLeft: [pad.side, pad.top],
|
|
353
|
+
paddingBottomRight: [pad.side, pad.bottom]
|
|
332
354
|
});
|
|
333
355
|
}
|
|
334
356
|
}
|