You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
djangoBlogStudy/src/servermanager/robot.py

338 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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:
# 使用正则从文章内容中提取第一张图片 URLpng/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 = '' # 待执行的命令