koishi-plugin-ets2-tools-tmp 3.3.62 → 3.3.64
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 +98 -9
- package/lib/command/tmpQuery/tmpQueryImg.js +106 -33
- package/lib/command/tmpQuery/tmpQueryText.js +14 -31
- package/lib/index.js +6 -29
- package/lib/resource/footprint.html +25 -3
- package/lib/resource/query.html +817 -151
- package/lib/util/constant.js +17 -1
- 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
|
};
|
|
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ActivityService = void 0;
|
|
4
4
|
|
|
5
5
|
const koishi_1 = require("koishi");
|
|
6
|
+
const { LeadRoleCallMessage, LeadRoleCallTime } = require("../util/constant");
|
|
6
7
|
|
|
7
8
|
const v1Methods = require('./tmpActivityService/tmpActivityServiceV1');
|
|
8
|
-
const v2Methods = require('./tmpActivityService/tmpActivityServiceV2');
|
|
9
9
|
|
|
10
10
|
class ActivityService {
|
|
11
11
|
constructor(ctx, config) {
|
|
@@ -14,16 +14,12 @@ class ActivityService {
|
|
|
14
14
|
this.todayActivities = [];
|
|
15
15
|
this.todayTMPEvents = [];
|
|
16
16
|
this.sentReminders = new Set();
|
|
17
|
+
this.sentRoleCalls = new Set();
|
|
17
18
|
this.sentNoActivityNotification = false;
|
|
18
19
|
this.timers = [];
|
|
19
20
|
this.logger = this.initLogger();
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
if (platformVersion === "v2") {
|
|
23
|
-
Object.assign(this, v2Methods);
|
|
24
|
-
} else {
|
|
25
|
-
Object.assign(this, v1Methods);
|
|
26
|
-
}
|
|
22
|
+
Object.assign(this, v1Methods);
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
initLogger() {
|
|
@@ -85,6 +81,21 @@ class ActivityService {
|
|
|
85
81
|
this.resetDailyData();
|
|
86
82
|
return "✅ 今日活动数据已重置完成!";
|
|
87
83
|
});
|
|
84
|
+
|
|
85
|
+
// 仅配置了联运部群号时才注册,方便立即验证效果(定时任务每天 15:00 才触发一次)
|
|
86
|
+
if (this.cfg.leadGroups && this.cfg.leadGroups.length > 0) {
|
|
87
|
+
this.ctx.command("联运接龙", "手动发布今日活动的联运接龙")
|
|
88
|
+
.action(async () => {
|
|
89
|
+
this.logger.debug("手动执行联运接龙命令");
|
|
90
|
+
const sent = await this.checkAndSendLeadRoleCall();
|
|
91
|
+
if (sent.length === 0) {
|
|
92
|
+
return this.todayActivities.length === 0
|
|
93
|
+
? "今日没有活动,无需发布接龙"
|
|
94
|
+
: "今日接龙已发布过了(需要重发请先执行「重置数据」)";
|
|
95
|
+
}
|
|
96
|
+
return `✅ 已发布接龙:${sent.join('、')}`;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
setupDailyTasks() {
|
|
@@ -151,6 +162,19 @@ class ActivityService {
|
|
|
151
162
|
}, `在线成员检查定时器: ${this.cfg.onlineCheck.time}`);
|
|
152
163
|
}
|
|
153
164
|
|
|
165
|
+
// 联运部接龙:配置了联运群号后,每天定时检查当天活动并发布接龙
|
|
166
|
+
const leadGroups = this.cfg.leadGroups || [];
|
|
167
|
+
if (leadGroups.length > 0) {
|
|
168
|
+
const leadTimeText = `${LeadRoleCallTime.hour}:${LeadRoleCallTime.minute.toString().padStart(2, "0")}`;
|
|
169
|
+
this.setupTimer(LeadRoleCallTime.hour, LeadRoleCallTime.minute, async () => {
|
|
170
|
+
this.logger.timing(`执行联运接龙任务 (${leadTimeText})`);
|
|
171
|
+
await this.checkAndSendLeadRoleCall();
|
|
172
|
+
}, `联运接龙定时器: ${leadTimeText}`);
|
|
173
|
+
this.logger.info(`[联运接龙] 已启用,${leadGroups.length} 个联运群,每天 ${leadTimeText} 检查当天活动并发布接龙`);
|
|
174
|
+
} else {
|
|
175
|
+
this.logger.debug("未配置联运部群号,跳过联运接龙定时器");
|
|
176
|
+
}
|
|
177
|
+
|
|
154
178
|
const minuteTimer = setInterval(async () => {
|
|
155
179
|
await this.checkAndSendActivityReminders();
|
|
156
180
|
}, koishi_1.Time.minute);
|
|
@@ -197,17 +221,19 @@ class ActivityService {
|
|
|
197
221
|
const previousActivityCount = this.todayActivities.length;
|
|
198
222
|
const previousTMPCount = this.todayTMPEvents.length;
|
|
199
223
|
const previousReminderCount = this.sentReminders.size;
|
|
224
|
+
const previousRoleCallCount = this.sentRoleCalls.size;
|
|
200
225
|
const previousNoActivityNotification = this.sentNoActivityNotification;
|
|
201
226
|
|
|
202
227
|
this.logger.info(`[数据重置] 开始重置数据,本地时间: ${localTime}, 本地日期: ${localDate}, UTC日期: ${utcDate}`);
|
|
203
|
-
this.logger.info(`[数据重置] 重置前数据: 活动${previousActivityCount}个, TMP${previousTMPCount}个, 提醒${previousReminderCount}个, 无活动通知${previousNoActivityNotification ? "已发送" : "未发送"}`);
|
|
228
|
+
this.logger.info(`[数据重置] 重置前数据: 活动${previousActivityCount}个, TMP${previousTMPCount}个, 提醒${previousReminderCount}个, 联运接龙${previousRoleCallCount}个, 无活动通知${previousNoActivityNotification ? "已发送" : "未发送"}`);
|
|
204
229
|
|
|
205
230
|
this.todayActivities = [];
|
|
206
231
|
this.todayTMPEvents = [];
|
|
207
232
|
this.sentReminders.clear();
|
|
233
|
+
this.sentRoleCalls.clear();
|
|
208
234
|
this.sentNoActivityNotification = false;
|
|
209
235
|
|
|
210
|
-
this.logger.info(`[数据重置] 每日数据已重置: 活动${previousActivityCount}→0, TMP${previousTMPCount}→0, 提醒${previousReminderCount}→0, 无活动通知${previousNoActivityNotification ? "已发送" : "未发送"}→未发送`);
|
|
236
|
+
this.logger.info(`[数据重置] 每日数据已重置: 活动${previousActivityCount}→0, TMP${previousTMPCount}→0, 提醒${previousReminderCount}→0, 联运接龙${previousRoleCallCount}→0, 无活动通知${previousNoActivityNotification ? "已发送" : "未发送"}→未发送`);
|
|
211
237
|
|
|
212
238
|
this.updateActivityData().then(() => {
|
|
213
239
|
this.logger.info(`[数据重置] 重置后数据更新完成: 活动${this.todayActivities.length}个, TMP${this.todayTMPEvents.length}个`);
|
|
@@ -611,6 +637,68 @@ class ActivityService {
|
|
|
611
637
|
throw lastError || new Error(`所有onebot适配器都无法发送消息到${groupType} ${groupId}`);
|
|
612
638
|
}
|
|
613
639
|
|
|
640
|
+
/**
|
|
641
|
+
* 联运部接龙:当天有活动时,在联运群发布接龙消息。
|
|
642
|
+
* 返回本次实际发布的活动名称数组(未配置群号 / 今日无活动 / 今日已发布时为空数组)。
|
|
643
|
+
*/
|
|
644
|
+
async checkAndSendLeadRoleCall() {
|
|
645
|
+
const groups = this.cfg.leadGroups || [];
|
|
646
|
+
if (groups.length === 0) {
|
|
647
|
+
this.logger.debug("[联运接龙] 未配置联运部群号,跳过");
|
|
648
|
+
return [];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 先刷新活动数据,避免依赖上一次定时检查(默认 14:00)的结果
|
|
652
|
+
await this.updateActivityData();
|
|
653
|
+
|
|
654
|
+
if (this.todayActivities.length === 0) {
|
|
655
|
+
this.logger.debug("[联运接龙] 今日没有活动,跳过");
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const template = this.cfg.leadCallMessage || LeadRoleCallMessage;
|
|
660
|
+
const now = new Date();
|
|
661
|
+
const dateText = `${now.getMonth() + 1}月${now.getDate()}日`;
|
|
662
|
+
const sent = [];
|
|
663
|
+
|
|
664
|
+
for (const activity of this.todayActivities) {
|
|
665
|
+
const name = activity.themeName || '未知活动';
|
|
666
|
+
const key = `rolecall_${activity.id}`;
|
|
667
|
+
if (this.sentRoleCalls.has(key)) {
|
|
668
|
+
this.logger.debug(`[联运接龙] "${name}" 今日已发布过,跳过`);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const message = template
|
|
673
|
+
.replace(/\{date\}/g, dateText)
|
|
674
|
+
.replace(/\{name\}/g, name)
|
|
675
|
+
.replace(/\\n/g, "\n")
|
|
676
|
+
.trim();
|
|
677
|
+
|
|
678
|
+
let anySuccess = false;
|
|
679
|
+
for (const groupId of groups) {
|
|
680
|
+
try {
|
|
681
|
+
await this.sendToGroup(groupId, message, "联运群");
|
|
682
|
+
this.logger.message(`[联运接龙] 已发布到联运群 ${groupId}: ${name}`);
|
|
683
|
+
anySuccess = true;
|
|
684
|
+
} catch (error) {
|
|
685
|
+
this.logger.error(`[联运接龙] 发布到联运群 ${groupId} 失败:`, error.message);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// 发送全部失败时不记「已发布」,留待下次触发(手动命令 / 重置数据后)重试
|
|
690
|
+
if (anySuccess) {
|
|
691
|
+
this.sentRoleCalls.add(key);
|
|
692
|
+
sent.push(name);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (sent.length > 0) {
|
|
697
|
+
this.logger.info(`[联运接龙] 本次发布完成:${sent.join('、')}`);
|
|
698
|
+
}
|
|
699
|
+
return sent;
|
|
700
|
+
}
|
|
701
|
+
|
|
614
702
|
async checkAutoClockStatus() {
|
|
615
703
|
await this.updateActivityData();
|
|
616
704
|
|
|
@@ -655,6 +743,7 @@ class ActivityService {
|
|
|
655
743
|
this.todayActivities = [];
|
|
656
744
|
this.todayTMPEvents = [];
|
|
657
745
|
this.sentReminders.clear();
|
|
746
|
+
this.sentRoleCalls.clear();
|
|
658
747
|
this.timers.forEach((timer) => {
|
|
659
748
|
clearTimeout(timer);
|
|
660
749
|
clearInterval(timer);
|
|
@@ -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
|
@@ -7,6 +7,7 @@ const koishi_1 = require("koishi");
|
|
|
7
7
|
const model = require('./database/model');
|
|
8
8
|
const { ServerType } = require('./util/constant');
|
|
9
9
|
const { MileageRankingType } = require('./util/constant');
|
|
10
|
+
const { LeadRoleCallMessage, LeadRoleCallTime } = require('./util/constant');
|
|
10
11
|
|
|
11
12
|
const commands = {
|
|
12
13
|
tmpQuery: require('./command/tmpQuery/tmpQuery'),
|
|
@@ -21,7 +22,6 @@ const commands = {
|
|
|
21
22
|
resetPassword: require('./command/ets-app/resetPassword/resetPassword'),
|
|
22
23
|
queryPoint: require('./command/ets-app/queryPoint/queryPoint'),
|
|
23
24
|
changePoint: require('./command/ets-app/changePoint/changePoint'),
|
|
24
|
-
addMember: require('./command/ets-app/addMember/addMemberV2'),
|
|
25
25
|
pointRanking: require('./command/ets-app/pointRanking/pointRanking'),
|
|
26
26
|
tmpVtc: require('./command/tmpVtc'),
|
|
27
27
|
tmpVtcOnline: require('./command/tmpVtcOnline/tmpVtcOnline'),
|
|
@@ -88,7 +88,6 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
88
88
|
mainSettings: koishi_1.Schema.boolean().default(false).description('是否启用车队平台积分查询功能'),
|
|
89
89
|
resetPassword: koishi_1.Schema.boolean().default(false).description('是否启用车队平台重置密码功能'),
|
|
90
90
|
changePoint: koishi_1.Schema.boolean().default(false).description('是否启用车队平台积分修改功能'),
|
|
91
|
-
addMember: koishi_1.Schema.boolean().default(false).description('是否启用车队平台新增成员功能'),
|
|
92
91
|
tmpActivityService: koishi_1.Schema.boolean().default(false).description('是否启用车队活动查询'),
|
|
93
92
|
tmpVersionCheck: koishi_1.Schema.boolean().default(false).description('是否启用TMP版本更新查询'),
|
|
94
93
|
tmpQueryGameTime: koishi_1.Schema.boolean().default(true).description('是否启用游戏时长查询功能'),
|
|
@@ -136,18 +135,9 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
136
135
|
mainSettings: koishi_1.Schema.object({
|
|
137
136
|
settings: koishi_1.Schema.object({
|
|
138
137
|
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
138
|
url: koishi_1.Schema.string().description("API服务器地址(不含协议)"),
|
|
144
139
|
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(""),
|
|
140
|
+
logOutput: koishi_1.Schema.boolean().description("是否输出日志").default(true)
|
|
151
141
|
}).description("车队平台配置")
|
|
152
142
|
}).description("车队平台配置"),
|
|
153
143
|
resetPassword: koishi_1.Schema.object({
|
|
@@ -160,12 +150,6 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
160
150
|
adminUsers: koishi_1.Schema.array(koishi_1.Schema.string()).description("管理员用户ID(拥有积分修改权限)").default([])
|
|
161
151
|
})
|
|
162
152
|
}).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
153
|
tmpActivityService: koishi_1.Schema.object({
|
|
170
154
|
api: koishi_1.Schema.object({
|
|
171
155
|
vtcId: koishi_1.Schema.string().description("VTC ID(用于TMP API)").default("")
|
|
@@ -211,6 +195,8 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
211
195
|
activityStartReminderMessage: koishi_1.Schema.string().description("活动开始提醒消息模板,支持变量:{name}, {server}, {startingPoint}, {terminalPoint}, {distance}, {banner}").default("活动 {name} 现在开始集合啦!\n服务器: {server}\n起点: {startingPoint}\n终点: {terminalPoint}\n距离: {distance}KM\n活动将于20:30分开始!"),
|
|
212
196
|
activityReminderTimes: koishi_1.Schema.array(koishi_1.Schema.number()).role("table").description("活动开始前提醒时间(分钟)").default([60, 30, 15])
|
|
213
197
|
}).description("主群配置"),
|
|
198
|
+
leadGroups: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("联运部群号(填写后,当天有活动时会在该群 " + LeadRoleCallTime.hour + ":00 自动发布接龙)").default([]),
|
|
199
|
+
leadCallMessage: koishi_1.Schema.string().description("联运接龙消息模板,支持变量:{date} 日期、{name} 活动名称").default(LeadRoleCallMessage),
|
|
214
200
|
debug: koishi_1.Schema.object({
|
|
215
201
|
debugMode: koishi_1.Schema.boolean().description("启用调试模式").default(false),
|
|
216
202
|
logApiResponses: koishi_1.Schema.boolean().description("记录API响应详情").default(false),
|
|
@@ -403,10 +389,9 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
403
389
|
|
|
404
390
|
if (cfg.commands?.resetPassword) {
|
|
405
391
|
ctx.command(`重置密码 [targetTeamId:string] [password:string]`, "重置欧卡车队平台密码")
|
|
406
|
-
.usage("
|
|
392
|
+
.usage("使用车队编号(teamId)指定目标;管理员可用 uid=12345")
|
|
407
393
|
.example(`重置密码 - 重置自己的密码`)
|
|
408
394
|
.example(`重置密码 789 - 管理员重置指定teamId的密码`)
|
|
409
|
-
.example(`重置密码 123456 Abc123def4 - V2.0指定QQ与新密码`)
|
|
410
395
|
.action(async ({ session }, targetTeamId, password) => await commands.resetPassword(ctx, cfg, session, targetTeamId, password));
|
|
411
396
|
}
|
|
412
397
|
|
|
@@ -420,20 +405,12 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
420
405
|
|
|
421
406
|
if (cfg.commands?.changePoint) {
|
|
422
407
|
ctx.command(`积分修改 <target:string> <changeType:string> <quantity:string> <reason:string>`, "修改欧卡车队平台积分")
|
|
423
|
-
.usage("管理员专用。changeType: 增加 或 减少。target
|
|
408
|
+
.usage("管理员专用。changeType: 增加 或 减少。target可输入车队编号或@群成员。reason为备注原因")
|
|
424
409
|
.example(`积分修改 @某人 增加 10 活动奖励 - 增加@某人10积分,备注活动奖励`)
|
|
425
410
|
.example(`积分修改 5 减少 5 违规扣分 - 减少车队编号5的用户5积分,备注违规扣分`)
|
|
426
411
|
.action(async ({ session }, target, changeType, quantity, reason) => await commands.changePoint(ctx, cfg, session, target, changeType, quantity, reason));
|
|
427
412
|
}
|
|
428
413
|
|
|
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
414
|
ctx.command('规则查询')
|
|
438
415
|
.action(async () => 'TruckersMP官方规则链接:https://truckersmp.com/knowledge-base/article/746');
|
|
439
416
|
}
|
|
@@ -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
|
}
|