Fastapi WeChat public account development brief

I applied for a WeChat official account at the end of last year. When the website was updated, I also updated the WeChat official account. The experience was mixed.

The bad:

  • The backend for editing articles does not support native markdown files;
  • After the article is published, it cannot be changed as flexibly as the website.

good aspect:

  • There are many WeChat users, and you can make money by driving traffic. Whether you can become popular depends on how talented you are. It is basically impossible for a site like mine to develop;
  • Be able to develop additional functions on the public account platform to facilitate yourself and provide some lightweight services to users.

This article mainly talks about how to use Python to develop a simple WeChat public account. The content points are:

  • Text auto-reply

  • Follow the official account and automatically reply

  • Text wrapping implementation

wechat-return1

I chose Fastapi as the framework because the backend of one of my sites implements Fastapi, including the website’s automatic update logic.

And the official account is mainly for API interaction, and Fastapi makes writing APIs simple and fast.

I won’t talk about how to register a public account and bind the server. There are a lot of them on the Internet. There is no need to write another one. Just add the code directly to save time.

Friendly reminder: The code may not necessarily run, because a lot of sensitive information related to website business has been deleted, and I am too lazy to test it. Only the important parts are shown. The code may not be beautiful, but most of it runs online.

1. WeChat signature verification

WeChat has its own set of signature algorithms to prove that the server connected to it is correct. Otherwise, if it were connected to a black server and messed up, wouldn't it cause chaos in the world? Only by passing the signature authentication, which shows that you are the correct service provider, can you provide subsequent content services.

 1
 2from fastapi import BackgroundTasks, FastAPI, Response, Request
 3import hashlib
 4import logging
 5
 6import reply
 7import receive
 8
 9WX_TOKEN = "your_token" #Your token, filled in the WeChat backend.
10
11logger = logging.getLogger("__name__")
12
13app = FastAPI()
14
15@app.get("/api/wx")
16async def handle_wx(signature, timestamp, nonce, echostr):
17     try:
18         wx_params= [WX_TOKEN, timestamp, nonce]
19         wx_params.sort()
20         hashcode = hashlib.sha1("".join(wx_params).encode('utf-8')).hexdigest()
21         logger.info(f"hashcode:{hashcode},signature:{signature}")
22         if hashcode == signature:
23             logger.info("Verification successful")
24             return int(echostr)
25         else:
26             logger.error("The encrypted string is not equal to the string returned by WeChat, verification failed!!!")
27             return "Verification failed!"
28     except Exception as error:
29         return f"An exception occurred in the WeChat server configuration verification: {error}"

I have to say here that WeChat’s official webpy document is still written in python2, which is already outdated. Anyway, just follow your own understanding and keep testing to verify that it passes.

2. Text reply

 1
 2@app.post("/api/wx")
 3async def handle_wx(request: Request):
 4     try:
 5         webData = await request.body() # Get the request body under Fastapi, which is different from webpy. After all, the framework is different and the writing method is different. Cows eat grass and people eat.
 6         print("Handle Post webdata is ", webData)
 7         recMsg = receive.parse_xml(webData) #Leave it to receive.py to parse
 8         toUser = recMsg.FromUserName
 9         fromUser = recMsg.ToUserName
10         if isinstance(recMsg, receive.Msg):
11             Content = recMsg.Content
12             Content = Content.decode("utf-8")
13             if recMsg.MsgType == 'text':
14                 if Content == "Open Sesame": # This is for users of Wine WeChat to download key files (possessed by Lei Feng, truly serving the people), the real code is not posted.
15                     pass
16                 elif Content.isnumeric() and len(Content)==6: # This is related to the stock list and returns Myanmar A information. Readers do not need to care.
17                     print("content:", Content)
18                     logger.info("Query list...")
19                     content = await check_stock(Content)
20                     logger.info("Start sending list information...")
21                     replyMsg = reply.TextMsg(toUser, fromUser, content)
22                     # return replyMsg.send()
23                     # print("reply send:", type(replyMsg.send())) #What is returned here is the string type
24                     return Response(content=replyMsg.send(), media_type="application/xml") # Be sure to specify 'application/xml', otherwise there will be problems with line breaks, this is very important! ! !
25                 else:
26                     logger.info("The text information is not matched and will not be processed yet")
27                     return "success"
28             # I haven’t thought about what picture service I need to provide at the moment. People are poor and the small station has limited bandwidth and cannot handle...
29             elif recMsg.MsgType == 'image':
30                 logger.info("The image information is not matched and will not be processed yet")
31                 return "success"
32             else:
33                 return reply.Msg().send()
34         elif isinstance(recMsg, receive.EventMsg): # Subscription and unsubscription belong to the ‘event’ type defined by WeChat
35             # Pay attention to the automatic reply content. Generally, what services are provided by the public account to return to users, such as the menu of a restaurant. Of course you have your own unique ideas, just pretend I didn't tell you.
36             if recMsg.Event == 'subscribe':
37                 logger.info("Someone else subscribed")
38                 content = "[🤗Finally waiting for you, thank you for your attention!]\n① Enter the 6-digit Myanmar A code to check whether it is marked\n② Enter 'Open Sesame' to get the temporary download address of wine WeChat related dependency packages\n③ Other functions To be developed..."
39                 replyMsg = reply.TextMsg(toUser, fromUser, content)
40                 return Response(content=replyMsg.send(), media_type="application/xml")
41             # The unsubscription message is not actually sent, but the log can still be printed.
42             if recMsg.Event == 'unsubscribe':
43                 logger.info("We lost a partner")
44                 content = "【🤝See you later, see you in the world!】\n"
45                 replyMsg = reply.TextMsg(toUser, fromUser, content)
46                 return Response(content=replyMsg.send(), media_type="application/xml")
47             else:
48                 logger.info("This event is not matched and will not be processed yet")
49                 return "success"
50         else:
51             logger.info("It is not a picture, text or event, so it will not be processed yet")
52             return reply.Msg().send()
53     except Exception as Argment:
54         return Argment

If you search online for how to change lines in WeChat official account development, you will find that many people cannot handle it and ask for help everywhere, and most of the respondents do not provide clear code examples. This can be said to be a conscientious work, and it is easy to understand at a glance.

The code is not beautiful, but it can really wrap lines, has pictures and facts, and has similar ideas in other programming languages.

wechat-return2

The information type must be specified as application/xml, otherwise it will be treated as ordinary text and cannot be line-wrapped according to WeChat rules.

receive.py and reply.py are similar to the official ones of WeChat, with only slight changes. The more code you write, the more hair you lose. You don’t have to be a fool to run code 😄

receive.py file content:

 1
 2# -*- coding: utf-8 -*-#
 3# filename: receive.py
 4import xml.etree.ElementTree as ET
 5
 6
 7def parse_xml(web_data):
 8     if len(web_data) == 0:
 9         return None
10     xmlData = ET.fromstring(web_data)
11     msg_type = xmlData.find("MsgType").text
12     if msg_type == "text":
13         return TextMsg(xmlData)
14     elif msg_type == "image":
15         return ImageMsg(xmlData)
16     elif msg_type == 'event':
17         event_type = xmlData.find('Event').text
18         if event_type == 'CLICK':
19             return Click(xmlData)
20         elif event_type in ('subscribe', 'unsubscribe'):
21             return Subscribe(xmlData)
22         #elif event_type == 'VIEW':
23             #return View(xmlData)
24         #elif event_type == 'LOCATION':
25             #return LocationEvent(xmlData)
26         #elif event_type == 'SCAN':
27             #return Scan(xmlData)
28
29
30
31class Msg(object):
32     def __init__(self, xmlData):
33         self.ToUserName = xmlData.find("ToUserName").text
34         self.FromUserName = xmlData.find("FromUserName").text
35         self.CreateTime = xmlData.find("CreateTime").text
36         self.MsgType = xmlData.find("MsgType").text
37         self.MsgId = xmlData.find("MsgId").text
38
39
40class TextMsg(Msg):
41     def __init__(self, xmlData):
42         Msg.__init__(self, xmlData)
43         self.Content = xmlData.find("Content").text.encode("utf-8")
44
45
46class ImageMsg(Msg):
47     def __init__(self, xmlData):
48         Msg.__init__(self, xmlData)
49         self.PicUrl = xmlData.find("PicUrl").text
50         self.MediaId = xmlData.find("MediaId").text
51
52
53class EventMsg(object):
54     def __init__(self, xmlData):
55         self.ToUserName = xmlData.find("ToUserName").text
56         self.FromUserName = xmlData.find("FromUserName").text
57         self.CreateTime = xmlData.find("CreateTime").text
58         self.MsgType = xmlData.find("MsgType").text
59         self.Event = xmlData.find("Event").text
60
61
62class Click(EventMsg):
63     def __init__(self, xmlData):
64         EventMsg.__init__(self, xmlData)
65         self.Eventkey = xmlData.find("EventKey").text
66
67
68class Subscribe(EventMsg):
69     def __init__(self, xmlData):
70         EventMsg.__init__(self, xmlData)
71         self.Eventkey = xmlData.find("EventKey").text

replay.py file content:

 1# -*- coding: utf-8 -*-#
 2# filename: reply.py
 3import time
 4
 5class Msg(object):
 6     def __init__(self):
 7         pass
 8
 9     def send(self):
10         return "success"
11
12class TextMsg(Msg):
13     def __init__(self, toUserName, fromUserName, content):
14         self.__dict = dict()
15         self.__dict['ToUserName'] = toUserName
16         self.__dict['FromUserName'] = fromUserName
17         self.__dict['CreateTime'] = int(time.time())
18         self.__dict['Content'] = content
19
20     def send(self):
21         XmlForm = """
22             <xml>
23                 <ToUserName><![CDATA[{ToUserName}]]></ToUserName>
24                 <FromUserName><![CDATA[{FromUserName}]]></FromUserName>
25                 <CreateTime>{CreateTime}</CreateTime>
26                 <MsgType><![CDATA[text]]></MsgType>
27                 <Content><![CDATA[{Content}]]></Content>
28             </xml>
29             """
30         return XmlForm.format(**self.__dict)
31
32class ImageMsg(Msg):
33     def __init__(self, toUserName, fromUserName, mediaId):
34         self.__dict = dict()
35         self.__dict['ToUserName'] = toUserName
36         self.__dict['FromUserName'] = fromUserName
37         self.__dict['CreateTime'] = int(time.time())
38         self.__dict['MediaId'] = mediaId
39
40     def send(self):
41         XmlForm = """
42             <xml>
43                 <ToUserName><![CDATA[{ToUserName}]]></ToUserName>
44                 <FromUserName><![CDATA[{FromUserName}]]></FromUserName>
45                 <CreateTime>{CreateTime}</CreateTime>
46                 <MsgType><![CDATA[image]]></MsgType>
47                 <Image>
48                 <MediaId><![CDATA[{MediaId}]]></MediaId>
49                 </Image>
50             </xml>
51             """
52         return XmlForm.format(**self.__dict)

The above code is very simple and practical. If you really can’t understand it, you can search various documents first and then contact us through the WeChat official account.

If I implement a function worth sharing in the future, I will write a follow-up chapter.

Lastmod: Friday, January 19, 2024

See Also:

Translations: