mioku-service-webui 4.0.8 → 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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-service-webui",
3
- "version": "4.0.8",
3
+ "version": "4.1.0",
4
4
  "description": "Mioku WebUI 服务",
5
5
  "main": "index.ts",
6
6
  "type": "module",
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
+ }
package/system.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  WebUISettings,
14
14
  } from "./types";
15
15
  import {
16
+ AUTH_PATH,
16
17
  CHAT_CONFIG_DIR,
17
18
  defaultWebUISettings,
18
19
  ensureDir,
@@ -1706,7 +1707,15 @@ async function notifyOwnerWebUIDownloaded(version: string): Promise<void> {
1706
1707
  }
1707
1708
 
1708
1709
  const bot = bots[0];
1709
- const message = `🎉 WebUI 已自动更新到 v${version},记得重启哦~`;
1710
+ const settings = getWebUISettings();
1711
+ const auth = readJsonFile<{ token?: string }>(AUTH_PATH, {});
1712
+ const url = `http://${settings.host}:${settings.port}`;
1713
+ const token = auth?.token || "未设置";
1714
+ const message = [
1715
+ `🎉 WebUI v${version} 已就绪`,
1716
+ `地址: ${url}`,
1717
+ `鉴权密钥: ${token}`,
1718
+ ].join("\n");
1710
1719
 
1711
1720
  for (const ownerId of owners) {
1712
1721
  try {