上次写了利用Python制作微信机器人,其中只实现了通过api来控制微信机器人来给自己发送消息,具体详情可查看:
利用Python制作微信机器人(一)

本篇博客主要编写如何是实现给微信机器人发送消息,并且回复自己消息。
现附上成品:

1. 验证url

整个自动回复的原理就是在给wx发送消息时候,wx后台会去调用我们自己填写的api,附带上我们发送的数据,然后可以在api中对发送的信息进行一些的处理,返回的就是我们要回复的内容。

对于要进行与机器人互动,则首先需要在企业微信管理平台上对自己的应用进行管理。设置消息接受,里边的api接受

然后在下面填上自己的后端服务url

注意,在这里写上的api名字,点保存时wx会去校验这个api。wx规定验证的逻辑为:对wx发来的数据进行解密处理,处理完成后并返回明文结果。
如果想看看详细的讲解可以跳转到 这里
下面是企业微信官方提供了解密的python3版本代码,在此我给贴了过来,并对其中的一些方法进行修改封装:

"""
Author: LiangDong
Date: 2021-11-14 13:22:41
LastEditTime: 2022-04-04 19:05:52
LastEditors: LiangDong
Description:
FilePath: /daily_send_notice/rebot/utils/decode.py
"""
import logging
import base64
import random
import hashlib
import time
import struct
from Crypto.Cipher import AES
import xml.etree.cElementTree as ET
import socketWXBizMsgCrypt_OK = 0
WXBizMsgCrypt_ValidateSignature_Error = -40001
WXBizMsgCrypt_ParseXml_Error = -40002
WXBizMsgCrypt_ComputeSignature_Error = -40003
WXBizMsgCrypt_IllegalAesKey = -40004
WXBizMsgCrypt_ValidateCorpid_Error = -40005
WXBizMsgCrypt_EncryptAES_Error = -40006
WXBizMsgCrypt_DecryptAES_Error = -40007
WXBizMsgCrypt_IllegalBuffer = -40008
WXBizMsgCrypt_EncodeBase64_Error = -40009
WXBizMsgCrypt_DecodeBase64_Error = -40010
WXBizMsgCrypt_GenReturnXml_Error = -40011"""
关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案
请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。
下载后,按照README中的“Installation”小节的提示进行pycrypto安装。
"""class FormatException(Exception):passdef throw_exception(message, exception_class=FormatException):"""my define raise exception function"""raise exception_class(message)class SHA1:"""计算企业微信的消息签名接口"""def getSHA1(self, token, timestamp, nonce, encrypt):"""用SHA1算法生成安全签名@param token:  票据@param timestamp: 时间戳@param encrypt: 密文@param nonce: 随机字符串@return: 安全签名"""try:sortlist = [token, str(timestamp), nonce, encrypt]sortlist.sort()sha = hashlib.sha1()sha.update("".join(sortlist).encode())return WXBizMsgCrypt_OK, sha.hexdigest()except Exception as e:logger = logging.getLogger()logger.exception(e)return WXBizMsgCrypt_ComputeSignature_Error, Noneclass XMLParse:"""提供提取消息格式中的密文及生成回复消息格式的接口"""# xml消息模板AES_TEXT_RESPONSE_TEMPLATE = """<xml>
<Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
<MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
<TimeStamp>%(timestamp)s</TimeStamp>
<Nonce><![CDATA[%(nonce)s]]></Nonce>
</xml>"""def extract(self, xmltext):"""提取出xml数据包中的加密消息@param xmltext: 待提取的xml字符串@return: 提取出的加密消息字符串"""try:xml_tree = ET.fromstring(xmltext)encrypt = xml_tree.find("Encrypt")return WXBizMsgCrypt_OK, encrypt.textexcept Exception as e:logger = logging.getLogger()logger.error(e)return WXBizMsgCrypt_ParseXml_Error, Nonedef generate(self, encrypt, signature, timestamp, nonce):"""生成xml消息@param encrypt: 加密后的消息密文@param signature: 安全签名@param timestamp: 时间戳@param nonce: 随机字符串@return: 生成的xml字符串"""resp_dict = {'msg_encrypt': encrypt,'msg_signaturet': signature,'timestamp': timestamp,'nonce': nonce,}resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dictreturn resp_xmlclass PKCS7Encoder():"""提供基于PKCS7算法的加解密接口"""block_size = 32def encode(self, text):""" 对需要加密的明文进行填充补位@param text: 需要进行填充补位操作的明文@return: 补齐明文字符串"""text_length = len(text)# 计算需要填充的位数amount_to_pad = self.block_size - (text_length % self.block_size)if amount_to_pad == 0:amount_to_pad = self.block_size# 获得补位所用的字符pad = chr(amount_to_pad)return text + (pad * amount_to_pad).encode()def decode(self, decrypted):"""删除解密后明文的补位字符@param decrypted: 解密后的明文@return: 删除补位字符后的明文"""pad = ord(decrypted[-1])if pad < 1 or pad > 32:pad = 0return decrypted[:-pad]class Prpcrypt(object):"""提供接收和推送给企业微信消息的加解密接口"""def __init__(self, key):# self.key = base64.b64decode(key+"=")self.key = key# 设置加解密模式为AES的CBC模式self.mode = AES.MODE_CBCdef encrypt(self, text, receiveid):"""对明文进行加密@param text: 需要加密的明文@return: 加密得到的字符串"""# 16位随机字符串添加到明文开头text = text.encode()text = self.get_random_str() + struct.pack("I", socket.htonl(len(text))) + \text + receiveid.encode()# 使用自定义的填充方式对明文进行补位填充pkcs7 = PKCS7Encoder()text = pkcs7.encode(text)# 加密cryptor = AES.new(self.key, self.mode, self.key[:16])try:ciphertext = cryptor.encrypt(text)# 使用BASE64对加密后的字符串进行编码return WXBizMsgCrypt_OK, base64.b64encode(ciphertext)except Exception as e:logger = logging.getLogger()logger.error(e)return WXBizMsgCrypt_EncryptAES_Error, Nonedef decrypt(self, text, receiveid):"""对解密后的明文进行补位删除@param text: 密文@return: 删除填充补位后的明文"""try:cryptor = AES.new(self.key, self.mode, self.key[:16])# 使用BASE64对密文进行解码,然后AES-CBC解密plain_text = cryptor.decrypt(base64.b64decode(text))except Exception as e:logger = logging.getLogger()logger.error(e)return WXBizMsgCrypt_DecryptAES_Error, Nonetry:pad = plain_text[-1]# 去掉补位字符串# pkcs7 = PKCS7Encoder()# plain_text = pkcs7.encode(plain_text)# 去除16位随机字符串content = plain_text[16:-pad]xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0])xml_content = content[4: xml_len + 4]from_receiveid = content[xml_len + 4:]except Exception as e:logger = logging.getLogger()logger.error(e)return WXBizMsgCrypt_IllegalBuffer, Noneif from_receiveid.decode('utf8') != receiveid:return WXBizMsgCrypt_ValidateCorpid_Error, Nonereturn 0, xml_contentdef get_random_str(self):""" 随机生成16位字符串@return: 16位字符串"""return str(random.randint(1000000000000000, 9999999999999999)).encode()class WXBizMsgCrypt(object):# 构造函数def __init__(self, sToken, sEncodingAESKey, sReceiveId):try:self.key = base64.b64decode(sEncodingAESKey + "=")assert len(self.key) == 32except:throw_exception("[error]: EncodingAESKey unvalid !", FormatException)# return WXBizMsgCrypt_IllegalAesKey,Noneself.m_sToken = sTokenself.m_sReceiveId = sReceiveId# 验证URL# @param sMsgSignature: 签名串,对应URL参数的msg_signature# @param sTimeStamp: 时间戳,对应URL参数的timestamp# @param sNonce: 随机串,对应URL参数的nonce# @param sEchoStr: 随机串,对应URL参数的echostr# @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效# @return:成功0,失败返回对应的错误码def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):sha1 = SHA1()ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr)if ret != 0:return ret, Noneif not signature == sMsgSignature:return WXBizMsgCrypt_ValidateSignature_Error, Nonepc = Prpcrypt(self.key)ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sReceiveId)return ret, sReplyEchoStrdef EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):# 将企业回复用户的消息加密打包# @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串# @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间# @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce# sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,# return:成功0,sEncryptMsg,失败返回对应的错误码Nonepc = Prpcrypt(self.key)ret, encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId)encrypt = encrypt.decode('utf8')if ret != 0:return ret, Noneif timestamp is None:timestamp = str(int(time.time()))# 生成安全签名sha1 = SHA1()ret, signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt)if ret != 0:return ret, NonexmlParse = XMLParse()return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):# 检验消息的真实性,并且获取解密后的明文# @param sMsgSignature: 签名串,对应URL参数的msg_signature# @param sTimeStamp: 时间戳,对应URL参数的timestamp# @param sNonce: 随机串,对应URL参数的nonce# @param sPostData: 密文,对应POST请求的数据#  xml_content: 解密后的原文,当return返回0时有效# @return: 成功0,失败返回对应的错误码# 验证安全签名xmlParse = XMLParse()ret, encrypt = xmlParse.extract(sPostData)if ret != 0:return ret, Nonesha1 = SHA1()ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt)if ret != 0:return ret, Noneif not signature == sMsgSignature:return WXBizMsgCrypt_ValidateSignature_Error, Nonepc = Prpcrypt(self.key)ret, xml_content = pc.decrypt(encrypt, self.m_sReceiveId)return ret, xml_content# todo, 填入自己上面网页生成的token
Token = ''
# todo, 填入自己上面网页生成的EncodingAESKEY
EncodingAESKEY = ''
# todo, 填入自己的的CompanyId
CompanyId = ''wxcpt = WXBizMsgCrypt(Token, EncodingAESKEY, CompanyId)def decode_url_wechat(msgSig, timeStamp, notice, echoStr):ret, sEchoStr = wxcpt.VerifyURL(msgSig, timeStamp, notice, echoStr)if(ret != 0):return("ERR: VerifyURL ret: " + str(ret))return sEchoStrdef decode_msg(msgSig, timeStamp, notice, dat):ret, Msg = wxcpt.DecryptMsg(dat, msgSig, timeStamp, notice)if ret != 0:return retreturn Msgdef assamble_res_data(msg, msgId, AgentID, nonce):timestamp = int(round(time.time()*1000))respData = "<xml><ToUserName>%s</ToUserName><FromUserName>admin</FromUserName><CreateTime>%s</CreateTime><MsgType>text</MsgType><Content>%s</Content><MsgId>%s</MsgId><AgentID>%s</AgentID></xml>" % (CompanyId, timestamp, msg, msgId, AgentID)ret, sEncryptMsg = wxcpt.EncryptMsg(respData, nonce, timestamp)if(ret != 0):return "ERR: EncryptMsg ret: " + str(ret)return sEncryptMsg

注意其中是需要把Token,EncodingAESKEY等信息改为自己的。我在注释中用todo标记了出来。然后对应的api可以这样来写:

@rp.route('/message', methods=['GET', 'POST'])
def get_message():"""description: 验证url"""msg_signature = request.args.get('msg_signature')timestamp = request.args.get('timestamp')nonce = request.args.get('nonce')enchostr = request.args.get('echostr')# 调用上述代码中封装的验证url的方法retStr = decode_url_wechat(msg_signature, timestamp, nonce, enchostr)return retStr

通过这样的方式,就可以成功通过url验证。

2.设置自动回复

企业微信有个地方设计的比较扯,就是上面验证url的逻辑,和实际上给机器人发消息回复的逻辑,并不一致。因此,在验证通过后,我们还需要重写这个api,用于实际我们的自动回复,具体的逻辑细节,还可以查看上面的微信官方链接。在此不做过多讲解,因为可以直接把这块解析什么的当黑盒处理,我们只要关注这个黑盒能否正常运行,下面是我自己实现的代码逻辑:

"""
Author: LiangDong
Date: 2021-11-14 13:20:56
LastEditTime: 2022-04-04 21:05:16
LastEditors: LiangDong
Description:
FilePath: /daily_send_notice/rebot/reply/auto_reply.py
"""
import xml.etree.cElementTree as ET
from flask.blueprints import Blueprint
from flask import request, current_app
from rebot.utils.decode import decode_url_wechat, decode_msg, assamble_res_datarp = Blueprint('reply', __name__)@rp.route('/message', methods=['GET', 'POST'])
def get_message():"""description: 返回消息param {*}return {*}"""msg_signature = request.args.get('msg_signature')timestamp = request.args.get('timestamp')nonce = request.args.get('nonce')data = request.data.decode('utf-8')# 此处用到的也是上面的那个解密方法ret = decode_msg(msg_signature, timestamp, nonce, data)ret = str(ret, encoding='utf-8')current_app.logger.info(ret)xml_tree = ET.fromstring(ret)timestamp = xml_tree.find("CreateTime").textmsgId = xml_tree.find("MsgId").textagentID = xml_tree.find("AgentID").textmsg_type = xml_tree.find("MsgType").textcontent = '解析失败'if msg_type == 'text':content = xml_tree.find("Content").textelif msg_type == 'image':content = xml_tree.find("PicUrl").textcurrent_app.logger.info('收到:%s' % content)# todo, 自定义函数,用于针对发来的信息做相应回复res_content = auto_reply_msg(content)# 对结果进行加密并返回res = assamble_res_data(res_content, msgId, agentID, nonce)current_app.logger.info('回复:%s' % res_content)return res

读者可以自己定义一个auto_reply_msg方法,来处理发来的数据。比如我从百度翻译官方申请了个免费的翻译api,这样就可以用机器人来做翻译工作,从其他网站拿到天气信息的api,来做天气预报等。

通过这样的方式,就可以直接给机器人发消息,并收到回复了。

利用Python制作微信机器人(二)相关推荐

  1. 利用Python制作微信机器人(三)实现爬取JD商品价格

    从前两篇的博客来看,目前已经实现了机器人单向给微信发消息,和与机器人进行交互式发消息,详情如下: 利用Python制作微信机器人(一)机器人单向发消息 利用Python制作微信机器人(二)与机器人进行 ...

  2. 利用Go制作微信机器人(二)回复消息

    利用Go制作微信机器人(一)发送消息 文章目录 前言 一.前置准备 二.url验证 三.消息回复 四.总结 前言 上一篇介绍了如何使用go来主动给微信发送消息.这一节主要是介绍如何接收消息并回复.对于 ...

  3. 利用Python制作微信跳一跳外挂,微信好友装逼神器!

    导语 前几天在GitHub上看到有人利用Python玩一款名为"跳一跳"的微信小程序,于是打算自己也来试一试,进群:711944363 获取微信跳一跳源码! 演示工具 电脑系统:W ...

  4. 用python做头像_如何利用python制作微信好友头像照片墙?

    这个不难,主要用到itchat和pillow这2个库,其中itchat用于获取微信好友头像照片,pillow用于拼接头像生成一个照片墙,下面我简单介绍一下实现过程,代码量不多,也很好理解,实验环境wi ...

  5. python表白代码照片墙-如何利用python制作微信好友头像照片墙?

    这个不难,主要用到itchat和pillow这2个库,其中itchat用于获取微信好友头像照片,pillow用于拼接头像生成一个照片墙,下面我简单介绍一下实现过程,代码量不多,也很好理解,实验环境wi ...

  6. python制作微信个人二维码_如何用Python制作微信好友个性签名词云图

    前言 上次查看了微信好友的位置信息,想了想,还是不过瘾,于是就琢磨起了把微信好友的个性签名拿到,然后分词,接着分析词频,最后弄出词云图来. 1.环境说明 Win10 系统下 Python3,编译器是 ...

  7. python制作微信个人二维码_Python制作微信机器人,随时陪你聊天

    之前给大家分享了怎么用 itchat 库制作微信朋友性别统计图,今天给大家介绍一个更强大的库:wxpy 库. wxpy 在 itchat 的基础上,通过大量接口优化提升了模块的易用性,并进行丰富的功能 ...

  8. python制作微信个人二维码怎么做_如何用Python制作微信的好友背景墙?

    前段时间,微信朋友圈开始出现了一种晒照片新形式,微信好友墙,即在一张大图片中展示出自己的所有微信好友的头像. 效果如下图,出于隐私考虑,这里作了模糊处理. 是不是很炫,而且这还是独一无二的,毕竟每个人 ...

  9. 利用Go制作微信机器人(一)发送消息

    这些天在学习Go,也写了几篇关于阅读Gin后端项目代码的博客.但编程这种,一定要实际上手练习,要不然都是纸上谈兵.于是就想上手自己实际写一些代码来练练手.思来想去,不知道能写些什么来练手.后来突然想到 ...

最新文章

  1. 在SQL中使用CRL函数示例
  2. SuperSocket 1.5 Documentation译文 2 ----- 实现你的AppServer和AppSession
  3. QT. 学习之路 一
  4. python全栈工程_Python全栈工程师(编码)
  5. Caffe: Vs13添加CUDA支持
  6. html表格美化代码,分享:记录一次使用纯CSS美化table表格的代码
  7. wpsppt流程图联系效果_风险隐患排查的手段—HAZOP 与检查表的区别及应用效果
  8. cocos2dx 3.x Value、Vector和Map意识
  9. 分布式任务队列--Celery的学习笔记
  10. c语言程序基本设计,C语言程序的设计基本6.ppt
  11. Android设备研发术语表
  12. SREng 使用指南(二)启动项目详细解说
  13. jvm优化_在JVM中记录世界停顿
  14. 轮播图代码,带定时器和小圆圈(易懂)
  15. 2范数和F范数的区别
  16. 【多线程】线程池的创建和参数设定
  17. up主迎来春天,开始叫马云爸爸啦,阿里巴巴通过淘宝收购B站2400万股
  18. 串口流控(CTS/RTS)使用详解
  19. 华为模拟器ensp——VLAN的配置
  20. 兔子是怎么吃掉狼的[转]

热门文章

  1. [双语阅读]世界各地庆奥巴马当选美国总统
  2. 案例+图解带你一文读懂SVG
  3. 智慧政务大数据 政务综合服务平台建设项目方案书(word)
  4. Linux 定时关机 crontab 定时执行任务
  5. Cesium:绘图工具v1.0
  6. 责任链模式以及应用场景
  7. CentOS7和CentOS8 FreeSWITCH 1.10.7 简单图形化界面7-内网的鼎兴通达FXO网关注册到公网的FreeSWITCH
  8. LTE模块连接网络调试助手(花生壳映射公网ip)
  9. Linux-11-打包压缩
  10. Linux数据库1——基本介绍