koishi-plugin-ets2-tools-tmp 3.4.5 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/command/ets-app/changePoint/changePointV1.js +28 -7
- package/lib/command/ets-app/pointRanking/pointRanking.js +12 -5
- package/lib/command/ets-app/queryPoint/queryPoint.js +15 -1
- package/lib/command/ets-app/queryPoint/queryPointV1.js +21 -5
- package/lib/command/ets-app/resetPassword/resetPasswordV1.js +32 -3
- package/lib/command/tmpBind.js +41 -4
- package/lib/command/tmpDlcMap.js +1 -1
- package/lib/command/tmpFootprint.js +3 -2
- package/lib/command/tmpMileageRanking.js +74 -23
- package/lib/command/tmpPosition.js +3 -2
- package/lib/command/tmpQuery/tmpQuery.js +70 -1
- package/lib/command/tmpQuery/tmpQueryImg.js +4 -3
- package/lib/command/tmpQuery/tmpQueryText.js +4 -3
- package/lib/command/tmpServer/tmpServer.js +47 -3
- package/lib/command/tmpServer/tmpServerImg.js +1 -1
- package/lib/command/tmpTraffic/tmpTraffic.js +64 -3
- package/lib/command/tmpTraffic/tmpTrafficMap.js +2 -2
- package/lib/command/tmpVersion.js +81 -9
- package/lib/command/tmpVersionCheck.js +53 -10
- package/lib/command/tmpVtcMileageRanking.js +75 -22
- package/lib/command/tmpVtcOnline/tmpVtcOnlineImg.js +1 -1
- package/lib/database/guildBind.js +150 -1
- package/lib/database/model.js +12 -0
- package/lib/index.js +139 -15
- package/lib/resource/command-suggest.html +222 -0
- package/lib/resource/tmp-version.html +292 -0
- package/lib/util/commandSuggest.js +140 -0
- package/lib/util/commandSuggestMiddleware.js +210 -0
- package/lib/util/localImageHost.js +219 -0
- package/lib/util/messageRender.js +234 -0
- package/lib/util/qqMarkdown.js +171 -0
- package/lib/util/qqMediaUpload.js +165 -0
- package/lib/util/qqOfficial.js +233 -0
- package/package.json +1 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
const qqOfficial = require('../../../util/qqOfficial');
|
|
1
2
|
module.exports = async (ctx, cfg, session, target, changeType, quantity, reason) => {
|
|
2
3
|
const { url, token, logOutput } = cfg.mainSettings?.settings || {};
|
|
3
4
|
const { adminUsers } = cfg.changePoint?.settings || {};
|
|
4
5
|
const currentUserQQ = session.userId;
|
|
5
6
|
const isPrivateChat = session.channelId === `private:${currentUserQQ}`;
|
|
6
7
|
const isAdmin = adminUsers.includes(currentUserQQ);
|
|
8
|
+
// QQ 官方机器人:只能拿到 unionid,没有 QQ 号
|
|
9
|
+
const official = qqOfficial.isQQOfficial(session);
|
|
10
|
+
const officialIdent = official ? qqOfficial.getQQIdentity(session) : null;
|
|
7
11
|
|
|
8
12
|
const log = (msg) => logOutput && ctx.logger.info(msg);
|
|
9
13
|
|
|
@@ -43,16 +47,33 @@ module.exports = async (ctx, cfg, session, target, changeType, quantity, reason)
|
|
|
43
47
|
let userId;
|
|
44
48
|
|
|
45
49
|
if (target) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
50
|
+
// 只有真正的 <at ...> 才算 QQ 号来源;纯数字既可能是 QQ 也可能是车队编号,
|
|
51
|
+
// 旧代码用 parseAtQQ 的“原样返回”会把车队编号误判成 QQ,这里显式区分。
|
|
52
|
+
const isAtMention = String(target).startsWith("<at ");
|
|
53
|
+
const parsedQQ = isAtMention ? parseAtQQ(target) : null;
|
|
54
|
+
const atId = parsedQQ && /^\d+$/.test(parsedQQ) ? parsedQQ : null;
|
|
55
|
+
const isTeamId = !isAtMention && /^\d+$/.test(target);
|
|
49
56
|
|
|
50
57
|
let queryUrl;
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
if (official) {
|
|
59
|
+
// 官方机器人:@ 拿到的是 openid 不是 QQ 号,只能按车队编号
|
|
60
|
+
if (isAtMention) {
|
|
61
|
+
return "QQ 官方机器人暂不支持通过 @ 指定成员,请改用车队编号(如「积分修改 789 增加 10 备注」)";
|
|
62
|
+
}
|
|
63
|
+
if (!officialIdent || !officialIdent.unionId) {
|
|
64
|
+
return "未获取到 unionid,无法执行积分修改。请确认机器人已开通 unionid 权限";
|
|
65
|
+
}
|
|
66
|
+
if (isTeamId) {
|
|
67
|
+
queryUrl = `https://${baseUrl}/api/user/info/list?token=${token}&page=1&limit=7&tmpId=&tmpName=&teamId=${encodeURIComponent(target)}&qq=&unionid=&state=0&teamRole=&isExecutive=&isManager=&adminUserId=`;
|
|
68
|
+
log(`[V1] 通过车队编号查询成员: ${target}`);
|
|
69
|
+
} else {
|
|
70
|
+
return "目标用户格式不正确,请输入车队编号";
|
|
71
|
+
}
|
|
72
|
+
} else if (isAtMention && atId) {
|
|
73
|
+
queryUrl = `https://${baseUrl}/api/user/info/list?token=${token}&page=1&limit=7&tmpId=&tmpName=&teamId=&qq=${encodeURIComponent(atId)}&unionid=&state=0&teamRole=&isExecutive=&isManager=&adminUserId=`;
|
|
74
|
+
log(`[V1] 通过QQ号查询成员: ${atId}`);
|
|
54
75
|
} else if (isTeamId) {
|
|
55
|
-
queryUrl = `https://${baseUrl}/api/user/info/list?page=
|
|
76
|
+
queryUrl = `https://${baseUrl}/api/user/info/list?token=${token}&page=1&limit=7&tmpId=&tmpName=&teamId=${encodeURIComponent(target)}&qq=&unionid=&state=0&teamRole=&isExecutive=&isManager=&adminUserId=`;
|
|
56
77
|
log(`[V1] 通过车队编号查询成员: ${target}`);
|
|
57
78
|
} else {
|
|
58
79
|
return "目标用户格式不正确,请输入车队编号或@群成员";
|
|
@@ -40,7 +40,7 @@ module.exports = async (ctx, cfg, session) => {
|
|
|
40
40
|
let totalPage = 1;
|
|
41
41
|
|
|
42
42
|
while (currentPage <= totalPage) {
|
|
43
|
-
const queryUrl = `https://${baseUrl}/api/user/info/list?page=${currentPage}&limit=100&tmpId=&tmpName=&teamId=&qq=&state=0&teamRole=&token=${token}`;
|
|
43
|
+
const queryUrl = `https://${baseUrl}/api/user/info/list?page=${currentPage}&limit=100&tmpId=&tmpName=&teamId=&qq=&unionid=&state=0&teamRole=&isExecutive=&isManager=&adminUserId=&token=${token}`;
|
|
44
44
|
log(`[积分排行] 请求第 ${currentPage} 页: ${queryUrl.replace(token, "***")}`);
|
|
45
45
|
|
|
46
46
|
const response = await ctx.http.get(queryUrl, { timeout: 10000 });
|
|
@@ -98,10 +98,17 @@ module.exports = async (ctx, cfg, session) => {
|
|
|
98
98
|
log(`[积分排行] 排行榜前10名已生成, 第一名: ${top10[0]?.tmpName} (${top10[0]?.rewardPoints}积分)`);
|
|
99
99
|
|
|
100
100
|
// 查询当前用户的排名
|
|
101
|
+
// onebot 用 QQ 号比对;QQ 官方机器人没有 QQ 号,改用 unionid 比对
|
|
101
102
|
let currentPlayer = null;
|
|
103
|
+
const official = qqOfficial.isQQOfficial(session);
|
|
104
|
+
const officialUnionId = official ? (qqOfficial.getQQIdentity(session).unionId || '') : '';
|
|
102
105
|
const currentUserQQ = String(session.userId || '');
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
const hasIdentity = official ? !!officialUnionId : !!currentUserQQ;
|
|
107
|
+
|
|
108
|
+
if (hasIdentity) {
|
|
109
|
+
const playerIndex = official
|
|
110
|
+
? sortedMembers.findIndex(m => String(m.unionid || '') !== '' && String(m.unionid) === officialUnionId)
|
|
111
|
+
: sortedMembers.findIndex(m => String(m.qq) === currentUserQQ);
|
|
105
112
|
if (playerIndex !== -1) {
|
|
106
113
|
const player = sortedMembers[playerIndex];
|
|
107
114
|
currentPlayer = {
|
|
@@ -114,7 +121,7 @@ module.exports = async (ctx, cfg, session) => {
|
|
|
114
121
|
};
|
|
115
122
|
log(`[积分排行] 找到当前用户: ${currentPlayer.tmpName}, 排名: ${currentPlayer.rank}, 积分: ${currentPlayer.rewardPoints}`);
|
|
116
123
|
} else {
|
|
117
|
-
log(`[积分排行]
|
|
124
|
+
log(`[积分排行] 未找到当前用户(${official ? 'unionid=' + officialUnionId : 'QQ=' + currentUserQQ})`);
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
|
|
@@ -138,7 +145,7 @@ module.exports = async (ctx, cfg, session) => {
|
|
|
138
145
|
const element = await page.$("#container");
|
|
139
146
|
return segment.image(await element.screenshot({
|
|
140
147
|
encoding: "binary"
|
|
141
|
-
}), "image/
|
|
148
|
+
}), "image/jpeg");
|
|
142
149
|
}
|
|
143
150
|
catch (e) {
|
|
144
151
|
log(`[积分排行] 渲染异常: ${e.message}`);
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
const queryPointV1 = require("./queryPointV1");
|
|
2
2
|
const tmpIdPicker = require("../../../util/tmpIdPicker");
|
|
3
|
+
const qqOfficial = require("../../../util/qqOfficial");
|
|
3
4
|
|
|
4
5
|
module.exports = async (ctx, cfg, session, targetQQ) => {
|
|
6
|
+
// QQ 官方机器人只能拿到 unionid,没有 QQ 号
|
|
7
|
+
if (qqOfficial.isQQOfficial(session)) {
|
|
8
|
+
const ident = qqOfficial.getQQIdentity(session);
|
|
9
|
+
// 指定了目标(@某人 / QQ号)时官方机器人无法解析,明确提示而不是静默失败
|
|
10
|
+
if (targetQQ) {
|
|
11
|
+
return "QQ 官方机器人暂不支持查询他人积分,请让对方自己发送「查询积分」";
|
|
12
|
+
}
|
|
13
|
+
if (!ident.unionId) {
|
|
14
|
+
return "未获取到你的 unionid,无法查询积分。请确认机器人已开通 unionid 权限";
|
|
15
|
+
}
|
|
16
|
+
return await queryPointV1(ctx, cfg, { unionid: ident.unionId });
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
let queryQQ = targetQQ;
|
|
6
20
|
if (!queryQQ) {
|
|
7
21
|
queryQQ = session.userId;
|
|
@@ -19,5 +33,5 @@ module.exports = async (ctx, cfg, session, targetQQ) => {
|
|
|
19
33
|
}
|
|
20
34
|
}
|
|
21
35
|
|
|
22
|
-
return await queryPointV1(ctx, cfg, queryQQ);
|
|
36
|
+
return await queryPointV1(ctx, cfg, { qq: queryQQ });
|
|
23
37
|
};
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 查询积分。
|
|
3
|
+
* @param {object|string} identity 查询身份:
|
|
4
|
+
* - { qq: '123456789' } onebot(QQ 号)
|
|
5
|
+
* - { unionid: 'UID_xxx' } QQ 官方机器人(unionid)
|
|
6
|
+
* - 传字符串时按 QQ 号处理(兼容旧调用)
|
|
7
|
+
*/
|
|
8
|
+
module.exports = async (ctx, cfg, identity) => {
|
|
2
9
|
const { Name, url, token, logOutput } = cfg.mainSettings?.settings || {};
|
|
3
10
|
|
|
11
|
+
// 兼容旧签名(直接传 QQ 号)
|
|
12
|
+
const query = (identity && typeof identity === 'object') ? identity : { qq: identity };
|
|
13
|
+
const qq = query.qq ? String(query.qq) : '';
|
|
14
|
+
const unionid = query.unionid ? String(query.unionid) : '';
|
|
15
|
+
const isUnionQuery = !qq && !!unionid;
|
|
16
|
+
const whoText = isUnionQuery ? `unionid=${unionid}` : `QQ号 ${qq}`;
|
|
17
|
+
|
|
4
18
|
try {
|
|
5
19
|
if (logOutput) {
|
|
6
|
-
ctx.logger.info(`开始查询用户 ${
|
|
20
|
+
ctx.logger.info(`开始查询用户 ${whoText} 的积分`);
|
|
7
21
|
}
|
|
8
22
|
|
|
9
|
-
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=
|
|
23
|
+
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=1&limit=7&tmpId=&tmpName=&teamId=&qq=${qq}&unionid=${unionid}&state=0&teamRole=&isExecutive=&isManager=&adminUserId=`;
|
|
10
24
|
if (logOutput) {
|
|
11
25
|
ctx.logger.info(`请求V1.0用户信息: ${userInfoUrl}`);
|
|
12
26
|
}
|
|
@@ -20,7 +34,7 @@ module.exports = async (ctx, cfg, queryQQ) => {
|
|
|
20
34
|
}
|
|
21
35
|
const userList = userInfoResponse.page?.list || [];
|
|
22
36
|
if (userList.length === 0) {
|
|
23
|
-
return `未找到
|
|
37
|
+
return `未找到 ${whoText} 关联的用户信息`;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
const userInfo = userList[0];
|
|
@@ -29,11 +43,13 @@ module.exports = async (ctx, cfg, queryQQ) => {
|
|
|
29
43
|
const teamId = userInfo.teamId || userInfo.uid || "未知编号";
|
|
30
44
|
const rewardPoints = userInfo.rewardPoints || userInfo.point || 0;
|
|
31
45
|
const joinDate = userInfo.joinDate || "未知";
|
|
46
|
+
// 展示用 QQ:优先用接口返回的真实 QQ 号(官方机器人本地没有 QQ 号)
|
|
47
|
+
const displayQQ = userInfo.qq || qq || '未绑定';
|
|
32
48
|
|
|
33
49
|
let message = `🚛 ${Name}平台 - 积分查询🚚\n`;
|
|
34
50
|
message += `👤 用户: ${tmpName}\n`;
|
|
35
51
|
message += `🆔 车队编号: ${teamId}\n`;
|
|
36
|
-
message += `📧 QQ: ${
|
|
52
|
+
message += `📧 QQ: ${displayQQ}\n`;
|
|
37
53
|
message += `🏆 职位: ${teamRole}\n`;
|
|
38
54
|
message += `⭐ 当前积分: ${rewardPoints}\n`;
|
|
39
55
|
message += `📅 加入日期: ${joinDate}`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const qqOfficial = require('../../../util/qqOfficial');
|
|
1
2
|
module.exports = async (ctx, cfg, session, targetTeamId, password) => {
|
|
2
3
|
const { url, token, logOutput } = cfg.mainSettings?.settings || {};
|
|
3
4
|
const { adminUsers } = cfg.resetPassword?.settings || {};
|
|
@@ -7,6 +8,28 @@ module.exports = async (ctx, cfg, session, targetTeamId, password) => {
|
|
|
7
8
|
|
|
8
9
|
const log = (msg) => logOutput && ctx.logger.info(msg);
|
|
9
10
|
|
|
11
|
+
// QQ 官方机器人:只能拿到 unionid,没有 QQ 号
|
|
12
|
+
const official = qqOfficial.isQQOfficial(session);
|
|
13
|
+
const officialUnionId = official ? (qqOfficial.getQQIdentity(session).unionId || '') : '';
|
|
14
|
+
|
|
15
|
+
/** 「查我自己」的查询参数:onebot 用 qq,官方机器人用 unionid */
|
|
16
|
+
const selfQuery = official
|
|
17
|
+
? (officialUnionId ? { unionid: officialUnionId } : null)
|
|
18
|
+
: { qq: currentUserQQ };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 判断某条平台用户记录是否属于「当前会话用户」。
|
|
22
|
+
* onebot 比 qq,官方机器人比 unionid。
|
|
23
|
+
*/
|
|
24
|
+
const isSelf = (userInfo) => {
|
|
25
|
+
if (!userInfo) return false;
|
|
26
|
+
if (official) {
|
|
27
|
+
if (!officialUnionId) return false;
|
|
28
|
+
return String(userInfo.unionid || '') === officialUnionId;
|
|
29
|
+
}
|
|
30
|
+
return String(userInfo.qq) === String(currentUserQQ);
|
|
31
|
+
};
|
|
32
|
+
|
|
10
33
|
const parseAtQQ = (raw) => {
|
|
11
34
|
if (!raw?.startsWith("<at ")) return raw;
|
|
12
35
|
const idStart = raw.indexOf('id="');
|
|
@@ -55,6 +78,10 @@ module.exports = async (ctx, cfg, session, targetTeamId, password) => {
|
|
|
55
78
|
|
|
56
79
|
if (targetTeamId?.startsWith("<at ")) {
|
|
57
80
|
if (!isAdmin) return "您没有权限重置其他用户的密码,请联系管理员";
|
|
81
|
+
// 官方机器人 @ 拿到的是 openid,不是 QQ 号,无法用于查询
|
|
82
|
+
if (official) {
|
|
83
|
+
return "QQ 官方机器人暂不支持通过 @ 指定成员,请改用车队编号(如「重置密码 789」)";
|
|
84
|
+
}
|
|
58
85
|
|
|
59
86
|
const idStart = targetTeamId.indexOf('id="');
|
|
60
87
|
if (idStart === -1) return "获取QQ号错误,请使用车队编号重置";
|
|
@@ -69,14 +96,16 @@ module.exports = async (ctx, cfg, session, targetTeamId, password) => {
|
|
|
69
96
|
if (isPrivateChat) {
|
|
70
97
|
if (!targetTeamId) {
|
|
71
98
|
log(`用户 ${currentUserQQ} 发起密码重置`);
|
|
72
|
-
|
|
99
|
+
if (!selfQuery) return "未获取到你的 unionid,无法重置密码。请确认机器人已开通 unionid 权限";
|
|
100
|
+
const userInfo = await getUserInfo(selfQuery);
|
|
73
101
|
teamId = userInfo.teamId;
|
|
74
102
|
if (!teamId) return "未找到您的信息,请联系管理员";
|
|
75
103
|
} else {
|
|
76
104
|
log(`用户 ${currentUserQQ} 尝试重置车队编号 ${targetTeamId} 的密码`);
|
|
77
105
|
const userInfo = await getUserInfo({ teamId: targetTeamId });
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
// 归属校验:官方机器人没有 QQ 号,改用 unionid 比对
|
|
108
|
+
if (!isAdmin && !isSelf(userInfo)) {
|
|
80
109
|
return "您没有权限重置其他成员的密码,请联系管理员";
|
|
81
110
|
}
|
|
82
111
|
|
|
@@ -84,7 +113,7 @@ module.exports = async (ctx, cfg, session, targetTeamId, password) => {
|
|
|
84
113
|
if (isAdmin) {
|
|
85
114
|
log(`管理员 ${currentUserQQ} 重置车队编号 ${targetTeamId} 的密码`);
|
|
86
115
|
} else {
|
|
87
|
-
log(`验证通过,车队编号 ${targetTeamId}
|
|
116
|
+
log(`验证通过,车队编号 ${targetTeamId} 的归属校验通过(${official ? 'unionid' : 'QQ'} 一致)`);
|
|
88
117
|
}
|
|
89
118
|
}
|
|
90
119
|
} else {
|
package/lib/command/tmpBind.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
const guildBind = require('../database/guildBind');
|
|
2
2
|
const truckersMpApi = require("../api/truckersMpApi");
|
|
3
3
|
const evmOpenApi = require('../api/evmOpenApi');
|
|
4
|
+
const qqOfficial = require('../util/qqOfficial');
|
|
5
|
+
const messageRender = require('../util/messageRender');
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* 绑定 TMP ID(支持绑定多个:追加入库,同一编号重复绑定会提示)
|
|
9
|
+
*
|
|
10
|
+
* 输出包装:QQ 官方机器人下把结果包成 qq:markdown 并附加按钮;
|
|
11
|
+
* onebot 原样返回字符串,行为与以前完全一致。
|
|
6
12
|
*/
|
|
7
13
|
module.exports = async (ctx, cfg, session, tmpId) => {
|
|
14
|
+
const text = await bindImpl(ctx, cfg, session, tmpId);
|
|
15
|
+
return await decorate(session, text);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** 实际绑定逻辑 */
|
|
19
|
+
async function bindImpl(ctx, cfg, session, tmpId) {
|
|
8
20
|
if (!tmpId || isNaN(tmpId)) {
|
|
9
21
|
return `请输入正确的玩家编号`;
|
|
10
22
|
}
|
|
@@ -13,16 +25,41 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
13
25
|
if (playerInfo.error) {
|
|
14
26
|
return '绑定失败 (查询玩家信息失败)';
|
|
15
27
|
}
|
|
16
|
-
//
|
|
17
|
-
const
|
|
28
|
+
// QQ 官方机器人下顺带记录 openid / union_id(onebot 下为 null,行为不变)
|
|
29
|
+
const identity = qqOfficial.buildIdentity(session);
|
|
30
|
+
const result = await guildBind.add(
|
|
31
|
+
ctx.database, session.platform, session.userId,
|
|
32
|
+
playerInfo.data.tmpId, playerInfo.data.name,
|
|
33
|
+
identity.isQQOfficial ? identity : null
|
|
34
|
+
);
|
|
18
35
|
if (result.exists) {
|
|
19
36
|
const hint = result.isDefault ? '(该编号已是你的默认绑定)' : '';
|
|
20
37
|
return `该编号已在绑定列表中 ( ${playerInfo.data.name} )${hint},无需重复绑定`;
|
|
21
38
|
}
|
|
22
|
-
const count = (await guildBind.
|
|
39
|
+
const count = (await guildBind.listWithUnion(
|
|
40
|
+
ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session)
|
|
41
|
+
)).length;
|
|
23
42
|
if (count === 1) {
|
|
24
43
|
return `绑定成功 ( ${playerInfo.data.name} ),当前已绑定 ${count} 个编号(已自动设为默认绑定)`;
|
|
25
44
|
}
|
|
26
45
|
const defaultTip = result.isDefault ? '(已设为默认绑定)' : '';
|
|
27
46
|
return `绑定成功 ( ${playerInfo.data.name} )${defaultTip},当前已绑定 ${count} 个编号`;
|
|
28
|
-
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 官方机器人下包装输出(markdown + 按钮);其它平台原样返回。
|
|
51
|
+
* 失败提示(短文本)不附按钮。
|
|
52
|
+
*/
|
|
53
|
+
async function decorate(session, text) {
|
|
54
|
+
if (!qqOfficial.isQQOfficial(session)) return text;
|
|
55
|
+
if (typeof text !== 'string') return text;
|
|
56
|
+
|
|
57
|
+
const isError = /失败|错误|不正确|已在绑定列表/.test(text);
|
|
58
|
+
if (isError) {
|
|
59
|
+
return await messageRender.renderText(session, text, { markdown: true });
|
|
60
|
+
}
|
|
61
|
+
const buttons = [
|
|
62
|
+
[qqOfficial.inputButton('我的绑定', '我的绑定', true), qqOfficial.inputButton('查询', '查询')],
|
|
63
|
+
];
|
|
64
|
+
return await messageRender.renderText(session, text, { buttons, markdown: true });
|
|
65
|
+
}
|
package/lib/command/tmpDlcMap.js
CHANGED
|
@@ -5,6 +5,7 @@ const common = require('../util/common')
|
|
|
5
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 qqOfficial = require('../util/qqOfficial')
|
|
8
9
|
const tmpIdPicker = require('../util/tmpIdPicker')
|
|
9
10
|
|
|
10
11
|
// 足迹统计数据的开始收集日期(仅用于近一年足迹图片中的提示展示)
|
|
@@ -101,7 +102,7 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
101
102
|
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选=发多张足迹图)
|
|
102
103
|
// 足迹不出图量大,禁用 all(避免一次请求打满服务器)
|
|
103
104
|
if (!tmpId) {
|
|
104
|
-
const bindings = await guildBind.
|
|
105
|
+
const bindings = await guildBind.listWithUnion(ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session))
|
|
105
106
|
if (bindings.length === 0) {
|
|
106
107
|
return `请输入正确的玩家编号,或绑定玩家编号`
|
|
107
108
|
}
|
|
@@ -256,7 +257,7 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
256
257
|
return (
|
|
257
258
|
segment.image(await element.screenshot({
|
|
258
259
|
encoding: "binary"
|
|
259
|
-
}), "image/
|
|
260
|
+
}), "image/jpeg")
|
|
260
261
|
)
|
|
261
262
|
} catch (e) {
|
|
262
263
|
return '渲染异常,请重试'
|
|
@@ -4,8 +4,12 @@ const common = require('../util/common');
|
|
|
4
4
|
const evmOpenApi = require('../api/evmOpenApi');
|
|
5
5
|
const guildBind = require('../database/guildBind');
|
|
6
6
|
const tmpIdPicker = require('../util/tmpIdPicker');
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const messageRender = require('../util/messageRender');
|
|
8
|
+
const qqOfficial = require('../util/qqOfficial');
|
|
9
|
+
module.exports = async (ctx, cfg, session, rankingType, target) => {
|
|
10
|
+
// 仅在图片模式下才需要 puppeteer;文本模式不拦截
|
|
11
|
+
const wantImage = messageRender.wantImage(messageRender.readType(cfg, 'tmpMileageRanking.type'));
|
|
12
|
+
if (wantImage && !ctx.puppeteer) {
|
|
9
13
|
return '未启用 Puppeteer 功能';
|
|
10
14
|
}
|
|
11
15
|
// 查询排行榜信息
|
|
@@ -49,26 +53,73 @@ module.exports = async (ctx, session, rankingType, target) => {
|
|
|
49
53
|
mileageRankingList: mileageRankingList.data,
|
|
50
54
|
playerMileageRanking: playerMileageRanking
|
|
51
55
|
};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
|
|
57
|
+
const buttons = buildRankingButtons(session, rankingType);
|
|
58
|
+
const text = buildRankingText(data);
|
|
59
|
+
|
|
60
|
+
return await messageRender.renderByType(ctx, session, cfg, 'tmpMileageRanking.type', {
|
|
61
|
+
text,
|
|
62
|
+
buttons,
|
|
63
|
+
caption: (Number(rankingType) === 2 ? '今日' : '总') + '里程排行榜',
|
|
64
|
+
alt: '里程排行榜',
|
|
65
|
+
image: async () => {
|
|
66
|
+
if (!ctx.puppeteer) return null;
|
|
67
|
+
let page;
|
|
68
|
+
try {
|
|
69
|
+
page = await ctx.puppeteer.page();
|
|
70
|
+
await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 });
|
|
71
|
+
await page.goto(`file:///${resolve(__dirname, '../resource/mileage-leaderboard.html')}`);
|
|
72
|
+
await page.evaluate(`setData(${JSON.stringify(data)})`);
|
|
73
|
+
await page.waitForNetworkIdle();
|
|
74
|
+
await common.sleep(500);
|
|
75
|
+
const element = await page.$("#container");
|
|
76
|
+
return segment.image(await element.screenshot({ encoding: "binary" }), "image/jpeg");
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.info(e);
|
|
79
|
+
return null;
|
|
80
|
+
} finally {
|
|
81
|
+
if (page) {
|
|
82
|
+
try { await page.close(); } catch { }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
72
85
|
}
|
|
73
|
-
}
|
|
86
|
+
});
|
|
74
87
|
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 排行榜按钮(仅 QQ 官方机器人可见)。
|
|
91
|
+
*/
|
|
92
|
+
function buildRankingButtons(session, rankingType) {
|
|
93
|
+
if (!qqOfficial.isQQOfficial(session)) return [];
|
|
94
|
+
const rows = [];
|
|
95
|
+
rows.push([qqOfficial.inputButton(
|
|
96
|
+
Number(rankingType) === 2 ? '查看总榜' : '查看今日榜',
|
|
97
|
+
Number(rankingType) === 2 ? '里程排行榜' : '今日里程排行榜',
|
|
98
|
+
true
|
|
99
|
+
)]);
|
|
100
|
+
rows.push([qqOfficial.inputButton('我的绑定', '我的绑定')]);
|
|
101
|
+
return rows;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 排行榜文字版。
|
|
106
|
+
*/
|
|
107
|
+
function buildRankingText(data) {
|
|
108
|
+
const typeText = Number(data.rankingType) === 2 ? '今日' : '总';
|
|
109
|
+
const list = Array.isArray(data.mileageRankingList) ? data.mileageRankingList : [];
|
|
110
|
+
const lines = [`## 里程排行榜(${typeText})`, ''];
|
|
111
|
+
list.slice(0, 10).forEach((p, i) => {
|
|
112
|
+
lines.push(`${i + 1}. ${p.name || '未知'} ${formatKm(p.mileage)}`);
|
|
113
|
+
});
|
|
114
|
+
if (data.playerMileageRanking) {
|
|
115
|
+
const me = data.playerMileageRanking;
|
|
116
|
+
const rank = list.indexOf(me) + 1;
|
|
117
|
+
lines.push('', `**你的排名**:第 ${rank || '-'} 名 ${formatKm(me.mileage)}`);
|
|
118
|
+
}
|
|
119
|
+
return lines.join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatKm(v) {
|
|
123
|
+
const n = Number(v);
|
|
124
|
+
return Number.isFinite(n) ? `${n.toLocaleString('en-US')} km` : '-';
|
|
125
|
+
}
|
|
@@ -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 qqOfficial = require('../util/qqOfficial')
|
|
4
5
|
const tmpIdPicker = require('../util/tmpIdPicker')
|
|
5
6
|
const truckyAppApi = require('../api/truckyAppApi')
|
|
6
7
|
const truckersMpApi = require('../api/truckersMpApi')
|
|
@@ -51,7 +52,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
51
52
|
}
|
|
52
53
|
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选=发多张定位图)
|
|
53
54
|
if (!tmpId) {
|
|
54
|
-
const bindings = await guildBind.
|
|
55
|
+
const bindings = await guildBind.listWithUnion(ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session))
|
|
55
56
|
if (bindings.length === 0) {
|
|
56
57
|
return `请输入正确的玩家编号`
|
|
57
58
|
}
|
|
@@ -144,7 +145,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
144
145
|
return (
|
|
145
146
|
segment.image(await element.screenshot({
|
|
146
147
|
encoding: "binary"
|
|
147
|
-
}), "image/
|
|
148
|
+
}), "image/jpeg")
|
|
148
149
|
)
|
|
149
150
|
} catch (e) {
|
|
150
151
|
return '渲染异常,请重试'
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
const tmpQueryText = require("./tmpQueryText");
|
|
2
2
|
const tmpQueryImg = require("./tmpQueryImg");
|
|
3
|
+
const messageRender = require("../../util/messageRender");
|
|
4
|
+
const qqOfficial = require("../../util/qqOfficial");
|
|
5
|
+
const qqMediaUpload = require("../../util/qqMediaUpload");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 玩家信息查询分发。
|
|
9
|
+
*
|
|
10
|
+
* 这里做一层输出包装:
|
|
11
|
+
* - onebot:原样返回(纯文本字符串 / 图片元素),行为与以前完全一致;
|
|
12
|
+
* - QQ 官方机器人:文本结果包成 qq:markdown,并在下方附加常用按钮。
|
|
13
|
+
*
|
|
14
|
+
* 注意:业务函数可能返回字符串、图片元素、或它们的数组(多绑定查询),
|
|
15
|
+
* 这里只处理「最终结果」,不改变任何查询逻辑。
|
|
16
|
+
*/
|
|
3
17
|
module.exports = async (ctx, cfg, session, tmpId) => {
|
|
18
|
+
const result = await queryRaw(ctx, cfg, session, tmpId);
|
|
19
|
+
|
|
20
|
+
// 非官方机器人 / 无结果 / 已列出序号等待回复(undefined)→ 原样返回
|
|
21
|
+
if (!qqOfficial.isQQOfficial(session) || result == null) {
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return await decorateForOfficial(ctx, cfg, session, result);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** 按配置分发到文字版或图片版(保持原有逻辑不变) */
|
|
29
|
+
async function queryRaw(ctx, cfg, session, tmpId) {
|
|
4
30
|
switch (cfg.tmpQuery?.type) {
|
|
5
31
|
case 1:
|
|
6
32
|
return await tmpQueryText(ctx, cfg, session, tmpId);
|
|
@@ -9,4 +35,47 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
9
35
|
default:
|
|
10
36
|
return '指令配置错误';
|
|
11
37
|
}
|
|
12
|
-
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 官方机器人下的输出包装。
|
|
42
|
+
* - 字符串 → qq:markdown + 按钮
|
|
43
|
+
* - 图片元素 → 图片 + 按钮(renderImage 内部处理)
|
|
44
|
+
* - 数组 → 逐项处理(多绑定结果)
|
|
45
|
+
*/
|
|
46
|
+
async function decorateForOfficial(ctx, cfg, session, result) {
|
|
47
|
+
const buttons = buildQueryButtons();
|
|
48
|
+
|
|
49
|
+
if (Array.isArray(result)) {
|
|
50
|
+
return Promise.all(result.map(item => decorateOne(ctx, cfg, session, item, buttons)));
|
|
51
|
+
}
|
|
52
|
+
return await decorateOne(ctx, cfg, session, result, buttons);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function decorateOne(ctx, cfg, session, item, buttons) {
|
|
56
|
+
if (item == null) return item;
|
|
57
|
+
|
|
58
|
+
// 图片元素:交给 renderImage(官方机器人下会变成「图片 + 按钮」)
|
|
59
|
+
if (typeof item === 'object' && item.type && item.type !== 'text') {
|
|
60
|
+
return await messageRender.renderImage(session, item, {
|
|
61
|
+
buttons,
|
|
62
|
+
uploader: await messageRender.buildUploader(ctx, cfg, session),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const text = typeof item === 'string' ? item : String(item);
|
|
67
|
+
// 过短的提示语(如「0公里」「请输入正确的玩家编号」)不附加按钮,避免干扰
|
|
68
|
+
if (text.length < 12) {
|
|
69
|
+
return await messageRender.renderText(session, text, { markdown: true });
|
|
70
|
+
}
|
|
71
|
+
return await messageRender.renderText(session, text, { buttons, markdown: true });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 查询结果下方的常用按钮 */
|
|
75
|
+
function buildQueryButtons() {
|
|
76
|
+
// 每行最多 2 个:手机端一行 4 个会把文字压成「再...」省略号
|
|
77
|
+
return [
|
|
78
|
+
[qqOfficial.inputButton('再查一次', '查询', true), qqOfficial.inputButton('我的绑定', '我的绑定')],
|
|
79
|
+
[qqOfficial.inputButton('里程排行榜', '里程排行榜'), qqOfficial.inputButton('VTC里程榜', 'vtc里程排行榜')],
|
|
80
|
+
];
|
|
81
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const dayjs = require('dayjs');
|
|
2
|
+
const qqOfficial = require('../../util/qqOfficial');
|
|
2
3
|
const dayjsRelativeTime = require('dayjs/plugin/relativeTime');
|
|
3
4
|
const dayjsLocaleZhCn = require('dayjs/locale/zh-cn');
|
|
4
5
|
const guildBind = require('../../database/guildBind');
|
|
@@ -99,7 +100,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
99
100
|
}
|
|
100
101
|
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选)
|
|
101
102
|
if (!tmpId) {
|
|
102
|
-
const bindings = await guildBind.
|
|
103
|
+
const bindings = await guildBind.listWithUnion(ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session));
|
|
103
104
|
if (bindings.length === 0) {
|
|
104
105
|
return `请输入正确的玩家编号`;
|
|
105
106
|
}
|
|
@@ -119,7 +120,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
119
120
|
}
|
|
120
121
|
// 「all / 全部」写法:一次性查询全部绑定(受多选上限截断)
|
|
121
122
|
if (tmpIdPicker.isAllArg(tmpId)) {
|
|
122
|
-
const bindings = await guildBind.
|
|
123
|
+
const bindings = await guildBind.listWithUnion(ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session));
|
|
123
124
|
const { ids, hint } = await tmpIdPicker.pickAll(ctx, session, bindings);
|
|
124
125
|
if (ids.length === 0) {
|
|
125
126
|
return `请输入正确的玩家编号`;
|
|
@@ -272,7 +273,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
272
273
|
if (logOutput) {
|
|
273
274
|
ctx.logger.info(`[TMP_BOT] tmpQueryImg:开始查询TmpID ${tmpId} 的积分`);
|
|
274
275
|
}
|
|
275
|
-
const userInfoUrl = `https://${platformUrl}/api/user/info/list?token=${platformToken}&page=
|
|
276
|
+
const userInfoUrl = `https://${platformUrl}/api/user/info/list?token=${platformToken}&page=1&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&unionid=&state=0&teamRole=&isExecutive=&isManager=&adminUserId=`;
|
|
276
277
|
if (logOutput) {
|
|
277
278
|
ctx.logger.info(`[TMP_BOT] 请求用户信息: ${userInfoUrl}`);
|
|
278
279
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const dayjs = require('dayjs');
|
|
2
|
+
const qqOfficial = require('../../util/qqOfficial');
|
|
2
3
|
const dayjsRelativeTime = require('dayjs/plugin/relativeTime');
|
|
3
4
|
const dayjsLocaleZhCn = require('dayjs/locale/zh-cn');
|
|
4
5
|
const guildBind = require('../../database/guildBind');
|
|
@@ -85,7 +86,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
85
86
|
}
|
|
86
87
|
// 如果没有传入tmpId,尝试从绑定记录获取(多绑定时会列出序号供选择,支持多选)
|
|
87
88
|
if (!tmpId) {
|
|
88
|
-
const bindings = await guildBind.
|
|
89
|
+
const bindings = await guildBind.listWithUnion(ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session));
|
|
89
90
|
if (bindings.length === 0) {
|
|
90
91
|
return `请输入正确的玩家编号`;
|
|
91
92
|
}
|
|
@@ -105,7 +106,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
105
106
|
}
|
|
106
107
|
// 「all / 全部」写法:一次性查询全部绑定(受多选上限截断)
|
|
107
108
|
if (tmpIdPicker.isAllArg(tmpId)) {
|
|
108
|
-
const bindings = await guildBind.
|
|
109
|
+
const bindings = await guildBind.listWithUnion(ctx.database, session.platform, session.userId, qqOfficial.buildIdentity(session));
|
|
109
110
|
const { ids, hint } = await tmpIdPicker.pickAll(ctx, session, bindings);
|
|
110
111
|
if (ids.length === 0) {
|
|
111
112
|
return `请输入正确的玩家编号`;
|
|
@@ -170,7 +171,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
170
171
|
if (logOutput) {
|
|
171
172
|
ctx.logger.info(`[TMP_BOT] tmpQuery:开始查询TmpID ${tmpId} 的积分`);
|
|
172
173
|
}
|
|
173
|
-
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=
|
|
174
|
+
const userInfoUrl = `https://${url}/api/user/info/list?token=${token}&page=1&limit=7&tmpId=${tmpId}&tmpName=&teamId=&qq=&unionid=&state=0&teamRole=&isExecutive=&isManager=&adminUserId=`;
|
|
174
175
|
if (logOutput) {
|
|
175
176
|
ctx.logger.info(`[TMP_BOT] 请求用户信息: ${userInfoUrl}`);
|
|
176
177
|
}
|