From e000e78c3d96c2334bc561330ab167b3445af9b2 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Sun, 9 Nov 2025 22:50:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor(maizone):=20=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E7=A8=B3=E5=81=A5=E6=80=A7=E5=B9=B6=E9=9B=86=E4=B8=AD=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E8=A7=86=E8=A7=89=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=AD=A4=E6=8F=90=E4=BA=A4=E5=AF=B9=20maizone=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=BC=95=E5=85=A5=E4=BA=86=E4=B8=80=E7=B3=BB=E5=88=97?= =?UTF-8?q?=E6=94=B9=E8=BF=9B=EF=BC=8C=E4=BB=A5=E5=A2=9E=E5=BC=BA=E5=85=B6?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7=E3=80=81=E5=8F=AF=E7=BB=B4=E6=8A=A4?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E5=B9=B6=E4=B8=8E=E6=A0=B8=E5=BF=83=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4=E3=80=82=E4=B8=BB?= =?UTF-8?q?=E8=A6=81=E6=9B=B4=E6=94=B9=E5=8C=85=E6=8B=AC=EF=BC=9A-=20?= =?UTF-8?q?=E9=9B=86=E4=B8=AD=E7=AE=A1=E7=90=86=E8=A7=86=E8=A7=89=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E9=85=8D=E7=BD=AE=EF=BC=9A=E6=8F=92=E4=BB=B6=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E5=AE=9A=E4=B9=89=E8=87=AA=E5=B7=B1=E7=9A=84=20`visio?= =?UTF-8?q?n=5Fmodel`=E3=80=82=E7=8E=B0=E5=9C=A8=EF=BC=8C=E5=AE=83?= =?UTF-8?q?=E4=BB=8E=E5=85=A8=E5=B1=80=E7=9A=84=20`model=5Fconfig.toml`=20?= =?UTF-8?q?=E4=B8=AD=E8=AF=BB=E5=8F=96=E9=BB=98=E8=AE=A4=E7=9A=84=E8=A7=86?= =?UTF-8?q?=E8=A7=89=E6=A8=A1=E5=9E=8B=EF=BC=8C=E4=BB=8E=E8=80=8C=E7=A1=AE?= =?UTF-8?q?=E4=BF=9D=E5=8D=95=E4=B8=80=E7=9A=84=E5=8F=AF=E4=BF=A1=E6=BA=90?= =?UTF-8?q?=E3=80=82-=20=E5=A2=9E=E5=BC=BA=E8=A7=A3=E6=9E=90=E7=A8=B3?= =?UTF-8?q?=E5=81=A5=E6=80=A7=EF=BC=9A=E5=9C=A8=20QZone=20HTML=20=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E9=80=BB=E8=BE=91=E4=B8=AD=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=20`isinstance`=20=E6=A3=80=E6=9F=A5=E3=80=82=E8=BF=99=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E9=98=B2=E6=AD=A2=E5=9C=A8=E6=9C=AA=E6=89=BE=E5=88=B0?= =?UTF-8?q?=E9=A2=84=E6=9C=9F=E5=85=83=E7=B4=A0=E6=97=B6=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E6=BD=9C=E5=9C=A8=E7=9A=84=20`AttributeError`=20=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=EF=BC=8C=E4=BD=BF=20Feed=20=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=9B=B4=E5=85=B7=E5=BC=B9=E6=80=A7=E3=80=82-=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=96=87=E4=BB=B6=20I/O=EF=BC=9A=E5=B0=86=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E8=BF=BD=E8=B8=AA=E5=99=A8=E7=9A=84=20JSON=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=93=8D=E4=BD=9C=E5=88=87=E6=8D=A2=E5=88=B0?= =?UTF-8?q?=E4=BA=8C=E8=BF=9B=E5=88=B6=E6=A8=A1=E5=BC=8F=E3=80=82=E8=BF=99?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86=E6=9D=A5?= =?UTF-8?q?=E8=87=AA=20`orjson`=20=E7=9A=84=E5=AD=97=E8=8A=82=E8=BE=93?= =?UTF-8?q?=E5=87=BA=EF=BC=8C=E4=BB=8E=E8=80=8C=E6=8F=90=E5=8D=87=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E5=92=8C=E6=AD=A3=E7=A1=AE=E6=80=A7=E3=80=82-=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20API=20=E4=BD=BF=E7=94=A8=EF=BC=9A=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BA=86=E4=BA=BA=E5=91=98=E6=95=B0=E6=8D=AE=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BD=BF=E7=94=A8=E6=9B=B4?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=9A=84=20`person=5Fapi.get=5Fperson=5Finfo?= =?UTF-8?q?`=20=E6=96=B9=E6=B3=95=EF=BC=8C=E5=B9=B6=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86=20`user=5Fid`=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../built_in/maizone_refactored/plugin.py | 1 - .../services/content_service.py | 7 ++-- .../services/qzone_service.py | 39 +++++++++++-------- .../services/reply_tracker_service.py | 10 ++--- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/plugins/built_in/maizone_refactored/plugin.py b/src/plugins/built_in/maizone_refactored/plugin.py index ee0826f9c..7513fff11 100644 --- a/src/plugins/built_in/maizone_refactored/plugin.py +++ b/src/plugins/built_in/maizone_refactored/plugin.py @@ -43,7 +43,6 @@ class MaiZoneRefactoredPlugin(BasePlugin): "plugin": {"enable": ConfigField(type=bool, default=True, description="是否启用插件")}, "models": { "text_model": ConfigField(type=str, default="maizone", description="生成文本的模型名称"), - "vision_model": ConfigField(type=str, default="sf-glm-4.5v", description="识别图片的模型名称(建议使用model_config.toml中配置的视觉模型)"), "siliconflow_apikey": ConfigField(type=str, default="", description="硅基流动AI生图API密钥"), }, "send": { diff --git a/src/plugins/built_in/maizone_refactored/services/content_service.py b/src/plugins/built_in/maizone_refactored/services/content_service.py index f4793477b..b446d1dbb 100644 --- a/src/plugins/built_in/maizone_refactored/services/content_service.py +++ b/src/plugins/built_in/maizone_refactored/services/content_service.py @@ -245,14 +245,15 @@ class ContentService: image_format = kind.extension image_base64 = base64.b64encode(image_bytes).decode("utf-8") - vision_model_name = self.get_config("models.vision_model", "vlm") + # 优先从全局配置读取视觉模型,如果未配置,则使用默认的 "vlm" + vision_model_name = config_api.get_global_config("model.vision.default_model", "vlm") - # 使用 llm_api 获取模型配置,支持自动fallback到备选模型 + # 使用 llm_api 获取模型配置 models = llm_api.get_available_models() vision_model_config = models.get(vision_model_name) if not vision_model_config: - logger.error(f"未找到视觉模型配置: {vision_model_name}") + logger.error(f"未在 model_config.toml 中找到视觉模型配置: {vision_model_name}") return None vision_model_config.temperature = 0.3 diff --git a/src/plugins/built_in/maizone_refactored/services/qzone_service.py b/src/plugins/built_in/maizone_refactored/services/qzone_service.py index 13e022866..476be1129 100644 --- a/src/plugins/built_in/maizone_refactored/services/qzone_service.py +++ b/src/plugins/built_in/maizone_refactored/services/qzone_service.py @@ -48,7 +48,7 @@ class QZoneService: content_service: ContentService, image_service: ImageService, cookie_service: CookieService, - reply_tracker: ReplyTrackerService = None, + reply_tracker: ReplyTrackerService | None = None, ): self.get_config = get_config self.content_service = content_service @@ -128,7 +128,8 @@ class QZoneService: target_person_id = await person_api.get_person_id_by_name(target_name) if not target_person_id: return {"success": False, "message": f"找不到名为'{target_name}'的好友"} - target_qq = await person_api.get_person_value(target_person_id, "user_id") + person_info = await person_api.get_person_info(target_person_id) + target_qq = person_info.get("user_id") if not target_qq: return {"success": False, "message": f"好友'{target_name}'没有关联QQ号"} @@ -155,7 +156,7 @@ class QZoneService: total_liked = 0 total_commented = 0 for feed in feeds: - result = await self._process_single_feed(feed, api_client, target_qq, target_name) + result = await self._process_single_feed(feed, api_client, str(target_qq), target_name) if result["liked"]: total_liked += 1 if result["commented"]: @@ -254,7 +255,7 @@ class QZoneService: if not target_qq or str(target_qq) == str(qq_account): # 确保不重复处理自己的 continue - result = await self._process_single_feed(feed, api_client, target_qq, target_qq) + result = await self._process_single_feed(feed, api_client, str(target_qq), str(target_qq)) monitor_stats["total"] += 1 if result.get("liked"): monitor_stats["liked"] += 1 @@ -1151,28 +1152,28 @@ class QZoneService: like_btn = soup.find("a", class_="qz_like_btn_v3") is_liked = False - if like_btn and isinstance(like_btn, bs4.Tag): - is_liked = like_btn.get("data-islike") == "1" + if isinstance(like_btn, bs4.Tag) and like_btn.get("data-islike") == "1": + is_liked = True if is_liked: continue text_div = soup.find("div", class_="f-info") - text = text_div.get_text(strip=True) if text_div else "" + text = text_div.get_text(strip=True) if isinstance(text_div, bs4.Tag) else "" # --- 借鉴原版插件的精确图片提取逻辑 --- image_urls = [] img_box = soup.find("div", class_="img-box") - if img_box: + if isinstance(img_box, bs4.Tag): for img in img_box.find_all("img"): - src = img.get("src") - # 排除QQ空间的小图标和表情 - if src and "qzonestyle.gtimg.cn" not in src: - image_urls.append(src) + if isinstance(img, bs4.Tag): + src = img.get("src") + if src and isinstance(src, str) and "qzonestyle.gtimg.cn" not in src: + image_urls.append(src) # 视频封面也视为图片 video_thumb = soup.select_one("div.video-img img") - if video_thumb and "src" in video_thumb.attrs: + if isinstance(video_thumb, bs4.Tag) and "src" in video_thumb.attrs: image_urls.append(video_thumb["src"]) # 去重 @@ -1181,11 +1182,13 @@ class QZoneService: comments = [] comment_divs = soup.find_all("div", class_="f-single-comment") for comment_div in comment_divs: + if not isinstance(comment_div, bs4.Tag): + continue # --- 处理主评论 --- author_a = comment_div.find("a", class_="f-nick") content_span = comment_div.find("span", class_="f-re-con") - if author_a and content_span: + if isinstance(author_a, bs4.Tag) and isinstance(content_span, bs4.Tag): comments.append( { "qq_account": str(comment_div.get("data-uin", "")), @@ -1199,21 +1202,23 @@ class QZoneService: # --- 处理这条主评论下的所有回复 --- reply_divs = comment_div.find_all("div", class_="f-single-re") for reply_div in reply_divs: + if not isinstance(reply_div, bs4.Tag): + continue reply_author_a = reply_div.find("a", class_="f-nick") reply_content_span = reply_div.find("span", class_="f-re-con") - if reply_author_a and reply_content_span: + if isinstance(reply_author_a, bs4.Tag) and isinstance(reply_content_span, bs4.Tag): comments.append( { "qq_account": str(reply_div.get("data-uin", "")), "nickname": reply_author_a.get_text(strip=True), "content": reply_content_span.get_text(strip=True).lstrip( ": " - ), # 移除回复内容前多余的冒号和空格 + ), "comment_tid": reply_div.get("data-tid", ""), "parent_tid": reply_div.get( "data-parent-tid", comment_div.get("data-tid", "") - ), # 如果没有父ID,则将父ID设为主评论ID + ), } ) diff --git a/src/plugins/built_in/maizone_refactored/services/reply_tracker_service.py b/src/plugins/built_in/maizone_refactored/services/reply_tracker_service.py index 8fa0ce1e4..0c6e9ef22 100644 --- a/src/plugins/built_in/maizone_refactored/services/reply_tracker_service.py +++ b/src/plugins/built_in/maizone_refactored/services/reply_tracker_service.py @@ -65,9 +65,9 @@ class ReplyTrackerService: try: if self.reply_record_file.exists(): try: - with open(self.reply_record_file, encoding="utf-8") as f: - file_content = f.read().strip() - if not file_content: # 文件为空 + with open(self.reply_record_file, "rb") as f: + file_content = f.read() + if not file_content.strip(): # 文件为空 logger.warning("回复记录文件为空,将创建新的记录") self.replied_comments = {} return @@ -118,8 +118,8 @@ class ReplyTrackerService: temp_file = self.reply_record_file.with_suffix(".tmp") # 先写入临时文件 - with open(temp_file, "w", encoding="utf-8"): - orjson.dumps(self.replied_comments, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS).decode("utf-8") + with open(temp_file, "wb") as f: + f.write(orjson.dumps(self.replied_comments, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS)) # 如果写入成功,重命名为正式文件 if temp_file.stat().st_size > 0: # 确保写入成功