diff --git a/plugins/weather/main.py b/plugins/weather/main.py index 942cbb7..90d788e 100644 --- a/plugins/weather/main.py +++ b/plugins/weather/main.py @@ -13,6 +13,7 @@ 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 管理器 ================= @@ -113,8 +114,9 @@ class WeatherPlugin(MessagePluginInterface): self.feature = self.register_feature() self.redis_manager = None self._config = {} - self.bot_client = None - self.task = None + # 使用统一的异步定时任务系统,避免手写休眠循环 + async_job.at_times(["08:00"])(self._execute_daily_push) + self.bot: WechatAPIClient = None def initialize(self, context: Dict[str, Any]) -> bool: """初始化""" @@ -136,14 +138,11 @@ class WeatherPlugin(MessagePluginInterface): def start(self) -> bool: self.LOG.info(f"[{self.name}] 插件已启动") self.status = PluginStatus.RUNNING - self.task = asyncio.create_task(self._daily_scheduler()) return True def stop(self) -> bool: self.LOG.info(f"[{self.name}] 插件已停止") self.status = PluginStatus.STOPPED - if self.task: - self.task.cancel() return True def can_process(self, message: Dict[str, Any]) -> bool: @@ -160,9 +159,7 @@ class WeatherPlugin(MessagePluginInterface): sender = message.get("sender") roomid = message.get("roomid", "") gbm: GroupBotManager = message.get("gbm") - bot: WechatAPIClient = message.get("bot") - - if not self.bot_client: self.bot_client = bot + self.bot: WechatAPIClient = message.get("bot") if roomid and gbm.get_group_permission(roomid, self.feature) == PermissionStatus.DISABLED: return False, "没有权限" @@ -202,7 +199,7 @@ class WeatherPlugin(MessagePluginInterface): # 反馈时告诉用户标准名 msg = f"✅ 订阅成功!\n已锁定城市:{city_info['adm1']} - {std_city_name}\n每天 08:00 推送早报。" - await bot.send_text_message(roomid or sender, msg, sender) + await self.bot.send_text_message(roomid or sender, msg, sender) # Step C: 顺便存历史 (用于明天对比) # 使用 ID 获取天气,避免再次 Geo 查询 @@ -217,7 +214,7 @@ class WeatherPlugin(MessagePluginInterface): if not self.redis_manager: return False, "数据库不可用" unique_id = f"{roomid}_{sender}" if roomid else f"private_{sender}" self.redis_manager.remove_subscription(unique_id) - await bot.send_text_message(roomid or sender, "✅ 已取消天气订阅。", sender) + await self.bot.send_text_message(roomid or sender, "✅ 已取消天气订阅。", sender) return True, "取消成功" # 3. 普通查询 (保持原逻辑,但也建议先 lookup 再查,或者复用 lookup) @@ -230,35 +227,12 @@ class WeatherPlugin(MessagePluginInterface): weather_text = await self._get_weather_text_response(input_city) if not weather_text: return False, "查询失败" - await bot.send_text_message(roomid or sender, weather_text, sender) + await self.bot.send_text_message(roomid or sender, weather_text, sender) return True, "发送成功" # ================= 定时任务系统 ================= - async def _daily_scheduler(self): - """每日 08:00 调度器""" - self.LOG.info("⏰ 天气订阅定时任务已启动...") - while self.status == PluginStatus.RUNNING: - try: - now = datetime.datetime.now() - target_time = now.replace(hour=8, minute=0, second=0, microsecond=0) - if now > target_time: - target_time += datetime.timedelta(days=1) - - wait_seconds = (target_time - now).total_seconds() - self.LOG.info(f"⏳ 下一次天气推送将在 {wait_seconds:.0f} 秒后") - - await asyncio.sleep(wait_seconds) - - if self.bot_client and self.redis_manager: - await self._execute_daily_push() - - await asyncio.sleep(60) - except asyncio.CancelledError: - break - except Exception as e: - self.LOG.error(f"定时任务异常: {e}") - await asyncio.sleep(60) + async def _execute_daily_push(self): """执行全量推送 (基于 ID 聚合)""" @@ -305,9 +279,9 @@ class WeatherPlugin(MessagePluginInterface): await asyncio.sleep(0.5) try: - if self.bot_client: - await self.bot_client.send_text_message(target_id, final_msg, - sender_id if room_id else None) + if self.bot: + await self.bot.send_at_message(target_id, final_msg, + [sender_id]) except Exception as send_e: self.LOG.error(f"推送给 {target_id} 失败: {send_e}") @@ -340,7 +314,7 @@ class WeatherPlugin(MessagePluginInterface): if life_tips: msg += "\n💡 **温馨提示**\n" + "\n".join(life_tips) + "\n" msg += f"\n👔 **穿衣**:{clothing}" - + # 增加一些趣味数据 msg += self._get_fun_stats(today) @@ -378,7 +352,7 @@ class WeatherPlugin(MessagePluginInterface): alerts.append("❄️ **冰雪天气**:路面湿滑,注意行车安全。") elif "冰雹" in text_day: alerts.append("☄️ **冰雹警报**:赶紧把车停到室内!") - + # 4. 风力 try: # windScaleDay 可能是 "1-2" 或 "3" @@ -390,12 +364,12 @@ class WeatherPlugin(MessagePluginInterface): alerts.append(f"🌬️ **大风警报**:{wind_dir}{wind_scale}级,发型要乱啦。") except: pass - + return alerts def _generate_life_tips(self, data: dict) -> List[str]: tips = [] - + # 1. 降水 (precip) precip = float(data.get('precip', 0.0)) text_day = data.get('textDay', '') @@ -415,14 +389,14 @@ class WeatherPlugin(MessagePluginInterface): tips.append("☠️ **紫外线爆表**:尽量留在室内,出门必须全副武装!") elif uv >= 6: tips.append("☂️ **紫外线强**:涂好防晒霜,戴上墨镜。") - + # 3. 湿度 (humidity) hum = int(data.get('humidity', 0)) if hum >= 90: tips.append("💧 空气像是能拧出水(湿度>90%),注意防潮除湿。") elif hum <= 20: tips.append("🌵 空气很干燥(湿度<20%),多喝水,小心静电。") - + # 4. 气压 (pressure) pres = int(data.get('pressure', 1000)) if pres < 995: @@ -440,7 +414,7 @@ class WeatherPlugin(MessagePluginInterface): sunset = data.get('sunset', '') if cloud < 20 and "晴" in text_day: tips.append(f"� 傍晚{sunset}左右可能有美丽的晚霞哦。") - + return tips def _get_fun_stats(self, data: dict) -> str: @@ -448,10 +422,10 @@ class WeatherPlugin(MessagePluginInterface): sunrise = data.get('sunrise', '-') sunset = data.get('sunset', '-') moon_phase = data.get('moonPhase', '') - moon_icon = data.get('moonPhaseIcon', '') # 假设API有这个,或者直接用 phase - + moon_icon = data.get('moonPhaseIcon', '') # 假设API有这个,或者直接用 phase + stats = [] - + # 计算白昼时长 try: sr_h, sr_m = map(int, sunrise.split(':')) @@ -471,14 +445,14 @@ class WeatherPlugin(MessagePluginInterface): def _get_clothing_advice(self, t_min: int, t_max: int) -> str: """根据最低和最高温综合给出穿衣建议""" avg_temp = (t_min + t_max) / 2 - + if t_min < -5: return "极寒!羽绒服+保暖内衣+围巾手套缺一不可。" elif t_min < 5: return "很冷,建议穿棉衣、羽绒服,里面穿毛衣。" elif t_min < 12: return "较冷,大衣或厚外套是标配。" - + if avg_temp < 18: return "凉爽,建议风衣、卫衣或薄外套,早晚保暖。" elif avg_temp < 24: @@ -547,7 +521,7 @@ class WeatherPlugin(MessagePluginInterface): data = today_fc.copy() # 补充 date 字段,方便后续处理 data["date"] = datetime.datetime.now().strftime("%Y-%m-%d") - + self.redis_manager.save_history(city_id, data) except Exception as e: self.LOG.error(f"存档失败: {e}")