""" 60秒读懂世界插件 每日新闻推送和指令查询 """ import httpx import uuid from pathlib import Path from loguru import logger from utils.plugin_base import PluginBase from utils.decorators import on_text_message, schedule from WechatHook import WechatHookClient import tomllib class News60s(PluginBase): """60秒读懂世界插件""" description = "60秒读懂世界 - 每日新闻推送" author = "ShiHao" version = "1.0.0" def __init__(self): super().__init__() self.config = None self.images_dir = None async def async_init(self): """异步初始化""" config_path = Path(__file__).parent / "config.toml" with open(config_path, "rb") as f: self.config = tomllib.load(f) self.images_dir = Path(__file__).parent / "images" self.images_dir.mkdir(exist_ok=True) logger.success("60秒读懂世界插件初始化完成") async def fetch_news_image(self) -> str: """获取60秒新闻图片""" try: timeout = httpx.Timeout(connect=10.0, read=120.0, write=10.0, pool=10.0) # 配置代理 proxy = None proxy_config = self.config.get("proxy", {}) if proxy_config.get("enabled", False): proxy_type = proxy_config.get("type", "socks5") proxy_host = proxy_config.get("host", "127.0.0.1") proxy_port = proxy_config.get("port", 7890) proxy = f"{proxy_type}://{proxy_host}:{proxy_port}" async with httpx.AsyncClient(timeout=timeout, proxy=proxy, follow_redirects=True) as client: response = await client.get("https://60s.viki.moe/v2/60s?encoding=image") response.raise_for_status() # 保存图片 uid = uuid.uuid4().hex[:8] file_path = self.images_dir / f"news_{uid}.jpg" with open(file_path, "wb") as f: f.write(response.content) logger.info(f"60秒新闻图片下载成功: {file_path}") return str(file_path) except Exception as e: logger.error(f"获取60秒新闻失败: {e}") return None def is_target_group(self, from_wxid: str) -> bool: """检查是否是目标群组""" whitelist = self.config["behavior"].get("group_whitelist", []) # 空白名单表示所有群组 if not whitelist: return True return from_wxid in whitelist @on_text_message(priority=70) async def handle_message(self, bot: WechatHookClient, message: dict): """处理文本消息""" if not self.config["behavior"]["enable_command"]: return True content = message.get("Content", "").strip() from_wxid = message.get("FromWxid", "") is_group = message.get("IsGroup", False) # 只处理群聊 if not is_group: return True # 检查是否是触发指令(支持 "@机器人 /60s" 或直接 "/60s") keywords = self.config["behavior"]["command_keywords"] matched = False for keyword in keywords: if content == keyword or content.endswith(f" {keyword}"): matched = True break if not matched: return True logger.info(f"收到60秒新闻请求: {from_wxid}") try: image_path = await self.fetch_news_image() if image_path: await bot.send_image(from_wxid, image_path) logger.success(f"已发送60秒新闻到: {from_wxid}") else: await bot.send_text(from_wxid, "❌ 获取新闻失败,请稍后重试") except Exception as e: logger.error(f"发送60秒新闻失败: {e}") await bot.send_text(from_wxid, f"❌ 发送失败: {str(e)}") return False @schedule('cron', hour=8, minute=0) async def scheduled_news(self, bot=None): """定时推送新闻""" if not self.config["schedule"]["enabled"]: return logger.info("开始定时推送60秒新闻") try: image_path = await self.fetch_news_image() if not image_path: logger.error("定时任务:获取新闻图片失败") return # 获取bot实例 if not bot: from utils.plugin_manager import PluginManager bot = PluginManager().bot if not bot: logger.error("定时任务:无法获取bot实例") return # 获取所有群聊 import tomllib with open("main_config.toml", "rb") as f: main_config = tomllib.load(f) # 推送到目标群组 whitelist = self.config["behavior"].get("group_whitelist", []) if whitelist: # 有白名单,只推送到白名单群组 target_groups = whitelist else: # 无白名单,推送到所有群聊(需要从数据库或其他地方获取) # 这里暂时只推送到白名单,避免骚扰 logger.warning("未配置群组白名单,跳过定时推送") return success_count = 0 for group_id in target_groups: try: await bot.send_image(group_id, image_path) success_count += 1 logger.info(f"已推送60秒新闻到: {group_id}") except Exception as e: logger.error(f"推送到 {group_id} 失败: {e}") logger.success(f"定时推送完成,成功: {success_count}/{len(target_groups)}") except Exception as e: logger.error(f"定时推送60秒新闻失败: {e}") def get_llm_tools(self): """返回LLM工具定义""" return [{ "type": "function", "function": { "name": "get_daily_news", "description": "获取每日60秒读懂世界新闻图片。当用户询问今日新闻、每日新闻、60秒新闻、早报等内容时调用此工具。", "parameters": { "type": "object", "properties": {}, "required": [] } } }] async def execute_llm_tool(self, tool_name: str, arguments: dict, bot: WechatHookClient, from_wxid: str) -> dict: """执行LLM工具调用""" if tool_name != "get_daily_news": return None try: logger.info(f"LLM工具调用60秒新闻: {from_wxid}") image_path = await self.fetch_news_image() if image_path: await bot.send_image(from_wxid, image_path) return { "success": True, "message": "已获取并发送今日新闻图片", "no_reply": True # 已发送图片,不需要AI再回复 } else: return { "success": False, "message": "获取新闻图片失败,请稍后重试" } except Exception as e: logger.error(f"LLM工具执行失败: {e}") return { "success": False, "message": f"执行失败: {str(e)}" }