diff --git a/src/common/database/db_migration.py b/src/common/database/db_migration.py new file mode 100644 index 000000000..037fd4be7 --- /dev/null +++ b/src/common/database/db_migration.py @@ -0,0 +1,78 @@ +# mmc/src/common/database/db_migration.py + +from sqlalchemy import inspect, text +from sqlalchemy.engine import Engine +from src.common.database.sqlalchemy_models import Base, get_engine +from src.common.logger import get_logger + +logger = get_logger("db_migration") + + +def check_and_migrate_database(): + """ + 检查数据库结构并自动迁移(添加缺失的表和列)。 + """ + logger.info("正在检查数据库结构并执行自动迁移...") + engine = get_engine() + inspector = inspect(engine) + + # 1. 获取数据库中所有已存在的表名 + db_table_names = set(inspector.get_table_names()) + + # 2. 遍历所有在代码中定义的模型 + for table_name, table in Base.metadata.tables.items(): + logger.debug(f"正在检查表: {table_name}") + + # 3. 如果表不存在,则创建它 + if table_name not in db_table_names: + logger.info(f"表 '{table_name}' 不存在,正在创建...") + try: + table.create(engine) + logger.info(f"表 '{table_name}' 创建成功。") + except Exception as e: + logger.error(f"创建表 '{table_name}' 失败: {e}") + continue + + # 4. 如果表已存在,则检查并添加缺失的列 + db_columns = {col["name"] for col in inspector.get_columns(table_name)} + model_columns = {col.name for col in table.c} + + missing_columns = model_columns - db_columns + if not missing_columns: + logger.debug(f"表 '{table_name}' 结构一致,无需修改。") + continue + + logger.info(f"在表 '{table_name}' 中发现缺失的列: {', '.join(missing_columns)}") + with engine.connect() as connection: + trans = connection.begin() + try: + for column_name in missing_columns: + column = table.c[column_name] + + # 构造并执行 ALTER TABLE 语句 + try: + column_type = column.type.compile(engine.dialect) + sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}" + + # 添加默认值和非空约束的处理 + if column.default is not None: + default_value = column.default.arg + if isinstance(default_value, str): + sql += f" DEFAULT '{default_value}'" + else: + sql += f" DEFAULT {default_value}" + + if not column.nullable: + sql += " NOT NULL" + + connection.execute(text(sql)) + logger.info(f"成功向表 '{table_name}' 添加列 '{column_name}'。") + except Exception as e: + logger.error(f"向表 '{table_name}' 添加列 '{column_name}' 失败: {e}") + + trans.commit() + except Exception as e: + logger.error(f"在表 '{table_name}' 添加列时发生错误,事务已回滚: {e}") + trans.rollback() + + logger.info("数据库结构检查与自动迁移完成。") diff --git a/src/common/database/db_migration_plan.md b/src/common/database/db_migration_plan.md new file mode 100644 index 000000000..591a94f88 --- /dev/null +++ b/src/common/database/db_migration_plan.md @@ -0,0 +1,138 @@ +# 数据库自动迁移模块 (`db_migration.py`) 设计文档 + +## 1. 目标 + +创建一个自动化的数据库迁移模块,用于在应用启动时检查数据库结构,并自动进行以下修复: + +1. **创建缺失的表**:如果代码模型中定义的表在数据库中不存在,则自动创建。 +2. **添加缺失的列**:如果数据库中的某个表现有的列比代码模型中定义的少,则自动添加缺失的列。 + +## 2. 实现思路 + +我们将使用 SQLAlchemy 的 `Inspector` 来获取数据库的元数据(即实际的表和列信息),并将其与 `SQLAlchemy` 模型(`Base.metadata`)中定义的结构进行比较。 + +核心逻辑分为以下几个步骤: + +1. **获取数据库引擎**:从现有代码中获取已初始化的 SQLAlchemy 引擎实例。 +2. **获取 Inspector**:通过引擎创建一个 `Inspector` 对象。 +3. **获取所有模型**:遍历 `Base.metadata.tables`,获取所有在代码中定义的表模型。 +4. **获取数据库中所有表名**:使用 `inspector.get_table_names()` 获取数据库中实际存在的所有表名。 +5. **创建缺失的表**:通过比较模型表名和数据库表名,找出所有缺失的表,并使用 `table.create(engine)` 来创建它们。 +6. **检查并添加缺失的列**: + * 遍历每一个代码中定义的表模型。 + * 使用 `inspector.get_columns(table_name)` 获取数据库中该表的实际列。 + * 比较模型列和实际列,找出所有缺失的列。 + * 对于每一个缺失的列,生成一个 `ALTER TABLE ... ADD COLUMN ...` 的 SQL 语句,并执行它。 + +## 3. 伪代码实现 + +```python +# mmc/src/common/database/db_migration.py + +from sqlalchemy import inspect, text +from sqlalchemy.engine import Engine +from src.common.database.sqlalchemy_models import Base, get_engine +from src.common.logger import get_logger + +logger = get_logger("db_migration") + +def check_and_migrate_database(): + """ + 检查数据库结构并自动迁移(添加缺失的表和列)。 + """ + logger.info("正在检查数据库结构并执行自动迁移...") + engine = get_engine() + inspector = inspect(engine) + + # 1. 获取数据库中所有已存在的表名 + db_table_names = set(inspector.get_table_names()) + + # 2. 遍历所有在代码中定义的模型 + for table_name, table in Base.metadata.tables.items(): + logger.debug(f"正在检查表: {table_name}") + + # 3. 如果表不存在,则创建它 + if table_name not in db_table_names: + logger.info(f"表 '{table_name}' 不存在,正在创建...") + try: + table.create(engine) + logger.info(f"表 '{table_name}' 创建成功。") + except Exception as e: + logger.error(f"创建表 '{table_name}' 失败: {e}") + continue + + # 4. 如果表已存在,则检查并添加缺失的列 + db_columns = {col['name'] for col in inspector.get_columns(table_name)} + model_columns = {col.name for col in table.c} + + missing_columns = model_columns - db_columns + if not missing_columns: + logger.debug(f"表 '{table_name}' 结构一致,无需修改。") + continue + + logger.info(f"在表 '{table_name}' 中发现缺失的列: {', '.join(missing_columns)}") + with engine.connect() as connection: + for column_name in missing_columns: + column = table.c[column_name] + + # 构造并执行 ALTER TABLE 语句 + # 注意:这里的实现需要考虑不同数据库(SQLite, MySQL)的语法差异 + # 为了简化,我们先使用一个通用的格式,后续可以根据需要进行扩展 + try: + column_type = column.type.compile(engine.dialect) + sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}" + + # 可以在这里添加对默认值、非空约束等的处理 + + connection.execute(text(sql)) + logger.info(f"成功向表 '{table_name}' 添加列 '{column_name}'。") + except Exception as e: + logger.error(f"向表 '{table_name}' 添加列 '{column_name}' 失败: {e}") + + # 提交事务 + if connection.in_transaction(): + connection.commit() + + logger.info("数据库结构检查与自动迁移完成。") + +``` + +## 4. 集成到启动流程 + +为了让这个迁移模块在应用启动时自动运行,我们需要在 `mmc/src/common/database/sqlalchemy_models.py` 的 `initialize_database` 函数中调用它。 + +修改后的 `initialize_database` 函数将如下所示: + +```python +# mmc/src/common/database/sqlalchemy_models.py + +# ... (其他 import) +from src.common.database.db_migration import check_and_migrate_database # 导入新函数 + +# ... (代码) + +def initialize_database(): + """初始化数据库引擎和会话""" + global _engine, _SessionLocal + + if _engine is not None: + return _engine, _SessionLocal + + # ... (数据库连接和引擎创建逻辑) + + _engine = create_engine(database_url, **engine_kwargs) + _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine) + + # 在这里,我们不再直接调用 create_all + # Base.metadata.create_all(bind=_engine) + + # 而是调用新的迁移函数,它会处理表的创建和列的添加 + check_and_migrate_database() + + logger.info(f"SQLAlchemy数据库初始化成功: {config.database_type}") + return _engine, _SessionLocal + +# ... (其他代码) +``` + +通过这样的修改,我们就可以在不改变现有初始化流程入口的情况下,无缝地集成自动化的数据库结构检查和修复功能。 \ No newline at end of file diff --git a/src/common/database/sqlalchemy_models.py b/src/common/database/sqlalchemy_models.py index 2e1665dd2..85ad6c281 100644 --- a/src/common/database/sqlalchemy_models.py +++ b/src/common/database/sqlalchemy_models.py @@ -525,8 +525,9 @@ def initialize_database(): _engine = create_engine(database_url, **engine_kwargs) _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine) - # 创建所有表 - Base.metadata.create_all(bind=_engine) + # 调用新的迁移函数,它会处理表的创建和列的添加 + from src.common.database.db_migration import check_and_migrate_database + check_and_migrate_database() logger.info(f"SQLAlchemy数据库初始化成功: {config.database_type}") return _engine, _SessionLocal @@ -540,7 +541,7 @@ def get_db_session(): _, SessionLocal = initialize_database() session = SessionLocal() yield session - # session.commit() + #session.commit() except Exception: if session: session.rollback() diff --git a/src/config/api_ada_configs.py b/src/config/api_ada_configs.py index e59be767c..a69a563ca 100644 --- a/src/config/api_ada_configs.py +++ b/src/config/api_ada_configs.py @@ -114,6 +114,9 @@ class ModelTaskConfig(ConfigBase): replyer_2: TaskConfig """normal_chat次要回复模型配置""" + maizone : TaskConfig + """maizone专用模型""" + emotion: TaskConfig """情绪模型配置""" diff --git a/src/plugins/built_in/maizone_refactored/plugin.py b/src/plugins/built_in/maizone_refactored/plugin.py index 90f466f1d..ccf8874e1 100644 --- a/src/plugins/built_in/maizone_refactored/plugin.py +++ b/src/plugins/built_in/maizone_refactored/plugin.py @@ -42,7 +42,7 @@ class MaiZoneRefactoredPlugin(BasePlugin): config_schema: dict = { "plugin": {"enable": ConfigField(type=bool, default=True, description="是否启用插件")}, "models": { - "text_model": ConfigField(type=str, default="replyer_1", description="生成文本的模型名称"), + "text_model": ConfigField(type=str, default="maizone", description="生成文本的模型名称"), "siliconflow_apikey": ConfigField(type=str, default="", description="硅基流动AI生图API密钥"), }, "send": { diff --git a/src/plugins/built_in/maizone_refactored/services/monitor_service.py b/src/plugins/built_in/maizone_refactored/services/monitor_service.py index 9c46d5370..9875af944 100644 --- a/src/plugins/built_in/maizone_refactored/services/monitor_service.py +++ b/src/plugins/built_in/maizone_refactored/services/monitor_service.py @@ -55,7 +55,6 @@ class MonitorService: interval_minutes = self.get_config("monitor.interval_minutes", 10) - logger.info("开始执行好友动态监控...") await self.qzone_service.monitor_feeds() logger.info(f"本轮监控完成,将在 {interval_minutes} 分钟后进行下一次检查。") 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 de470019b..eb3664e61 100644 --- a/src/plugins/built_in/maizone_refactored/services/qzone_service.py +++ b/src/plugins/built_in/maizone_refactored/services/qzone_service.py @@ -3,6 +3,7 @@ QQ空间服务模块 封装了所有与QQ空间API的直接交互,是插件的核心业务逻辑层。 """ + import asyncio import base64 import json @@ -29,6 +30,7 @@ class QZoneService: """ QQ空间服务类,负责所有API交互和业务流程编排。 """ + # --- API Endpoints --- ZONE_LIST_URL = "https://user.qzone.qq.com/proxy/domain/ic2.qzone.qq.com/cgi-bin/feeds/feeds3_html_more" EMOTION_PUBLISH_URL = "https://user.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_publish_v6" @@ -37,8 +39,13 @@ class QZoneService: LIST_URL = "https://user.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6" REPLY_URL = "https://user.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_re_feeds" - - def __init__(self, get_config: Callable, content_service: ContentService, image_service: ImageService, cookie_service: CookieService): + def __init__( + self, + get_config: Callable, + content_service: ContentService, + image_service: ImageService, + cookie_service: CookieService, + ): self.get_config = get_config self.content_service = content_service self.image_service = image_service @@ -53,7 +60,7 @@ class QZoneService: return {"success": False, "message": "生成说说内容失败"} await self.image_service.generate_images_for_story(story) - + qq_account = config_api.get_global_config("bot.qq_account", "") api_client = await self._get_api_client(qq_account, stream_id) if not api_client: @@ -78,7 +85,7 @@ class QZoneService: return {"success": False, "message": "根据活动生成说说内容失败"} await self.image_service.generate_images_for_story(story) - + qq_account = config_api.get_global_config("bot.qq_account", "") # 注意:定时任务通常在后台运行,没有特定的用户会话,因此 stream_id 为 None api_client = await self._get_api_client(qq_account, stream_id=None) @@ -116,7 +123,7 @@ class QZoneService: feeds = await api_client["list_feeds"](target_qq, num_to_read) if not feeds: return {"success": True, "message": f"没有从'{target_name}'的空间获取到新说说。"} - + for feed in feeds: await self._process_single_feed(feed, api_client, target_qq, target_name) await asyncio.sleep(random.uniform(3, 7)) @@ -136,11 +143,11 @@ class QZoneService: return try: - feeds = await api_client["monitor_list_feeds"](20) # 监控时检查最近20条动态 + feeds = await api_client["monitor_list_feeds"](20) # 监控时检查最近20条动态 if not feeds: logger.info("监控完成:未发现新说说") return - + logger.info(f"监控任务: 发现 {len(feeds)} 条新动态,准备处理...") for feed in feeds: target_qq = feed.get("target_qq") @@ -153,7 +160,7 @@ class QZoneService: await self._reply_to_own_feed_comments(feed, api_client) else: await self._process_single_feed(feed, api_client, target_qq, target_qq) - + await asyncio.sleep(random.uniform(5, 10)) except Exception as e: logger.error(f"监控好友动态时发生异常: {e}", exc_info=True) @@ -171,24 +178,31 @@ class QZoneService: return # 筛选出未被自己回复过的主评论 - my_comment_tids = {c['parent_tid'] for c in comments if c.get('parent_tid') and c.get('qq_account') == qq_account} - comments_to_reply = [c for c in comments if not c.get('parent_tid') and c.get('comment_tid') not in my_comment_tids] + my_comment_tids = { + c["parent_tid"] for c in comments if c.get("parent_tid") and c.get("qq_account") == qq_account + } + comments_to_reply = [ + c for c in comments if not c.get("parent_tid") and c.get("comment_tid") not in my_comment_tids + ] if not comments_to_reply: return logger.info(f"发现自己说说下的 {len(comments_to_reply)} 条新评论,准备回复...") for comment in comments_to_reply: - reply_content = await self.content_service.generate_comment_reply(content, comment.get('content', ''), comment.get('nickname', '')) + reply_content = await self.content_service.generate_comment_reply( + content, comment.get("content", ""), comment.get("nickname", "") + ) if reply_content: - success = await api_client["reply"](fid, qq_account, comment.get('nickname', ''), reply_content, comment.get('comment_tid')) + success = await api_client["reply"]( + fid, qq_account, comment.get("nickname", ""), reply_content, comment.get("comment_tid") + ) if success: logger.info(f"成功回复'{comment.get('nickname', '')}'的评论: '{reply_content}'") else: logger.error(f"回复'{comment.get('nickname', '')}'的评论失败") await asyncio.sleep(random.uniform(10, 20)) - async def _process_single_feed(self, feed: Dict, api_client: Dict, target_qq: str, target_name: str): """处理单条说说,决定是否评论和点赞""" content = feed.get("content", "") @@ -207,7 +221,7 @@ class QZoneService: images = [] if not os.path.exists(image_dir): return images - + try: files = sorted([f for f in os.listdir(image_dir) if os.path.isfile(os.path.join(image_dir, f))]) for filename in files: @@ -228,21 +242,26 @@ class QZoneService: async def _get_api_client(self, qq_account: str, stream_id: Optional[str]) -> Optional[Dict]: cookies = await self.cookie_service.get_cookies(qq_account, stream_id) - if not cookies: return None - - p_skey = cookies.get('p_skey') or cookies.get('p_skey'.upper()) - if not p_skey: return None - + if not cookies: + return None + + p_skey = cookies.get("p_skey") or cookies.get("p_skey".upper()) + if not p_skey: + return None + gtk = self._generate_gtk(p_skey) - uin = cookies.get('uin', '').lstrip('o') - + uin = cookies.get("uin", "").lstrip("o") + async def _request(method, url, params=None, data=None, headers=None): - final_headers = {'referer': f'https://user.qzone.qq.com/{uin}', 'origin': 'https://user.qzone.qq.com'} - if headers: final_headers.update(headers) - + final_headers = {"referer": f"https://user.qzone.qq.com/{uin}", "origin": "https://user.qzone.qq.com"} + if headers: + final_headers.update(headers) + async with aiohttp.ClientSession(cookies=cookies) as session: timeout = aiohttp.ClientTimeout(total=20) - async with session.request(method, url, params=params, data=data, headers=final_headers, timeout=timeout) as response: + async with session.request( + method, url, params=params, data=data, headers=final_headers, timeout=timeout + ) as response: response.raise_for_status() return await response.text() @@ -250,11 +269,18 @@ class QZoneService: """发布说说""" try: post_data = { - "syn_tweet_verson": "1", "paramstr": "1", "who": "1", - "con": content, "feedversion": "1", "ver": "1", - "ugc_right": "1", "to_sign": "0", "hostuin": uin, - "code_version": "1", "format": "json", - "qzreferrer": f"https://user.qzone.qq.com/{uin}" + "syn_tweet_verson": "1", + "paramstr": "1", + "who": "1", + "con": content, + "feedversion": "1", + "ver": "1", + "ugc_right": "1", + "to_sign": "0", + "hostuin": uin, + "code_version": "1", + "format": "json", + "qzreferrer": f"https://user.qzone.qq.com/{uin}", } if images: pic_bos, richvals = [], [] @@ -265,17 +291,17 @@ class QZoneService: # This is a placeholder for the actual image upload logic which is quite complex. # In a real scenario, you would call a dedicated `_upload_image` method here. # For now, we assume the upload is successful and we get back dummy data. - pass # Simplified for this example + pass # Simplified for this example # Dummy data for illustration if images: - post_data['pic_bo'] = 'dummy_pic_bo' - post_data['richtype'] = '1' - post_data['richval'] = 'dummy_rich_val' + post_data["pic_bo"] = "dummy_pic_bo" + post_data["richtype"] = "1" + post_data["richval"] = "dummy_rich_val" - res_text = await _request("POST", self.EMOTION_PUBLISH_URL, params={'g_tk': gtk}, data=post_data) + res_text = await _request("POST", self.EMOTION_PUBLISH_URL, params={"g_tk": gtk}, data=post_data) result = json.loads(res_text) - tid = result.get('tid', '') + tid = result.get("tid", "") return bool(tid), tid except Exception as e: logger.error(f"发布说说异常: {e}", exc_info=True) @@ -285,27 +311,44 @@ class QZoneService: """获取指定用户说说列表""" try: params = { - 'g_tk': gtk, "uin": t_qq, "ftype": 0, "sort": 0, "pos": 0, - "num": num, "replynum": 100, "callback": "_preloadCallback", - "code_version": 1, "format": "jsonp", "need_comment": 1 + "g_tk": gtk, + "uin": t_qq, + "ftype": 0, + "sort": 0, + "pos": 0, + "num": num, + "replynum": 100, + "callback": "_preloadCallback", + "code_version": 1, + "format": "jsonp", + "need_comment": 1, } res_text = await _request("GET", self.LIST_URL, params=params) - json_str = res_text[len('_preloadCallback('):-2] + json_str = res_text[len("_preloadCallback(") : -2] json_data = json.loads(json_str) - if json_data.get('code') != 0: return [] + if json_data.get("code") != 0: + return [] feeds_list = [] - my_name = json_data.get('logininfo', {}).get('name', '') + my_name = json_data.get("logininfo", {}).get("name", "") for msg in json_data.get("msglist", []): - is_commented = any(c.get("name") == my_name for c in msg.get("commentlist", []) if isinstance(c, dict)) + is_commented = any( + c.get("name") == my_name for c in msg.get("commentlist", []) if isinstance(c, dict) + ) if not is_commented: - feeds_list.append({ - "tid": msg.get("tid", ""), - "content": msg.get("content", ""), - "created_time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(msg.get("created_time", 0))), - "rt_con": msg.get("rt_con", {}).get("content", "") if isinstance(msg.get("rt_con"), dict) else "" - }) + feeds_list.append( + { + "tid": msg.get("tid", ""), + "content": msg.get("content", ""), + "created_time": time.strftime( + "%Y-%m-%d %H:%M:%S", time.localtime(msg.get("created_time", 0)) + ), + "rt_con": msg.get("rt_con", {}).get("content", "") + if isinstance(msg.get("rt_con"), dict) + else "", + } + ) return feeds_list except Exception as e: logger.error(f"获取说说列表失败: {e}", exc_info=True) @@ -315,9 +358,15 @@ class QZoneService: """评论说说""" try: data = { - "topicId": f'{t_qq}_{feed_id}__1', "uin": uin, "hostUin": t_qq, - "content": text, "format": "fs", "plat": "qzone", "source": "ic", - "platformid": 52, "ref": "feeds" + "topicId": f"{t_qq}_{feed_id}__1", + "uin": uin, + "hostUin": t_qq, + "content": text, + "format": "fs", + "plat": "qzone", + "source": "ic", + "platformid": 52, + "ref": "feeds", } await _request("POST", self.COMMENT_URL, params={"g_tk": gtk}, data=data) return True @@ -329,17 +378,24 @@ class QZoneService: """点赞说说""" try: data = { - 'opuin': uin, 'unikey': f'http://user.qzone.qq.com/{t_qq}/mood/{feed_id}', - 'curkey': f'http://user.qzone.qq.com/{t_qq}/mood/{feed_id}', - 'from': 1, 'appid': 311, 'typeid': 0, 'abstime': int(time.time()), - 'fid': feed_id, 'active': 0, 'format': 'json', 'fupdate': 1 + "opuin": uin, + "unikey": f"http://user.qzone.qq.com/{t_qq}/mood/{feed_id}", + "curkey": f"http://user.qzone.qq.com/{t_qq}/mood/{feed_id}", + "from": 1, + "appid": 311, + "typeid": 0, + "abstime": int(time.time()), + "fid": feed_id, + "active": 0, + "format": "json", + "fupdate": 1, } - await _request("POST", self.DOLIKE_URL, params={'g_tk': gtk}, data=data) + await _request("POST", self.DOLIKE_URL, params={"g_tk": gtk}, data=data) return True except Exception as e: logger.error(f"点赞说说异常: {e}", exc_info=True) return False - + async def _reply(fid, host_qq, target_name, content, comment_tid): """回复评论""" try: @@ -355,7 +411,7 @@ class QZoneService: "ref": "feeds", "richtype": "", "richval": "", - "paramstr": f"@{target_name} {content}" + "paramstr": f"@{target_name} {content}", } await _request("POST", self.REPLY_URL, params={"g_tk": gtk}, data=data) return True @@ -367,38 +423,55 @@ class QZoneService: """监控好友动态""" try: params = { - "uin": uin, "scope": 0, "view": 1, "filter": "all", "flag": 1, - "applist": "all", "pagenum": 1, "count": num, "format": "json", - "g_tk": gtk, "useutf8": 1, "outputhtmlfeed": 1 + "uin": uin, + "scope": 0, + "view": 1, + "filter": "all", + "flag": 1, + "applist": "all", + "pagenum": 1, + "count": num, + "format": "json", + "g_tk": gtk, + "useutf8": 1, + "outputhtmlfeed": 1, } res_text = await _request("GET", self.ZONE_LIST_URL, params=params) - json_str = res_text[len('_Callback('):-2].replace('undefined', 'null') + + # 增加对返回内容的校验 + if not res_text.startswith("_Callback(") or not res_text.endswith(");"): + logger.warning(f"监控好友动态返回格式异常: {res_text}") + return [] + + json_str = res_text[len("_Callback(") : -2].replace("undefined", "null") json_data = json5.loads(json_str) feeds_data = [] if isinstance(json_data, dict): - data_level1 = json_data.get('data') + data_level1 = json_data.get("data") if isinstance(data_level1, dict): - feeds_data = data_level1.get('data', []) - + feeds_data = data_level1.get("data", []) + feeds_list = [] for feed in feeds_data: - if str(feed.get('appid', '')) != '311' or str(feed.get('uin', '')) == str(uin): - continue - - html_content = feed.get('html', '') - soup = bs4.BeautifulSoup(html_content, 'html.parser') - like_btn = soup.find('a', class_='qz_like_btn_v3') - if isinstance(like_btn, bs4.element.Tag) and like_btn.get('data-islike') == '1': + if str(feed.get("appid", "")) != "311" or str(feed.get("uin", "")) == str(uin): continue - text_div = soup.find('div', class_='f-info') + html_content = feed.get("html", "") + soup = bs4.BeautifulSoup(html_content, "html.parser") + like_btn = soup.find("a", class_="qz_like_btn_v3") + if isinstance(like_btn, bs4.element.Tag) and like_btn.get("data-islike") == "1": + continue + + text_div = soup.find("div", class_="f-info") text = text_div.get_text(strip=True) if text_div else "" - - feeds_list.append({ - 'target_qq': feed.get('uin'), - 'tid': feed.get('key'), - 'content': text, - }) + + feeds_list.append( + { + "target_qq": feed.get("uin"), + "tid": feed.get("key"), + "content": text, + } + ) return feeds_list except Exception as e: logger.error(f"监控好友动态失败: {e}", exc_info=True) @@ -411,4 +484,4 @@ class QZoneService: "like": _like, "reply": _reply, "monitor_list_feeds": _monitor_list_feeds, - } \ No newline at end of file + } diff --git a/template/model_config_template.toml b/template/model_config_template.toml index 8a53d5149..b38c10819 100644 --- a/template/model_config_template.toml +++ b/template/model_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "1.2.3" +version = "1.2.4" # 配置文件版本号迭代规则同bot_config.toml @@ -145,6 +145,11 @@ model_list = ["siliconflow-deepseek-v3"] temperature = 0.3 max_tokens = 800 +[model_task_config.maizone] # maizone模型 +model_list = ["siliconflow-deepseek-v3"] +temperature = 0.7 +max_tokens = 800 + [model_task_config.vlm] # 图像识别模型 model_list = ["qwen2.5-vl-72b"] max_tokens = 800