Files
abot/plugins/daily_news/main.py
liuwei bb73d07809 重构:新增定时插件业务逻辑内聚到各自插件目录
- daily_news 插件内置百度新闻与60s图片获取逻辑,移除对 base.func_news 的业务依赖\n- epic_free 插件内置周五判断与免费游戏抓取逻辑,移除对 base.func_epic 的业务依赖\n- daily_ranking 插件内置排行生成与积分奖励逻辑,不再依赖 MessageStorage 业务封装\n- sehuatang_push 改为引用插件目录内的抓取与PDF生成实现,将核心业务代码迁入插件目录\n- 确保新插件可独立承载自身业务逻辑,平台层仅提供调度与基础设施能力
2026-04-16 16:16:07 +08:00

192 lines
7.0 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.
# -*- coding: utf-8 -*-
import asyncio
import base64
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
import requests
from base.plugin_common.message_plugin_interface import MessagePluginInterface
from base.plugin_common.plugin_interface import PluginStatus
from utils.robot_cmd.robot_command import GroupBotManager
from wechat_ipad.models.appmsg_xml import LINK_XML_NEWS
class DailyNewsPlugin(MessagePluginInterface):
"""每日新闻定时插件。"""
FEATURE_KEY = "DAILY_NEWS"
FEATURE_DESCRIPTION = "📰 每日新闻自动播报 [每日8:30定时发送]"
@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 "ABOT Team"
@property
def commands(self) -> List[str]:
# 该插件只负责后台调度,不处理前台命令。
return []
@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.feature = self.register_feature()
def initialize(self, context: Dict[str, Any]) -> bool:
self.LOG.debug(f"正在初始化 {self.name} 插件...")
return True
def start(self) -> bool:
self.status = PluginStatus.RUNNING
return True
def stop(self) -> bool:
self.status = PluginStatus.STOPPED
return True
def can_process(self, message: Dict[str, Any]) -> bool:
return False
async def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
return False, None
def get_schedule_actions(self) -> List[Dict[str, Any]]:
"""声明插件可调度动作。"""
return [
{
"action_key": "baidu_news_daily_push",
"name": "百度新闻日报推送",
"description": "每天推送百度新闻文本、60秒新闻图和资讯卡片",
"trigger_type": "at_times",
"trigger_config": {"time_list": ["08:30"]},
"target_scope": "all_enabled_groups",
"target_config": {},
"payload": {},
"default_enabled": True,
}
]
async def run_scheduled_action(self, action_key: str, context: Dict[str, Any]) -> Dict[str, Any]:
if action_key != "baidu_news_daily_push":
return {
"success": False,
"summary": f"不支持的动作: {action_key}",
"detail": {"action_key": action_key},
}
if not self.bot:
return {"success": False, "summary": "bot 未注入", "detail": {}}
target_groups = [str(g).strip() for g in (context.get("target_groups") or []) if str(g).strip()]
if not target_groups:
# 兜底:当后台未指定范围时,直接按群功能开关收集目标群。
target_groups = [
gid for gid in GroupBotManager.get_group_list()
if GroupBotManager.get_group_permission(gid, self.feature).value == "enabled"
]
if not target_groups:
return {"success": False, "summary": "没有可推送目标群", "detail": {"target_count": 0}}
try:
# 新闻抓取逻辑内聚在插件内,避免依赖外部业务模块。
text_news = await asyncio.to_thread(self._get_baidu_news)
image_url = await asyncio.to_thread(self._get_news_60s_image)
except Exception as e:
return {"success": False, "summary": f"新闻抓取失败: {e}", "detail": {"error": str(e)}}
# 图片接口返回 URL统一下载为 base64 再发送,兼容 wechat_ipad 图片发送接口。
image_base64 = ""
if image_url:
try:
image_base64 = await asyncio.to_thread(self._download_image_as_base64, image_url)
except Exception as e:
self.LOG.warning(f"每日新闻图片下载失败,将仅发送文本和卡片: {e}")
success_groups = []
failed_groups = {}
for gid in target_groups:
try:
if text_news:
await self.bot.send_text_message(gid, text_news)
if image_base64:
await self.bot.send_image_message(gid, image_base64)
await self.bot.send_link_xml_message(LINK_XML_NEWS, gid)
success_groups.append(gid)
except Exception as e:
failed_groups[gid] = str(e)
return {
"success": len(failed_groups) == 0,
"summary": f"每日新闻推送完成: 成功{len(success_groups)}群, 失败{len(failed_groups)}",
"detail": {
"target_count": len(target_groups),
"success_groups": success_groups,
"failed_groups": failed_groups,
},
}
@staticmethod
def _download_image_as_base64(url: str) -> str:
"""下载图片并转为 base64便于统一发送。"""
resp = requests.get(url, timeout=15)
resp.raise_for_status()
return base64.b64encode(resp.content).decode("utf-8")
@staticmethod
def _get_baidu_news() -> str:
"""获取百度热榜文本(插件内实现)。"""
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) "
"Gecko/20100101 Firefox/110.0"
)
}
url = "https://top.baidu.com/api/board?platform=wise&tab=realtime"
now = datetime.now()
current_date = now.strftime("%Y年%m月%d")
weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
output = f"当前日期:{current_date} {weekdays[now.weekday()]}\n\n"
resp = requests.get(url, headers=headers, timeout=15)
resp.raise_for_status()
post = resp.json()
cards = post.get("data", {}).get("cards", [])
index = 1
for card in cards:
for block in card.get("content", []):
for article in block.get("content", []):
if isinstance(article, dict) and "word" in article:
title = str(article.get("word", "")).strip().replace(" ", "_")
output += f"{index} :#{title}\n"
index += 1
return output
@staticmethod
def _get_news_60s_image() -> Optional[str]:
"""获取 60s 新闻图片地址(插件内实现)。"""
api_url = "http://192.168.2.32:4399/v2/60s"
resp = requests.get(api_url, timeout=15)
resp.raise_for_status()
data = resp.json()
return (data or {}).get("data", {}).get("image")