import logging # 用于记录日志信息 import os # 用于操作系统相关功能(如环境变量、文件路径) import re # 正则表达式,用于文本匹配和提取 import jsonpickle # 将 Python 对象序列化为 JSON 字符串(支持复杂对象) from django.conf import settings # Django 配置设置,如 BASE_DIR from werobot import WeRoBot # WeRoBot 微信机器人框架核心类 from werobot.replies import ArticlesReply, Article # 构造图文回复消息 from werobot.session.filestorage import FileStorage # 文件存储会话后端(备用) # 项目内部工具与 API from djangoblog.utils import get_sha256 # SHA256 加密函数,用于密码安全 from servermanager.api.blogapi import BlogApi # 博客文章搜索/获取接口 from servermanager.api.commonapi import ChatGPT, CommandHandler # 聊天与命令处理 from .MemcacheStorage import MemcacheStorage # 自定义基于缓存的会话存储 # 初始化微信机器人实例 robot = WeRoBot( token=os.environ.get('DJANGO_WEROBOT_TOKEN') or 'lylinux', # 微信公众号 Token enable_session=True # 启用会话功能,用于维护用户状态 ) # 创建自定义的缓存会话存储实例(基于 Redis/Memcached) memstorage = MemcacheStorage() # 检查缓存存储是否可用 if memstorage.is_available: # 如果可用,使用 MemcacheStorage 作为会话后端 robot.config['SESSION_STORAGE'] = memstorage else: # 如果不可用,回退到文件存储 # 删除旧的会话文件(避免冲突或损坏) session_file = os.path.join(settings.BASE_DIR, 'werobot_session') if os.path.exists(session_file): os.remove(session_file) # 使用本地文件存储会话数据 robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session') # 实例化博客 API 和命令处理器 blogapi = BlogApi() # 提供博客文章查询服务 cmd_handler = CommandHandler() # 提供服务器命令执行功能 logger = logging.getLogger(__name__) # 获取当前模块的日志记录器 def convert_to_article_reply(articles, message): """ 将博客文章列表转换为微信图文消息回复格式。 参数: articles (list): 包含文章对象的列表(通常来自 blogapi 查询结果) message: 当前微信消息对象,用于构造回复 返回: ArticlesReply: 可直接返回给用户的图文消息回复对象 流程: 1. 创建一个图文回复对象 2. 遍历每篇文章,提取标题、摘要、首张图片、链接 3. 构造 Article 对象并添加到回复中 """ reply = ArticlesReply(message=message) from blog.templatetags.blog_tags import truncatechars_content # 导入截断内容的模板标签 for post in articles: # 使用正则从文章内容中提取第一张图片 URL(png/jpg) imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body) imgurl = imgs[0] if imgs else '' # 如果有图取第一张,否则为空 article = Article( title=post.title, description=truncatechars_content(post.body), # 截断内容作为描述 img=imgurl, url=post.get_full_url() # 文章完整 URL ) reply.add_article(article) return reply @robot.filter(re.compile(r"^\?.*")) def search(message, session): """ 处理以 '?' 开头的消息,用于搜索博客文章。 示例: "?python" 搜索包含 python 的文章 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 图文消息 或 文本提示 """ s = message.content searchstr = str(s).replace('?', '') # 去掉问号 result = blogapi.search_articles(searchstr) # 调用博客 API 搜索 if result: articles = list(map(lambda x: x.object, result)) # 提取文章对象 reply = convert_to_article_reply(articles, message) return reply else: return '没有找到相关文章。' @robot.filter(re.compile(r'^category\s*$', re.I)) def category(message, session): """ 处理 "category" 消息,返回所有文章分类目录。 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 文本消息:列出所有分类名称 """ categorys = blogapi.get_category_lists() content = ','.join(map(lambda x: x.name, categorys)) # 拼接分类名 return '所有文章分类目录:' + content @robot.filter(re.compile(r'^recent\s*$', re.I)) def recents(message, session): """ 处理 "recent" 消息,返回最新发布的文章。 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 图文消息(最新文章) 或 文本提示 """ articles = blogapi.get_recent_articles() if articles: reply = convert_to_article_reply(articles, message) return reply else: return "暂时还没有文章" @robot.filter(re.compile('^help$', re.I)) def help(message, session): """ 处理 "help" 消息,返回帮助文档。 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 文本消息:详细的使用说明和命令列表 """ return '''欢迎关注! 默认会与图灵机器人聊天~~ 你可以通过下面这些命令来获得信息 ?关键字搜索文章. 如?python. category获得文章分类目录及文章数. category-***获得该分类目录文章 如category-python recent获得最新文章 help获得帮助. weather:获得天气 如weather:西安 idcard:获得身份证信息 如idcard:61048119xxxxxxxxxx music:音乐搜索 如music:阴天快乐 PS:以上标点符号都不支持中文标点~~ ''' @robot.filter(re.compile(r'^weather\:.*$', re.I)) def weather(message, session): """ 处理 "weather:" 开头的消息(天气查询功能)。 当前为占位符,功能正在建设中。 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 文本消息:提示功能建设中 """ return "建设中..." @robot.filter(re.compile(r'^idcard\:.*$', re.I)) def idcard(message, session): """ 处理 "idcard:" 开头的消息(身份证信息查询功能)。 当前为占位符,功能正在建设中。 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 文本消息:提示功能建设中 """ return "建设中..." @robot.handler def echo(message, session): """ 默认消息处理器,当没有其他 filter 匹配时调用。 创建 MessageHandler 实例处理消息。 参数: message: 微信消息对象 session: 当前用户会话对象 返回: 处理结果(文本或图文消息) """ handler = MessageHandler(message, session) return handler.handler() class MessageHandler: """ 消息处理器类,负责处理用户消息,尤其是管理员命令和认证流程。 使用会话(session)维护用户状态(是否管理员、是否已认证等)。 """ def __init__(self, message, session): self.message = message self.session = session self.userid = message.source # 用户唯一标识(OpenID) # 尝试从会话中加载用户信息 try: info = session[self.userid] self.userinfo = jsonpickle.decode(info) # 反序列化为 WxUserInfo 对象 except Exception as e: # 如果出错(首次访问或会话丢失),创建默认用户信息 userinfo = WxUserInfo() self.userinfo = userinfo @property def is_admin(self): """判断当前用户是否为管理员""" return self.userinfo.isAdmin @property def is_password_set(self): """判断当前管理员是否已完成密码验证""" return self.userinfo.isPasswordSet def save_session(self): """ 将当前用户信息保存回会话。 使用 jsonpickle 序列化对象,并存入 session。 """ info = jsonpickle.encode(self.userinfo) self.session[self.userid] = info def handler(self): """ 核心消息处理逻辑,根据用户状态和输入内容返回相应响应。 处理流程: 1. 管理员退出登录 2. 管理员登录请求 3. 管理员密码验证 4. 执行管理员命令 5. 默认:调用 ChatGPT 进行聊天 返回: str: 要回复给用户的消息内容 """ info = self.message.content # 退出管理员模式 if self.userinfo.isAdmin and info.upper() == 'EXIT': self.userinfo = WxUserInfo() self.save_session() return "退出成功" # 请求进入管理员模式 if info.upper() == 'ADMIN': self.userinfo.isAdmin = True self.save_session() return "输入管理员密码" # 管理员密码验证阶段 if self.userinfo.isAdmin and not self.userinfo.isPasswordSet: # 获取配置中的管理员密码(SHA256 加密后) passwd = settings.WXADMIN if settings.TESTING: passwd = '123' # 测试环境下使用简单密码 # 验证用户输入的密码(双重 SHA256) if passwd.upper() == get_sha256(get_sha256(info)).upper(): self.userinfo.isPasswordSet = True self.save_session() return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助" else: # 密码错误次数限制 if self.userinfo.Count >= 3: self.userinfo = WxUserInfo() self.save_session() return "超过验证次数" self.userinfo.Count += 1 self.save_session() return "验证失败,请重新输入管理员密码:" # 管理员已认证,可执行命令 if self.userinfo.isAdmin and self.userinfo.isPasswordSet: if self.userinfo.Command != '' and info.upper() == 'Y': # 用户确认执行命令 return cmd_handler.run(self.userinfo.Command) else: if info.upper() == 'HELPME': # 显示命令帮助 return cmd_handler.get_help() # 记录待执行的命令,等待用户确认 self.userinfo.Command = info self.save_session() return "确认执行: " + info + " 命令?" # 默认行为:调用 ChatGPT 进行普通聊天 return ChatGPT.chat(info) class WxUserInfo(): """ 微信用户信息类,用于在会话中存储用户状态。 包括是否为管理员、是否已通过密码验证、尝试次数、待执行命令等。 """ def __init__(self): self.isAdmin = False # 是否请求成为管理员 self.isPasswordSet = False # 是否已通过密码验证 self.Count = 0 # 密码尝试次数 self.Command = '' # 待执行的命令