插件定时能力扩展:接入天气/群总结/百科问答/成员画像并补齐周月触发器编辑
- 将 weather、message_summary、game_task、member_context 从硬编码 async_job 注册迁移为插件调度能力(get_schedule_actions/run_scheduled_action)\n- 保持原有默认时间与默认启用行为,新增执行统计结果用于后台日志展示\n- 为群总结与天气推送增加目标群范围适配,支持按后台配置选择 all/白名单/单群执行\n- 成员交互摘要支持日/周/月三类动作接入调度中心,兼容指定群与全量群刷新\n- 后台插件调度页面新增 every_week_time 与 every_month_last_day_time 的编辑支持
This commit is contained in:
@@ -4,7 +4,7 @@ import os
|
||||
import json
|
||||
import asyncio
|
||||
import datetime
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from typing import Dict, Any, List, Optional, Set, Tuple
|
||||
|
||||
from base.plugin_common.message_plugin_interface import MessagePluginInterface
|
||||
from base.plugin_common.plugin_interface import PluginStatus
|
||||
@@ -13,7 +13,6 @@ 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 wechat_ipad import WechatAPIClient
|
||||
from utils.decorator.async_job import async_job
|
||||
|
||||
|
||||
# ================= Redis 管理器 =================
|
||||
@@ -114,8 +113,6 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
self.feature = self.register_feature()
|
||||
self.redis_manager = None
|
||||
self._config = {}
|
||||
# 使用统一的异步定时任务系统,避免手写休眠循环
|
||||
async_job.at_times(["08:00"])(self._execute_daily_push)
|
||||
self.bot: WechatAPIClient = None
|
||||
|
||||
def initialize(self, context: Dict[str, Any]) -> bool:
|
||||
@@ -230,10 +227,56 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
await self.bot.send_text_message(roomid or sender, weather_text, sender)
|
||||
return True, "发送成功"
|
||||
|
||||
def get_schedule_actions(self) -> List[Dict[str, Any]]:
|
||||
"""声明天气插件支持的可调度动作。"""
|
||||
return [
|
||||
{
|
||||
"action_key": "daily_push",
|
||||
"name": "天气订阅日报推送",
|
||||
"description": "按订阅关系推送天气日报,可限定目标群范围",
|
||||
"trigger_type": "at_times",
|
||||
"trigger_config": {"time_list": ["08:00"]},
|
||||
"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 != "daily_push":
|
||||
return {
|
||||
"success": False,
|
||||
"summary": f"不支持的动作: {action_key}",
|
||||
"detail": {"action_key": action_key},
|
||||
}
|
||||
|
||||
# 调度中心会按 target_scope 解析目标群,这里仅做最终过滤。
|
||||
target_groups = context.get("target_groups") or []
|
||||
target_group_set = {str(g).strip() for g in target_groups if str(g).strip()} or None
|
||||
result = await self._execute_daily_push(allowed_group_ids=target_group_set)
|
||||
return {
|
||||
"success": bool(result.get("failed_targets", 0) == 0),
|
||||
"summary": (
|
||||
f"天气推送完成: 目标{result.get('total_targets', 0)},"
|
||||
f"成功{result.get('success_targets', 0)},失败{result.get('failed_targets', 0)}"
|
||||
),
|
||||
"detail": result,
|
||||
}
|
||||
|
||||
# ================= 定时任务系统 =================
|
||||
|
||||
async def _execute_daily_push(self):
|
||||
"""执行全量推送 (基于 ID 聚合)"""
|
||||
async def _execute_daily_push(self, allowed_group_ids: Optional[Set[str]] = None) -> Dict[str, int]:
|
||||
"""执行全量推送 (基于 ID 聚合)。
|
||||
|
||||
Args:
|
||||
allowed_group_ids: 允许发送的群ID集合;为 None 时表示不过滤群范围。
|
||||
|
||||
Returns:
|
||||
dict: 推送统计信息,便于后台调度日志展示。
|
||||
"""
|
||||
self.LOG.info("🚀 [Weather] 开始执行每日推送任务...")
|
||||
|
||||
if not self.bot:
|
||||
@@ -241,7 +284,8 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
|
||||
subs = self.redis_manager.get_all_subscriptions()
|
||||
self.LOG.info(f"📋 [Weather] 共获取到 {len(subs)} 条订阅记录")
|
||||
if not subs: return
|
||||
if not subs:
|
||||
return {"cities": 0, "total_targets": 0, "success_targets": 0, "failed_targets": 0}
|
||||
|
||||
# 1. 按 [city_id] 聚合 (真正的去重)
|
||||
# 结构: {"101250101": {"name": "长沙", "users": [...]}, ...}
|
||||
@@ -260,6 +304,10 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
self.LOG.info(f"🏙️ [Weather] 聚合为 {len(agg_map)} 个城市待处理: {[info['name'] for info in agg_map.values()]}")
|
||||
|
||||
# 2. 遍历 ID 获取天气
|
||||
total_targets = 0
|
||||
success_targets = 0
|
||||
failed_targets = 0
|
||||
|
||||
for city_id, info in agg_map.items():
|
||||
try:
|
||||
city_name = info["name"]
|
||||
@@ -287,6 +335,12 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
for user in user_list:
|
||||
room_id = user.get('room_id')
|
||||
sender_id = user.get('sender_id')
|
||||
# 若指定了目标群范围,仅向范围内群聊推送;私聊订阅在此模式下不发送。
|
||||
if allowed_group_ids is not None:
|
||||
if not room_id:
|
||||
continue
|
||||
if room_id not in allowed_group_ids:
|
||||
continue
|
||||
target_id = room_id if room_id else sender_id
|
||||
if target_id not in target_map:
|
||||
target_map[target_id] = {
|
||||
@@ -298,6 +352,7 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
target_map[target_id]['mentions'].add(sender_id)
|
||||
|
||||
self.LOG.info(f"📤 [Weather] 准备向 {len(target_map)} 个目标(群/人) 发送推送")
|
||||
total_targets += len(target_map)
|
||||
|
||||
for target_id, info in target_map.items():
|
||||
await asyncio.sleep(0.5)
|
||||
@@ -310,10 +365,13 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
# 私聊:直接发送文本
|
||||
await self.bot.send_text_message(target_id, final_msg)
|
||||
self.LOG.info(f"✅ [Weather] 推送成功 -> {target_id}")
|
||||
success_targets += 1
|
||||
else:
|
||||
self.LOG.error(f"❌ [Weather] Bot未就绪,无法推送给 -> {target_id}")
|
||||
failed_targets += 1
|
||||
except Exception as send_e:
|
||||
self.LOG.error(f"❌ [Weather] 推送给 {target_id} 失败: {send_e}")
|
||||
failed_targets += 1
|
||||
else:
|
||||
self.LOG.warning(f"⚠️ [Weather] 城市 {city_name} 分析后无推送内容(可能是数据缺失)")
|
||||
|
||||
@@ -323,6 +381,13 @@ class WeatherPlugin(MessagePluginInterface):
|
||||
except Exception as e:
|
||||
self.LOG.error(f"❌ [Weather] 处理城市ID {city_id} 发生异常: {e}")
|
||||
|
||||
return {
|
||||
"cities": len(agg_map),
|
||||
"total_targets": total_targets,
|
||||
"success_targets": success_targets,
|
||||
"failed_targets": failed_targets,
|
||||
}
|
||||
|
||||
# ================= 核心分析算法 (逻辑不变) =================
|
||||
|
||||
def _analyze_weather_change(self, city_name: str, api_data: dict, history_data: Optional[dict]) -> str:
|
||||
|
||||
Reference in New Issue
Block a user