Files
WechatHookBot/plugins/News60s/main.py
2025-12-03 15:48:44 +08:00

215 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)}"
}