import asyncio import threading import time # 添加这一行 from typing import Dict, Any, List, Optional, Tuple from base.plugin_common.message_plugin_interface import MessagePluginInterface from base.plugin_common.plugin_interface import PluginStatus from utils.decorator.plugin_decorators import plugin_stats_decorator from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager from utils.decorator.points_decorator import plugin_points_cost from utils.markdown_to_image import convert_md_str_to_image from utils.ai.unified_llm import UnifiedLLMClient from wechat_ipad import WechatAPIClient # 导入新闻抓取函数 from .news_crawler import nbc, cnn, abc, fox, bbc class GlobalNewsPlugin(MessagePluginInterface): """全球新闻插件""" # 功能权限常量 FEATURE_KEY = "GLOBAL_NEWS" FEATURE_DESCRIPTION = "🌍 全球新闻功能 [全球新闻, 国际新闻, 环球新闻, 政经新闻]" @property def name(self) -> str: return "全球新闻" @property def version(self) -> str: return "1.0.0" @property def description(self) -> str: return "提供全球政治经济新闻查询功能" @property def author(self) -> str: return "liu.wei" @property def command_prefix(self) -> Optional[str]: return "" # 不需要前缀,直接匹配命令 @property def commands(self) -> List[str]: return self._commands @property def feature_key(self) -> Optional[str]: return self.FEATURE_KEY @property def feature_description(self) -> Optional[str]: return self.FEATURE_DESCRIPTION def __init__(self): super().__init__() self.bot: WechatAPIClient = None self._news_tasks = {} # 存储正在进行的新闻抓取任务 # 注册功能权限 self.feature = self.register_feature() def initialize(self, context: Dict[str, Any]) -> bool: """初始化插件""" self.LOG.debug(f"正在初始化 {self.name} 插件...") # 保存上下文对象 self.event_system = context.get("event_system") self._commands = self._config.get("GlobalNews", {}).get("command", ["全球新闻", "国际新闻", "环球新闻", "政经新闻"]) self.command_format = self._config.get("GlobalNews", {}).get("command-format", "全球新闻 - 获取最新的全球政治经济新闻") plugin_config = self._config.get("GlobalNews", {}) self.enable = plugin_config.get("enable", True) llm_config = plugin_config.get("llm", {}) or {} if not llm_config: # 严格场景路由:仅由 scene 决定使用哪个后端。 llm_config = { "scene": plugin_config.get("scene", ""), } self.llm_client = UnifiedLLMClient(llm_config) self.LOG.debug(f"[{self.name}] 插件初始化完成,指令:{self._commands}") return True def start(self) -> bool: """启动插件""" self.LOG.debug(f"[{self.name}] 插件已启动") self.status = PluginStatus.RUNNING return True def stop(self) -> bool: """停止插件""" self.LOG.info(f"[{self.name}] 插件已停止") self.status = PluginStatus.STOPPED return True def can_process(self, message: Dict[str, Any]) -> bool: """检查是否可以处理该消息""" if not self.enable: return False content = str(message.get("content", "")).strip() command = content.split(" ")[0] return command in self._commands @plugin_stats_decorator(plugin_name="全球新闻") @plugin_points_cost(1, "全球新闻消耗积分", FEATURE_KEY) async def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]: """处理消息""" content = str(message.get("content", "")).strip() self.LOG.debug(f"插件执行: {self.name}:{content}") sender = message.get("sender") roomid = message.get("roomid", "") gbm: GroupBotManager = message.get("gbm") bot: WechatAPIClient = message.get("bot") # 检查权限 if roomid and gbm.get_group_permission(roomid, self.feature) == PermissionStatus.DISABLED: return False, "没有权限" # 生成唯一任务ID task_id = f"{sender}_{roomid}_{int(time.time())}" # 发送等待消息 await self.bot.send_text_message( (roomid if roomid else sender), "🌍正在获取全球新闻,请稍候...", sender) # 启动异步任务 self._start_news_task(task_id, sender, roomid) return True, "新闻获取任务已启动" def _start_news_task(self, task_id: str, sender: str, roomid: str): """启动异步新闻获取任务""" thread = threading.Thread( target=self._fetch_news_thread, args=(task_id, sender, roomid) ) thread.daemon = True thread.start() self._news_tasks[task_id] = thread self.LOG.info(f"启动新闻获取任务: {task_id}") async def _fetch_news_thread(self, task_id: str, sender: str, roomid: str): """在单独的线程中运行异步新闻获取任务""" try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) news_result = loop.run_until_complete(self._fetch_news_async()) loop.close() # 处理结果 if news_result: # 发送新闻图片 receiver = roomid if roomid else sender await self.bot.send_image_message(receiver, news_result) await self.bot.send_text_message("🌍全球新闻获取完成!", receiver, sender) else: await self.bot.send_text_message( (roomid if roomid else sender), "❌获取新闻失败,请稍后再试", sender) except Exception as e: self.LOG.error(f"新闻获取任务出错: {e}") await self.bot.send_text_message((roomid if roomid else sender), f"❌获取新闻出错: {str(e)}", sender) finally: # 清理任务 if task_id in self._news_tasks: del self._news_tasks[task_id] async def _fetch_news_async(self) -> str: """异步获取所有新闻源的新闻""" try: # 创建所有新闻源的任务 tasks = [ self._run_in_executor(nbc), self._run_in_executor(cnn), self._run_in_executor(abc), self._run_in_executor(fox), self._run_in_executor(bbc) ] # 并行执行所有任务 results = await asyncio.gather(*tasks) # 合并结果 news_titles = "\n".join(results) # 使用AI分析新闻 markdown_news = await self._run_in_executor(self.analyze_news_titles, news_titles) # 转换为图片 image_path = await self._run_in_executor( convert_md_str_to_image, markdown_news, "news_output.png" ) return image_path except Exception as e: self.LOG.error(f"异步获取新闻失败: {e}") return "" async def _run_in_executor(self, func, *args): """在线程池中运行同步函数""" loop = asyncio.get_event_loop() return await loop.run_in_executor(None, func, *args) def analyze_news_titles(self, content: str) -> Optional[str]: """同步分析新闻标题,便于在线程池中复用。""" response = self.llm_client.run( prompt=content, user="a-bot-global_news", inputs={"query": content}, tag="global_news", ) if not response: self.LOG.error(f"新闻分析请求失败: {self.llm_client.last_error}") return None return response.get("text") or None