koishi-plugin-ets2-tools-tmp 3.3.64 → 3.4.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/api/evmOpenApi.js +7 -29
- package/lib/api/truckyAppApi.js +4 -2
- package/lib/command/tmpActivityService.js +10 -2
- package/lib/command/tmpBind.js +8 -4
- package/lib/command/tmpFootprint.js +34 -11
- package/lib/command/tmpMileageRanking.js +15 -4
- package/lib/command/tmpPosition.js +17 -4
- package/lib/command/tmpQuery/tmpQueryImg.js +22 -4
- package/lib/command/tmpQuery/tmpQueryText.js +48 -18
- package/lib/command/tmpTraffic/tmpTrafficMap.js +48 -7
- package/lib/command/tmpTraffic/tmpTrafficText.js +15 -3
- package/lib/command/tmpVtcMileageRanking.js +15 -4
- package/lib/database/guildBind.js +50 -19
- package/lib/database/model.js +6 -0
- package/lib/index.js +71 -1
- package/lib/resource/footprint.html +34 -11
- package/lib/resource/package/ets-map.js +23 -0
- package/lib/resource/query.html +91 -4
- package/lib/resource/traffic.html +11 -0
- package/lib/util/constant.js +10 -1
- package/lib/util/tmpIdPicker.js +94 -0
- package/package.json +2 -3
package/lib/api/evmOpenApi.js
CHANGED
|
@@ -21,43 +21,19 @@ async function requestWithFallback(http, path) {
|
|
|
21
21
|
module.exports = {
|
|
22
22
|
/**
|
|
23
23
|
* 查询服务器列表
|
|
24
|
+
* game: 'ETS2' 欧卡 / 'ATS' 美卡(接口 game 参数 1=欧卡 / 2=美卡,不传默认欧卡)
|
|
24
25
|
*/
|
|
25
26
|
async serverList(http, game) {
|
|
27
|
+
const gameCode = game === 'ATS' ? 2 : 1
|
|
26
28
|
let result = null
|
|
27
29
|
try {
|
|
28
|
-
result =
|
|
29
|
-
? await apiLog.get(http, 'evm.proxy.truckersmp.servers', `${BASE_APIS[0]}/proxy/truckersmp/servers`)
|
|
30
|
-
: await requestWithFallback(http, `/server/list`)
|
|
30
|
+
result = await requestWithFallback(http, `/server/list?game=${gameCode}`)
|
|
31
31
|
} catch {
|
|
32
32
|
return {
|
|
33
33
|
error: true
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if (game === 'ATS') {
|
|
38
|
-
const servers = result?.response
|
|
39
|
-
const isError = result?.error === true || !Array.isArray(servers)
|
|
40
|
-
return {
|
|
41
|
-
error: isError,
|
|
42
|
-
...(isError ? {} : {
|
|
43
|
-
data: servers
|
|
44
|
-
.filter(server => server.game === 'ATS')
|
|
45
|
-
.map(server => ({
|
|
46
|
-
serverName: server.name,
|
|
47
|
-
isOnline: server.online ? 1 : 0,
|
|
48
|
-
playerCount: server.players,
|
|
49
|
-
maxPlayer: server.maxplayers,
|
|
50
|
-
queueCount: server.queue || 0,
|
|
51
|
-
afkEnable: server.afkenabled ? 1 : 0,
|
|
52
|
-
collisionsEnable: server.collisions ? 1 : 0,
|
|
53
|
-
policeCarEnable: server.policecarsforplayers ? 1 : 0,
|
|
54
|
-
speedLimiterEnable: server.speedlimiter > 0 ? 1 : 0,
|
|
55
|
-
playerHistory: []
|
|
56
|
-
}))
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
37
|
// 拼接返回数据
|
|
62
38
|
let data = {
|
|
63
39
|
error: result.code !== 200
|
|
@@ -181,11 +157,13 @@ module.exports = {
|
|
|
181
157
|
},
|
|
182
158
|
/**
|
|
183
159
|
* 查询玩家历史数据
|
|
160
|
+
* @param game 可选,2=美卡(接口不传默认欧卡)
|
|
184
161
|
*/
|
|
185
|
-
async mapPlayerHistory(http, tmpId, serverId, startTime, endTime) {
|
|
162
|
+
async mapPlayerHistory(http, tmpId, serverId, startTime, endTime, game) {
|
|
186
163
|
let result = null
|
|
187
164
|
try {
|
|
188
|
-
|
|
165
|
+
const gameParam = game ? `&game=${game}` : ''
|
|
166
|
+
result = await requestWithFallback(http, `/map/playerHistory?tmpId=${tmpId || ''}&serverId=${serverId || ''}&startTime=${startTime || ''}&endTime=${endTime || ''}${gameParam}`)
|
|
189
167
|
} catch {
|
|
190
168
|
return {
|
|
191
169
|
error: true
|
package/lib/api/truckyAppApi.js
CHANGED
|
@@ -27,11 +27,13 @@ module.exports = {
|
|
|
27
27
|
},
|
|
28
28
|
/**
|
|
29
29
|
* 查询热门交通数据
|
|
30
|
+
* @param game 'ets2' 欧卡 / 'ats' 美卡
|
|
30
31
|
*/
|
|
31
|
-
async trafficTop (http, serverName) {
|
|
32
|
+
async trafficTop (http, serverName, game) {
|
|
33
|
+
const gameCode = game || 'ets2'
|
|
32
34
|
let result = null
|
|
33
35
|
try {
|
|
34
|
-
result = await apiLog.get(http, 'trucky.trafficTop', `${BASE_API}/v2/traffic/top?game
|
|
36
|
+
result = await apiLog.get(http, 'trucky.trafficTop', `${BASE_API}/v2/traffic/top?game=${gameCode}&server=${serverName}`)
|
|
35
37
|
} catch {
|
|
36
38
|
return {
|
|
37
39
|
error: true
|
|
@@ -669,16 +669,24 @@ class ActivityService {
|
|
|
669
669
|
continue;
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
const
|
|
672
|
+
const text = template
|
|
673
673
|
.replace(/\{date\}/g, dateText)
|
|
674
674
|
.replace(/\{name\}/g, name)
|
|
675
675
|
.replace(/\\n/g, "\n")
|
|
676
676
|
.trim();
|
|
677
677
|
|
|
678
|
+
// 模板以「@全体成员」开头时替换成真实的全体 @(onebot 适配器会转成 [CQ:at,qq=all]);
|
|
679
|
+
// 纯文本的 "@全体成员" 在 QQ 里只是一串字,不会触发全员提醒。
|
|
680
|
+
// 想退回纯文本:把模板开头的「@全体成员」删掉即可。
|
|
681
|
+
const AT_ALL_PREFIX = '@全体成员';
|
|
682
|
+
const payload = text.startsWith(AT_ALL_PREFIX)
|
|
683
|
+
? [koishi_1.h('at', { type: 'all' }), text.slice(AT_ALL_PREFIX.length)]
|
|
684
|
+
: text;
|
|
685
|
+
|
|
678
686
|
let anySuccess = false;
|
|
679
687
|
for (const groupId of groups) {
|
|
680
688
|
try {
|
|
681
|
-
await this.sendToGroup(groupId,
|
|
689
|
+
await this.sendToGroup(groupId, payload, "联运群");
|
|
682
690
|
this.logger.message(`[联运接龙] 已发布到联运群 ${groupId}: ${name}`);
|
|
683
691
|
anySuccess = true;
|
|
684
692
|
} catch (error) {
|
package/lib/command/tmpBind.js
CHANGED
|
@@ -2,7 +2,7 @@ const guildBind = require('../database/guildBind');
|
|
|
2
2
|
const truckersMpApi = require("../api/truckersMpApi");
|
|
3
3
|
const evmOpenApi = require('../api/evmOpenApi');
|
|
4
4
|
/**
|
|
5
|
-
* 绑定 TMP ID
|
|
5
|
+
* 绑定 TMP ID(支持绑定多个:追加入库,同一编号重复绑定会提示)
|
|
6
6
|
*/
|
|
7
7
|
module.exports = async (ctx, cfg, session, tmpId) => {
|
|
8
8
|
if (!tmpId || isNaN(tmpId)) {
|
|
@@ -13,7 +13,11 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
13
13
|
if (playerInfo.error) {
|
|
14
14
|
return '绑定失败 (查询玩家信息失败)';
|
|
15
15
|
}
|
|
16
|
-
//
|
|
17
|
-
guildBind.
|
|
18
|
-
|
|
16
|
+
// 追加绑定(同一 tmpId 已绑定过则提示,不重复入库)
|
|
17
|
+
const result = await guildBind.add(ctx.database, session.platform, session.userId, playerInfo.data.tmpId, playerInfo.data.name);
|
|
18
|
+
if (result.exists) {
|
|
19
|
+
return `该编号已在绑定列表中 ( ${playerInfo.data.name} ),无需重复绑定`;
|
|
20
|
+
}
|
|
21
|
+
const count = (await guildBind.list(ctx.database, session.platform, session.userId)).length;
|
|
22
|
+
return `绑定成功 ( ${playerInfo.data.name} ),当前已绑定 ${count} 个编号`;
|
|
19
23
|
};
|
|
@@ -2,9 +2,10 @@ const { segment } = require('koishi')
|
|
|
2
2
|
const dayjs = require('dayjs')
|
|
3
3
|
const { resolve } = require('path')
|
|
4
4
|
const common = require('../util/common')
|
|
5
|
-
const { PromodsIds, ServerType } = require('../util/constant')
|
|
5
|
+
const { PromodsIds, AtsIds, AtsFootprintStartDate, ServerType } = require('../util/constant')
|
|
6
6
|
const evmOpenApi = require('../api/evmOpenApi')
|
|
7
7
|
const guildBind = require('../database/guildBind')
|
|
8
|
+
const tmpIdPicker = require('../util/tmpIdPicker')
|
|
8
9
|
|
|
9
10
|
// 足迹统计数据的开始收集日期(仅用于近一年足迹图片中的提示展示)
|
|
10
11
|
const FOOTPRINT_DATA_START_DATE = '2026-08-19'
|
|
@@ -81,13 +82,25 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
81
82
|
return `请输入正确的玩家编号,或绑定玩家编号`
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
// 如果没有传入tmpId
|
|
85
|
+
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选=发多张足迹图)
|
|
85
86
|
if (!tmpId) {
|
|
86
|
-
|
|
87
|
-
if (
|
|
87
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId)
|
|
88
|
+
if (bindings.length === 0) {
|
|
88
89
|
return `请输入正确的玩家编号,或绑定玩家编号`
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
const picked = await tmpIdPicker.pick(ctx, session, bindings)
|
|
92
|
+
if (picked === null) {
|
|
93
|
+
return // 已列出序号等待用户回复
|
|
94
|
+
}
|
|
95
|
+
if (picked.length > 1) {
|
|
96
|
+
const results = []
|
|
97
|
+
for (const id of picked) {
|
|
98
|
+
const r = await module.exports(ctx, session, serverType, id, date)
|
|
99
|
+
if (r != null) results.push(r)
|
|
100
|
+
}
|
|
101
|
+
return results.length === 1 ? results[0] : results
|
|
102
|
+
}
|
|
103
|
+
tmpId = picked[0]
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
// 查询玩家信息
|
|
@@ -131,16 +144,23 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
131
144
|
return '日期传递错误';
|
|
132
145
|
}
|
|
133
146
|
|
|
134
|
-
|
|
147
|
+
// 是否为美卡足迹(决定历史接口的 game 参数与过滤规则)
|
|
148
|
+
const isAts = ServerType.ats === serverType
|
|
149
|
+
// 查询美卡历史需要显式传 game=2(不传默认欧卡,欧卡/Promods 维持原样)
|
|
150
|
+
let mapPlayerHistory = await evmOpenApi.mapPlayerHistory(ctx.http, tmpId, null, startTime, endTime, isAts ? 2 : undefined)
|
|
135
151
|
if (mapPlayerHistory.error) {
|
|
136
152
|
return '查询玩家历史位置数据失败,请稍后重试'
|
|
137
153
|
}
|
|
138
154
|
|
|
139
155
|
// 过滤非对应服务器数据(单次遍历统计轨迹范围与有效点数,避免百万级数据产生额外数组拷贝)
|
|
140
156
|
const promodsIdSet = new Set(PromodsIds)
|
|
157
|
+
const atsIdSet = new Set(AtsIds)
|
|
141
158
|
const isTargetServer = item => {
|
|
142
|
-
if (
|
|
143
|
-
return
|
|
159
|
+
if (isAts) {
|
|
160
|
+
return atsIdSet.has(item.serverId)
|
|
161
|
+
} else if (ServerType.ets === serverType) {
|
|
162
|
+
// 欧卡:排除 Promods 与美卡的服务器
|
|
163
|
+
return !promodsIdSet.has(item.serverId) && !atsIdSet.has(item.serverId)
|
|
144
164
|
} else if (ServerType.promods === serverType) {
|
|
145
165
|
return promodsIdSet.has(item.serverId)
|
|
146
166
|
}
|
|
@@ -191,16 +211,19 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
191
211
|
|
|
192
212
|
// 拼接数据(今日足迹展示今日里程;其余展示该区间里程(distance 累加)与玩家总里程)
|
|
193
213
|
const isToday = date === 'today'
|
|
214
|
+
const rangeTextMap = { today: '今日', yesterday: '昨日', sevenday: '近七日', month: '近一个月', year: '近一年' }[date] || ''
|
|
194
215
|
let data = {
|
|
195
|
-
mapType: ServerType.promods === serverType ? 'promods' : 'ets',
|
|
216
|
+
mapType: isAts ? 'ats' : (ServerType.promods === serverType ? 'promods' : 'ets'),
|
|
196
217
|
name: playerInfo.data.name,
|
|
197
218
|
smallAvatarUrl: playerInfo.data.smallAvatarUrl,
|
|
198
|
-
rangeText:
|
|
219
|
+
rangeText: isAts ? `${rangeTextMap}(美卡)` : rangeTextMap,
|
|
199
220
|
isToday,
|
|
200
221
|
mileageLabel: isToday ? '今日里程' : '该区间里程',
|
|
201
222
|
mileage: isToday ? playerInfo.data.todayMileage : intervalDistance,
|
|
202
223
|
totalMileage: isToday ? null : playerInfo.data.mileage,
|
|
203
|
-
|
|
224
|
+
etsMileage: isToday ? null : playerInfo.data.mileageEts2,
|
|
225
|
+
atsMileage: isToday ? null : playerInfo.data.mileageAts,
|
|
226
|
+
dataStartDate: date === 'year' ? (isAts ? AtsFootprintStartDate : FOOTPRINT_DATA_START_DATE) : null,
|
|
204
227
|
segments
|
|
205
228
|
}
|
|
206
229
|
|
|
@@ -3,6 +3,7 @@ const { resolve } = require('path');
|
|
|
3
3
|
const common = require('../util/common');
|
|
4
4
|
const evmOpenApi = require('../api/evmOpenApi');
|
|
5
5
|
const guildBind = require('../database/guildBind');
|
|
6
|
+
const tmpIdPicker = require('../util/tmpIdPicker');
|
|
6
7
|
module.exports = async (ctx, session, rankingType) => {
|
|
7
8
|
if (!ctx.puppeteer) {
|
|
8
9
|
return '未启用 Puppeteer 功能';
|
|
@@ -15,11 +16,21 @@ module.exports = async (ctx, session, rankingType) => {
|
|
|
15
16
|
else if (mileageRankingList.data.length === 0) {
|
|
16
17
|
return '暂无数据';
|
|
17
18
|
}
|
|
18
|
-
//
|
|
19
|
-
|
|
19
|
+
// 查询当前玩家的排行信息(多绑定时先单选一个,排行榜只高亮一个人)
|
|
20
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId);
|
|
21
|
+
let bindTmpId = null;
|
|
22
|
+
if (bindings.length === 1) {
|
|
23
|
+
bindTmpId = String(bindings[0].tmp_id);
|
|
24
|
+
} else if (bindings.length > 1) {
|
|
25
|
+
const picked = await tmpIdPicker.pick(ctx, session, bindings, { allowMulti: false });
|
|
26
|
+
if (picked === null) {
|
|
27
|
+
return; // 已列出序号等待用户回复
|
|
28
|
+
}
|
|
29
|
+
bindTmpId = picked[0];
|
|
30
|
+
}
|
|
20
31
|
let playerMileageRanking = null;
|
|
21
|
-
if (
|
|
22
|
-
let playerMileageRankingResult = await evmOpenApi.mileageRankingList(ctx.http, rankingType,
|
|
32
|
+
if (bindTmpId) {
|
|
33
|
+
let playerMileageRankingResult = await evmOpenApi.mileageRankingList(ctx.http, rankingType, bindTmpId);
|
|
23
34
|
if (!playerMileageRankingResult.error && playerMileageRankingResult.data.length > 0) {
|
|
24
35
|
playerMileageRanking = playerMileageRankingResult.data[0];
|
|
25
36
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { segment } = require('koishi')
|
|
2
2
|
const { resolve } = require('path')
|
|
3
3
|
const guildBind = require('../database/guildBind')
|
|
4
|
+
const tmpIdPicker = require('../util/tmpIdPicker')
|
|
4
5
|
const truckyAppApi = require('../api/truckyAppApi')
|
|
5
6
|
const truckersMpApi = require('../api/truckersMpApi')
|
|
6
7
|
const evmOpenApi = require('../api/evmOpenApi')
|
|
@@ -32,13 +33,25 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
32
33
|
}
|
|
33
34
|
tmpId = guildBindData.tmp_id;
|
|
34
35
|
}
|
|
35
|
-
// 如果没有传入tmpId
|
|
36
|
+
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选=发多张定位图)
|
|
36
37
|
if (!tmpId) {
|
|
37
|
-
|
|
38
|
-
if (
|
|
38
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId)
|
|
39
|
+
if (bindings.length === 0) {
|
|
39
40
|
return `请输入正确的玩家编号`
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
const picked = await tmpIdPicker.pick(ctx, session, bindings)
|
|
43
|
+
if (picked === null) {
|
|
44
|
+
return // 已列出序号等待用户回复
|
|
45
|
+
}
|
|
46
|
+
if (picked.length > 1) {
|
|
47
|
+
const results = []
|
|
48
|
+
for (const id of picked) {
|
|
49
|
+
const r = await module.exports(ctx, cfg, session, id)
|
|
50
|
+
if (r != null) results.push(r)
|
|
51
|
+
}
|
|
52
|
+
return results.length === 1 ? results[0] : results
|
|
53
|
+
}
|
|
54
|
+
tmpId = picked[0]
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
// 查询玩家信息
|
|
@@ -2,6 +2,7 @@ const dayjs = require('dayjs');
|
|
|
2
2
|
const dayjsRelativeTime = require('dayjs/plugin/relativeTime');
|
|
3
3
|
const dayjsLocaleZhCn = require('dayjs/locale/zh-cn');
|
|
4
4
|
const guildBind = require('../../database/guildBind');
|
|
5
|
+
const tmpIdPicker = require('../../util/tmpIdPicker');
|
|
5
6
|
const truckyAppApi = require('../../api/truckyAppApi');
|
|
6
7
|
const evmOpenApi = require('../../api/evmOpenApi');
|
|
7
8
|
const baiduTranslate = require('../../util/baiduTranslate');
|
|
@@ -80,13 +81,25 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
80
81
|
}
|
|
81
82
|
tmpId = guildBindData.tmp_id;
|
|
82
83
|
}
|
|
83
|
-
// 如果没有传入tmpId
|
|
84
|
+
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选)
|
|
84
85
|
if (!tmpId) {
|
|
85
|
-
|
|
86
|
-
if (
|
|
86
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId);
|
|
87
|
+
if (bindings.length === 0) {
|
|
87
88
|
return `请输入正确的玩家编号`;
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
+
const picked = await tmpIdPicker.pick(ctx, session, bindings);
|
|
91
|
+
if (picked === null) {
|
|
92
|
+
return; // 已列出序号等待用户回复
|
|
93
|
+
}
|
|
94
|
+
if (picked.length > 1) {
|
|
95
|
+
const results = [];
|
|
96
|
+
for (const id of picked) {
|
|
97
|
+
const r = await module.exports(ctx, cfg, session, id);
|
|
98
|
+
if (r != null) results.push(r);
|
|
99
|
+
}
|
|
100
|
+
return results.length === 1 ? results[0] : results;
|
|
101
|
+
}
|
|
102
|
+
tmpId = picked[0];
|
|
90
103
|
}
|
|
91
104
|
// 并行请求玩家信息与线上状态(互不依赖,避免串行等待)
|
|
92
105
|
const [playerInfo, playerMapInfo] = await Promise.all([
|
|
@@ -176,8 +189,13 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
176
189
|
data.sponsorAmount = playerInfo.data.sponsorAmount;
|
|
177
190
|
data.sponsorCumulativeAmount = playerInfo.data.sponsorCumulativeAmount;
|
|
178
191
|
data.sponsorHide = playerInfo.data.sponsorHide;
|
|
192
|
+
// 里程:mileage/todayMileage 为欧卡+美卡合计,另附两款游戏的单独里程
|
|
179
193
|
data.mileage = playerInfo.data.mileage;
|
|
180
194
|
data.todayMileage = playerInfo.data.todayMileage;
|
|
195
|
+
data.mileageEts2 = playerInfo.data.mileageEts2;
|
|
196
|
+
data.mileageAts = playerInfo.data.mileageAts;
|
|
197
|
+
data.todayMileageEts2 = playerInfo.data.todayMileageEts2;
|
|
198
|
+
data.todayMileageAts = playerInfo.data.todayMileageAts;
|
|
181
199
|
data.isOnline = false;
|
|
182
200
|
data.onlineStatus = '离线';
|
|
183
201
|
if (playerMapInfo && !playerMapInfo.error) {
|
|
@@ -2,11 +2,38 @@ const dayjs = require('dayjs');
|
|
|
2
2
|
const dayjsRelativeTime = require('dayjs/plugin/relativeTime');
|
|
3
3
|
const dayjsLocaleZhCn = require('dayjs/locale/zh-cn');
|
|
4
4
|
const guildBind = require('../../database/guildBind');
|
|
5
|
+
const tmpIdPicker = require('../../util/tmpIdPicker');
|
|
5
6
|
const truckyAppApi = require('../../api/truckyAppApi');
|
|
6
7
|
const evmOpenApi = require('../../api/evmOpenApi');
|
|
7
8
|
const baiduTranslate = require('../../util/baiduTranslate');
|
|
8
9
|
dayjs.extend(dayjsRelativeTime);
|
|
9
10
|
dayjs.locale(dayjsLocaleZhCn);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 里程文本:>=1000 米换算成公里;0 显示成「0公里」(表示这款游戏没跑过)
|
|
14
|
+
*/
|
|
15
|
+
function formatMileageText(value) {
|
|
16
|
+
const n = Number(value) || 0;
|
|
17
|
+
if (n <= 0) {
|
|
18
|
+
return '0公里';
|
|
19
|
+
}
|
|
20
|
+
if (n >= 1000) {
|
|
21
|
+
return Number((n / 1000).toFixed(1)).toLocaleString('en-US') + '公里';
|
|
22
|
+
}
|
|
23
|
+
return n + '米';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 欧卡/美卡里程明细文本(两者都为 0 时返回空串)
|
|
28
|
+
*/
|
|
29
|
+
function formatMileageDetail(ets2, ats) {
|
|
30
|
+
const ets2Val = Number(ets2) || 0;
|
|
31
|
+
const atsVal = Number(ats) || 0;
|
|
32
|
+
if (ets2Val <= 0 && atsVal <= 0) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
return ` (欧卡 ${formatMileageText(ets2Val)} / 美卡 ${formatMileageText(atsVal)})`;
|
|
36
|
+
}
|
|
10
37
|
/**
|
|
11
38
|
* 用户组
|
|
12
39
|
*/
|
|
@@ -43,13 +70,25 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
43
70
|
}
|
|
44
71
|
tmpId = guildBindData.tmp_id;
|
|
45
72
|
}
|
|
46
|
-
// 如果没有传入tmpId
|
|
73
|
+
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选)
|
|
47
74
|
if (!tmpId) {
|
|
48
|
-
|
|
49
|
-
if (
|
|
75
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId);
|
|
76
|
+
if (bindings.length === 0) {
|
|
50
77
|
return `请输入正确的玩家编号`;
|
|
51
78
|
}
|
|
52
|
-
|
|
79
|
+
const picked = await tmpIdPicker.pick(ctx, session, bindings);
|
|
80
|
+
if (picked === null) {
|
|
81
|
+
return; // 已列出序号等待用户回复
|
|
82
|
+
}
|
|
83
|
+
if (picked.length > 1) {
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const id of picked) {
|
|
86
|
+
const r = await module.exports(ctx, cfg, session, id);
|
|
87
|
+
if (r != null) results.push(r);
|
|
88
|
+
}
|
|
89
|
+
return results.length === 1 ? results[0] : results;
|
|
90
|
+
}
|
|
91
|
+
tmpId = picked[0];
|
|
53
92
|
}
|
|
54
93
|
// 并行请求玩家信息与线上状态(互不依赖,避免串行等待)
|
|
55
94
|
const [playerInfo, playerMapInfo] = await Promise.all([
|
|
@@ -139,23 +178,14 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
139
178
|
}
|
|
140
179
|
}
|
|
141
180
|
message += '\n🚫封禁次数: ' + (playerInfo.data.banCount || 0);
|
|
181
|
+
// 历史/今日里程为欧卡+美卡合计,后面附上两款游戏的单独里程
|
|
142
182
|
if (playerInfo.data.mileage) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (mileage > 1000) {
|
|
146
|
-
mileage = (mileage / 1000).toFixed(1);
|
|
147
|
-
mileageUnit = '公里';
|
|
148
|
-
}
|
|
149
|
-
message += '\n\n🚩历史里程: ' + mileage + mileageUnit;
|
|
183
|
+
message += '\n\n🚩历史里程: ' + formatMileageText(playerInfo.data.mileage)
|
|
184
|
+
+ formatMileageDetail(playerInfo.data.mileageEts2, playerInfo.data.mileageAts);
|
|
150
185
|
}
|
|
151
186
|
if (playerInfo.data.todayMileage) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (todayMileage > 1000) {
|
|
155
|
-
todayMileage = (todayMileage / 1000).toFixed(1);
|
|
156
|
-
mileageUnit = '公里';
|
|
157
|
-
}
|
|
158
|
-
message += '\n🚩今日里程: ' + todayMileage + mileageUnit;
|
|
187
|
+
message += '\n🚩今日里程: ' + formatMileageText(playerInfo.data.todayMileage)
|
|
188
|
+
+ formatMileageDetail(playerInfo.data.todayMileageEts2, playerInfo.data.todayMileageAts);
|
|
159
189
|
}
|
|
160
190
|
if (playerMapInfo && !playerMapInfo.error) {
|
|
161
191
|
message += '\n📶在线状态: ' + (playerMapInfo.data.online ? `在线🟢 (${playerMapInfo.data.serverDetails.name})` : '离线⚫');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const truckyAppApi = require('../../api/truckyAppApi');
|
|
2
2
|
const evmOpenApi = require('../../api/evmOpenApi');
|
|
3
|
+
const truckersMpMapApi = require('../../api/truckersMpMapApi');
|
|
3
4
|
const baiduTranslate = require('../../util/baiduTranslate');
|
|
4
5
|
const { resolve } = require("path");
|
|
5
6
|
const common = require("../../util/common");
|
|
@@ -31,6 +32,28 @@ const serverAlias = {
|
|
|
31
32
|
mapType: 'ets',
|
|
32
33
|
serverId: 7,
|
|
33
34
|
bounds: [[-94189, 93775], [79264, -78999]]
|
|
35
|
+
},
|
|
36
|
+
// 美卡:evm 的 /map/playerList 没有美卡数据,玩家位置改走 ets2map tracker(坐标同为游戏坐标)
|
|
37
|
+
'美s': {
|
|
38
|
+
name: 'eusim',
|
|
39
|
+
game: 'ats',
|
|
40
|
+
mapType: 'ats',
|
|
41
|
+
serverId: 8,
|
|
42
|
+
bounds: [[-122000, 100000], [39000, -88000]]
|
|
43
|
+
},
|
|
44
|
+
'美uss': {
|
|
45
|
+
name: 'ussim',
|
|
46
|
+
game: 'ats',
|
|
47
|
+
mapType: 'ats',
|
|
48
|
+
serverId: 10,
|
|
49
|
+
bounds: [[-122000, 100000], [39000, -88000]]
|
|
50
|
+
},
|
|
51
|
+
'美usa': {
|
|
52
|
+
name: 'usarc',
|
|
53
|
+
game: 'ats',
|
|
54
|
+
mapType: 'ats',
|
|
55
|
+
serverId: 45,
|
|
56
|
+
bounds: [[-122000, 100000], [39000, -88000]]
|
|
34
57
|
}
|
|
35
58
|
};
|
|
36
59
|
/**
|
|
@@ -72,24 +95,42 @@ module.exports = async (ctx, cfg, serverName) => {
|
|
|
72
95
|
// 根据别名获取服务器信息
|
|
73
96
|
let serverInfo = serverAlias[serverName];
|
|
74
97
|
if (!serverInfo) {
|
|
75
|
-
return '请输入正确的服务器名称 (s1, s2, p, a)';
|
|
98
|
+
return '请输入正确的服务器名称 (s1, s2, p, a, 美s, 美uss, 美usa)';
|
|
76
99
|
}
|
|
77
100
|
// 查询路况信息
|
|
78
|
-
let trafficData = await truckyAppApi.trafficTop(ctx.http, serverInfo.name);
|
|
101
|
+
let trafficData = await truckyAppApi.trafficTop(ctx.http, serverInfo.name, serverInfo.game);
|
|
79
102
|
if (trafficData.error) {
|
|
80
103
|
return '查询路况信息失败';
|
|
81
104
|
}
|
|
82
|
-
//
|
|
83
|
-
let mapData
|
|
105
|
+
// 查询地图玩家数据(美卡走 ets2map tracker,欧卡/Promods 走 evm)
|
|
106
|
+
let mapData;
|
|
107
|
+
if (serverInfo.game === 'ats') {
|
|
108
|
+
mapData = await truckersMpMapApi.area(ctx.http, serverInfo.serverId,
|
|
109
|
+
serverInfo.bounds[0][0], serverInfo.bounds[0][1], serverInfo.bounds[1][0], serverInfo.bounds[1][1]);
|
|
110
|
+
} else {
|
|
111
|
+
mapData = await evmOpenApi.mapPlayerList(ctx.http, serverInfo.serverId,
|
|
112
|
+
serverInfo.bounds[0][0], serverInfo.bounds[0][1], serverInfo.bounds[1][0], serverInfo.bounds[1][1]);
|
|
113
|
+
}
|
|
114
|
+
// 两个接口的坐标字段名不同:tracker 为 X/Y,evm 为 axisX/axisY(数值都是游戏坐标)
|
|
115
|
+
const coordOf = serverInfo.game === 'ats'
|
|
116
|
+
? (item => [item.X, item.Y])
|
|
117
|
+
: (item => [item.axisX, item.axisY]);
|
|
118
|
+
// 接口失败或返回异常时用空数组兜底,避免对 undefined 调 map 导致指令崩溃
|
|
119
|
+
const playerCoordinateList = (!mapData.error && Array.isArray(mapData.data))
|
|
120
|
+
? mapData.data.map(coordOf)
|
|
121
|
+
: [];
|
|
84
122
|
|
|
85
123
|
switch (serverName) {
|
|
86
124
|
case 's1':
|
|
87
125
|
case 's2':
|
|
88
|
-
case 'a':
|
|
126
|
+
case 'a':
|
|
127
|
+
case '美s':
|
|
128
|
+
case '美uss':
|
|
129
|
+
case '美usa': {
|
|
89
130
|
let data = {
|
|
90
131
|
mapType: serverInfo.mapType,
|
|
91
132
|
trafficList: [],
|
|
92
|
-
playerCoordinateList
|
|
133
|
+
playerCoordinateList
|
|
93
134
|
};
|
|
94
135
|
for (const traffic of trafficData.data) {
|
|
95
136
|
data.trafficList.push({
|
|
@@ -126,7 +167,7 @@ module.exports = async (ctx, cfg, serverName) => {
|
|
|
126
167
|
let data = {
|
|
127
168
|
mapType: serverInfo.mapType,
|
|
128
169
|
trafficList: [],
|
|
129
|
-
playerCoordinateList
|
|
170
|
+
playerCoordinateList
|
|
130
171
|
};
|
|
131
172
|
for (const traffic of trafficData.data) {
|
|
132
173
|
data.trafficList.push({
|
|
@@ -11,7 +11,19 @@ const serverNameAlias = {
|
|
|
11
11
|
'S1': 'sim1',
|
|
12
12
|
'S2': 'sim2',
|
|
13
13
|
'P': 'eupromods1',
|
|
14
|
-
'A': 'arc1'
|
|
14
|
+
'A': 'arc1',
|
|
15
|
+
// 美卡(Trucky 的美卡服务器代号:eusim/ussim/usarc)
|
|
16
|
+
'美s': 'eusim',
|
|
17
|
+
'美uss': 'ussim',
|
|
18
|
+
'美usa': 'usarc'
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 别名对应的车队游戏(路况接口的 game 参数)
|
|
22
|
+
*/
|
|
23
|
+
const aliasGame = {
|
|
24
|
+
'美s': 'ats',
|
|
25
|
+
'美uss': 'ats',
|
|
26
|
+
'美usa': 'ats'
|
|
15
27
|
};
|
|
16
28
|
/**
|
|
17
29
|
* 路况程度转中文
|
|
@@ -36,9 +48,9 @@ const typeToZh = {
|
|
|
36
48
|
module.exports = async (ctx, cfg, serverName) => {
|
|
37
49
|
let serverQueryName = serverNameAlias[serverName];
|
|
38
50
|
if (!serverQueryName) {
|
|
39
|
-
return '请输入正确的服务器名称 (s1, s2, p, a)';
|
|
51
|
+
return '请输入正确的服务器名称 (s1, s2, p, a, 美s, 美uss, 美usa)';
|
|
40
52
|
}
|
|
41
|
-
let trafficData = await truckyAppApi.trafficTop(ctx.http, serverQueryName);
|
|
53
|
+
let trafficData = await truckyAppApi.trafficTop(ctx.http, serverQueryName, aliasGame[serverName]);
|
|
42
54
|
if (trafficData.error) {
|
|
43
55
|
return '查询路况信息失败';
|
|
44
56
|
}
|
|
@@ -3,6 +3,7 @@ const { resolve } = require('path');
|
|
|
3
3
|
const common = require('../util/common');
|
|
4
4
|
const evmOpenApi = require('../api/evmOpenApi');
|
|
5
5
|
const guildBind = require('../database/guildBind');
|
|
6
|
+
const tmpIdPicker = require('../util/tmpIdPicker');
|
|
6
7
|
module.exports = async (ctx, cfg, session, rankingType) => {
|
|
7
8
|
if (!ctx.puppeteer) {
|
|
8
9
|
return '未启用 Puppeteer 功能';
|
|
@@ -19,11 +20,21 @@ module.exports = async (ctx, cfg, session, rankingType) => {
|
|
|
19
20
|
else if (mileageRankingList.data.length === 0) {
|
|
20
21
|
return '暂无数据';
|
|
21
22
|
}
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// 从排行榜中查找当前玩家的排行信息(多绑定时先单选一个,排行榜只高亮一个人)
|
|
24
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId);
|
|
25
|
+
let bindTmpId = null;
|
|
26
|
+
if (bindings.length === 1) {
|
|
27
|
+
bindTmpId = String(bindings[0].tmp_id);
|
|
28
|
+
} else if (bindings.length > 1) {
|
|
29
|
+
const picked = await tmpIdPicker.pick(ctx, session, bindings, { allowMulti: false });
|
|
30
|
+
if (picked === null) {
|
|
31
|
+
return; // 已列出序号等待用户回复
|
|
32
|
+
}
|
|
33
|
+
bindTmpId = picked[0];
|
|
34
|
+
}
|
|
24
35
|
let playerMileageRanking = null;
|
|
25
|
-
if (
|
|
26
|
-
playerMileageRanking = mileageRankingList.data.find(p => p.tmpId ==
|
|
36
|
+
if (bindTmpId) {
|
|
37
|
+
playerMileageRanking = mileageRankingList.data.find(p => p.tmpId == bindTmpId) || null;
|
|
27
38
|
}
|
|
28
39
|
// 拼接页面数据
|
|
29
40
|
let data = {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TMP ID 绑定(支持一人绑定多个)
|
|
3
|
+
* 表 tmp_guild_bind:一个绑定一行,同一 (platform, user_id) 可有多行;
|
|
4
|
+
* get() 返回第一行(默认绑定),保持与旧版行为兼容。
|
|
5
|
+
*/
|
|
1
6
|
module.exports = {
|
|
2
7
|
/**
|
|
3
|
-
*
|
|
8
|
+
* 获取默认绑定(第一行,兼容旧逻辑)
|
|
4
9
|
* @param db 数据源
|
|
5
10
|
* @param platform 平台
|
|
6
11
|
* @param userId 用户编号
|
|
@@ -16,26 +21,52 @@ module.exports = {
|
|
|
16
21
|
return null;
|
|
17
22
|
},
|
|
18
23
|
/**
|
|
19
|
-
*
|
|
20
|
-
* @
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
* 获取全部绑定(按绑定先后排序)
|
|
25
|
+
* @returns 绑定行数组,可能为空数组
|
|
26
|
+
*/
|
|
27
|
+
async list(db, platform, userId) {
|
|
28
|
+
const guildBindList = await db.get('tmp_guild_bind', {
|
|
29
|
+
platform,
|
|
30
|
+
user_id: userId
|
|
31
|
+
});
|
|
32
|
+
if (!guildBindList || guildBindList.length === 0) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return guildBindList.sort((a, b) => a.id - b.id);
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* 追加绑定(同一 tmpId 重复绑定时不会重复入库)
|
|
23
39
|
* @param tmpId TMP ID
|
|
40
|
+
* @param tmpName TMP 玩家名称(用于多绑定列表展示,可为 null)
|
|
41
|
+
* @returns { exists, row } exists=true 表示该 tmpId 已绑定过
|
|
24
42
|
*/
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
async add(db, platform, userId, tmpId, tmpName) {
|
|
44
|
+
const guildBindList = await this.list(db, platform, userId);
|
|
45
|
+
const existed = guildBindList.find(row => String(row.tmp_id) === String(tmpId));
|
|
46
|
+
if (existed) {
|
|
47
|
+
return { exists: true, row: existed };
|
|
48
|
+
}
|
|
49
|
+
const row = await db.create('tmp_guild_bind', {
|
|
50
|
+
platform: platform,
|
|
51
|
+
user_id: userId,
|
|
52
|
+
tmp_id: tmpId,
|
|
53
|
+
tmp_name: tmpName || null
|
|
54
|
+
});
|
|
55
|
+
return { exists: false, row };
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* 删除指定主键的绑定
|
|
59
|
+
*/
|
|
60
|
+
async remove(db, id) {
|
|
61
|
+
await db.remove('tmp_guild_bind', id);
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* 删除指定用户的全部绑定
|
|
65
|
+
*/
|
|
66
|
+
async removeAll(db, platform, userId) {
|
|
67
|
+
await db.remove('tmp_guild_bind', {
|
|
68
|
+
platform,
|
|
69
|
+
user_id: userId
|
|
39
70
|
});
|
|
40
71
|
}
|
|
41
72
|
};
|
package/lib/database/model.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const model = require('./database/model');
|
|
|
8
8
|
const { ServerType } = require('./util/constant');
|
|
9
9
|
const { MileageRankingType } = require('./util/constant');
|
|
10
10
|
const { LeadRoleCallMessage, LeadRoleCallTime } = require('./util/constant');
|
|
11
|
+
const tmpIdPicker = require('./util/tmpIdPicker');
|
|
11
12
|
|
|
12
13
|
const commands = {
|
|
13
14
|
tmpQuery: require('./command/tmpQuery/tmpQuery'),
|
|
@@ -258,8 +259,44 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
258
259
|
|
|
259
260
|
if (cfg.commands?.tmpQuery) {
|
|
260
261
|
ctx.command('绑定 <tmpId>')
|
|
261
|
-
.usage("绑定TmpId")
|
|
262
|
+
.usage("绑定TmpId(可绑定多个,查询时会列出序号供选择)")
|
|
262
263
|
.action(async ({ session }, tmpId) => await commands.tmpBind(ctx, cfg, session, tmpId));
|
|
264
|
+
|
|
265
|
+
ctx.command('我的绑定')
|
|
266
|
+
.usage("查看已绑定的全部TMP编号")
|
|
267
|
+
.action(async ({ session }) => {
|
|
268
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId);
|
|
269
|
+
if (bindings.length === 0) {
|
|
270
|
+
return '还没有绑定编号,使用「绑定 tmpId」添加';
|
|
271
|
+
}
|
|
272
|
+
const lines = bindings.map((b, i) => `${i + 1}. ${b.tmp_name || '未知'} (${b.tmp_id})`);
|
|
273
|
+
return `已绑定 ${bindings.length} 个编号:\n${lines.join('\n')}${bindings.length > 1 ? '\n查询时会列出序号供选择;使用「解绑 序号」可删除' : ''}`;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
ctx.command('解绑 <index:string>')
|
|
277
|
+
.usage("删除已绑定的编号(序号见「我的绑定」,all 为全部删除)")
|
|
278
|
+
.example("解绑 2")
|
|
279
|
+
.example("解绑 all")
|
|
280
|
+
.action(async ({ session }, index) => {
|
|
281
|
+
const bindings = await guildBind.list(ctx.database, session.platform, session.userId);
|
|
282
|
+
if (bindings.length === 0) {
|
|
283
|
+
return '还没有绑定编号';
|
|
284
|
+
}
|
|
285
|
+
if (!index) {
|
|
286
|
+
return '请输入要解绑的序号或 all(序号见「我的绑定」)';
|
|
287
|
+
}
|
|
288
|
+
if (/^all$/i.test(index.trim())) {
|
|
289
|
+
await guildBind.removeAll(ctx.database, session.platform, session.userId);
|
|
290
|
+
return `已删除全部 ${bindings.length} 个绑定`;
|
|
291
|
+
}
|
|
292
|
+
const idx = parseInt(index, 10);
|
|
293
|
+
if (isNaN(idx) || idx < 1 || idx > bindings.length) {
|
|
294
|
+
return `序号无效,可用范围 1-${bindings.length}(序号见「我的绑定」)`;
|
|
295
|
+
}
|
|
296
|
+
const row = bindings[idx - 1];
|
|
297
|
+
await guildBind.remove(ctx.database, row.id);
|
|
298
|
+
return `已解绑 ${idx}. ${row.tmp_name || '未知'} (${row.tmp_id})`;
|
|
299
|
+
});
|
|
263
300
|
}
|
|
264
301
|
|
|
265
302
|
if (cfg.commands?.tmpTraffic) {
|
|
@@ -338,6 +375,36 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
338
375
|
.example("近一年足迹p 12345")
|
|
339
376
|
.action(async ({ session }, tmpId) => await commands.tmpFootprint(ctx, session, ServerType.promods, tmpId, 'year'));
|
|
340
377
|
|
|
378
|
+
ctx.command('近一年足迹美 [tmpId:string]')
|
|
379
|
+
.usage("查询美卡(ATS)服务器足迹")
|
|
380
|
+
.example("近一年足迹美")
|
|
381
|
+
.example("近一年足迹美 12345")
|
|
382
|
+
.action(async ({ session }, tmpId) => await commands.tmpFootprint(ctx, session, ServerType.ats, tmpId, 'year'));
|
|
383
|
+
|
|
384
|
+
ctx.command('近一个月足迹美 [tmpId:string]')
|
|
385
|
+
.usage("查询美卡(ATS)服务器足迹")
|
|
386
|
+
.example("近一个月足迹美")
|
|
387
|
+
.example("近一个月足迹美 12345")
|
|
388
|
+
.action(async ({ session }, tmpId) => await commands.tmpFootprint(ctx, session, ServerType.ats, tmpId, 'month'));
|
|
389
|
+
|
|
390
|
+
ctx.command('近七日足迹美 [tmpId:string]')
|
|
391
|
+
.usage("查询美卡(ATS)服务器足迹")
|
|
392
|
+
.example("近七日足迹美")
|
|
393
|
+
.example("近七日足迹美 12345")
|
|
394
|
+
.action(async ({ session }, tmpId) => await commands.tmpFootprint(ctx, session, ServerType.ats, tmpId, 'sevenday'));
|
|
395
|
+
|
|
396
|
+
ctx.command('昨日足迹美 [tmpId:string]')
|
|
397
|
+
.usage("查询美卡(ATS)服务器足迹")
|
|
398
|
+
.example("昨日足迹美")
|
|
399
|
+
.example("昨日足迹美 12345")
|
|
400
|
+
.action(async ({ session }, tmpId) => await commands.tmpFootprint(ctx, session, ServerType.ats, tmpId, 'yesterday'));
|
|
401
|
+
|
|
402
|
+
ctx.command('今日足迹美 [tmpId:string]')
|
|
403
|
+
.usage("查询美卡(ATS)服务器今日足迹")
|
|
404
|
+
.example("今日足迹美")
|
|
405
|
+
.example("今日足迹美 12345")
|
|
406
|
+
.action(async ({ session }, tmpId) => await commands.tmpFootprint(ctx, session, ServerType.ats, tmpId, 'today'));
|
|
407
|
+
|
|
341
408
|
ctx.command('近一个月足迹 [tmpId:string]')
|
|
342
409
|
.usage("查询ETS服务器今日足迹")
|
|
343
410
|
.example("近一个月足迹")
|
|
@@ -419,6 +486,9 @@ function apply(ctx, cfg) {
|
|
|
419
486
|
// 初始化 API 请求日志(调试模式开启后,truckersmp/trucky 等接口会输出请求与错误日志)
|
|
420
487
|
apiLog.init(ctx, cfg.debugMode, cfg.apiRetryCount);
|
|
421
488
|
|
|
489
|
+
// 多绑定选择器中间件(捕获用户回复的序号,完成"列出绑定→序号选择"交互)
|
|
490
|
+
tmpIdPicker.register(ctx);
|
|
491
|
+
|
|
422
492
|
try {
|
|
423
493
|
model(ctx);
|
|
424
494
|
if (cfg.debugMode) {
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
z-index: 1001;
|
|
99
99
|
display: flex;
|
|
100
100
|
align-items: center;
|
|
101
|
-
gap:
|
|
102
|
-
padding:
|
|
101
|
+
gap: 12px;
|
|
102
|
+
padding: 10px 16px;
|
|
103
103
|
border-radius: 14px;
|
|
104
104
|
box-sizing: border-box;
|
|
105
105
|
}
|
|
@@ -107,16 +107,17 @@
|
|
|
107
107
|
display: flex;
|
|
108
108
|
flex-direction: column;
|
|
109
109
|
gap: 2px;
|
|
110
|
-
min-width: 110px;
|
|
111
110
|
}
|
|
112
111
|
.mileage-block .label {
|
|
113
|
-
font-size:
|
|
114
|
-
letter-spacing:
|
|
112
|
+
font-size: 10px;
|
|
113
|
+
letter-spacing: 1.5px;
|
|
115
114
|
color: #8fa3c8;
|
|
115
|
+
white-space: nowrap;
|
|
116
116
|
}
|
|
117
117
|
.mileage-block .value {
|
|
118
|
-
font-size:
|
|
118
|
+
font-size: 18px;
|
|
119
119
|
font-weight: 800;
|
|
120
|
+
white-space: nowrap;
|
|
120
121
|
background: linear-gradient(90deg, #4ade80, #a3e635);
|
|
121
122
|
-webkit-background-clip: text;
|
|
122
123
|
background-clip: text;
|
|
@@ -136,14 +137,15 @@
|
|
|
136
137
|
.legend {
|
|
137
138
|
display: flex;
|
|
138
139
|
align-items: center;
|
|
139
|
-
gap:
|
|
140
|
+
gap: 10px;
|
|
140
141
|
}
|
|
141
142
|
.legend .item {
|
|
142
143
|
display: flex;
|
|
143
144
|
align-items: center;
|
|
144
|
-
gap:
|
|
145
|
-
font-size:
|
|
145
|
+
gap: 5px;
|
|
146
|
+
font-size: 10.5px;
|
|
146
147
|
color: #9fb3d9;
|
|
148
|
+
white-space: nowrap;
|
|
147
149
|
}
|
|
148
150
|
.legend .line-sample {
|
|
149
151
|
width: 20px;
|
|
@@ -165,13 +167,15 @@
|
|
|
165
167
|
/* 数据说明(近一年足迹时显示统计开始日期) */
|
|
166
168
|
.note {
|
|
167
169
|
margin-left: auto;
|
|
168
|
-
font-size:
|
|
170
|
+
font-size: 10.5px;
|
|
169
171
|
color: #fcd34d;
|
|
170
172
|
background: rgba(251, 191, 36, .1);
|
|
171
173
|
border: 1px solid rgba(251, 191, 36, .32);
|
|
172
|
-
padding:
|
|
174
|
+
padding: 4px 10px;
|
|
173
175
|
border-radius: 8px;
|
|
174
176
|
letter-spacing: .5px;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
flex-shrink: 0;
|
|
175
179
|
}
|
|
176
180
|
.hidden { display: none; }
|
|
177
181
|
</style>
|
|
@@ -201,6 +205,11 @@
|
|
|
201
205
|
<span class="label">总里程</span>
|
|
202
206
|
<span class="value" id="total-distance">0.0 km</span>
|
|
203
207
|
</div>
|
|
208
|
+
<div class="v-divider" id="game-mileage-divider"></div>
|
|
209
|
+
<div class="mileage-block" id="game-mileage-block">
|
|
210
|
+
<span class="label" id="game-mileage-label">欧卡总里程</span>
|
|
211
|
+
<span class="value" id="game-mileage-distance">0.0 km</span>
|
|
212
|
+
</div>
|
|
204
213
|
<div class="v-divider"></div>
|
|
205
214
|
<div class="legend">
|
|
206
215
|
<div class="item"><span class="line-sample"></span>行驶轨迹</div>
|
|
@@ -233,10 +242,24 @@
|
|
|
233
242
|
document.getElementById('distance').classList.add('cyan');
|
|
234
243
|
const totalKm = Number(((data.totalMileage || 0) / 1000).toFixed(1)).toLocaleString('zh-CN');
|
|
235
244
|
document.getElementById('total-distance').innerText = `${totalKm} km`;
|
|
245
|
+
// 本游戏总里程:美卡足迹显示美卡总里程,欧卡/Promods 足迹显示欧卡总里程
|
|
246
|
+
const gameBlock = document.getElementById('game-mileage-block');
|
|
247
|
+
const gameDivider = document.getElementById('game-mileage-divider');
|
|
248
|
+
const gameValue = data.mapType === 'ats' ? data.atsMileage : data.etsMileage;
|
|
249
|
+
if (gameValue != null) {
|
|
250
|
+
document.getElementById('game-mileage-label').innerText = data.mapType === 'ats' ? '美卡总里程' : '欧卡总里程';
|
|
251
|
+
const gameKm = Number((gameValue / 1000).toFixed(1)).toLocaleString('zh-CN');
|
|
252
|
+
document.getElementById('game-mileage-distance').innerText = `${gameKm} km`;
|
|
253
|
+
} else {
|
|
254
|
+
gameBlock.classList.add('hidden');
|
|
255
|
+
gameDivider.classList.add('hidden');
|
|
256
|
+
}
|
|
236
257
|
} else {
|
|
237
258
|
// 今日足迹不显示总里程块
|
|
238
259
|
document.getElementById('total-mileage-block').classList.add('hidden');
|
|
239
260
|
document.getElementById('total-divider').classList.add('hidden');
|
|
261
|
+
document.getElementById('game-mileage-block').classList.add('hidden');
|
|
262
|
+
document.getElementById('game-mileage-divider').classList.add('hidden');
|
|
240
263
|
}
|
|
241
264
|
|
|
242
265
|
// 近一年足迹显示统计开始日期
|
|
@@ -50,6 +50,29 @@ let mapConfig = {
|
|
|
50
50
|
y / 2.598541 + mapConfig.promods.multipliers.y
|
|
51
51
|
]
|
|
52
52
|
}
|
|
53
|
+
},
|
|
54
|
+
// 美卡(ATS):换算参数来自瓦片导出配置 TileMapInfo.json
|
|
55
|
+
// (游戏坐标 x∈[-120098.891, 41144.094] y∈[-79127.37, 82115.62] 线性铺满 131072 网格)
|
|
56
|
+
ats: {
|
|
57
|
+
tileUrl: 'https://ets_tiles.cnly.top/20260903/tmp-ats-yellow/Tiles/{z}/{x}/{y}.png',
|
|
58
|
+
multipliers: {
|
|
59
|
+
x: 97626.58,
|
|
60
|
+
y: 64321.45
|
|
61
|
+
},
|
|
62
|
+
breakpoints: {},
|
|
63
|
+
bounds: {
|
|
64
|
+
y: 131072,
|
|
65
|
+
x: 131072
|
|
66
|
+
},
|
|
67
|
+
maxZoom: 8,
|
|
68
|
+
minZoom: 0,
|
|
69
|
+
// 游戏地转地图坐标
|
|
70
|
+
calculateMapCoordinate (x, y) {
|
|
71
|
+
return [
|
|
72
|
+
x / 1.230185 + mapConfig.ats.multipliers.x,
|
|
73
|
+
y / 1.230185 + mapConfig.ats.multipliers.y
|
|
74
|
+
];
|
|
75
|
+
}
|
|
53
76
|
}
|
|
54
77
|
}
|
|
55
78
|
|
package/lib/resource/query.html
CHANGED
|
@@ -505,6 +505,47 @@
|
|
|
505
505
|
--acc-tint: rgba(52, 211, 153, 0.06);
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
+
/* 里程明细(欧卡/美卡):一行两个小格,与遥测网格同一套视觉 */
|
|
509
|
+
.mini-grid {
|
|
510
|
+
display: grid;
|
|
511
|
+
gap: 6px 8px;
|
|
512
|
+
margin-top: 6px;
|
|
513
|
+
position: relative;
|
|
514
|
+
z-index: 1;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.mini-grid.cols-2 {
|
|
518
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.mini-cell {
|
|
522
|
+
min-width: 0;
|
|
523
|
+
display: flex;
|
|
524
|
+
align-items: center;
|
|
525
|
+
justify-content: space-between;
|
|
526
|
+
gap: 8px;
|
|
527
|
+
min-height: 26px;
|
|
528
|
+
padding: 4px 9px;
|
|
529
|
+
border-radius: var(--r-sm);
|
|
530
|
+
background: rgba(255, 255, 255, 0.04);
|
|
531
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
532
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.mini-k {
|
|
536
|
+
font-size: 11px;
|
|
537
|
+
color: var(--t2);
|
|
538
|
+
white-space: nowrap;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.mini-v {
|
|
542
|
+
font-size: 12.5px;
|
|
543
|
+
font-weight: 600;
|
|
544
|
+
color: var(--t1);
|
|
545
|
+
text-align: right;
|
|
546
|
+
font-variant-numeric: tabular-nums;
|
|
547
|
+
}
|
|
548
|
+
|
|
508
549
|
/* ===== GLASS BAR ===== */
|
|
509
550
|
.glass-bar {
|
|
510
551
|
margin: 11px 14px;
|
|
@@ -922,13 +963,33 @@
|
|
|
922
963
|
<div class="section mileage-section" id="mileage-section" style="display:none;">
|
|
923
964
|
<div class="section-title">🚩 里程信息</div>
|
|
924
965
|
<div class="info-row" id="mileage-row" style="display:none;">
|
|
925
|
-
<div class="info-label">🚩
|
|
966
|
+
<div class="info-label">🚩 总里程</div>
|
|
926
967
|
<div class="info-value" id="tmp-mileage"></div>
|
|
927
968
|
</div>
|
|
969
|
+
<div class="mini-grid cols-2" id="mileage-detail" style="display:none;">
|
|
970
|
+
<div class="mini-cell">
|
|
971
|
+
<span class="mini-k">欧卡</span>
|
|
972
|
+
<span class="mini-v" id="tmp-mileage-ets2"></span>
|
|
973
|
+
</div>
|
|
974
|
+
<div class="mini-cell">
|
|
975
|
+
<span class="mini-k">美卡</span>
|
|
976
|
+
<span class="mini-v" id="tmp-mileage-ats"></span>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
928
979
|
<div class="info-row" id="today-mileage-row" style="display:none;">
|
|
929
980
|
<div class="info-label">🚩 今日里程</div>
|
|
930
981
|
<div class="info-value" id="tmp-today-mileage"></div>
|
|
931
982
|
</div>
|
|
983
|
+
<div class="mini-grid cols-2" id="today-mileage-detail" style="display:none;">
|
|
984
|
+
<div class="mini-cell">
|
|
985
|
+
<span class="mini-k">欧卡</span>
|
|
986
|
+
<span class="mini-v" id="tmp-today-mileage-ets2"></span>
|
|
987
|
+
</div>
|
|
988
|
+
<div class="mini-cell">
|
|
989
|
+
<span class="mini-k">美卡</span>
|
|
990
|
+
<span class="mini-v" id="tmp-today-mileage-ats"></span>
|
|
991
|
+
</div>
|
|
992
|
+
</div>
|
|
932
993
|
</div>
|
|
933
994
|
|
|
934
995
|
<!-- Game Time Section -->
|
|
@@ -990,10 +1051,24 @@
|
|
|
990
1051
|
|
|
991
1052
|
function formatMileage(val) {
|
|
992
1053
|
if (!val) return null;
|
|
993
|
-
if (val > 1000) return (val / 1000).toFixed(1) + ' 公里';
|
|
1054
|
+
if (val > 1000) return Number((val / 1000).toFixed(1)).toLocaleString('en-US') + ' 公里';
|
|
994
1055
|
return val + ' 米';
|
|
995
1056
|
}
|
|
996
1057
|
|
|
1058
|
+
// 单款游戏的里程:0 也照常显示成「0 公里」(表示这款没跑过),只有 null/空 才不显示
|
|
1059
|
+
function formatMileageZero(val) {
|
|
1060
|
+
if (val === null || val === undefined || val === '') return null;
|
|
1061
|
+
var n = Number(val);
|
|
1062
|
+
if (!isFinite(n)) return null;
|
|
1063
|
+
if (n <= 0) return '0 公里';
|
|
1064
|
+
return n >= 1000 ? Number((n / 1000).toFixed(1)).toLocaleString('en-US') + ' 公里' : n + ' 米';
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// 两款游戏里程是否值得单独列出(都为 0 就不占地方)
|
|
1068
|
+
function hasMileageDetail(ets2, ats) {
|
|
1069
|
+
return Number(ets2) > 0 || Number(ats) > 0;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
997
1072
|
/* ==================================================================
|
|
998
1073
|
* V1 车队平台 · 实时遥测
|
|
999
1074
|
* 仅渲染「有值」的字段(null / undefined / 空串一律不显示)
|
|
@@ -1388,19 +1463,31 @@
|
|
|
1388
1463
|
banCountEl.innerText = banCount + '次';
|
|
1389
1464
|
banCountEl.className = banCount > 0 ? 'info-value has-bans' : 'info-value no-bans';
|
|
1390
1465
|
|
|
1391
|
-
// Mileage
|
|
1466
|
+
// Mileage:总里程 / 今日里程是欧卡+美卡合计,另附两款游戏的单独里程
|
|
1392
1467
|
const mileage = formatMileage(data.mileage);
|
|
1393
1468
|
const todayMileage = formatMileage(data.todayMileage);
|
|
1394
|
-
|
|
1469
|
+
const hasMileageBreakdown = hasMileageDetail(data.mileageEts2, data.mileageAts);
|
|
1470
|
+
const hasTodayBreakdown = hasMileageDetail(data.todayMileageEts2, data.todayMileageAts);
|
|
1471
|
+
if (mileage || todayMileage || hasMileageBreakdown || hasTodayBreakdown) {
|
|
1395
1472
|
document.getElementById('mileage-section').style.display = '';
|
|
1396
1473
|
if (mileage) {
|
|
1397
1474
|
document.getElementById('mileage-row').style.display = '';
|
|
1398
1475
|
document.getElementById('tmp-mileage').innerText = mileage;
|
|
1399
1476
|
}
|
|
1477
|
+
if (hasMileageBreakdown) {
|
|
1478
|
+
document.getElementById('mileage-detail').style.display = '';
|
|
1479
|
+
document.getElementById('tmp-mileage-ets2').innerText = formatMileageZero(data.mileageEts2) || '';
|
|
1480
|
+
document.getElementById('tmp-mileage-ats').innerText = formatMileageZero(data.mileageAts) || '';
|
|
1481
|
+
}
|
|
1400
1482
|
if (todayMileage) {
|
|
1401
1483
|
document.getElementById('today-mileage-row').style.display = '';
|
|
1402
1484
|
document.getElementById('tmp-today-mileage').innerText = todayMileage;
|
|
1403
1485
|
}
|
|
1486
|
+
if (hasTodayBreakdown) {
|
|
1487
|
+
document.getElementById('today-mileage-detail').style.display = '';
|
|
1488
|
+
document.getElementById('tmp-today-mileage-ets2').innerText = formatMileageZero(data.todayMileageEts2) || '';
|
|
1489
|
+
document.getElementById('tmp-today-mileage-ats').innerText = formatMileageZero(data.todayMileageAts) || '';
|
|
1490
|
+
}
|
|
1404
1491
|
}
|
|
1405
1492
|
|
|
1406
1493
|
// Game Time
|
|
@@ -264,6 +264,17 @@
|
|
|
264
264
|
calculateMapCoordinate(x, y) {
|
|
265
265
|
return [x / 2.598541 + mapConfig.promods.multipliers.x, y / 2.598541 + mapConfig.promods.multipliers.y];
|
|
266
266
|
}
|
|
267
|
+
},
|
|
268
|
+
// 美卡(ATS):换算参数来自瓦片导出配置 TileMapInfo.json
|
|
269
|
+
ats: {
|
|
270
|
+
tileUrl: 'https://ets_tiles.cnly.top/20260903/tmp-ats-yellow/Tiles/{z}/{x}/{y}.png',
|
|
271
|
+
multipliers: { x: 97626.58, y: 64321.45 },
|
|
272
|
+
breakpoints: {},
|
|
273
|
+
bounds: { y: 131072, x: 131072 },
|
|
274
|
+
maxZoom: 8, minZoom: 0,
|
|
275
|
+
calculateMapCoordinate(x, y) {
|
|
276
|
+
return [x / 1.230185 + mapConfig.ats.multipliers.x, y / 1.230185 + mapConfig.ats.multipliers.y];
|
|
277
|
+
}
|
|
267
278
|
}
|
|
268
279
|
};
|
|
269
280
|
|
package/lib/util/constant.js
CHANGED
|
@@ -26,12 +26,21 @@ module.exports = {
|
|
|
26
26
|
* P服务器ID集合
|
|
27
27
|
*/
|
|
28
28
|
PromodsIds: [50, 51],
|
|
29
|
+
/**
|
|
30
|
+
* 美卡(ATS)服务器ID集合
|
|
31
|
+
*/
|
|
32
|
+
AtsIds: [8, 10, 45],
|
|
33
|
+
/**
|
|
34
|
+
* 美卡足迹记录开始日期(接口自 2026-09-30 19:00 起记录美卡轨迹)
|
|
35
|
+
*/
|
|
36
|
+
AtsFootprintStartDate: '2026-09-30',
|
|
29
37
|
/**
|
|
30
38
|
* 服务器类型
|
|
31
39
|
*/
|
|
32
40
|
ServerType: {
|
|
33
41
|
ets: 1,
|
|
34
|
-
promods: 2
|
|
42
|
+
promods: 2,
|
|
43
|
+
ats: 3
|
|
35
44
|
},
|
|
36
45
|
/**
|
|
37
46
|
* 联运部接龙:每天发布的时间
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多绑定选择器:用户绑定了多个 tmpId 时,列出序号让用户回复选择(支持多选)。
|
|
3
|
+
* 原理:需要选择的指令先回复编号列表并挂起(pending),由前置中间件捕获用户下一条
|
|
4
|
+
* "纯序号"消息后解析并继续执行;超时/取消/其他内容均视为放弃。
|
|
5
|
+
*/
|
|
6
|
+
const MAX_PICK = 5; // 单次多选上限
|
|
7
|
+
const PICK_TIMEOUT = 60_000; // 等待回复时长(毫秒)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* key: `${platform}:${channelId}:${userId}` → { bindings, allowMulti, max, finish }
|
|
11
|
+
* 按平台+频道+用户隔离,避免群里其他人误触发。
|
|
12
|
+
*/
|
|
13
|
+
const pending = new Map();
|
|
14
|
+
|
|
15
|
+
function keyOf(session) {
|
|
16
|
+
return `${session.platform}:${session.channelId || 'private'}:${session.userId}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 注册选择器中间件(在插件 apply 中调用一次,前置执行)
|
|
21
|
+
*/
|
|
22
|
+
function register(ctx) {
|
|
23
|
+
ctx.middleware((session, next) => {
|
|
24
|
+
const key = keyOf(session);
|
|
25
|
+
const record = pending.get(key);
|
|
26
|
+
if (!record) return next();
|
|
27
|
+
const content = (session.content || '').trim();
|
|
28
|
+
// 取消
|
|
29
|
+
if (/^取消$/.test(content)) {
|
|
30
|
+
record.finish(null);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// 纯序号消息(支持 1,3 / 1 3 / 1、3 等分隔写法)
|
|
34
|
+
if (/^\d{1,6}(?:\s*[,,、 ]\s*\d{1,6})*$/.test(content)) {
|
|
35
|
+
const indexes = [...content.matchAll(/\d{1,6}/g)].map(m => parseInt(m[0], 10));
|
|
36
|
+
const picked = [...new Set(indexes)]
|
|
37
|
+
.filter(i => i >= 1 && i <= record.bindings.length)
|
|
38
|
+
.slice(0, record.allowMulti ? record.max : 1)
|
|
39
|
+
.map(i => String(record.bindings[i - 1].tmp_id));
|
|
40
|
+
if (picked.length === 0) {
|
|
41
|
+
record.finish(null);
|
|
42
|
+
session.send('没有有效的序号,已取消').catch(() => { });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
record.finish(picked);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// 其他内容:视为放弃挂起,消息继续按正常流程处理
|
|
49
|
+
record.finish(null);
|
|
50
|
+
return next();
|
|
51
|
+
}, true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 需要绑定 tmpId 的指令调用:绑定数 ≤1 时直接返回不打扰,≥2 时列出序号并等待选择。
|
|
56
|
+
* @param bindings guildBind.list() 的结果
|
|
57
|
+
* @param opts { allowMulti?: boolean } 是否允许多选(默认允许)
|
|
58
|
+
* @returns 选中的 tmpId 字符串数组;返回 null 表示已在等待用户回复,调用方应直接 return
|
|
59
|
+
*/
|
|
60
|
+
async function pick(ctx, session, bindings, opts = {}) {
|
|
61
|
+
if (!bindings || bindings.length === 0) return [];
|
|
62
|
+
if (bindings.length === 1) return [String(bindings[0].tmp_id)];
|
|
63
|
+
const allowMulti = opts.allowMulti !== false;
|
|
64
|
+
const max = allowMulti ? MAX_PICK : 1;
|
|
65
|
+
const lines = bindings.map((b, i) => `${i + 1}. ${b.tmp_name || '未知'} (${b.tmp_id})`);
|
|
66
|
+
const tip = allowMulti
|
|
67
|
+
? `请回复序号选择(最多 ${max} 个,多选用逗号分隔,如 1,3)`
|
|
68
|
+
: '请回复一个序号';
|
|
69
|
+
await session.send(`您绑定了多个编号:\n${lines.join('\n')}\n${tip}\n回复"取消"放弃,60 秒内有效`);
|
|
70
|
+
return await new Promise(resolve => {
|
|
71
|
+
const key = keyOf(session);
|
|
72
|
+
const record = {
|
|
73
|
+
bindings,
|
|
74
|
+
allowMulti,
|
|
75
|
+
max,
|
|
76
|
+
timer: null,
|
|
77
|
+
finish(result) {
|
|
78
|
+
clearTimeout(record.timer);
|
|
79
|
+
pending.delete(key);
|
|
80
|
+
resolve(result);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
record.timer = setTimeout(() => {
|
|
84
|
+
if (pending.get(key) === record) {
|
|
85
|
+
pending.delete(key);
|
|
86
|
+
resolve(null);
|
|
87
|
+
session.send('选择超时,已取消').catch(() => { });
|
|
88
|
+
}
|
|
89
|
+
}, PICK_TIMEOUT);
|
|
90
|
+
pending.set(key, record);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { register, pick, MAX_PICK, PICK_TIMEOUT };
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-ets2-tools-tmp",
|
|
3
3
|
"description": "欧卡2 TruckersMP信息查询、车队平台查询及活动提醒",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.4.0",
|
|
5
5
|
"contributors": [
|
|
6
|
-
"opwop <slhp1013@qq.com>"
|
|
7
|
-
"bot_actions <168329908@qq.com>"
|
|
6
|
+
"opwop <slhp1013@qq.com>"
|
|
8
7
|
],
|
|
9
8
|
"main": "lib/index.js",
|
|
10
9
|
"typings": "lib/index.d.ts",
|