mioku-service-webui 4.0.9 → 4.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/index.ts +2 -0
- package/package.json +1 -1
- package/routes/index.ts +1 -0
- package/routes/stranger.ts +145 -0
package/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
createManageRoutes,
|
|
22
22
|
createStoreRoutes,
|
|
23
23
|
createDataManagementRoutes,
|
|
24
|
+
createStrangerRoutes,
|
|
24
25
|
} from "./routes";
|
|
25
26
|
|
|
26
27
|
export interface WebUIServiceAPI {
|
|
@@ -100,6 +101,7 @@ class WebUIRuntime {
|
|
|
100
101
|
this.app.route("/api/plugin-config", createPluginConfigRoutes());
|
|
101
102
|
this.app.route("/api/meme", createMemeRoutes());
|
|
102
103
|
this.app.route("/api/data-management", createDataManagementRoutes());
|
|
104
|
+
this.app.route("/api/stranger", createStrangerRoutes());
|
|
103
105
|
|
|
104
106
|
this.app.get(
|
|
105
107
|
"/api/ws/logs",
|
package/package.json
CHANGED
package/routes/index.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { createDBRoutes, createPluginConfigRoutes, createMemeRoutes } from "./db
|
|
|
5
5
|
export { createManageRoutes } from "./manage";
|
|
6
6
|
export { createStoreRoutes } from "./store";
|
|
7
7
|
export { createDataManagementRoutes } from "./data-management";
|
|
8
|
+
export { createStrangerRoutes } from "./stranger";
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { connectedBots, logger } from "mioki";
|
|
3
|
+
|
|
4
|
+
interface StrangerInfoResponse {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
data?: { userId: string; nickname: string };
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface CacheEntry {
|
|
11
|
+
nickname: string | null;
|
|
12
|
+
expiresAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
16
|
+
const FETCH_TIMEOUT_MS = 4000;
|
|
17
|
+
|
|
18
|
+
const nicknameCache = new Map<string, CacheEntry>();
|
|
19
|
+
const inflight = new Map<string, Promise<string | null>>();
|
|
20
|
+
|
|
21
|
+
function getConnectedBots(): any[] {
|
|
22
|
+
return Array.from(connectedBots.values()).filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeUserId(input: unknown): string {
|
|
26
|
+
return String(input ?? "").trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isValidQQNumber(id: string): boolean {
|
|
30
|
+
return /^\d{5,12}$/.test(id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getCached(userId: string): string | null | undefined {
|
|
34
|
+
const entry = nicknameCache.get(userId);
|
|
35
|
+
if (!entry) return undefined;
|
|
36
|
+
if (entry.expiresAt < Date.now()) {
|
|
37
|
+
nicknameCache.delete(userId);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return entry.nickname;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function setCached(userId: string, nickname: string | null): void {
|
|
44
|
+
nicknameCache.set(userId, {
|
|
45
|
+
nickname,
|
|
46
|
+
expiresAt: Date.now() + CACHE_TTL_MS,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function fetchNicknameFromBot(
|
|
51
|
+
bot: any,
|
|
52
|
+
userId: string,
|
|
53
|
+
): Promise<string | null> {
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
56
|
+
try {
|
|
57
|
+
const res = await bot.api("get_stranger_info", { user_id: userId });
|
|
58
|
+
const data = res?.data || res;
|
|
59
|
+
const nickname = String(data?.nickname ?? "").trim();
|
|
60
|
+
return nickname || null;
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
logger.warn(
|
|
63
|
+
`stranger-info: bot ${bot?.uin || bot?.bot_id || "?"} 查询 ${userId} 失败: ${error?.message || error}`,
|
|
64
|
+
);
|
|
65
|
+
return null;
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function lookupNickname(userId: string): Promise<string | null> {
|
|
72
|
+
const cached = getCached(userId);
|
|
73
|
+
if (cached !== undefined) return cached;
|
|
74
|
+
|
|
75
|
+
const pending = inflight.get(userId);
|
|
76
|
+
if (pending) return pending;
|
|
77
|
+
|
|
78
|
+
const promise = (async () => {
|
|
79
|
+
const bots = getConnectedBots();
|
|
80
|
+
if (bots.length === 0) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const results = await Promise.all(
|
|
84
|
+
bots.map((bot) => fetchNicknameFromBot(bot, userId)),
|
|
85
|
+
);
|
|
86
|
+
for (const nickname of results) {
|
|
87
|
+
if (nickname) {
|
|
88
|
+
setCached(userId, nickname);
|
|
89
|
+
return nickname;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
setCached(userId, null);
|
|
93
|
+
return null;
|
|
94
|
+
})();
|
|
95
|
+
|
|
96
|
+
inflight.set(userId, promise);
|
|
97
|
+
try {
|
|
98
|
+
return await promise;
|
|
99
|
+
} finally {
|
|
100
|
+
inflight.delete(userId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createStrangerRoutes() {
|
|
105
|
+
const app = new Hono();
|
|
106
|
+
|
|
107
|
+
app.post("/info", async (c) => {
|
|
108
|
+
const body = await c.req.json().catch(() => ({}));
|
|
109
|
+
const userId = normalizeUserId(body?.user_id);
|
|
110
|
+
if (!isValidQQNumber(userId)) {
|
|
111
|
+
return c.json(
|
|
112
|
+
{ ok: false, error: "INVALID_USER_ID" } satisfies StrangerInfoResponse,
|
|
113
|
+
400,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const nickname = await lookupNickname(userId);
|
|
119
|
+
if (!nickname) {
|
|
120
|
+
return c.json(
|
|
121
|
+
{
|
|
122
|
+
ok: false,
|
|
123
|
+
error: "NICKNAME_NOT_FOUND",
|
|
124
|
+
} satisfies StrangerInfoResponse,
|
|
125
|
+
404,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return c.json({
|
|
129
|
+
ok: true,
|
|
130
|
+
data: { userId, nickname },
|
|
131
|
+
} satisfies StrangerInfoResponse);
|
|
132
|
+
} catch (error: any) {
|
|
133
|
+
logger.error(`stranger-info 失败: ${error?.message || error}`);
|
|
134
|
+
return c.json(
|
|
135
|
+
{
|
|
136
|
+
ok: false,
|
|
137
|
+
error: "INTERNAL_ERROR",
|
|
138
|
+
} satisfies StrangerInfoResponse,
|
|
139
|
+
500,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return app;
|
|
145
|
+
}
|