koishi-plugin-jscn-aaaquery 1.0.22 → 1.1.1
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/index.d.ts +1 -0
- package/lib/index.js +125 -32
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -32,7 +32,9 @@ var Config = import_koishi.Schema.object({
|
|
|
32
32
|
password: import_koishi.Schema.string().required().description("运维系统登录密码").default("admin"),
|
|
33
33
|
baseUrl: import_koishi.Schema.string().default("http://172.16.251.75:8090/nms/api").description("运维系统API地址"),
|
|
34
34
|
// 添加RADIUS API的配置项
|
|
35
|
-
radiusApiUrl: import_koishi.Schema.string().description("RADIUS API基础URL").default("http://111.208.114.248:28210/api/radius")
|
|
35
|
+
radiusApiUrl: import_koishi.Schema.string().description("RADIUS API基础URL").default("http://111.208.114.248:28210/api/radius"),
|
|
36
|
+
// 验证码模式
|
|
37
|
+
captchaMode: import_koishi.Schema.const("interactive").description("登录验证码获取方式(发送图片到对话,用户输入)").default("interactive")
|
|
36
38
|
});
|
|
37
39
|
function evaluateOpticalPower(power) {
|
|
38
40
|
if (power === "设备不在线" || power === void 0 || power === null) {
|
|
@@ -83,6 +85,7 @@ function apply(ctx, config) {
|
|
|
83
85
|
const requestWaitQueue = [];
|
|
84
86
|
const messageQueue = [];
|
|
85
87
|
let isProcessingMessage = false;
|
|
88
|
+
let currentSession = null;
|
|
86
89
|
const MESSAGE_TIMEOUT2 = 1 * 60 * 1e3;
|
|
87
90
|
const MESSAGE_CLEAN_INTERVAL2 = 60 * 1e3;
|
|
88
91
|
const MAX_QUEUE_LENGTH = 50;
|
|
@@ -255,47 +258,110 @@ function apply(ctx, config) {
|
|
|
255
258
|
console.log("使用缓存的token,过期时间:", new Date(tokenExpireTime).toLocaleString());
|
|
256
259
|
return currentToken;
|
|
257
260
|
}
|
|
258
|
-
|
|
259
|
-
console.log("token即将过期,后台刷新token");
|
|
260
|
-
refreshTokenInBackground();
|
|
261
|
-
return currentToken;
|
|
262
|
-
}
|
|
263
|
-
return refreshToken();
|
|
261
|
+
return refreshToken(currentSession);
|
|
264
262
|
}
|
|
265
263
|
__name(getToken, "getToken");
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
264
|
+
const captchaPendingMap = /* @__PURE__ */ new Map();
|
|
265
|
+
const CAPTCHA_TIMEOUT = 5 * 60 * 1e3;
|
|
266
|
+
setInterval(() => {
|
|
267
|
+
const now = Date.now();
|
|
268
|
+
for (const [key, task] of captchaPendingMap) {
|
|
269
|
+
if (now - task.timestamp > CAPTCHA_TIMEOUT) {
|
|
270
|
+
task.reject(new Error("验证码输入超时(5分钟),登录已取消"));
|
|
271
|
+
captchaPendingMap.delete(key);
|
|
272
|
+
console.log(`清理超时的验证码等待任务: ${key}`);
|
|
273
|
+
}
|
|
274
274
|
}
|
|
275
|
+
}, 30 * 1e3);
|
|
276
|
+
function extractBase64(dataUri) {
|
|
277
|
+
const commaIdx = dataUri.indexOf(",");
|
|
278
|
+
if (commaIdx >= 0) return dataUri.substring(commaIdx + 1);
|
|
279
|
+
return dataUri;
|
|
275
280
|
}
|
|
276
|
-
__name(
|
|
277
|
-
async function
|
|
281
|
+
__name(extractBase64, "extractBase64");
|
|
282
|
+
async function fetchCaptcha() {
|
|
283
|
+
const response = await ctx.http.get(`${config.baseUrl}/admin/captcha`, {
|
|
284
|
+
headers: {
|
|
285
|
+
"Accept": "application/json"
|
|
286
|
+
},
|
|
287
|
+
timeout: 1e4
|
|
288
|
+
});
|
|
289
|
+
if (response.status === 200 && response.data && response.data.captchaKey && response.data.captchaImage) {
|
|
290
|
+
return {
|
|
291
|
+
captchaKey: response.data.captchaKey,
|
|
292
|
+
captchaImage: response.data.captchaImage
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
throw new Error(`获取验证码失败:${response.message || "未知错误"}`);
|
|
296
|
+
}
|
|
297
|
+
__name(fetchCaptcha, "fetchCaptcha");
|
|
298
|
+
async function refreshToken(session) {
|
|
278
299
|
try {
|
|
279
300
|
console.log("开始登录请求刷新token...");
|
|
280
301
|
await waitForRequestSlot();
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
302
|
+
let captchaKey = "";
|
|
303
|
+
let captchaCode = "";
|
|
304
|
+
if (config.captchaMode === "interactive") {
|
|
305
|
+
if (!session) {
|
|
306
|
+
throw new Error("交互式验证码需要 session,但未检测到用户会话,请稍后重试");
|
|
307
|
+
}
|
|
308
|
+
const MAX_CAPTCHA_ATTEMPTS = 5;
|
|
309
|
+
const sessionKey = `${session.userId || "anon"}:${session.channelId || "dm"}`;
|
|
310
|
+
for (let attempt = 1; attempt <= MAX_CAPTCHA_ATTEMPTS; attempt++) {
|
|
311
|
+
captchaPendingMap.delete(sessionKey);
|
|
312
|
+
const { captchaKey: key, captchaImage } = await fetchCaptcha();
|
|
313
|
+
captchaKey = key;
|
|
314
|
+
try {
|
|
315
|
+
const base64Data = extractBase64(captchaImage);
|
|
316
|
+
const promptText = attempt > 1 ? `🔐 验证码错误,请重新输入新图中的验证码(第${attempt}/${MAX_CAPTCHA_ATTEMPTS}次)` : "🔐 请输入图中验证码(5分钟内有效,超时需重新发起查询)";
|
|
317
|
+
await session.send([
|
|
318
|
+
import_koishi.h.image(Buffer.from(base64Data, "base64"), "image/png"),
|
|
319
|
+
promptText
|
|
320
|
+
]);
|
|
321
|
+
} catch (sendError) {
|
|
322
|
+
console.error("发送验证码图片失败:", sendError);
|
|
323
|
+
throw new Error("发送验证码图片失败,请检查图片发送权限");
|
|
288
324
|
}
|
|
325
|
+
captchaCode = await new Promise((resolve, reject) => {
|
|
326
|
+
captchaPendingMap.set(sessionKey, {
|
|
327
|
+
session,
|
|
328
|
+
captchaKey,
|
|
329
|
+
captchaImage,
|
|
330
|
+
resolve,
|
|
331
|
+
reject,
|
|
332
|
+
timestamp: Date.now()
|
|
333
|
+
});
|
|
334
|
+
console.log(`验证码等待任务已注册: ${sessionKey}, captchaKey: ${captchaKey}, 第${attempt}轮`);
|
|
335
|
+
});
|
|
336
|
+
const response = await ctx.http.post(
|
|
337
|
+
`${config.baseUrl}/admin/login`,
|
|
338
|
+
`loginName=${encodeURIComponent(config.loginName)}&pwd=${encodeURIComponent(config.password)}&captchaKey=${encodeURIComponent(captchaKey)}&captchaCode=${encodeURIComponent(captchaCode)}`,
|
|
339
|
+
{
|
|
340
|
+
headers: {
|
|
341
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
342
|
+
"Accept": "application/json"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
console.log("登录响应:", JSON.stringify(response, null, 2));
|
|
347
|
+
if (response.status === 200 && response.data && response.data.token) {
|
|
348
|
+
currentToken = response.data.token;
|
|
349
|
+
tokenExpireTime = response.data.timeStamp * 1e3;
|
|
350
|
+
console.log("获取新token成功,过期时间:", new Date(tokenExpireTime).toLocaleString());
|
|
351
|
+
return currentToken;
|
|
352
|
+
}
|
|
353
|
+
const message = response.data?.message || response.message || "";
|
|
354
|
+
if (message.includes("验证码") || message.includes("captcha")) {
|
|
355
|
+
console.log(`验证码错误,第${attempt}轮失败,准备进入第${attempt + 1}轮`);
|
|
356
|
+
captchaPendingMap.delete(sessionKey);
|
|
357
|
+
if (attempt === MAX_CAPTCHA_ATTEMPTS) {
|
|
358
|
+
throw new Error("验证码错误次数过多,请稍后重新发起查询");
|
|
359
|
+
}
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
throw new Error(`登录失败:${message || "未知错误"}`);
|
|
289
363
|
}
|
|
290
|
-
);
|
|
291
|
-
console.log("登录响应:", JSON.stringify(response, null, 2));
|
|
292
|
-
if (response.status === 200 && response.data && response.data.token) {
|
|
293
|
-
currentToken = response.data.token;
|
|
294
|
-
tokenExpireTime = response.data.timeStamp * 1e3;
|
|
295
|
-
console.log("获取新token成功,过期时间:", new Date(tokenExpireTime).toLocaleString());
|
|
296
|
-
return currentToken;
|
|
297
364
|
}
|
|
298
|
-
throw new Error(`登录失败:${response.message || "未知错误"}`);
|
|
299
365
|
} catch (error) {
|
|
300
366
|
console.error("登录错误详情:", error);
|
|
301
367
|
if (error.response) {
|
|
@@ -893,6 +959,7 @@ function apply(ctx, config) {
|
|
|
893
959
|
return;
|
|
894
960
|
}
|
|
895
961
|
console.log("开始处理消息队列中的任务");
|
|
962
|
+
currentSession = task.session || null;
|
|
896
963
|
const result = await task.handler();
|
|
897
964
|
if (result && task.session?.send) {
|
|
898
965
|
const formattedResult = formatOutputMessage(result);
|
|
@@ -1069,6 +1136,26 @@ function apply(ctx, config) {
|
|
|
1069
1136
|
}
|
|
1070
1137
|
}
|
|
1071
1138
|
__name(smartQueryMacAddress, "smartQueryMacAddress");
|
|
1139
|
+
ctx.middleware(async (session, next) => {
|
|
1140
|
+
const input = (session.stripped?.content || session.content || "").trim();
|
|
1141
|
+
if (!input) return next();
|
|
1142
|
+
const sessionKey = `${session.userId || "anon"}:${session.channelId || "dm"}`;
|
|
1143
|
+
const pendingTask = captchaPendingMap.get(sessionKey);
|
|
1144
|
+
if (!pendingTask) {
|
|
1145
|
+
return next();
|
|
1146
|
+
}
|
|
1147
|
+
console.log(`捕获到验证码输入: "${input}",对应 captchaKey: ${pendingTask.captchaKey}`);
|
|
1148
|
+
if (!/^[a-z0-9]{2,8}$/i.test(input)) {
|
|
1149
|
+
return next();
|
|
1150
|
+
}
|
|
1151
|
+
captchaPendingMap.delete(sessionKey);
|
|
1152
|
+
pendingTask.resolve(input.replace(/\s+/g, "").toLowerCase());
|
|
1153
|
+
try {
|
|
1154
|
+
await session.send("✅ 验证码已接收,正在登录...");
|
|
1155
|
+
} catch (_) {
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
});
|
|
1072
1159
|
ctx.middleware(async (session, next) => {
|
|
1073
1160
|
console.log("原始消息:", session.content);
|
|
1074
1161
|
const originalInput = session.stripped.content.trim();
|
|
@@ -1620,6 +1707,12 @@ function apply(ctx, config) {
|
|
|
1620
1707
|
const result = clearCache();
|
|
1621
1708
|
return formatOutputMessage(`✅ ${result}`);
|
|
1622
1709
|
});
|
|
1710
|
+
ctx.command("强制刷新token", "强制清除缓存的token,重新获取登录验证码").action(async ({ session }) => {
|
|
1711
|
+
currentToken = null;
|
|
1712
|
+
tokenExpireTime = 0;
|
|
1713
|
+
const token = await refreshToken(session);
|
|
1714
|
+
return formatOutputMessage(`✅ Token已刷新,过期时间: ${new Date(tokenExpireTime).toLocaleString()}`);
|
|
1715
|
+
});
|
|
1623
1716
|
async function diagnoseNetwork() {
|
|
1624
1717
|
const results = [];
|
|
1625
1718
|
results.push("🌐 网络连接诊断");
|