Repository: NapNeko/NapCatQQ-Desktop
Pull request #33: 发布1.5.0版本
Author: login: MliKiowa
Created: 2025-05-02T10:17:21Z | Merged: 2025-05-02T10:23:25Z

好的，这是将 pull request 摘要翻译成中文的结果：

## Sourcery 总结

发布 NapCatQQ 桌面版 1.5.0，引入了对应用程序功能和用户体验的重大改进和调整。

新功能：
- 添加了引导窗口
- 重新引入了二维码弹出窗口
- 重新引入了 DPI 设置
- 添加了关闭按钮行为设置

Bug 修复：
- 修复了未安装 NapCatQQ 时远程更新日志检索问题
- 修复了不一致的 QQ 版本格式

增强功能：
- 调整了组件页面左侧图标高度
- 更新了 QQ 介绍内容
- 将托盘图标选项更改为中文
- 将 NapCat 重命名为 NapCatQQ

杂项：
- 删除了托盘图标“添加 Bot”按钮
- 删除了托盘图标“Bot 列表”按钮
- 将旧的更新日志移动到单独的文件

<details>
<summary>Original summary in English</summary>

## Sourcery 总结

发布 NapCatQQ 桌面版 1.5.0，引入了应用程序功能和用户体验的重大改进

新功能：
- 添加了引导窗口
- 重新引入二维码弹出窗口
- 重新引入 DPI 设置
- 添加了关闭按钮行为设置

Bug 修复：
- 修复了未安装 NapCatQQ 时远程更新日志检索的问题
- 修复了不一致的 QQ 版本格式

增强功能：
- 调整了组件页面左侧边栏图标高度
- 更新了 QQ 介绍内容
- 将托盘图标选项更改为中文
- 将 NapCat 重命名为 NapCatQQ

杂项：
- 删除了托盘图标“添加 Bot”按钮
- 删除了托盘图标“Bot 列表”按钮
- 将旧的更新日志移动到单独的文件

<details>
<summary>Original summary in English</summary>

## Summary by Sourcery

Release NapCatQQ Desktop version 1.5.0, introducing significant improvements to application functionality and user experience

New Features:
- Added guide window
- Reintroduced QR code popup
- Reintroduced DPI settings
- Added close button behavior settings

Bug Fixes:
- Fixed remote update log retrieval when NapCatQQ is not installed
- Fixed inconsistent QQ version formatting

Enhancements:
- Adjusted component page left sidebar icon height
- Updated QQ introduction content
- Changed tray icon options to Chinese
- Renamed NapCat to NapCatQQ

Chores:
- Removed tray icon 'Add Bot' button
- Removed tray icon 'Bot List' button
- Moved old changelog to a separate file

</details>

</details>


Diff:
diff --git a/main.py b/main.py
index 57117c43..c3592e11 100644
--- a/main.py
+++ b/main.py
@@ -1,29 +1,42 @@
 # -*- coding: utf-8 -*-
 # 标准库导入
+import os
 import sys
-from pathlib import Path
 
+from PySide6.QtCore import Qt
 from PySide6.QtWidgets import QApplication
 
 # 项目内模块导入
+from src.Core.Config import cfg
 from src.Core.Utils.mutex import SingleInstanceApplication
+from src.Core.Utils.PathFunc import PathFunc
 
 if __name__ == "__main__":
     # 实现单实例应用程序检查
     if SingleInstanceApplication().is_running():
         sys.exit()
 
+    # 执行路径验证
+    PathFunc().path_validator()
+
+    # 设置DPI缩放
+    if cfg.get(cfg.dpiScale) == "Auto":
+        QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
+    else:
+        os.environ["QT_SCALE_FACTOR"] = str(cfg.get(cfg.dpiScale))
+
     app = QApplication(sys.argv)
 
-    # if (Path.cwd() / "config" / "config.json").exists():
-    #     # 项目内模块导入
-    #     from src.Ui.MainWindow import MainWindow
+    if cfg.get(cfg.MainWindow):
+        # 项目内模块导入
+        from src.Ui.MainWindow import MainWindow
 
-    #     MainWindow().initialize()
-    # else:
-    # 项目内模块导入
-    from src.Ui.GuideWindow.guide_window import GuideWindow
+        MainWindow().initialize()
+    else:
+        # 项目内模块导入
+        from src.Ui.GuideWindow.guide_window import GuideWindow
 
-    GuideWindow().initialize()
+        GuideWindow().initialize()
+        cfg.set(cfg.MainWindow, True)
 
     sys.exit(app.exec())
diff --git a/pyproject.toml b/pyproject.toml
index b86a17c8..f7641d4e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,8 +11,6 @@ dependencies = [
     "psutil>=6.0.0",
     "markdown>=3.7",
     "nuitka>=2.4.8",
-    "frida>=16.5.6",
-    "websockets>=13.1",
     "Fluent-Widgets-QIAO>=0.4.0",
 ]
 requires-python = "==3.12.*"
diff --git a/requirements.txt b/requirements.txt
index 8d17ccae..437c7f1d 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/src/Core/Config/__init__.py b/src/Core/Config/__init__.py
index e12cd0ec..c60d6b45 100644
--- a/src/Core/Config/__init__.py
+++ b/src/Core/Config/__init__.py
@@ -3,6 +3,7 @@
 import json
 import time
 import platform
+from pathlib import Path
 
 # 第三方库导入
 from qfluentwidgets import RangeValidator, RangeConfigItem, TabCloseButtonDisplayMode, qconfig
@@ -21,7 +22,7 @@
 from PySide6.QtCore import Signal, QLocale
 
 # 项目内模块导入
-from src.Core.Config.enum import Language
+from src.Core.Config.enum import Language, CloseActionEnum
 from src.Core.Utils.PathFunc import PathFunc
 
 
@@ -46,6 +47,7 @@ class Config(QConfig):
     StartTime = ConfigItem(group="Info", name="StartTime", default="")
     SystemType = ConfigItem(group="Info", name="SystemType", default="")
     PlatformType = ConfigItem(group="Info", name="PlatformType", default="")
+    MainWindow = ConfigItem(group="Info", name="MainWindow", default=False, validator=BoolValidator())
 
     # 路径项
     # 注意: default 为空字符串则默认以程序根目录为路径
@@ -69,6 +71,20 @@ class Config(QConfig):
         serializer=EnumSerializer(Theme),
     )
     themeColor = ColorConfigItem(group="Personalize", name="ThemeColor", default="#009faa")
+    dpiScale = OptionsConfigItem(
+        group="Personalized",
+        name="DpiScale",
+        default="Auto",
+        validator=OptionsValidator([1, 1.25, 1.5, 1.75, 2, "Auto"]),
+        restart=True,
+    )
+    closeBtnAction = OptionsConfigItem(
+        group="Personalized",
+        name="CloseBtnAction",
+        default=CloseActionEnum.CLOSE,
+        validator=OptionsValidator(list(CloseActionEnum)),
+        serializer=EnumSerializer(CloseActionEnum),
+    )
 
     windowOpacity = RangeConfigItem(
         group="Personalize", name="WindowOpacity", default=100, validator=RangeValidator(10, 100)
diff --git a/src/Core/Utils/GetVersion.py b/src/Core/Utils/GetVersion.py
index aaf6fd2f..a30737ad 100644
--- a/src/Core/Utils/GetVersion.py
+++ b/src/Core/Utils/GetVersion.py
@@ -120,8 +120,12 @@ def getNapCatDesktop(self) -> dict | None:
         """
         ## 获取 NCD 相关内容
         """
-        if (response := self.request(Urls.NCD_REPO_API.value, "NapCat Desktop")) is not None:
-            return {"version": response["tag_name"], "update_log": response["body"]}
+        try:
+            if (response := self.request(Urls.NCD_REPO_API.value, "NapCat Desktop")) is not None:
+                return {"version": response["tag_name"], "update_log": response["body"]}
+        except (httpx.RequestError, PermissionError, Exception) as e:
+            logger.error(f"获取 NCD 版本信息失败: {e}")
+            return None
 
     @staticmethod
     def request(url: QUrl, name: str) -> dict | None:
@@ -183,7 +187,7 @@ def getQQ() -> None:
 
             with open(str(qq_path / "versions" / "config.json"), "r", encoding="utf-8") as file:
                 # 读取 config.json 文件获取版本信息
-                return json.load(file)["curVersion"]
+                return json.load(file)["curVersion"].replace("-", ".")
         except FileNotFoundError:
             # 文件不存在则返回 None
             logger.error("获取 QQ 版本信息失败: 文件不存在")
diff --git a/src/Ui/AddPage/Connect.py b/src/Ui/AddPage/Connect.py
index f2bc3ae3..691ed702 100644
--- a/src/Ui/AddPage/Connect.py
+++ b/src/Ui/AddPage/Connect.py
@@ -160,7 +160,6 @@ def removeCard(self, card: ConfigCardBase) -> None:
         card.close()
 
     def getValue(self) -> dict:
-        print([_.getValue() for _ in self.cards if isinstance(_, HttpServerConfigCard)])
         return {
             "httpServers": [_.getValue() for _ in self.cards if isinstance(_, HttpServerConfigCard)],
             "httpSseServers": [_.getValue() for _ in self.cards if isinstance(_, HttpSSEConfigCard)],
diff --git a/src/Ui/BotListPage/BotWidget/__init__.py b/src/Ui/BotListPage/BotWidget/__init__.py
index 4ecf70f8..ed70692d 100644
--- a/src/Ui/BotListPage/BotWidget/__init__.py
+++ b/src/Ui/BotListPage/BotWidget/__init__.py
@@ -24,7 +24,7 @@
 from src.Core.Utils.email import Email
 from src.Ui.common.info_bar import info_bar, error_bar, success_bar, warning_bar
 from src.Core.Utils.RunNapCat import create_napcat_process
-from src.Ui.common.message_box import AskBox
+from src.Ui.common.message_box import AskBox, ImageBox
 from src.Core.Config.ConfigModel import Config
 from src.Core.Config.OperateConfig import delete_config, update_config
 from src.Ui.BotListPage.BotWidget.BotSetupPage import BotSetupPage
@@ -209,6 +209,7 @@ def _napcatHandleStdout(self) -> None:
         # 遍历所有匹配项并移除
         while (matches := QRegularExpression(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").globalMatch(data)).hasNext():
             data = data.replace(matches.next().captured(0), "")
+            data = data.replace("\n\n", "\n")
 
         # 获取 CodeEditor 的 cursor 并移动插入输出内容
         cursor = self.botLogPage.textCursor()
@@ -217,6 +218,7 @@ def _napcatHandleStdout(self) -> None:
         self.botLogPage.setTextCursor(cursor)
 
         # 分发给其他处理函数
+        self._getQRCode(data)
         self._getBofOffline(data)
 
     @Slot()
@@ -229,6 +231,24 @@ def _napcatProcessFinishedSlot(self, exit_code, exit_status) -> None:
         cursor.insertText(f"进程结束，退出码为 {exit_code}，状态为 {exit_status}")
         self.botLogPage.setTextCursor(cursor)
 
+    def _getQRCode(self, data: str) -> None:
+        """
+        ## 判断是否需要显示二维码
+        """
+        if "二维码已保存到" in data:
+            # 项目内模块导入
+            from src.Ui.MainWindow import MainWindow
+
+            pattern = rf"[a-zA-Z]:\\(?:[^\\\s]+\\)*[^\\\s]+"
+            if not (match := QRegularExpression(pattern).match(data)).hasMatch():
+                # 如果匹配不成功, 则退出
+                return
+
+            box = ImageBox(self.tr("请扫码登陆"), match.captured(0), MainWindow())
+            box.cancelButton.hide()
+            box.imageLabel.scaledToHeight(256)
+            box.exec()
+
     def _getBofOffline(self, data: str):
         """
         ## 检测用户状态
@@ -291,6 +311,7 @@ def _deleteButtonSlot(self) -> None:
                 # 处理 TabBar
                 # 项目内模块导入
                 from src.Ui.MainWindow.Window import MainWindow
+
                 MainWindow().title_bar.tabBar.removeTabByKey(f"{self.config.bot.QQID}")
             else:
                 error_bar(self.tr("删除配置文件时引发错误, 请前往 设置 > log 查看错误原因"))
@@ -345,7 +366,7 @@ def _pivotSlot(self, index: int) -> None:
                 "runButton": "hide" if self.isRun else "show",
                 "stopButton": "show" if self.isRun else "hide",
                 "rebootButton": "show" if self.isRun else "hide",
-            }
+            },
         }
 
         # 根据 widget.objectName() 执行相应操作
diff --git a/src/Ui/MainWindow/SystemTryIcon.py b/src/Ui/MainWindow/SystemTryIcon.py
index 7d53d5f1..0fe82c3b 100644
--- a/src/Ui/MainWindow/SystemTryIcon.py
+++ b/src/Ui/MainWindow/SystemTryIcon.py
@@ -34,32 +34,19 @@ def __init__(self, parent: "MainWindow" = None):
 
         self.menu.addWidget(NCDCard(), selectable=False)
         self.menu.addSeparator()
-        self.menu.addAction(Action(FIF.ADD, self.tr("Add Bot"), triggered=self.addBotSlot))
-        self.menu.addAction(Action(FIF.MENU, self.tr("Bot List"), triggered=self.botListSlot))
+        self.menu.addAction(Action(FIF.PLAY, self.tr("运行所有机器人"), triggered=self.runAllBotSlot))
+        self.menu.addAction(Action(FIF.PAUSE, self.tr("停止所有机器人"), triggered=self.stopAllBotBotSlot))
         self.menu.addSeparator()
-        self.menu.addAction(Action(FIF.PLAY, self.tr("Run All Bots"), triggered=self.runAllBotSlot))
-        self.menu.addAction(Action(FIF.PAUSE, self.tr("Stop All Bots"), triggered=self.stopAllBotBotSlot))
-        self.menu.addSeparator()
-        self.menu.addAction(Action(FIF.CLOSE, self.tr("Close"), triggered=self.closeSlot))
+        self.menu.addAction(Action(FIF.CLOSE, self.tr("关闭程序"), triggered=self.closeSlot))
 
         # 设置控件
         self.setIcon(parent.windowIcon())
-        self.setToolTip("NapCat Desktop")
+        self.setToolTip("NapCatQQ Desktop")
         self.setContextMenu(self.menu)
 
         # 链接信号
         self.activated.connect(self.clickSlot)
 
-    @Slot()
-    def addBotSlot(self) -> None:
-        self.checkShow()
-        self.parent().add_widget_button.click()
-
-    @Slot()
-    def botListSlot(self) -> None:
-        self.checkShow()
-        self.parent().bot_list_widget_button.click()
-
     @Slot()
     def runAllBotSlot(self) -> None:
         self.checkShow()
@@ -105,11 +92,11 @@ class NCDCard(QWidget):
     def __init__(self):
         super().__init__()
         self.avatar = AvatarWidget(":Global/logo.png", self)
-        self.nameLabel = BodyLabel("NapCat Desktop", self)
+        self.nameLabel = BodyLabel("NapCatQQ Desktop", self)
         self.licenseLabel = CaptionLabel("License: GPL-v3", self)
         self.licenseLabel.setTextColor(QColor(96, 96, 96), QColor(206, 206, 206))
 
-        self.setFixedSize(200, 60)
+        self.setFixedSize(218, 60)
         self.avatar.setRadius(24)
         self.avatar.move(2, 6)
         self.nameLabel.move(64, 13)
diff --git a/src/Ui/MainWindow/TitleBar.py b/src/Ui/MainWindow/TitleBar.py
index 0cf9f349..f88b6ade 100644
--- a/src/Ui/MainWindow/TitleBar.py
+++ b/src/Ui/MainWindow/TitleBar.py
@@ -3,7 +3,7 @@
 from typing import TYPE_CHECKING
 
 # 第三方库导入
-from qfluentwidgets import TabBar, TabCloseButtonDisplayMode
+from qfluentwidgets import TabBar
 from qfluentwidgets.common import Theme
 from qfluentwidgets.window import MSFluentTitleBar
 from qframelesswindow.titlebar import CloseButton, MaximizeButton, MinimizeButton
@@ -14,6 +14,7 @@
 # 项目内模块导入
 from src.Ui.Icon import NapCatDesktopIcon
 from src.Core.Config import cfg
+from src.Core.Config.enum import CloseActionEnum
 
 if TYPE_CHECKING:
     # 项目内模块导入
@@ -52,7 +53,7 @@ def set_buttons(self) -> None:
         button_info = {
             "minBtn": (MinBtn, self.window().showMinimized),
             "maxBtn": (MaxBtn, self.__toggle_maximization),
-            "closeBtn": (CloseBtn, self.window().close),
+            "closeBtn": (CloseBtn, self.__close),
         }
 
         for btn_name, (btn_class, slot) in button_info.items():
@@ -80,6 +81,13 @@ def __toggle_maximization(self) -> None:
         else:
             self.window().showMaximized()
 
+    def __close(self) -> None:
+        """关闭窗口"""
+        if cfg.get(cfg.closeBtnAction) == CloseActionEnum.CLOSE:
+            self.window().close()
+        else:
+            self.window().hide()
+
     def canDrag(self, pos: QPoint) -> bool:
         if not super().canDrag(pos):
             return False
diff --git a/src/Ui/MainWindow/Window.py b/src/Ui/MainWindow/Window.py
index 0bdab5a9..00bab97e 100644
--- a/src/Ui/MainWindow/Window.py
+++ b/src/Ui/MainWindow/Window.py
@@ -39,8 +39,6 @@ def __init__(self) -> None:
 
     def initialize(self) -> None:
         """初始化"""
-        # 执行路径验证
-        PathFunc().path_validator()
         # 调用方法
         self.setWindow()
         self.setItem()
@@ -116,7 +114,3 @@ def setTrayIcon(self):
         """
         self.trayIcon = SystemTrayIcon(self)
         self.trayIcon.show()
-
-    def closeEvent(self, event):
-        self.hide()
-        event.ignore()
diff --git a/src/Ui/SetupPage/SetupScrollArea.py b/src/Ui/SetupPage/SetupScrollArea.py
index d693ebec..4626bc6f 100644
--- a/src/Ui/SetupPage/SetupScrollArea.py
+++ b/src/Ui/SetupPage/SetupScrollArea.py
@@ -74,9 +74,26 @@ def _createConfigCards(self) -> None:
             icon=FluentIcon.FIT_PAGE,
             title=self.tr("窗口透明度"),
             content=self.tr("设置窗口的透明度"),
+            parent=self.personalGroup,
+        )
+        self.zoomCard = OptionsSettingCard(
+            configItem=cfg.dpiScale,
+            icon=FluentIcon.ZOOM,
+            title=self.tr("界面缩放"),
+            content=self.tr("更改 Widget 和字体的大小"),
+            texts=["100%", "125%", "150%", "175%", "200%", self.tr("跟随系统")],
+            parent=self.personalGroup,
         )
         self.bgSettingCard = BackgroundSettingCard(self)
         self.titleTabBarSettingCard = TitleTabBarSettingCard(self)
+        self.closeBtnCard = OptionsSettingCard(
+            configItem=cfg.closeBtnAction,
+            icon=FluentIcon.CLOSE,
+            title=self.tr("关闭按钮"),
+            content=self.tr("选择点击关闭按钮时的行为"),
+            texts=[self.tr("关闭程序"), self.tr("最小化隐藏到托盘")],
+            parent=self.personalGroup,
+        )
 
         # 创建组 - 事件
         self.eventGroup = SettingCardGroup(title=self.tr("事件"), parent=self.view)
@@ -92,9 +109,11 @@ def _setLayout(self) -> None:
         self.personalGroup.addSettingCard(self.themeCard)
         self.personalGroup.addSettingCard(self.themeColorCard)
         # self.personalGroup.addSettingCard(self.languageCard)
+        self.personalGroup.addSettingCard(self.zoomCard)
         self.personalGroup.addSettingCard(self.bgSettingCard)
         self.personalGroup.addSettingCard(self.windowOpacityCard)
         self.personalGroup.addSettingCard(self.titleTabBarSettingCard)
+        self.personalGroup.addSettingCard(self.closeBtnCard)
 
         self.eventGroup.addSettingCard(self.botOfflineEventCard)
 
diff --git a/src/Ui/UnitPage/Base.py b/src/Ui/UnitPage/Base.py
index 3d8ea83b..d5246f36 100644
--- a/src/Ui/UnitPage/Base.py
+++ b/src/Ui/UnitPage/Base.py
@@ -103,7 +103,7 @@ def __init__(self, parent) -> None:
         self.setMinimumWidth(230)
 
         self.iconLabel.setBorderRadius(8, 8, 8, 8)
-        self.iconLabel.scaledToWidth(100)
+        self.iconLabel.scaledToHeight(128)
 
         self.installButton.setMinimumWidth(140)
         self.updateButton.setMinimumWidth(140)
diff --git a/src/Ui/UnitPage/NCDPage.py b/src/Ui/UnitPage/NCDPage.py
index 7654bb63..2975bfd7 100644
--- a/src/Ui/UnitPage/NCDPage.py
+++ b/src/Ui/UnitPage/NCDPage.py
@@ -19,13 +19,13 @@
 
 class NCDPage(PageBase):
     """
-    ## NapCat Desktop 更新页面
+    ## NapCatQQ Desktop 更新页面
     """
 
     def __init__(self, parent) -> None:
         super().__init__(parent=parent)
         self.setObjectName("UnitNCDPage")
-        self.appCard.setName("NapCat Desktop")
+        self.appCard.setName("NapCatQQ Desktop")
         self.appCard.setHyperLabelName(self.tr("仓库地址"))
         self.appCard.setHyperLabelUrl(Urls.NCD_REPO.value)
 
diff --git a/src/Ui/UnitPage/QQPage.py b/src/Ui/UnitPage/QQPage.py
index a1f2c8ee..5fc67f5e 100644
--- a/src/Ui/UnitPage/QQPage.py
+++ b/src/Ui/UnitPage/QQPage.py
@@ -17,11 +17,32 @@
 from src.Core.NetworkFunc.Downloader import QQDownloader
 
 DESCRIPTION_TEXT = """
-## 为什么要 QQ?
+## **NapCatQQ与NTQQ的奇幻物语**  
 
-NapCat 通过魔法的手段获得了 QQ 的发送消息、接收消息等接口，为了方便使用，猫猫框架将通过一种名为 [OneBot](https://11.onebot.dev) 的约定将你的 HTTP / WebSocket 请求按照规范读取，再去调用猫猫框架所获得的QQ发送接口之类的接口。
+在科技与魔法交织的NapCatQQ Robot世界，存在着名为**NTQQ**的混沌之力。这股足以扭曲现实的力量，曾令无数试图驾驭它的勇者湮灭在历史长河中——直到那只被遗弃的橘色猫娘出现。  
 
-所以 NapCat 只是让 QQ 以一种更加优雅的方式运行在你的服务器上，而不是 QQ 本身, 但是本质上还是离不开 QQ 的。
+蜷缩在废墟里的NapCatQQ发现JavaScript代码块时，还以为找到了新玩具。  
+**“喵？硬硬的……不能吃呢……”**  
+她无意识地用肉垫拍打键盘，却意外打通了与NTQQ的量子纠缠。从此，这只走路都会左脚绊右脚的猫娘，获得了通过代码改变现实的能力——尽管她自己完全不懂原理。  
+
+**“天空变成棉花糖了喵？月亮被吃掉了喵？”**  
+随着NapCatQQ的胡乱敲击，世界开始呈现荒诞的叠加态。七彩暴雨淹没城市，时停的蝴蝶堵塞交通……人们终于意识到，最可怕的不是力量本身，而是力量掌握在一只根本不懂自己在做什么的萌物手里。  
+
+### **转机：秩序降临**  
+直到那位戴着圆框眼镜的架构师出现。他带来的不是宝剑，而是一卷泛着蓝光的**OneBot规范文档**。  
+**“所有NTQQ调用必须经过WS/HTTP协议封装。”**  
+这条铁律为混沌设立了边界。现在，NapCatQQ每次想改变现实，都得乖乖把意图写成标准化API请求。  
+
+**“POST……喵？200状态码……是成功的意思喵？”**  
+猫娘歪着头戳弄终端，她依然会把响应体当成小鱼干图案，但至少世界不再因她的一个喷嚏而维度折叠。那些曾被彩虹云朵和会说话的松鼠搞怕了的居民们，也逐渐学会欣赏这份笨拙的可爱——毕竟当毛茸茸的尾巴扫过键盘时，连报错信息都会变成爱心形状。  
+
+### **为什么是QQ？**  
+NapCatQQ并非QQ本身，而是通过魔法将QQ的接口**“驯化”**成更柔软的模样。就像猫咪把死老鼠叼到主人枕边是爱的表现，她只是用**OneBot规范**重新封装QQ协议，让冷冰冰的通讯代码多些温暖绒毛的触感。  
+
+**“记住，她不是QQ，只是让QQ变得可以蹭你脸颊的魔法。”**  
+
+现在，这个世界依然充满意外——但至少，是可控的意外。而NapCatQQ，依然是那只会在执行完API调用后，眨巴着眼睛问：  
+**“这次……没搞砸喵？”**
 """
 
 
diff --git a/src/Ui/common/message_box.py b/src/Ui/common/message_box.py
index ffc0ba01..94190eab 100644
--- a/src/Ui/common/message_box.py
+++ b/src/Ui/common/message_box.py
@@ -100,7 +100,6 @@ def __init__(self, title: str, image: str | QImage | QPixmap, parent: "MainWindo
         # 设置属性
         self.imageLabel.scaledToWidth(120)
         self.widget.setMinimumSize(420, 230)
-        self.yesButton.setText(self.tr("刷新"))
 
         # 添加到布局
         self.viewLayout.addWidget(self.titleLabel)


Reviews:
user: login: sourcery-ai[bot]
body: 嘿 @MliKiowa - 我已经审查了你的更改 - 这里有一些反馈：

- 考虑新的 QQ 页面描述的叙述风格是否与应用程序的整体基调一致。
- 验证删除 `frida` 和 `websockets` 依赖项是否会对功能产生负面影响。
- 确保从“NapCat”到“NapCatQQ”的重命名一致地应用于所有相关的面向用户的文本。

<details>
<summary>以下是我在审查期间查看的内容</summary>

- 🟡 **一般问题**：发现 2 个问题
- 🟢 **安全性**：一切看起来都不错
- 🟢 **测试**：一切看起来都不错
- 🟡 **复杂性**：发现 1 个问题
- 🟢 **文档**：一切看起来都不错
</details>

***

<details>
<summary>Sourcery 对开源是免费的 - 如果你喜欢我们的评论，请考虑分享它们✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
帮助我更有用！请点击每个评论上的👍或👎，我将使用反馈来改进你的评论。
</sub>

<details>
<summary>Original comment in English</summary>

Hey @MliKiowa - I've reviewed your changes - here's some feedback:

- Consider if the narrative style of the new QQ page description aligns with the application's overall tone.
- Verify that removing the `frida` and `websockets` dependencies doesn't negatively impact functionality.
- Ensure the renaming from 'NapCat' to 'NapCatQQ' is consistently applied across all relevant user-facing text.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 2 issues found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>

</details>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T10:25:24Z

Review comments:
diff_hunk: @@ -34,32 +34,19 @@ def __init__(self, parent: "MainWindow" = None):
 
         self.menu.addWidget(NCDCard(), selectable=False)
         self.menu.addSeparator()
-        self.menu.addAction(Action(FIF.ADD, self.tr("Add Bot"), triggered=self.addBotSlot))
-        self.menu.addAction(Action(FIF.MENU, self.tr("Bot List"), triggered=self.botListSlot))
+        self.menu.addAction(Action(FIF.PLAY, self.tr("运行所有机器人"), triggered=self.runAllBotSlot))
+        self.menu.addAction(Action(FIF.PAUSE, self.tr("停止所有机器人"), triggered=self.stopAllBotBotSlot))
path: src/Ui/MainWindow/SystemTryIcon.py
user: login: sourcery-ai[bot]
body: **小问题（拼写错误）：** 检查 stopAllBotBotSlot 的命名。

方法名中多余的“Bot”是否有意为之？如果不是，请重命名以使其更清晰。

建议的实现方式：

```python
        self.menu.addAction(Action(FIF.PAUSE, self.tr("停止所有机器人"), triggered=self.stopAllBotSlot))

```

确保 stopAllBotBotSlot 的方法定义也重命名为 stopAllBotSlot。此外，如果文件或项目的其他位置存在对旧方法的任何内部引用或调用，请更新它们。

<details>
<summary>Original comment in English</summary>

**nitpick (typo):** Check the naming of stopAllBotBotSlot.

Is the extra “Bot” in the method name intentional? If not, please rename it for clarity.

Suggested implementation:

```python
        self.menu.addAction(Action(FIF.PAUSE, self.tr("停止所有机器人"), triggered=self.stopAllBotSlot))

```

Ensure that the method definition for stopAllBotBotSlot is also renamed to stopAllBotSlot. Additionally, update any internal references or calls to the old method name if they exist elsewhere in the file or project.

</details>
created_at: 2025-05-02T10:25:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -209,6 +209,7 @@ def _napcatHandleStdout(self) -> None:
         # 遍历所有匹配项并移除
         while (matches := QRegularExpression(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").globalMatch(data)).hasNext():
             data = data.replace(matches.next().captured(0), "")
+            data = data.replace("\n\n", "\n")
path: src/Ui/BotListPage/BotWidget/__init__.py
user: login: sourcery-ai[bot]
body: **建议（性能）：** 考虑换行符替换的位置。

将换行符规范化移到 ANSI 转义循环之外——在删除所有转义序列后应用 data.replace("\n\n","\n") 一次，以避免重复替换。

建议的实现方式：

```python
            # 从循环内部删除换行符规范化以避免重复替换

```

```python
        # 在删除所有转义序列后执行一次换行符规范化
        data = data.replace("\n\n", "\n")
        # 获取 CodeEditor 的 cursor 并移动插入输出内容

```

<details>
<summary>Original comment in English</summary>

**suggestion (performance):** Consider the placement of newline replacement.

Move the newline normalization outside the ANSI‐escape loop—apply data.replace("\n\n","\n") once after removing all escape sequences to avoid repeated replacements.

Suggested implementation:

```python
            # Removed newline normalization from inside the loop to avoid repetitive replacements

```

```python
        # Perform newline normalization once after removing all escape sequences
        data = data.replace("\n\n", "\n")
        # 获取 CodeEditor 的 cursor 并移动插入输出内容

```

</details>
created_at: 2025-05-02T10:25:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -229,6 +231,24 @@ def _napcatProcessFinishedSlot(self, exit_code, exit_status) -> None:
         cursor.insertText(f"进程结束，退出码为 {exit_code}，状态为 {exit_status}")
         self.botLogPage.setTextCursor(cursor)
 
+    def _getQRCode(self, data: str) -> None:
path: src/Ui/BotListPage/BotWidget/__init__.py
user: login: sourcery-ai[bot]
body: **问题（复杂性）：** 考虑将 `_getQRCode` 拆分为 `_extractQRCodePath` 和 `_showQRCodeDialog` 以分离关注点。

```markdown
考虑将 `_getQRCode` 中的职责拆分为两个较小的辅助方法：一个通过正则表达式提取 QR 码路径，另一个处理对话框创建。例如：

```python
def _extractQRCodePath(self, data: str) -> Optional[str]:
    pattern = r"[a-zA-Z]:\\(?:[^\\\s]+\\)*[^\\\s]+"
    match = QRegularExpression(pattern).match(data)
    return match.captured(0) if match.hasMatch() else None

def _showQRCodeDialog(self, path: str) -> None:
    from src.Ui.MainWindow import MainWindow
    box = ImageBox(self.tr("请扫码登陆"), path, MainWindow())
    box.cancelButton.hide()
    box.imageLabel.scaledToHeight(256)
    box.exec()

def _getQRCode(self, data: str) -> None:
    if "二维码已保存到" in data:
        qr_path = self._extractQRCodePath(data)
        if qr_path:
            self._showQRCodeDialog(qr_path)
```

这些更改隔离了正则表达式匹配和对话框管理，减少了嵌套并提高了清晰度，同时保持了所有功能完整。
```

<details>
<summary>Original comment in English</summary>

**issue (complexity):** Consider splitting `_getQRCode` into `_extractQRCodePath` and `_showQRCodeDialog` to separate concerns.

```markdown
Consider splitting the responsibilities in `_getQRCode` into two smaller helper methods: one to extract the QR code path via regex, and another to handle dialog creation. For example:

```python
def _extractQRCodePath(self, data: str) -> Optional[str]:
    pattern = r"[a-zA-Z]:\\(?:[^\\\s]+\\)*[^\\\s]+"
    match = QRegularExpression(pattern).match(data)
    return match.captured(0) if match.hasMatch() else None

def _showQRCodeDialog(self, path: str) -> None:
    from src.Ui.MainWindow import MainWindow
    box = ImageBox(self.tr("请扫码登陆"), path, MainWindow())
    box.cancelButton.hide()
    box.imageLabel.scaledToHeight(256)
    box.exec()

def _getQRCode(self, data: str) -> None:
    if "二维码已保存到" in data:
        qr_path = self._extractQRCodePath(data)
        if qr_path:
            self._showQRCodeDialog(qr_path)
```

These changes isolate regex matching and dialog management, reducing nesting and improving clarity while keeping all functionality intact.
```

</details>
created_at: 2025-05-02T10:25:23Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-05-02T10:17:30Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

```
## 审查者指南

此拉取请求通过添加 DPI 缩放和关闭按钮行为的配置选项来实现 1.5.0 版本，并将它们集成到 UI 和应用程序启动中。它引入了首次运行引导窗口，通过 stdout 解析触发的弹出窗口重新实现了 QR 码显示，将品牌更新为“NapCatQQ”，调整了 UI 布局，修复了版本处理中的错误，并删除了未使用的依赖项。

### 文件级别更改

| 变更 | 详情 | 文件 |
| ------ | ------- | ----- |
| 引入了首次运行引导窗口并重构了应用程序启动逻辑。 | <ul><li>添加了基于新的 `MainWindow` 配置标志有条件地显示 `GuideWindow` 的逻辑。</li><li>将路径验证和 DPI 缩放初始化移动到 `main.py`。</li></ul> | `main.py`<br/>`src/Core/Config/__init__.py`<br/>`src/Ui/MainWindow/Window.py` |
| 添加了 DPI 缩放和关闭按钮行为的配置选项。 | <ul><li>添加了 `dpiScale` 配置项和 UI 设置卡。</li><li>添加了 `closeBtnAction` 配置项和 UI 设置卡。</li><li>基于新配置在标题栏中实现了关闭按钮逻辑（关闭或隐藏）。</li><li>删除了之前强制隐藏的 `closeEvent` 覆盖。</li></ul> | `src/Core/Config/__init__.py`<br/>`src/Ui/SetupPage/SetupScrollArea.py`<br/>`src/Ui/MainWindow/TitleBar.py`<br/>`src/Ui/MainWindow/Window.py`<br/>`main.py` |
| 使用通过 stdout 解析触发的弹出窗口重新实现了 QR 码显示。 | <ul><li>在 `_napcatHandleStdout` 中添加了 stdout 解析以检测 QR 码文件路径。</li><li>在模态 `ImageBox` 中显示了检测到的 QR 码图像。</li></ul> | `src/Ui/BotListPage/BotWidget/__init__.py`<br/>`src/Ui/common/message_box.py` |
| 更新了 UI 文本，品牌更新为“NapCatQQ”，并调整了布局。 | <ul><li>将“NapCat Desktop”的实例重命名为“NapCatQQ Desktop”。</li><li>替换了 QQ 介绍文本。</li><li>将托盘图标菜单项文本更新为中文，并删除了“添加 Bot”/“Bot 列表”按钮。</li><li>调整了组件图标高度缩放。</li></ul> | `src/Ui/UnitPage/QQPage.py`<br/>`src/Ui/MainWindow/SystemTryIcon.py`<br/>`src/Ui/UnitPage/NCDPage.py`<br/>`src/Ui/UnitPage/Base.py` |
| 修复了版本处理错误并清理了依赖项。 | <ul><li>为远程更新日志获取添加了错误处理。</li><li>标准化了 QQ 版本字符串格式（用点替换了连字符）。</li><li>删除了未使用的 `frida` 和 `websockets` 依赖项。</li></ul> | `src/Core/Utils/GetVersion.py`<br/>`pyproject.toml` |

---

<details>
<summary>提示和命令</summary>

#### 与 Sourcery 互动

- **触发新的审查：** 在拉取请求上评论 `@sourcery-ai review`。
- **继续讨论：** 直接回复 Sourcery 的审查评论。
- **从审查评论生成 GitHub issue：** 通过回复审查评论，要求 Sourcery 从审查评论创建一个 issue。您也可以回复审查评论并使用 `@sourcery-ai issue` 从中创建一个 issue。
- **生成拉取请求标题：** 在拉取请求标题中的任何位置写入 `@sourcery-ai` 以随时生成标题。您也可以在拉取请求上评论 `@sourcery-ai title` 以随时（重新）生成标题。
- **生成拉取请求摘要：** 在拉取请求正文中的任何位置写入 `@sourcery-ai summary` 以随时在您想要的位置生成 PR 摘要。您也可以在拉取请求上评论 `@sourcery-ai summary` 以随时（重新）生成摘要。
- **生成审查者指南：** 在拉取请求上评论 `@sourcery-ai guide` 以随时（重新）生成审查者指南。
- **解决所有 Sourcery 评论：** 在拉取请求上评论 `@sourcery-ai resolve` 以解决所有 Sourcery 评论。如果您已经解决了所有评论并且不想再看到它们，这将非常有用。
- **驳回所有 Sourcery 审查：** 在拉取请求上评论 `@sourcery-ai dismiss` 以驳回所有现有的 Sourcery 审查。如果您想从新的审查开始，这将特别有用 - 不要忘记评论 `@sourcery-ai review` 以触发新的审查！

#### 自定义您的体验

访问您的 [仪表板](https://app.sourcery.ai) 以：
- 启用或禁用审查功能，例如 Sourcery 生成的拉取请求摘要、审查者指南等。
- 更改审查语言。
- 添加、删除或编辑自定义审查说明。
- 调整其他审查设置。

#### 获取帮助

- [联系我们的支持团队](mailto:support@sourcery.ai) 提出问题或反馈。
- 访问我们的 [文档](https://docs.sourcery.ai) 获取详细指南和信息。
- 通过在 [X/Twitter](https://x.com/SourceryAI)、[LinkedIn](https://www.linkedin.com/company/sourcery-ai/) 或 [GitHub](https://github.com/sourcery-ai) 上关注我们，与 Sourcery 团队保持联系。

</details>
```

<details>
<summary>Original review guide in English</summary>

## Reviewer's Guide

This pull request implements version 1.5.0 by adding configuration options for DPI scaling and close button behavior, integrating them into the UI and application startup. It introduces a first-run guide window, re-implements QR code display via a popup triggered by stdout parsing, updates branding to 'NapCatQQ', adjusts UI layouts, fixes bugs in version handling, and removes unused dependencies.

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Introduced a first-run guide window and refactored application startup logic. | <ul><li>Added logic to display `GuideWindow` conditionally based on a new `MainWindow` config flag.</li><li>Moved path validation and DPI scaling initialization to `main.py`.</li></ul> | `main.py`<br/>`src/Core/Config/__init__.py`<br/>`src/Ui/MainWindow/Window.py` |
| Added configuration options for DPI scaling and close button behavior. | <ul><li>Added `dpiScale` config item and UI setting card.</li><li>Added `closeBtnAction` config item and UI setting card.</li><li>Implemented close button logic (close or hide) in the title bar based on the new config.</li><li>Removed the previous `closeEvent` override that forced hiding.</li></ul> | `src/Core/Config/__init__.py`<br/>`src/Ui/SetupPage/SetupScrollArea.py`<br/>`src/Ui/MainWindow/TitleBar.py`<br/>`src/Ui/MainWindow/Window.py`<br/>`main.py` |
| Re-implemented QR code display using a popup triggered by stdout parsing. | <ul><li>Added stdout parsing in `_napcatHandleStdout` to detect QR code file paths.</li><li>Displayed the detected QR code image in a modal `ImageBox`.</li></ul> | `src/Ui/BotListPage/BotWidget/__init__.py`<br/>`src/Ui/common/message_box.py` |
| Updated UI text, branding to 'NapCatQQ', and adjusted layouts. | <ul><li>Renamed instances of 'NapCat Desktop' to 'NapCatQQ Desktop'.</li><li>Replaced the QQ introduction text.</li><li>Updated tray icon menu item text to Chinese and removed 'Add Bot'/'Bot List' buttons.</li><li>Adjusted component icon height scaling.</li></ul> | `src/Ui/UnitPage/QQPage.py`<br/>`src/Ui/MainWindow/SystemTryIcon.py`<br/>`src/Ui/UnitPage/NCDPage.py`<br/>`src/Ui/UnitPage/Base.py` |
| Fixed version handling bugs and cleaned up dependencies. | <ul><li>Added error handling for remote update log fetching.</li><li>Standardized QQ version string format (replaced hyphens with dots).</li><li>Removed unused `frida` and `websockets` dependencies.</li></ul> | `src/Core/Utils/GetVersion.py`<br/>`pyproject.toml` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-02T10:23:25Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=NapNeko_NapCatQQ-Desktop&pullRequest=33) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=NapNeko_NapCatQQ-Desktop&pullRequest=33&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=NapNeko_NapCatQQ-Desktop&pullRequest=33&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=NapNeko_NapCatQQ-Desktop&pullRequest=33&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=NapNeko_NapCatQQ-Desktop&pullRequest=33&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=NapNeko_NapCatQQ-Desktop&pullRequest=33&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=NapNeko_NapCatQQ-Desktop&pullRequest=33)


author_association: NONE