koishi-plugin-ets2-tools-tmp 1.3.3 → 2.0.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.
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module.exports = async (ctx, cfg, session, targetQQ) => {
|
|
2
|
-
const { url, token, logOutput } = cfg.mainSettings?.settings || {};
|
|
2
|
+
const { url, token, logOutput, platformVersion } = cfg.mainSettings?.settings || {};
|
|
3
|
+
if ((platformVersion || "v1").toLowerCase() === "v2") {
|
|
4
|
+
return "车队平台V2.0暂不支持积分查询";
|
|
5
|
+
}
|
|
3
6
|
let queryQQ = targetQQ;
|
|
4
7
|
if (!queryQQ) {
|
|
5
8
|
queryQQ = session.userId;
|
|
@@ -71,4 +74,4 @@ module.exports = async (ctx, cfg, session, targetQQ) => {
|
|
|
71
74
|
return "系统错误,请稍后重试";
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
|
-
};
|
|
77
|
+
};
|
|
@@ -1,12 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const { h } = require("koishi");
|
|
2
|
+
|
|
3
|
+
module.exports = async (ctx, cfg, session, targetTeamId, password) => {
|
|
4
|
+
const { url, token, logOutput, platformVersion, mailEnabled, mailTo, mailSubject, mailTemplate, mailFromName } = cfg.mainSettings?.settings || {};
|
|
3
5
|
const { adminUsers } = cfg.resetPassword?.settings || {};
|
|
4
6
|
const currentUserQQ = session.userId;
|
|
5
7
|
const isAdmin = adminUsers.includes(currentUserQQ);
|
|
6
8
|
const isPrivateChat = session.channelId === `private:${currentUserQQ}`;
|
|
9
|
+
const platform = (platformVersion || "v1").toLowerCase();
|
|
7
10
|
|
|
8
11
|
// 日志工具函数
|
|
9
12
|
const log = (msg) => logOutput && ctx.logger.info(msg);
|
|
13
|
+
|
|
14
|
+
const normalizeBaseUrl = (baseUrl, fallback) => {
|
|
15
|
+
if (!baseUrl) return fallback;
|
|
16
|
+
if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) return baseUrl;
|
|
17
|
+
return `https://${baseUrl}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const parseAtQQ = (raw) => {
|
|
21
|
+
if (!raw?.startsWith("<at ")) return raw;
|
|
22
|
+
const idStart = raw.indexOf('id="');
|
|
23
|
+
if (idStart === -1) return "";
|
|
24
|
+
const idEnd = raw.indexOf('"', idStart + 4);
|
|
25
|
+
if (idEnd === -1) return "";
|
|
26
|
+
return raw.substring(idStart + 4, idEnd);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const normalizeEmail = (value) => {
|
|
30
|
+
if (!value) return "";
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
const bracketMatch = trimmed.match(/<([^>]+)>/);
|
|
33
|
+
const raw = bracketMatch ? bracketMatch[1] : trimmed;
|
|
34
|
+
return raw.replace(/[\s\u200B-\u200D\uFEFF]+/g, "");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const sendMail = async (targetMail, subject, content) => {
|
|
38
|
+
const mailBot = ctx.bots.find((bot) => bot.platform === "mail");
|
|
39
|
+
if (!mailBot) {
|
|
40
|
+
log("[V2] 未发现可用的 mail 适配器,无法发送邮件");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const normalizedTarget = targetMail && targetMail.includes(":")
|
|
44
|
+
? targetMail
|
|
45
|
+
: `private:${targetMail}`;
|
|
46
|
+
const fromAddress = mailBot.user?.id || mailBot.selfId || "mail";
|
|
47
|
+
log(`[V2] 邮件发送状态: 由 ${fromAddress} 发送到 ${targetMail} (channelId=${normalizedTarget}, subject=${subject || ""})`);
|
|
48
|
+
log(`[V2] 邮件发送详情: mailFromName=${mailFromName || ""}, rawContentLength=${(content || "").length}`);
|
|
49
|
+
try {
|
|
50
|
+
const rawMessage = content || "";
|
|
51
|
+
const message = h("message", { subject, fromName: mailFromName || undefined }, rawMessage);
|
|
52
|
+
log(`[V2] 邮件发送详情: rawMessage=${rawMessage}`);
|
|
53
|
+
log(`[V2] 邮件发送详情: messageElement=${JSON.stringify(message)}`);
|
|
54
|
+
await mailBot.sendMessage(normalizedTarget, message);
|
|
55
|
+
log(`[V2] 邮件发送成功: 由 ${fromAddress} 发送到 ${targetMail} (channelId=${normalizedTarget})`);
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
log(`[V2] 邮件发送失败: 由 ${fromAddress} 发送到 ${targetMail} (channelId=${normalizedTarget}), 错误: ${error.message}`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const randomChar = (chars) => chars[Math.floor(Math.random() * chars.length)];
|
|
64
|
+
const generatePassword = () => {
|
|
65
|
+
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
66
|
+
const digits = "0123456789";
|
|
67
|
+
const all = letters + digits;
|
|
68
|
+
const chars = [randomChar(letters), randomChar(digits)];
|
|
69
|
+
for (let i = 0; i < 8; i++) {
|
|
70
|
+
chars.push(randomChar(all));
|
|
71
|
+
}
|
|
72
|
+
for (let i = chars.length - 1; i > 0; i--) {
|
|
73
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
74
|
+
[chars[i], chars[j]] = [chars[j], chars[i]];
|
|
75
|
+
}
|
|
76
|
+
return chars.join("");
|
|
77
|
+
};
|
|
10
78
|
|
|
11
79
|
// 通用HTTP请求函数
|
|
12
80
|
const fetchData = async (apiUrl) => {
|
|
@@ -46,6 +114,97 @@ module.exports = async (ctx, cfg, session, targetTeamId) => {
|
|
|
46
114
|
};
|
|
47
115
|
|
|
48
116
|
try {
|
|
117
|
+
if (platform === "v2") {
|
|
118
|
+
if (!token) return "未配置车队平台V2.0 API认证令牌";
|
|
119
|
+
const baseUrl = normalizeBaseUrl(url, "https://open.vtcm.link");
|
|
120
|
+
|
|
121
|
+
if (!isAdmin && !isPrivateChat) {
|
|
122
|
+
return "您没有权限重置其他用户的密码,请联系管理员重置,或私聊机器人重置";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let targetQQ = "";
|
|
126
|
+
let memberUid = "";
|
|
127
|
+
let userEmail = "";
|
|
128
|
+
const rawTarget = targetTeamId ? targetTeamId.trim() : "";
|
|
129
|
+
const uidMatch = rawTarget.match(/^uid\s*=\s*(\d+)$/i);
|
|
130
|
+
|
|
131
|
+
if (uidMatch) {
|
|
132
|
+
if (!isAdmin) return "仅管理员可使用 uid 参数重置他人密码";
|
|
133
|
+
memberUid = uidMatch[1];
|
|
134
|
+
log(`[V2] 使用 UID 方式重置密码,uid=${memberUid}`);
|
|
135
|
+
const memberUrl = `${baseUrl}/members/get?uid=${encodeURIComponent(memberUid)}`;
|
|
136
|
+
log(`获取成员信息: ${memberUrl}`);
|
|
137
|
+
const memberResponse = await ctx.http.get(memberUrl, { headers: { token } });
|
|
138
|
+
log(`成员信息响应: ${JSON.stringify(memberResponse)}`);
|
|
139
|
+
if (memberResponse.code !== 200 || !memberResponse.data?.uid) {
|
|
140
|
+
return memberResponse.msg || "未找到用户信息";
|
|
141
|
+
}
|
|
142
|
+
targetQQ = memberResponse.data.qq || targetQQ;
|
|
143
|
+
const rawEmail = memberResponse.data.email || "";
|
|
144
|
+
log(`[V2] 成员邮箱原始值: ${rawEmail}`);
|
|
145
|
+
userEmail = normalizeEmail(rawEmail);
|
|
146
|
+
log(`[V2] 成员邮箱规范化后: ${userEmail}`);
|
|
147
|
+
} else {
|
|
148
|
+
targetQQ = rawTarget ? parseAtQQ(rawTarget) : currentUserQQ;
|
|
149
|
+
log(`[V2] 使用 QQ 方式重置密码,rawTarget=${rawTarget}, parsedQQ=${targetQQ}`);
|
|
150
|
+
if (!targetQQ) return "获取QQ号错误,请使用QQ号重置";
|
|
151
|
+
if (!/^\d+$/.test(targetQQ)) return "QQ号格式不正确,请输入纯数字QQ号";
|
|
152
|
+
if (!isAdmin && targetQQ !== currentUserQQ) {
|
|
153
|
+
return "您没有权限重置其他成员的密码,请联系管理员";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const memberUrl = `${baseUrl}/members/get?qq=${encodeURIComponent(targetQQ)}`;
|
|
157
|
+
log(`获取成员信息: ${memberUrl}`);
|
|
158
|
+
const memberResponse = await ctx.http.get(memberUrl, { headers: { token } });
|
|
159
|
+
log(`成员信息响应: ${JSON.stringify(memberResponse)}`);
|
|
160
|
+
|
|
161
|
+
if (memberResponse.code !== 200 || !memberResponse.data?.uid) {
|
|
162
|
+
return memberResponse.msg || "未找到用户信息";
|
|
163
|
+
}
|
|
164
|
+
memberUid = memberResponse.data.uid;
|
|
165
|
+
targetQQ = memberResponse.data.qq || targetQQ;
|
|
166
|
+
const rawEmail = memberResponse.data.email || "";
|
|
167
|
+
log(`[V2] 成员邮箱原始值: ${rawEmail}`);
|
|
168
|
+
userEmail = normalizeEmail(rawEmail);
|
|
169
|
+
log(`[V2] 成员邮箱规范化后: ${userEmail}`);
|
|
170
|
+
}
|
|
171
|
+
if (userEmail) {
|
|
172
|
+
log(`[V2] 成员邮箱: ${userEmail}`);
|
|
173
|
+
} else {
|
|
174
|
+
log("[V2] 成员邮箱为空,可能无法发送到用户邮箱");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const newPassword = password?.trim() || generatePassword();
|
|
178
|
+
log(`[V2] 即将重置密码,uid=${memberUid}, newPassword=${newPassword}`);
|
|
179
|
+
const resetUrl = `${baseUrl}/members/${memberUid}/password`;
|
|
180
|
+
log(`重置密码请求: ${resetUrl}`);
|
|
181
|
+
const resetResponse = await ctx.http.post(resetUrl, { password: newPassword }, { headers: { token } });
|
|
182
|
+
log(`重置密码响应: ${JSON.stringify(resetResponse)}`);
|
|
183
|
+
|
|
184
|
+
if (resetResponse.code !== 200) {
|
|
185
|
+
return resetResponse.msg || "未知错误";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const effectiveMailTo = normalizeEmail(userEmail || mailTo);
|
|
189
|
+
if (mailEnabled && effectiveMailTo) {
|
|
190
|
+
const template = mailTemplate || "车队平台重置密码成功,uid={uid},qq={qq},新密码:{password}";
|
|
191
|
+
const body = template
|
|
192
|
+
.replace(/{uid}/g, memberUid || "")
|
|
193
|
+
.replace(/{qq}/g, targetQQ || "")
|
|
194
|
+
.replace(/{password}/g, newPassword)
|
|
195
|
+
.replace(/{psw}/g, newPassword);
|
|
196
|
+
const subject = mailSubject || "重置密码通知";
|
|
197
|
+
await sendMail(effectiveMailTo, subject, body);
|
|
198
|
+
} else if (mailEnabled) {
|
|
199
|
+
log("[V2] 已开启邮件发送,但未找到用户邮箱或 mailTo");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const isAdminOp = isAdmin && (targetQQ || memberUid);
|
|
203
|
+
return isAdminOp
|
|
204
|
+
? `管理员操作:车队编号 ${memberUid} 的密码重置成功!新密码已发送到用户邮箱。`
|
|
205
|
+
: "密码重置成功!新密码已发送到您的邮箱,请查收。";
|
|
206
|
+
}
|
|
207
|
+
|
|
49
208
|
let teamId, targetQQ;
|
|
50
209
|
|
|
51
210
|
// 处理@提及的QQ号
|
|
@@ -108,4 +267,4 @@ module.exports = async (ctx, cfg, session, targetTeamId) => {
|
|
|
108
267
|
return error.message.includes("未找到") ? error.message : "系统错误,请稍后重试";
|
|
109
268
|
}
|
|
110
269
|
}
|
|
111
|
-
};
|
|
270
|
+
};
|
|
@@ -80,7 +80,10 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
80
80
|
}
|
|
81
81
|
message += '\n🚚车队角色: ' + playerInfo.data.vtcRole;
|
|
82
82
|
if (playerInfo.data.vtcId == 86009) {
|
|
83
|
-
const { url, token, logOutput } = cfg.mainSettings?.settings || {};
|
|
83
|
+
const { url, token, logOutput, platformVersion } = cfg.mainSettings?.settings || {};
|
|
84
|
+
if ((platformVersion || "v1").toLowerCase() === "v2") {
|
|
85
|
+
message += "\n车队平台V2.0暂不支持积分查询";
|
|
86
|
+
} else {
|
|
84
87
|
try {
|
|
85
88
|
if (logOutput) {
|
|
86
89
|
ctx.logger.info(`tmpQuery:开始查询TmpID ${tmpId} 的积分`);
|
|
@@ -103,6 +106,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
103
106
|
message += '查询出错';
|
|
104
107
|
}
|
|
105
108
|
}
|
|
109
|
+
}
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
message += '\n\n🚫是否封禁: ' + (playerInfo.data.isBan ? '是' : '否');
|
package/lib/index.js
CHANGED
|
@@ -97,10 +97,19 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
97
97
|
}).description('路况查询配置'),
|
|
98
98
|
mainSettings: koishi_1.Schema.object({
|
|
99
99
|
settings: koishi_1.Schema.object({
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
platformVersion: koishi_1.Schema.union([
|
|
101
|
+
koishi_1.Schema.const("v1").description("车队平台V1.0"),
|
|
102
|
+
koishi_1.Schema.const("v2").description("车队平台V2.0")
|
|
103
|
+
]).default("v1").description("车队平台版本"),
|
|
104
|
+
url: koishi_1.Schema.string().description("API服务器地址(V1平台地址 / V2 OpenAPI地址,不含协议)"),
|
|
105
|
+
token: koishi_1.Schema.string().description("API认证令牌(V1平台Token / V2 OpenAPI Token)"),
|
|
106
|
+
mailEnabled: koishi_1.Schema.boolean().description("V2.0重置密码后发送邮件(需启用 adapter-mail)").default(false),
|
|
107
|
+
mailTo: koishi_1.Schema.string().description("V2.0重置密码通知邮箱(自定义邮箱地址)").default(""),
|
|
108
|
+
mailSubject: koishi_1.Schema.string().description("V2.0重置密码邮件标题").default("重置密码通知"),
|
|
109
|
+
mailTemplate: koishi_1.Schema.string().description("V2.0重置密码邮件内容模板,支持变量:{uid} {qq} {password} {psw}").default("我们已将您平台的密码进行重置,您的账号:{uid},新的密码为:{psw} 请妥善保管好您的密码,以防泄露"),
|
|
110
|
+
mailFromName: koishi_1.Schema.string().description("V2.0重置密码邮件发件人名字(覆盖 adapter-mail 的 name)").default(""),
|
|
102
111
|
logOutput: koishi_1.Schema.boolean().description("是否输出日志").default(true)
|
|
103
|
-
})
|
|
112
|
+
}).description("V1/V2通用配置")
|
|
104
113
|
}).description("车队平台配置"),
|
|
105
114
|
resetPassword: koishi_1.Schema.object({
|
|
106
115
|
settings: koishi_1.Schema.object({
|
|
@@ -160,6 +169,30 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
160
169
|
}).description('功能配置')
|
|
161
170
|
]);
|
|
162
171
|
|
|
172
|
+
|
|
173
|
+
function logDisabledCommands(ctx, cfg) {
|
|
174
|
+
const commandFlags = cfg.commands || {};
|
|
175
|
+
const disabled = [];
|
|
176
|
+
const commandList = [
|
|
177
|
+
{ key: 'tmpQuery', label: '??/??' },
|
|
178
|
+
{ key: 'tmpServer', label: '?????/?????' },
|
|
179
|
+
{ key: 'tmpTraffic', label: '??' },
|
|
180
|
+
{ key: 'tmpPosition', label: '??' },
|
|
181
|
+
{ key: 'tmpVersion', label: 'tmp??' },
|
|
182
|
+
{ key: 'tmpDlcMap', label: '??dlc??' },
|
|
183
|
+
{ key: 'tmpMileageRanking', label: '?????/???????' },
|
|
184
|
+
{ key: 'tmpVtc', label: 'vtc??' },
|
|
185
|
+
{ key: 'resetPassword', label: '????' },
|
|
186
|
+
{ key: 'mainSettings', label: '????' }
|
|
187
|
+
];
|
|
188
|
+
for (const item of commandList) {
|
|
189
|
+
if (commandFlags[item.key] === false) disabled.push(item.label);
|
|
190
|
+
}
|
|
191
|
+
if (disabled.length) {
|
|
192
|
+
ctx.logger.info(`[TMP-BOT] ??????????? commands ?????${disabled.join('?')}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
163
196
|
function registerBaseCommands(ctx, cfg) {
|
|
164
197
|
if (cfg.commands?.tmpQuery) {
|
|
165
198
|
ctx.command('查询 <tmpId>')
|
|
@@ -225,11 +258,12 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
225
258
|
}
|
|
226
259
|
|
|
227
260
|
if (cfg.commands?.resetPassword) {
|
|
228
|
-
ctx.command(`重置密码 [targetTeamId:string]`, "重置欧卡车队平台密码")
|
|
229
|
-
.usage("
|
|
261
|
+
ctx.command(`重置密码 [targetTeamId:string] [password:string]`, "重置欧卡车队平台密码")
|
|
262
|
+
.usage("V1.0使用teamId,V2.0使用QQ号;管理员可用 uid=12345;V2.0可指定新密码")
|
|
230
263
|
.example(`重置密码 - 重置自己的密码`)
|
|
231
|
-
.example(`重置密码 - 管理员重置指定teamId的密码`)
|
|
232
|
-
.
|
|
264
|
+
.example(`重置密码 789 - 管理员重置指定teamId的密码`)
|
|
265
|
+
.example(`重置密码 123456 Abc123def4 - V2.0指定QQ与新密码`)
|
|
266
|
+
.action(async ({ session }, targetTeamId, password) => await commands.resetPassword(ctx, cfg, session, targetTeamId, password));
|
|
233
267
|
}
|
|
234
268
|
|
|
235
269
|
if (cfg.commands?.mainSettings) {
|
|
@@ -257,6 +291,8 @@ function apply(ctx, cfg) {
|
|
|
257
291
|
|
|
258
292
|
registerBaseCommands(ctx, cfg);
|
|
259
293
|
|
|
294
|
+
logDisabledCommands(ctx, cfg);
|
|
295
|
+
|
|
260
296
|
if (cfg.commands?.tmpActivityService) {
|
|
261
297
|
const activityConfig = {
|
|
262
298
|
...cfg.tmpActivityService,
|
|
@@ -274,4 +310,4 @@ __name(apply, "apply");
|
|
|
274
310
|
Config,
|
|
275
311
|
apply,
|
|
276
312
|
name
|
|
277
|
-
});
|
|
313
|
+
});
|