koishi-plugin-jscn-aaaquery 1.0.21 → 1.1.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/index.d.ts +1 -0
- package/lib/index.js +251 -48
- 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;
|
|
@@ -260,7 +263,7 @@ function apply(ctx, config) {
|
|
|
260
263
|
refreshTokenInBackground();
|
|
261
264
|
return currentToken;
|
|
262
265
|
}
|
|
263
|
-
return refreshToken();
|
|
266
|
+
return refreshToken(currentSession);
|
|
264
267
|
}
|
|
265
268
|
__name(getToken, "getToken");
|
|
266
269
|
async function refreshTokenInBackground() {
|
|
@@ -274,28 +277,107 @@ function apply(ctx, config) {
|
|
|
274
277
|
}
|
|
275
278
|
}
|
|
276
279
|
__name(refreshTokenInBackground, "refreshTokenInBackground");
|
|
277
|
-
|
|
280
|
+
const captchaPendingMap = /* @__PURE__ */ new Map();
|
|
281
|
+
const CAPTCHA_TIMEOUT = 5 * 60 * 1e3;
|
|
282
|
+
setInterval(() => {
|
|
283
|
+
const now = Date.now();
|
|
284
|
+
for (const [key, task] of captchaPendingMap) {
|
|
285
|
+
if (now - task.timestamp > CAPTCHA_TIMEOUT) {
|
|
286
|
+
task.reject(new Error("验证码输入超时(5分钟),登录已取消"));
|
|
287
|
+
captchaPendingMap.delete(key);
|
|
288
|
+
console.log(`清理超时的验证码等待任务: ${key}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}, 30 * 1e3);
|
|
292
|
+
function extractBase64(dataUri) {
|
|
293
|
+
const commaIdx = dataUri.indexOf(",");
|
|
294
|
+
if (commaIdx >= 0) return dataUri.substring(commaIdx + 1);
|
|
295
|
+
return dataUri;
|
|
296
|
+
}
|
|
297
|
+
__name(extractBase64, "extractBase64");
|
|
298
|
+
async function fetchCaptcha() {
|
|
299
|
+
const response = await ctx.http.get(`${config.baseUrl}/admin/captcha`, {
|
|
300
|
+
headers: {
|
|
301
|
+
"Accept": "application/json"
|
|
302
|
+
},
|
|
303
|
+
timeout: 1e4
|
|
304
|
+
});
|
|
305
|
+
if (response.status === 200 && response.data && response.data.captchaKey && response.data.captchaImage) {
|
|
306
|
+
return {
|
|
307
|
+
captchaKey: response.data.captchaKey,
|
|
308
|
+
captchaImage: response.data.captchaImage
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
throw new Error(`获取验证码失败:${response.message || "未知错误"}`);
|
|
312
|
+
}
|
|
313
|
+
__name(fetchCaptcha, "fetchCaptcha");
|
|
314
|
+
async function refreshToken(session) {
|
|
278
315
|
try {
|
|
279
316
|
console.log("开始登录请求刷新token...");
|
|
280
317
|
await waitForRequestSlot();
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
318
|
+
let captchaKey = "";
|
|
319
|
+
let captchaCode = "";
|
|
320
|
+
if (config.captchaMode === "interactive") {
|
|
321
|
+
if (!session) {
|
|
322
|
+
throw new Error("交互式验证码需要 session,但未检测到用户会话,请稍后重试");
|
|
323
|
+
}
|
|
324
|
+
const MAX_CAPTCHA_ATTEMPTS = 5;
|
|
325
|
+
const sessionKey = `${session.userId || "anon"}:${session.channelId || "dm"}`;
|
|
326
|
+
for (let attempt = 1; attempt <= MAX_CAPTCHA_ATTEMPTS; attempt++) {
|
|
327
|
+
captchaPendingMap.delete(sessionKey);
|
|
328
|
+
const { captchaKey: key, captchaImage } = await fetchCaptcha();
|
|
329
|
+
captchaKey = key;
|
|
330
|
+
try {
|
|
331
|
+
const base64Data = extractBase64(captchaImage);
|
|
332
|
+
const promptText = attempt > 1 ? `🔐 验证码错误,请重新输入新图中的验证码(第${attempt}/${MAX_CAPTCHA_ATTEMPTS}次)` : "🔐 请输入图中验证码(5分钟内有效,超时需重新发起查询)";
|
|
333
|
+
await session.send([
|
|
334
|
+
import_koishi.h.image(Buffer.from(base64Data, "base64"), "image/png"),
|
|
335
|
+
promptText
|
|
336
|
+
]);
|
|
337
|
+
} catch (sendError) {
|
|
338
|
+
console.error("发送验证码图片失败:", sendError);
|
|
339
|
+
throw new Error("发送验证码图片失败,请检查图片发送权限");
|
|
288
340
|
}
|
|
341
|
+
captchaCode = await new Promise((resolve, reject) => {
|
|
342
|
+
captchaPendingMap.set(sessionKey, {
|
|
343
|
+
session,
|
|
344
|
+
captchaKey,
|
|
345
|
+
captchaImage,
|
|
346
|
+
resolve,
|
|
347
|
+
reject,
|
|
348
|
+
timestamp: Date.now()
|
|
349
|
+
});
|
|
350
|
+
console.log(`验证码等待任务已注册: ${sessionKey}, captchaKey: ${captchaKey}, 第${attempt}轮`);
|
|
351
|
+
});
|
|
352
|
+
const response = await ctx.http.post(
|
|
353
|
+
`${config.baseUrl}/admin/login`,
|
|
354
|
+
`loginName=${encodeURIComponent(config.loginName)}&pwd=${encodeURIComponent(config.password)}&captchaKey=${encodeURIComponent(captchaKey)}&captchaCode=${encodeURIComponent(captchaCode)}`,
|
|
355
|
+
{
|
|
356
|
+
headers: {
|
|
357
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
358
|
+
"Accept": "application/json"
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
console.log("登录响应:", JSON.stringify(response, null, 2));
|
|
363
|
+
if (response.status === 200 && response.data && response.data.token) {
|
|
364
|
+
currentToken = response.data.token;
|
|
365
|
+
tokenExpireTime = response.data.timeStamp * 1e3;
|
|
366
|
+
console.log("获取新token成功,过期时间:", new Date(tokenExpireTime).toLocaleString());
|
|
367
|
+
return currentToken;
|
|
368
|
+
}
|
|
369
|
+
const message = response.data?.message || response.message || "";
|
|
370
|
+
if (message.includes("验证码") || message.includes("captcha")) {
|
|
371
|
+
console.log(`验证码错误,第${attempt}轮失败,准备进入第${attempt + 1}轮`);
|
|
372
|
+
captchaPendingMap.delete(sessionKey);
|
|
373
|
+
if (attempt === MAX_CAPTCHA_ATTEMPTS) {
|
|
374
|
+
throw new Error("验证码错误次数过多,请稍后重新发起查询");
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
throw new Error(`登录失败:${message || "未知错误"}`);
|
|
289
379
|
}
|
|
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
380
|
}
|
|
298
|
-
throw new Error(`登录失败:${response.message || "未知错误"}`);
|
|
299
381
|
} catch (error) {
|
|
300
382
|
console.error("登录错误详情:", error);
|
|
301
383
|
if (error.response) {
|
|
@@ -647,40 +729,77 @@ function apply(ctx, config) {
|
|
|
647
729
|
__name(queryOrderLog, "queryOrderLog");
|
|
648
730
|
async function queryRadiusUser(account) {
|
|
649
731
|
return addToQueue("radiusUser", { account }, async () => {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
{
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
"Accept": "application/json"
|
|
732
|
+
const maxRetries = 2;
|
|
733
|
+
let lastError = null;
|
|
734
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
735
|
+
try {
|
|
736
|
+
console.log(`开始查询RADIUS用户信息... (第${attempt}次尝试)`);
|
|
737
|
+
console.log(`RADIUS API地址: ${config.radiusApiUrl}/userquery`);
|
|
738
|
+
const response = await ctx.http.post(
|
|
739
|
+
`${config.radiusApiUrl}/userquery`,
|
|
740
|
+
{
|
|
741
|
+
login_name: account,
|
|
742
|
+
org_code: "403",
|
|
743
|
+
// 固定值
|
|
744
|
+
channel: "ALL"
|
|
745
|
+
// 固定值
|
|
665
746
|
},
|
|
666
|
-
|
|
667
|
-
|
|
747
|
+
{
|
|
748
|
+
headers: {
|
|
749
|
+
"Content-Type": "application/json",
|
|
750
|
+
"Accept": "application/json",
|
|
751
|
+
"User-Agent": "JSCN-AAAQuery/1.0"
|
|
752
|
+
},
|
|
753
|
+
timeout: 15e3
|
|
754
|
+
// 减少超时时间到15秒
|
|
755
|
+
}
|
|
756
|
+
);
|
|
757
|
+
console.log("RADIUS查询成功,响应:", JSON.stringify(response, null, 2));
|
|
758
|
+
return response;
|
|
759
|
+
} catch (error) {
|
|
760
|
+
lastError = error;
|
|
761
|
+
console.error(`RADIUS查询第${attempt}次尝试失败:`, error.message || error);
|
|
762
|
+
if (error.code) {
|
|
763
|
+
console.error(`错误代码: ${error.code}`);
|
|
668
764
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
765
|
+
if (error.cause) {
|
|
766
|
+
console.error(`错误原因: ${error.cause.message || error.cause}`);
|
|
767
|
+
if (error.cause.code) {
|
|
768
|
+
console.error(`原因代码: ${error.cause.code}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (error.response) {
|
|
772
|
+
console.error("HTTP响应状态:", error.response.status);
|
|
773
|
+
console.error("HTTP响应数据:", error.response.data);
|
|
774
|
+
}
|
|
775
|
+
const isNetworkError = error.code === "UND_ERR_CONNECT_TIMEOUT" || error.code === "UND_ERR_SOCKET" || error.message?.includes("fetch failed") || error.message?.includes("timeout") || error.message?.includes("network");
|
|
776
|
+
if (isNetworkError && attempt < maxRetries) {
|
|
777
|
+
console.log(`网络错误,${attempt < maxRetries ? "准备重试" : "重试次数已用完"}`);
|
|
778
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3 * attempt));
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
break;
|
|
676
782
|
}
|
|
677
|
-
const fallbackResponse = {
|
|
678
|
-
code: "ERROR",
|
|
679
|
-
message: `查询RADIUS系统失败: ${error.message || "未知错误,可能是网络连接问题"}`,
|
|
680
|
-
data: null
|
|
681
|
-
};
|
|
682
|
-
return fallbackResponse;
|
|
683
783
|
}
|
|
784
|
+
console.error("RADIUS查询最终失败,所有重试已用完");
|
|
785
|
+
let errorMessage = "查询RADIUS系统失败";
|
|
786
|
+
if (lastError?.code === "UND_ERR_CONNECT_TIMEOUT") {
|
|
787
|
+
errorMessage = "RADIUS服务器连接超时,请检查网络连接或服务器状态";
|
|
788
|
+
} else if (lastError?.code === "UND_ERR_SOCKET") {
|
|
789
|
+
errorMessage = "RADIUS服务器网络连接失败,请检查网络配置";
|
|
790
|
+
} else if (lastError?.message?.includes("fetch failed")) {
|
|
791
|
+
errorMessage = "RADIUS服务器请求失败,可能是网络问题或服务器不可达";
|
|
792
|
+
} else if (lastError?.response?.status) {
|
|
793
|
+
errorMessage = `RADIUS服务器返回错误状态码: ${lastError.response.status}`;
|
|
794
|
+
} else {
|
|
795
|
+
errorMessage = `RADIUS查询失败: ${lastError?.message || "未知错误"}`;
|
|
796
|
+
}
|
|
797
|
+
const fallbackResponse = {
|
|
798
|
+
code: "ERROR",
|
|
799
|
+
message: errorMessage,
|
|
800
|
+
data: null
|
|
801
|
+
};
|
|
802
|
+
return fallbackResponse;
|
|
684
803
|
});
|
|
685
804
|
}
|
|
686
805
|
__name(queryRadiusUser, "queryRadiusUser");
|
|
@@ -856,6 +975,7 @@ function apply(ctx, config) {
|
|
|
856
975
|
return;
|
|
857
976
|
}
|
|
858
977
|
console.log("开始处理消息队列中的任务");
|
|
978
|
+
currentSession = task.session || null;
|
|
859
979
|
const result = await task.handler();
|
|
860
980
|
if (result && task.session?.send) {
|
|
861
981
|
const formattedResult = formatOutputMessage(result);
|
|
@@ -1032,6 +1152,26 @@ function apply(ctx, config) {
|
|
|
1032
1152
|
}
|
|
1033
1153
|
}
|
|
1034
1154
|
__name(smartQueryMacAddress, "smartQueryMacAddress");
|
|
1155
|
+
ctx.middleware(async (session, next) => {
|
|
1156
|
+
const input = (session.stripped?.content || session.content || "").trim();
|
|
1157
|
+
if (!input) return next();
|
|
1158
|
+
const sessionKey = `${session.userId || "anon"}:${session.channelId || "dm"}`;
|
|
1159
|
+
const pendingTask = captchaPendingMap.get(sessionKey);
|
|
1160
|
+
if (!pendingTask) {
|
|
1161
|
+
return next();
|
|
1162
|
+
}
|
|
1163
|
+
console.log(`捕获到验证码输入: "${input}",对应 captchaKey: ${pendingTask.captchaKey}`);
|
|
1164
|
+
if (!/^[a-z0-9]{2,8}$/i.test(input)) {
|
|
1165
|
+
return next();
|
|
1166
|
+
}
|
|
1167
|
+
captchaPendingMap.delete(sessionKey);
|
|
1168
|
+
pendingTask.resolve(input.replace(/\s+/g, "").toLowerCase());
|
|
1169
|
+
try {
|
|
1170
|
+
await session.send("✅ 验证码已接收,正在登录...");
|
|
1171
|
+
} catch (_) {
|
|
1172
|
+
}
|
|
1173
|
+
return;
|
|
1174
|
+
});
|
|
1035
1175
|
ctx.middleware(async (session, next) => {
|
|
1036
1176
|
console.log("原始消息:", session.content);
|
|
1037
1177
|
const originalInput = session.stripped.content.trim();
|
|
@@ -1583,6 +1723,53 @@ function apply(ctx, config) {
|
|
|
1583
1723
|
const result = clearCache();
|
|
1584
1724
|
return formatOutputMessage(`✅ ${result}`);
|
|
1585
1725
|
});
|
|
1726
|
+
ctx.command("强制刷新token", "强制清除缓存的token,重新获取登录验证码").action(async ({ session }) => {
|
|
1727
|
+
currentToken = null;
|
|
1728
|
+
tokenExpireTime = 0;
|
|
1729
|
+
const token = await refreshToken(session);
|
|
1730
|
+
return formatOutputMessage(`✅ Token已刷新,过期时间: ${new Date(tokenExpireTime).toLocaleString()}`);
|
|
1731
|
+
});
|
|
1732
|
+
async function diagnoseNetwork() {
|
|
1733
|
+
const results = [];
|
|
1734
|
+
results.push("🌐 网络连接诊断");
|
|
1735
|
+
results.push(getSeparator());
|
|
1736
|
+
try {
|
|
1737
|
+
const radiusUrl = new URL(config.radiusApiUrl || "http://111.208.114.248:28210/api/radius");
|
|
1738
|
+
const host = radiusUrl.hostname;
|
|
1739
|
+
const port = radiusUrl.port || "80";
|
|
1740
|
+
results.push(`RADIUS服务器: ${host}:${port}`);
|
|
1741
|
+
try {
|
|
1742
|
+
const startTime = Date.now();
|
|
1743
|
+
const response = await ctx.http.get(`${config.radiusApiUrl}/health`, {
|
|
1744
|
+
timeout: 5e3,
|
|
1745
|
+
validateStatus: /* @__PURE__ */ __name(() => true, "validateStatus")
|
|
1746
|
+
// 接受任何状态码
|
|
1747
|
+
});
|
|
1748
|
+
const endTime = Date.now();
|
|
1749
|
+
const latency = endTime - startTime;
|
|
1750
|
+
results.push(`✅ 连接正常 (延迟: ${latency}ms)`);
|
|
1751
|
+
results.push(` 状态码: ${response.status || "N/A"}`);
|
|
1752
|
+
} catch (error) {
|
|
1753
|
+
results.push(`❌ 连接失败`);
|
|
1754
|
+
if (error.code === "UND_ERR_CONNECT_TIMEOUT") {
|
|
1755
|
+
results.push(` 原因: 连接超时`);
|
|
1756
|
+
} else if (error.code === "UND_ERR_SOCKET") {
|
|
1757
|
+
results.push(` 原因: 网络连接失败`);
|
|
1758
|
+
} else {
|
|
1759
|
+
results.push(` 原因: ${error.message || "未知错误"}`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
results.push("");
|
|
1763
|
+
results.push("📋 当前配置");
|
|
1764
|
+
results.push(`RADIUS API地址: ${config.radiusApiUrl || "未配置"}`);
|
|
1765
|
+
results.push(`运维系统地址: ${config.baseUrl || "未配置"}`);
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
results.push(`❌ 诊断过程出错: ${error.message}`);
|
|
1768
|
+
}
|
|
1769
|
+
results.push(getSeparator());
|
|
1770
|
+
return results.join("\n");
|
|
1771
|
+
}
|
|
1772
|
+
__name(diagnoseNetwork, "diagnoseNetwork");
|
|
1586
1773
|
function getSystemStatus() {
|
|
1587
1774
|
updateCacheStats();
|
|
1588
1775
|
const hitRate = cacheStats.hits + cacheStats.misses > 0 ? Math.round(cacheStats.hits / (cacheStats.hits + cacheStats.misses) * 100) : 0;
|
|
@@ -1612,6 +1799,9 @@ function apply(ctx, config) {
|
|
|
1612
1799
|
ctx.command("系统状态").alias("/系统状态").action(async ({ session }) => {
|
|
1613
1800
|
return formatOutputMessage(getSystemStatus());
|
|
1614
1801
|
});
|
|
1802
|
+
ctx.command("网络诊断").alias("/网络诊断").action(async ({ session }) => {
|
|
1803
|
+
return formatOutputMessage(await diagnoseNetwork());
|
|
1804
|
+
});
|
|
1615
1805
|
ctx.command("使用说明", "显示插件的使用方法和命令说明").alias("/使用说明").action(async ({ session }) => {
|
|
1616
1806
|
const helpText = [
|
|
1617
1807
|
"📋 JSCN小帮手使用说明",
|
|
@@ -1655,6 +1845,19 @@ function apply(ctx, config) {
|
|
|
1655
1845
|
" 示例:",
|
|
1656
1846
|
" 输入 GDC8510019239048 → 触发账号查询",
|
|
1657
1847
|
" 输入 00:11:22:33:44:55 → 触发终端查询",
|
|
1848
|
+
"",
|
|
1849
|
+
"6. 系统管理",
|
|
1850
|
+
" 命令:系统状态",
|
|
1851
|
+
" 功能:查看系统运行状态、缓存统计、队列状态等。",
|
|
1852
|
+
"",
|
|
1853
|
+
" 命令:网络诊断",
|
|
1854
|
+
" 功能:诊断RADIUS服务器网络连接状态,帮助排查连接问题。",
|
|
1855
|
+
"",
|
|
1856
|
+
" 命令:缓存统计",
|
|
1857
|
+
" 功能:查看缓存使用情况和命中率统计。",
|
|
1858
|
+
"",
|
|
1859
|
+
" 命令:清理缓存",
|
|
1860
|
+
" 功能:清理所有缓存数据。",
|
|
1658
1861
|
getSeparator()
|
|
1659
1862
|
].join("\n");
|
|
1660
1863
|
return formatOutputMessage(helpText);
|