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.
DjangoBlog/servermanager/robot.py

273 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 # 用于复杂对象的JSON序列化与反序列化
from django.conf import settings # 导入Django项目设置
from werobot import WeRoBot # 导入微信机器人框架
from werobot.replies import ArticlesReply, Article # 导入微信消息回复类型
from werobot.session.filestorage import FileStorage # 导入文件存储会话的类
from djangoblog.utils import get_sha256 # 导入自定义的SHA256加密工具
from servermanager.api.blogapi import BlogApi # 导入博客相关API
from servermanager.api.commonapi import ChatGPT, CommandHandler # 导入聊天机器人和命令处理工具
from .MemcacheStorage import MemcacheStorage # 导入自定义的Memcache会话存储类
# 马莹初始化微信机器人从环境变量获取token默认使用'lylinux',启用会话功能
robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN')
or 'lylinux', enable_session=True)
# 马莹实例化Memcache存储对象
memstorage = MemcacheStorage()
# 马莹检查Memcache是否可用可用则使用Memcache存储会话否则使用文件存储
if memstorage.is_available:
robot.config['SESSION_STORAGE'] = memstorage
else:
# 马莹:若文件存储的会话文件已存在则删除
if os.path.exists(os.path.join(settings.BASE_DIR, 'werobot_session')):
os.remove(os.path.join(settings.BASE_DIR, 'werobot_session'))
# 马莹:使用文件存储会话
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):
"""
将文章列表转换为微信图文消息回复
:param articles: 文章对象列表
:param message: 接收的消息对象
:return: 图文消息回复对象
"""
reply = ArticlesReply(message=message)
# 马莹:导入自定义的模板标签用于截断文章内容
from blog.templatetags.blog_tags import truncatechars_content
for post in articles:
# 马莹:从文章内容中提取第一张图片作为图文消息的封面图
imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body)
imgurl = ''
if imgs:
imgurl = imgs[0]
# 马莹:创建文章对象并添加到回复中
article = Article(
title=post.title,
description=truncatechars_content(post.body), # 马莹:截断文章内容作为描述
img=imgurl,
url=post.get_full_url() # 马莹:文章的完整链接
)
reply.add_article(article)
return reply
@robot.filter(re.compile(r"^\?.*"))
def search(message, session):
"""
处理文章搜索功能,匹配以?开头的消息
:param message: 接收的消息对象
:param session: 会话对象
:return: 搜索结果的图文回复或提示信息
"""
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不区分大小写
:param message: 接收的消息对象
:param session: 会话对象
:return: 分类目录字符串
"""
categorys = blogapi.get_category_lists() # 马莹调用博客API获取分类列表
# 马莹:将分类名称拼接为字符串返回
content = ','.join(map(lambda x: x.name, categorys))
return '所有文章分类目录:' + content
@robot.filter(re.compile(r'^recent\s*$', re.I))
def recents(message, session):
"""
处理获取最新文章的功能匹配recent不区分大小写
:param message: 接收的消息对象
:param session: 会话对象
:return: 最新文章的图文回复或提示信息
"""
articles = blogapi.get_recent_articles() # 马莹调用博客API获取最新文章
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不区分大小写
:param message: 接收的消息对象
:param session: 会话对象
:return: 帮助信息字符串
"""
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:开头的消息(不区分大小写)
:param message: 接收的消息对象
:param session: 会话对象
:return: 建设中提示
"""
return "建设中..."
@robot.filter(re.compile(r'^idcard\:.*$', re.I))
def idcard(message, session):
"""
处理身份证信息查询功能建设中匹配以idcard:开头的消息(不区分大小写)
:param message: 接收的消息对象
:param session: 会话对象
:return: 建设中提示
"""
return "建设中..."
@robot.handler
def echo(message, session):
"""
通用消息处理器,处理未被上述过滤器匹配的消息
:param message: 接收的消息对象
:param session: 会话对象
:return: 消息处理结果
"""
handler = MessageHandler(message, session)
return handler.handler()
class MessageHandler:
"""消息处理类,处理用户消息并返回相应结果"""
def __init__(self, message, session):
"""
初始化方法
:param message: 接收的消息对象
:param session: 会话对象
"""
userid = message.source # 获取用户ID
self.message = message
self.session = session
self.userid = userid
try:
# 马莹:从会话中获取用户信息,反序列化为对象
info = session[userid]
self.userinfo = jsonpickle.decode(info)
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):
"""将用户信息序列化并保存到会话中"""
info = jsonpickle.encode(self.userinfo)
self.session[self.userid] = info
def handler(self):
"""处理消息的核心方法"""
info = self.message.content # 马莹:获取消息内容
# 马莹管理员退出功能若为管理员且消息为EXIT则退出管理员模式
if self.userinfo.isAdmin and info.upper() == 'EXIT':
self.userinfo = WxUserInfo()
self.save_session()
return "退出成功"
# 马莹进入管理员模式消息为ADMIN时标记用户为管理员待验证密码
if info.upper() == 'ADMIN':
self.userinfo.isAdmin = True
self.save_session()
return "输入管理员密码"
# 马莹:管理员密码验证:若为管理员且未设置密码,则验证输入的密码
if self.userinfo.isAdmin and not self.userinfo.isPasswordSet:
passwd = settings.WXADMIN # 马莹:从设置中获取管理员密码
if settings.TESTING:
passwd = '123' # 马莹测试环境下密码为123
# 马莹验证密码双重SHA256加密后比较
if passwd.upper() == get_sha256(get_sha256(info)).upper():
self.userinfo.isPasswordSet = True # 标记密码已设置
self.save_session()
return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助"
else:
# 马莹密码错误处理超过3次则退出管理员模式
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:
# 马莹确认执行命令若之前有命令且当前消息为Y则执行命令
if self.userinfo.Command != '' and info.upper() == 'Y':
return cmd_handler.run(self.userinfo.Command)
else:
# 马莹帮助信息输入helpme获取命令帮助
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 = '' # 马莹:待执行的命令