diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b5930b7a..8d646198 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -36,8 +36,11 @@ jobs: - name: Install dependencies run: | + export ELECTRON_SKIP_BINARY_DOWNLOAD=1 corepack enable - yarn install + yarn install --no-immutable + cd src/webui/FE + npm install - name: Build run: | diff --git "a/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" "b/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" index 662f1a8b..75f862dc 100644 --- "a/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" +++ "b/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" @@ -1,3 +1,12 @@ +V7.12.10 +更新时间 2026-04-30 + +* 修复偶现获取不到引用的消息 +* 修复发送的 at 消息可能显示为空白 +* 修复 get_stranger_info API 获取的会员相关信息不正确 +* 支持退出 WebUI 登录 + +================= V7.12.9 更新时间 2026-04-29 diff --git a/package-dist.json b/package-dist.json index d639eeaf..73dab60a 100644 --- a/package-dist.json +++ b/package-dist.json @@ -1 +1 @@ -{"name":"llonebot-dist","version":"7.12.9","type":"module","description":"","main":"llbot.js","author":"linyuchen","repository":{"type":"git","url":"https://github.com/LLOneBot/LuckyLilliaBot"}} \ No newline at end of file +{"name":"llonebot-dist","version":"7.12.10","type":"module","description":"","main":"llbot.js","author":"linyuchen","repository":{"type":"git","url":"https://github.com/LLOneBot/LuckyLilliaBot"}} \ No newline at end of file diff --git a/src/main/pmhq/mixins/user.ts b/src/main/pmhq/mixins/user.ts index 7ab39dae..53e0a0c0 100644 --- a/src/main/pmhq/mixins/user.ts +++ b/src/main/pmhq/mixins/user.ts @@ -36,6 +36,7 @@ export function UserMixin PMHQBase>(Base: T) { const numbers = Object.fromEntries(info.body.properties.numberProperties.map(p => [p.key, p.value])) const bytes = Object.fromEntries(info.body.properties.bytesProperties.map(p => [p.key, p.value])) const business = bytes[107] ? Misc.UserInfoBusiness.decode(bytes[107]) : undefined + const vipInfo = business?.body.lists.find((e) => e.type === 1) return { uin: info.body.uin, nick: bytes[20002]?.toString() ?? '', @@ -53,9 +54,9 @@ export function UserMixin PMHQBase>(Base: T) { labels: bytes[104] ? Misc.UserInfoLabel.decode(bytes[104]).labels.map(e => e.content) : [], school: bytes[20021]?.toString() ?? '', remark: bytes[103]?.toString() ?? '', - isVip: !!business?.body.lists[0], - isYearsVip: !!business?.body.lists[0]?.isYear, - vipLevel: business?.body.lists[0]?.level ?? 0 + isVip: !!vipInfo, + isYearsVip: !!vipInfo?.isYear, + vipLevel: vipInfo?.level ?? 0 } } diff --git a/src/milky/transform/message/outgoing.ts b/src/milky/transform/message/outgoing.ts index fc6a8152..8d97df9c 100644 --- a/src/milky/transform/message/outgoing.ts +++ b/src/milky/transform/message/outgoing.ts @@ -30,7 +30,8 @@ export async function transformOutgoingMessage( } else if (segment.type === 'mention' && isGroup) { const memberUin = segment.data.user_id.toString() const memberUid = await ctx.ntUserApi.getUidByUin(memberUin, peerUid) - elements.push(SendElement.at(memberUin, memberUid, AtType.One, '')) + const info = await ctx.ntGroupApi.getGroupMember(peerUid, memberUid) + elements.push(SendElement.at(memberUin, memberUid, AtType.One, `@${info.cardName || info.nick}`)) } else if (segment.type === 'mention_all' && isGroup) { elements.push(SendElement.at('', '', AtType.All, '@全体成员')) } else if (segment.type === 'face') { diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index 9e84df4a..24dd8ded 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -290,7 +290,7 @@ export class NTQQMsgApi extends Service { chatInfo: peer, filterMsgType: [], filterSendersUid, - filterMsgToTime: filterMsgTime, + filterMsgToTime: String(filterMsgTime + 1), // 获取到的消息时间可能比 replyMsgTime 多一毫秒 filterMsgFromTime: filterMsgTime, isReverseOrder: true, isIncludeCurrent: true, diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index 8207ddcf..58aabdf2 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -73,7 +73,7 @@ export class NTQQUserApi extends Service { return (await this.ctx.pmhq.invoke('nodeIKernelUixConvertService/getUin', [[uid]])).uinInfo.get(uid) }, async () => { - return (await this.fetchUserDetailInfo(uid)).detail.get(uid)?.uin + return (await this.getUserSimpleInfo(uid)).uin }, ] @@ -91,7 +91,7 @@ export class NTQQUserApi extends Service { return '' } - // 这个会从服务器拉取,比较可靠 + /** 始终会从服务器拉取 */ async fetchUserDetailInfo(uid: string) { return await this.ctx.pmhq.invoke( 'nodeIKernelProfileService/fetchUserDetailInfo', @@ -119,6 +119,7 @@ export class NTQQUserApi extends Service { return result } + /** 无缓存时会从服务器拉取 */ async getUserSimpleInfo(uid: string, force = true) { const data = await this.ctx.pmhq.invoke>( 'nodeIKernelProfileService/getUserSimpleInfo', @@ -134,6 +135,7 @@ export class NTQQUserApi extends Service { return data.get(uid)! } + /** 无缓存时会获取不到用户信息 */ async getCoreAndBaseInfo(uids: string[]) { return await this.ctx.pmhq.invoke( 'nodeIKernelProfileService/getCoreAndBaseInfo', diff --git a/src/ntqqapi/entities.ts b/src/ntqqapi/entities.ts index f807b565..6a1240cd 100644 --- a/src/ntqqapi/entities.ts +++ b/src/ntqqapi/entities.ts @@ -1,4 +1,3 @@ -import ffmpeg from 'fluent-ffmpeg' import faceConfig from './helper/face_config.json' import pathLib from 'node:path' import { diff --git a/src/onebot11/helper/createMessage.ts b/src/onebot11/helper/createMessage.ts index 5ed94361..d17366ba 100644 --- a/src/onebot11/helper/createMessage.ts +++ b/src/onebot11/helper/createMessage.ts @@ -65,9 +65,12 @@ export async function createSendElements( } else if (peer.chatType === ChatType.Group) { const uid = await ctx.ntUserApi.getUidByUin(atQQ, peer.peerUid) - let display = '' + let display if (segment.data.name) { display = `@${segment.data.name}` + } else { + const info = await ctx.ntGroupApi.getGroupMember(peer.peerUid, uid) + display = `@${info.cardName || info.nick}` } sendElements.push(SendElement.at(atQQ, uid, AtType.One, display)) } diff --git a/src/satori/message.ts b/src/satori/message.ts index 200abfd1..30f9d314 100644 --- a/src/satori/message.ts +++ b/src/satori/message.ts @@ -383,7 +383,13 @@ export class MessageEncoder { this.elements.push(SendElement.at('', '', NT.AtType.All, '@全体成员')) } else { const uid = await this.ctx.ntUserApi.getUidByUin(attrs.id, this.peer.peerUid) - const display = attrs.name ? '@' + attrs.name : '' + let display + if (attrs.name) { + display = `@${attrs.name}` + } else { + const info = await this.ctx.ntGroupApi.getGroupMember(this.peer.peerUid, uid) + display = `@${info.cardName || info.nick}` + } this.elements.push(SendElement.at(attrs.id, uid, NT.AtType.One, display)) } } else if (type === 'a') { diff --git a/src/version.ts b/src/version.ts index 8645968f..1ee4a885 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '7.12.9' +export const version = '7.12.10' diff --git a/src/webui/FE/App.tsx b/src/webui/FE/App.tsx index a11e03d7..4e1dfff2 100644 --- a/src/webui/FE/App.tsx +++ b/src/webui/FE/App.tsx @@ -16,6 +16,7 @@ import { import { WebQQPage, WebQQFullscreen } from './components/WebQQ'; import { Config, ResConfig, EmailConfig } from './types'; import { apiFetch, setPasswordPromptHandler } from './utils/api'; +import { deleteCookie } from './utils/cookie'; import { Save, Loader2, Eye, EyeOff, Plus, Trash2, Menu, Cpu, Milk, ExternalLink } from 'lucide-react'; import { defaultConfig } from '../../main/config/defaultConfig' import { version } from '../../version' @@ -936,6 +937,10 @@ function App() { setShowSettingsDialog(false)} + onLogout={() => { + deleteCookie('webui_token') + window.location.reload() + }} /> ); diff --git a/src/webui/FE/components/common/SettingsDialog.tsx b/src/webui/FE/components/common/SettingsDialog.tsx index bf5b4aba..e92aac68 100644 --- a/src/webui/FE/components/common/SettingsDialog.tsx +++ b/src/webui/FE/components/common/SettingsDialog.tsx @@ -1,16 +1,18 @@ import React from 'react' -import { X, Sun, Moon, Monitor, Eye, EyeOff } from 'lucide-react' +import { X, Sun, Moon, Monitor, Eye, EyeOff, LogOut } from 'lucide-react' import { useThemeStore } from '../../stores/themeStore' import { useSettingsStore } from '../../stores/settingsStore' interface SettingsDialogProps { visible: boolean onClose: () => void + onLogout?: () => void } -const SettingsDialog: React.FC = ({ visible, onClose }) => { +const SettingsDialog: React.FC = ({ visible, onClose, onLogout }) => { const { mode, setMode } = useThemeStore() const { autoHideSidebarInWebQQ, setAutoHideSidebarInWebQQ, showWebQQFullscreenButton, setShowWebQQFullscreenButton } = useSettingsStore() + const [showLogoutConfirm, setShowLogoutConfirm] = React.useState(false) if (!visible) return null @@ -111,7 +113,14 @@ const SettingsDialog: React.FC = ({ visible, onClose }) => {/* Footer */} -
+
+
+ + {/* Logout Confirm Dialog */} + {showLogoutConfirm && ( +
+
setShowLogoutConfirm(false)} /> +
+

确认退出

+

+ 确定要退出 WebUI 吗?这不会退出 QQ,仅清除 WebUI 登录状态。 +

+
+ + +
+
+
+ )}
) } diff --git a/src/webui/FE/vite.config.ts b/src/webui/FE/vite.config.ts index 37a939a9..3d86f674 100644 --- a/src/webui/FE/vite.config.ts +++ b/src/webui/FE/vite.config.ts @@ -15,7 +15,8 @@ export default defineConfig({ }, }, server: { - port: 5173, + host: '127.0.0.1', + port: 15173, open: true, proxy: { '/api': {