""" 企业微信消息处理器 处理用户消息和事件,实现菜单功能 """ import time import json from typing import Optional, Dict, Any, List from datetime import datetime try: from ..core.config_manager import get_config from ..core.logger import get_logger from ..notification.wechat import send_system_notification from ..storage.postgresql import save_all_announcements_by_source_to_storage from ..storage.md_generator import generate_onu_md from ..core.models import Announcement except ImportError: try: from core.config_manager import get_config from core.logger import get_logger from notification.wechat import send_system_notification from storage.postgresql import save_all_announcements_by_source_to_storage from storage.md_generator import generate_onu_md from core.models import Announcement except ImportError as e: raise ImportError(f"消息处理器导入失败: {e}") logger = get_logger(__name__) class WeChatMessageHandler: """企业微信消息处理器""" def __init__(self): self.config = get_config() self.monitor_app = None # 菜单配置 self.menu_config = { "crawl": { "key": "crawl_now", "name": "立即爬取", "description": "立即执行一次公告爬取" }, "today_summary": { "key": "today_summary", "name": "今日总结", "description": "查看今日公告统计" }, "custom_crawl": { "key": "custom_crawl", "name": "自定义爬取", "description": "输入关键词进行爬取" } } logger.info("企业微信消息处理器初始化完成") def _get_monitor_app(self): """获取监控应用实例""" if self.monitor_app is None: # 动态导入避免循环导入 try: from ..main import GXGPMonitorApp self.monitor_app = GXGPMonitorApp() # 初始化但不启动服务器 if not self.monitor_app.initialize(): logger.error("监控应用初始化失败") return None except ImportError: logger.error("无法导入监控应用") return None return self.monitor_app def handle_event(self, event: str, event_key: Optional[str], from_user: str) -> Optional[str]: """处理事件消息""" try: logger.info(f"处理事件: {event}, key: {event_key}, user: {from_user}") if event == 'click': # 菜单点击事件 if event_key == 'crawl_now': return self._handle_crawl_now(from_user) elif event_key == 'today_summary': return self._handle_today_summary(from_user) elif event_key.startswith('custom_crawl'): return self._handle_custom_crawl(event_key, from_user) else: return self._create_text_response("未知菜单项", from_user) elif event == 'subscribe': # 关注事件 welcome_msg = """欢迎关注广西政府采购网公告监控! 我可以帮您: • 自动监控最新采购公告 • 筛选您关心的关键词信息 • 及时推送重要更新 点击下方菜单开始使用。""" return self._create_text_response(welcome_msg, from_user) elif event == 'unsubscribe': # 取消关注事件 logger.info(f"用户 {from_user} 取消关注") return None else: logger.info(f"未处理的event类型: {event}") return None except Exception as e: logger.error(f"事件处理异常: {str(e)}") return self._create_text_response("处理失败,请稍后重试", from_user) def handle_text_message(self, content: str, from_user: str) -> Optional[str]: """处理文本消息""" try: logger.info(f"处理文本消息: {content}, user: {from_user}") # 移除前后空格 content = content.strip() if content == "帮助" or content == "help": return self._handle_help(from_user) elif content.startswith("爬取"): return self._handle_manual_crawl(content, from_user) elif content.startswith("总结"): return self._handle_today_summary(from_user) elif content.startswith("关键词"): return self._handle_keyword_search(content, from_user) else: # 默认当作关键词搜索 return self._handle_keyword_search(f"关键词 {content}", from_user) except Exception as e: logger.error(f"文本消息处理异常: {str(e)}") return self._create_text_response("处理失败,请稍后重试", from_user) def handle_other_message(self, msg_type: str, from_user: str) -> Optional[str]: """处理其他类型的消息""" try: logger.info(f"处理其他消息类型: {msg_type}, user: {from_user}") if msg_type == 'image': return self._create_text_response("收到图片消息,但我只能处理文本消息", from_user) elif msg_type == 'voice': return self._create_text_response("收到语音消息,但我只能处理文本消息", from_user) else: return self._create_text_response(f"收到{msg_type}消息,暂不支持此类型", from_user) except Exception as e: logger.error(f"其他消息处理异常: {str(e)}") return self._create_text_response("处理失败,请稍后重试", from_user) def _handle_crawl_now(self, from_user: str) -> Optional[str]: """处理立即爬取菜单""" try: logger.info(f"用户 {from_user} 触发立即爬取") # 获取监控应用 app = self._get_monitor_app() if not app: return self._create_text_response("系统初始化失败,请稍后重试", from_user) # 执行爬取 result = app.run_crawl() if result.get("success"): total = result.get("total_crawled", 0) filtered = result.get("filtered", 0) saved = result.get("saved", 0) response = f"""✅ 爬取完成! 📊 统计信息: • 总共发现: {total} 条公告 • 关键词筛选: {filtered} 条 • 已保存: {saved} 条 如有匹配的公告,我会及时推送通知。""" else: error = result.get("error", "未知错误") response = f"❌ 爬取失败: {error}" return self._create_text_response(response, from_user) except Exception as e: logger.error(f"立即爬取处理异常: {str(e)}") return self._create_text_response("爬取失败,请稍后重试", from_user) def _handle_today_summary(self, from_user: str) -> Optional[str]: """处理今日总结菜单""" try: logger.info(f"用户 {from_user} 请求今日总结") # 这里可以查询今日的公告统计 # 由于数据库查询较为复杂,这里先返回简单的响应 response = """📅 今日公告统计 由于系统正在优化中,今日统计功能暂时不可用。 您可以: • 点击"立即爬取"获取最新数据 • 发送关键词进行搜索 • 发送"帮助"查看更多功能""" return self._create_text_response(response, from_user) except Exception as e: logger.error(f"今日总结处理异常: {str(e)}") return self._create_text_response("获取统计失败,请稍后重试", from_user) def _handle_custom_crawl(self, event_key: str, from_user: str) -> Optional[str]: """处理自定义爬取菜单""" try: logger.info(f"用户 {from_user} 触发自定义爬取") response = """🔍 自定义爬取 请回复您想要搜索的关键词,我将为您执行爬取并筛选相关公告。 例如: • 大化 • 信息化 • 政府采购 发送关键词开始搜索。""" return self._create_text_response(response, from_user) except Exception as e: logger.error(f"自定义爬取处理异常: {str(e)}") return self._create_text_response("操作失败,请稍后重试", from_user) def _handle_help(self, from_user: str) -> Optional[str]: """处理帮助命令""" help_text = """🤖 广西政府采购网公告监控助手 📋 菜单功能: • 立即爬取 - 执行一次公告爬取 • 今日总结 - 查看今日公告统计 • 自定义爬取 - 输入关键词搜索 💬 文本命令: • 发送关键词 - 搜索相关公告 • "爬取 [关键词]" - 指定关键词爬取 • "总结" - 查看今日统计 • "帮助" - 显示此帮助信息 📢 自动推送: 系统会自动监控最新公告,并推送匹配关键词的信息。 💡 使用提示: • 关键词支持多个,用空格分隔 • 公告按时间倒序显示 • 点击公告标题可查看详情""" return self._create_text_response(help_text, from_user) def _handle_manual_crawl(self, content: str, from_user: str) -> Optional[str]: """处理手动爬取命令""" try: # 解析关键词 parts = content.split() if len(parts) < 2: return self._create_text_response("请指定爬取关键词,例如:爬取 大化", from_user) keywords = parts[1:] logger.info(f"用户 {from_user} 手动爬取关键词: {keywords}") # 获取监控应用 app = self._get_monitor_app() if not app: return self._create_text_response("系统初始化失败,请稍后重试", from_user) # 执行爬取(手动爬取,只筛选今天的公告) result = app.run_crawl(keywords=keywords, manual_crawl=True) if result.get("success"): total = result.get("total_crawled", 0) filtered = result.get("filtered", 0) filtered_announcements = result.get("filtered_announcements", []) # 获取今天的日期范围 from datetime import datetime, date today = date.today() time_period = f"{today.strftime('%Y-%m-%d')} 00:00 至 {datetime.now().strftime('%Y-%m-%d %H:%M')}" if filtered > 0: # 生成markdown汇总消息并发送 try: from ..storage.md_generator import MarkdownGenerator from ..notification.wechat import send_system_notification # 生成markdown内容 md_generator = MarkdownGenerator() title = f"手动爬取结果 - 关键词: {' '.join(keywords)}" markdown_content = md_generator.generate_markdown(filtered_announcements, title, time_period) # 发送markdown消息 notify_success = send_system_notification( title="🔍 搜索完成", content=markdown_content ) if notify_success: # 成功发送markdown消息,返回空响应(不发送额外文本消息) response = "" else: response = f"""✅ 爬取完成! 🔍 搜索条件: • 关键词: {' '.join(keywords)} • 时间段: {time_period} 📊 统计结果: • 总共发现: {total} 条公告 • 匹配筛选: {filtered} 条 ⚠️ 公告汇总推送失败,但数据已生成。""" except Exception as notify_error: logger.error(f"生成或发送公告汇总失败: {notify_error}") # 降级处理:手动构建简单的文本响应 announcement_list = [] for i, ann in enumerate(filtered_announcements[:10], 1): # 最多显示10条 announcement_list.append(f"{i}. {ann.title[:50]}...") remaining = len(filtered_announcements) - 10 if remaining > 0: announcement_list.append(f"... 还有 {remaining} 条公告") response = f"""✅ 爬取完成! 🔍 搜索条件: • 关键词: {' '.join(keywords)} • 时间段: {time_period} 📊 统计结果: • 总共发现: {total} 条公告 • 匹配筛选: {filtered} 条 📋 匹配公告: {chr(10).join(announcement_list)} 💡 公告详情已保存,可通过其他方式查看。""" else: # 没有找到匹配的公告,发送markdown格式的空结果 try: from ..storage.md_generator import MarkdownGenerator from ..notification.wechat import send_system_notification md_generator = MarkdownGenerator() title = f"手动爬取结果 - 关键词: {' '.join(keywords)}" markdown_content = md_generator.generate_markdown([], title, time_period) notify_success = send_system_notification( title="🔍 搜索完成", content=markdown_content ) if notify_success: response = "" else: response = f"""✅ 爬取完成! 🔍 搜索条件: • 关键词: {' '.join(keywords)} • 时间段: {time_period} 📊 统计结果: • 总共发现: {total} 条公告 • 匹配筛选: 0 条 ❌ 在指定时间段内没有找到匹配的公告。""" except Exception as notify_error: logger.error(f"生成或发送公告汇总失败: {notify_error}") response = f"""✅ 爬取完成! 🔍 搜索条件: • 关键词: {' '.join(keywords)} • 时间段: {time_period} 📊 统计结果: • 总共发现: {total} 条公告 • 匹配筛选: 0 条 ❌ 在指定时间段内没有找到匹配的公告。""" else: error = result.get("error", "未知错误") response = f"❌ 爬取失败: {error}" return self._create_text_response(response, from_user) except Exception as e: logger.error(f"手动爬取处理异常: {str(e)}") return self._create_text_response("爬取失败,请稍后重试", from_user) def _handle_keyword_search(self, content: str, from_user: str) -> Optional[str]: """处理关键词搜索""" try: # 解析关键词 parts = content.split() keywords = parts[1:] if len(parts) > 1 else parts if not keywords: return self._create_text_response("请提供搜索关键词", from_user) logger.info(f"用户 {from_user} 关键词搜索: {keywords}") # 这里可以实现关键词搜索逻辑 # 目前先返回提示信息 response = f"""🔍 关键词搜索 搜索关键词: {' '.join(keywords)} 由于系统正在优化中,搜索功能暂时不可用。 您可以: • 使用"爬取 [关键词]"执行新的爬取 • 点击菜单中的"立即爬取" • 发送"帮助"查看更多功能""" return self._create_text_response(response, from_user) except Exception as e: logger.error(f"关键词搜索处理异常: {str(e)}") return self._create_text_response("搜索失败,请稍后重试", from_user) def _create_text_response(self, content: str, to_user: str) -> str: """创建文本消息响应""" timestamp = str(int(time.time())) response_xml = f""" {timestamp} """ return response_xml