|
|
|
|
@ -0,0 +1,255 @@
|
|
|
|
|
# 导入必要的模块
|
|
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
import jsonpickle # 用于对象序列化
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from werobot import WeRoBot # 微信机器人框架
|
|
|
|
|
from werobot.replies import ArticlesReply, Article # 微信图文回复
|
|
|
|
|
from werobot.session.filestorage import FileStorage # 文件会话存储
|
|
|
|
|
|
|
|
|
|
from djangoblog.utils import get_sha256 # 加密工具函数
|
|
|
|
|
from servermanager.api.blogapi import BlogApi # 博客API
|
|
|
|
|
from servermanager.api.commonapi import ChatGPT, CommandHandler # 聊天和命令处理
|
|
|
|
|
from .MemcacheStorage import MemcacheStorage # 自定义缓存存储
|
|
|
|
|
|
|
|
|
|
# 初始化微信机器人,从环境变量获取token,默认使用'lylinux'
|
|
|
|
|
robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN') or 'lylinux', enable_session=True)
|
|
|
|
|
|
|
|
|
|
# 初始化Memcached会话存储
|
|
|
|
|
memstorage = MemcacheStorage()
|
|
|
|
|
# 检查Memcached是否可用,如果可用则使用,否则使用文件存储
|
|
|
|
|
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):
|
|
|
|
|
"""
|
|
|
|
|
将文章列表转换为微信图文回复格式
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
articles: 文章对象列表
|
|
|
|
|
message: 微信消息对象
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
ArticlesReply: 图文回复对象
|
|
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
|
url=post.get_full_url() # 文章完整URL
|
|
|
|
|
)
|
|
|
|
|
reply.add_article(article)
|
|
|
|
|
return reply
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@robot.filter(re.compile(r"^\?.*"))
|
|
|
|
|
def search(message, session):
|
|
|
|
|
"""
|
|
|
|
|
文章搜索功能
|
|
|
|
|
触发方式: ?关键字
|
|
|
|
|
例如: ?python
|
|
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
|
"""
|
|
|
|
|
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):
|
|
|
|
|
"""天气查询功能(建设中)"""
|
|
|
|
|
return "建设中..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@robot.filter(re.compile(r'^idcard\:.*$', re.I))
|
|
|
|
|
def idcard(message, session):
|
|
|
|
|
"""身份证查询功能(建设中)"""
|
|
|
|
|
return "建设中..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@robot.handler
|
|
|
|
|
def echo(message, session):
|
|
|
|
|
"""
|
|
|
|
|
默认消息处理器
|
|
|
|
|
处理所有未匹配到其他过滤器的消息
|
|
|
|
|
"""
|
|
|
|
|
handler = MessageHandler(message, session)
|
|
|
|
|
return handler.handler()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MessageHandler:
|
|
|
|
|
"""消息处理器类,负责处理用户会话和权限验证"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, message, session):
|
|
|
|
|
userid = message.source
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
# 管理员退出命令
|
|
|
|
|
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:
|
|
|
|
|
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 = '' # 待确认的命令
|