Itchat is a library that calls wechat Web API to control wechat personal account, but newly registered accounts in the past two years cannot log in to the Web version of wechat, and wechat limits the number of devices for multi-terminal login, which can only be logged in on one mobile phone and one iPad/Mac/PC. However, when itChat is used for wechat robot, it will occupy a device, which leads to wechat offline on iPad/Mac/PC, which seriously affects normal work and life. This article is an attempt to roboticize some personal accounts that cannot use wechat Web API, such as ItChat.

demand

To meet the needs of work and study, I will collect some good articles every week. Previously, links of wechat articles were copied by iPhone and pasted directly into files on Mac. Then, links of these articles were run to analyze the code to obtain the required data. However, the number of public accounts concerned is too many, wechat itself does not support the group of public accounts, can only take the way to join the wechat reading bookshelf to group the public accounts, but wechat reading does not support direct copy of the article link, so that the whole process is very complicated. In addition, wechat public accounts can only select apps of Tencent’s own system to share links. If they want to share links outside, they need to open them in Safari and then share them, which increases the operation time.

Wechat reading is ok, can be shared to external programs, with Telegram can also be very convenient to meet my needs, but wechat reading or directly shared to wechat robot is more convenient, one step less, and usually in the wechat program when you read a good article can also be directly forwarded to the robot.

So the summary of the demand is: from “wechat”, “wechat reading” directly share the public number of articles to the wechat robot, and the wechat robot will be selected to record the article to a certain place, reduce the process of time.

Solution research

The trumpet I used was used when I participated in the entrepreneurship competition last year. I wanted to attract people into the group by sending keywords to the robot account, but I found itChat could not be used at that time. Subsequently, wechat issued a series of bans on automatic marketing tools. These are not and will not be discussed in this article.

  1. Enterprise WeChat

The articles of the wechat public account can be directly shared to the enterprise wechat. I vaguely remember that I made an enterprise wechat robot to push epidemic data at the beginning of the year. I think this way is feasible. After a series of attempts, it is found that the enterprise wechat robot can only send messages but not receive messages, that is, the robot can not read the information sent by users; I changed another way of thinking and created an enterprise wechat application. I could always get the content by reading the chat records in the enterprise wechat group. However, AFTER trying, I found that the API always jumped without permission. After I checked through the API documents, I found that it was necessary to authenticate the enterprise and charge for reading the archived chat records. It took me a long time to get the result, which was angrily deleted by the enterprise wechat.

  1. Hook

The protocol of wechat Web version is the common Web API interface. Since the Web protocol is not available, you can use the protocol on other platforms. Hook is needed here. Simply speaking, it is to call the protocol of wechat itself to send and receive messages.

After a long search on GitHub, we came up with two suitable solutions, python-Wechaty and WechatPCAPI, but both had their share of gripes. The former requires participation in open source development to obtain time-limited Token or purchase it with additional payment. This Token is essentially a certificate to call its server (the server should use iPad/Mac and other wechat protocols). In my opinion, on the one hand, it needs to pay, and on the other hand, the service is not sustainable due to lack of control. In case of force majeure, services are no longer provided, migration is more troublesome; The latter is supposed to be a backup of WechatPCAPI’s original author’s repository. After actual installation and debugging, I found that the system was missing DLL files (it was related to the computer system itself, not the DLL files in the library). I found it very troublesome to debug, and the system I normally use is not Windows. Running a Windows Server on the Server and do not know what problems will appear, so give up this program.

  1. Magic change macOS wechat plug-in

When I was about to give up automation and use traditional methods, I realized that if there is a Hook for PC, there must be a Hook for Mac. I went around GitHub and found no fully functional libraries such as ItChat or WechatPCAPI except for the random advertisements. Wechatextension-formac, which supports automatic message forwarding, authentication-free login, remote Mac control, and automatic AI reply. AI autoreply is what I need, by forwarding messages to an API interface that provides other back-end capabilities.

Magic change macOS wechat plug-in

Before the magic change wechat plug-in, I searched in GitHub Issues and found that there are many similar needs, but it should be considered that it may be applied to gray and black production, marketing and other aspects of wechat strictly prohibited, has not been realized.

After that, I was still a little resistant to changing the code for a language I didn’t know at all, and I found Alfred in the wechat plugin that I hadn’t turned on before:

Alfred can send messages, query chat history, etc., which makes me feel like this plugin should be a Hook that can be called directly:

After leafing through a few simple call file, found that the plugin will provide Web service at http://127.0.0.1:52700/wechat-plugin/, or send messages, and query message can be used directly call these interfaces implemented.

BASE_URL = 'http://127.0.0.1:52700/wechat-plugin/'

USER_URL = BASE_URL + 'user' # [GET] List of recent chats.

CHAT_LOG_URL = BASE_URL + 'chatlog' [userId, count] # [userId, count]

SEND_MESSAGE_URL = BASE_URL + 'send-message' # [POST] [userId, content, srvId] (Content-type: application/x-www-form-urlencoded)

OPEN_SESSION_URL = BASE_URL + 'open-session' # [POST] [userId] (Content-type: application/x-www-form-urlencoded) # [POST] [userId] (Content-type: application/x-www-form-urlencoded)
Copy the code

:::warning

Although the wechat plugin supports multiple wechat client openings, the Alfred plugin only supports one wechat call API because the new wechat is launched with port 52700 already occupied

: : :

Sends a message to solve, but did not find a way to monitor the new message (it is possible that I didn’t find), simple guess this plugin is to query the database to get news record, so need to monitor the API content changes the way I feel too not elegant, especially on the time period set is a worth thinking problem, so take this as an alternative.

After struggling for a while, I still couldn’t accept listening for API changes to get messages, so I turned to the magic of the wechat plug-in. I searched “AIReply” for the Clone code, but did not find the relevant API address. Then I searched the submission record of the whole warehouse to find the submission status (I found it was sloppy when WRITING the article, so I could just search the keyword “.com “directly). After turning over more than ten pages of submission records, I finally found a submission record named “Ai automatic flirting”, and checked the code to confirm that the Ai called was Tencent’s.

This is very easy to do, just replace the Tencent intelligent chat API address with their own service address, Tencent intelligent chat API return method is as follows:

{
  "ret": 0."msg": "ok"."data": {
      "session": "10000"."answer": "xyzlab666"}}Copy the code

Very quickly built a Django service, after just need to WeChatExtension/WeChatExtension/Sources/Helper/YMNetWorkHelper m the URL in the address can be modified for your service:

static NSString const *AI_API = @"https://api.ai.qq.com/fcgi-bin/nlp/nlp_textchat"; # replace with your own API addressCopy the code

With this change, it is possible to automatically reply to messages, but there is still a certain distance from my needs, then make magic changes.

Now that I have found the entry position of the AI reply, I just need to find where the file was called. After searching, I find a method about using the AI reply in the WeChat+hook.m file.

#pragma mark - Other
- (void)autoReplyByAI:(AddMsg *)addMsg
{
    if(addMsg.msgType ! =1) {
         return;
    }
    
    NSString *userName = addMsg.fromUserName.string;
    
    MMSessionMgr *sessionMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("MMSessionMgr")];
    WCContactData *msgContact = nil;
    
    if (LargerOrEqualVersion(@ 2.3.26 "")) {
        msgContact = [sessionMgr getSessionContact:userName];
    } else {
        msgContact = [sessionMgr getContact:userName];
    }
    
    if ([msgContact isBrandContact] || [msgContact isSelf]) {
        // This message is sent by the public account or by myself
        return;
    }
    YMAIAutoModel *AIModel = [[YMWeChatPluginConfig sharedConfig] AIReplyModel];
    if (AIModel.specificContacts.count < 1) {
        return;
    }
    
    [AIModel.specificContacts enumerateObjectsUsingBlock:^(NSString *wxid, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([wxid isEqualToString:addMsg.fromUserName.string]) {
            
            NSString *content = @ "";
            NSString *session = @ "";
            if ([wxid containsString:@"@chatroom"]) {
                NSArray *contents = [addMsg.content.string componentsSeparatedByString:@":\n"];
                NSArray *sessions = [wxid componentsSeparatedByString:@ "@"];
                if (contents.count > 1) {
                    content = contents[1];
                }
                if (sessions.count > 1) {
                    session = sessions[0]; }}else {
                content = addMsg.content.string;
                session = wxid;
            }
            
            [[YMNetWorkHelper share] GET:content session:session success:^(NSString *content, NSString *session) {
                [[YMMessageManager shareManager] sendTextMessage:content toUsrName:addMsg.fromUserName.string delay:kArc4random_Double_inSpace(3.8)]; }]; }}]; }Copy the code

Ymmessagehelper. m is used to process messages. There is a code for parsing the applet:

+ (void)parseMiniProgramMsg:(AddMsg *)addMsg
{
    // Information 49 is displayed
    if (addMsg.msgType == 49) {
        / / XML dict
        // save the code
        if (type.intValue == 33) {// Displays applets
            // save the code
        } else if (type.intValue == 2001) {// Display the red packet information
            // save the code
        }else if (type.intValue == 2000) {// Display transfer information
            // save the code}}}Copy the code

It is speculated that the message types of type 49 belong to link sharing, small program sharing, red envelope sharing and other information. Modify WeChat+hook.m to support this type of message:

#pragma mark - Other
- (void)autoReplyByAI:(AddMsg *)addMsg
{
    if(addMsg.msgType ! =1&& addMsg.msgType ! =49) { // Support messages of type 49
         return;
}
Copy the code

With a few tweaks, the Django back end now receives information about post sharing links, but it receives data in XML format, which has too many garbage messages to reduce unnecessary consumption. I made a further change to the autoReplyByAI method:

#include "XMLReader.h" // Import header files
[AIModel.specificContacts enumerateObjectsUsingBlock:^(NSString *wxid, NSUInteger idx, BOOL * _Nonnull stop) {
        if (addMsg.msgType == 1) {
          / / the original code
        } else if (addMsg.msgType == 49) {
            NSString *msgContentStr = nil;
            NSString *session = @ "";
            if ([addMsg.fromUserName.string containsString:@"@chatroom"]) {
                NSArray *msgAry = [addMsg.content.string componentsSeparatedByString:@":\n
      ];
                NSArray *sessions = [wxid componentsSeparatedByString:@ "@"];
                if (msgAry.count > 1) {
                    msgContentStr = [NSString stringWithFormat:@ "
      ,msgAry[1]];
                } else {
                    msgAry = [addMsg.content.string componentsSeparatedByString:@":\n<msg"];
                    if (msgAry.count > 1) {
                        msgContentStr = [NSString stringWithFormat:@"<msg%@",msgAry[1]];
                    }
                    if (sessions.count > 1) {
                        session = sessions[0]; }}}else {
                msgContentStr = addMsg.content.string;
                session = wxid;
            }
            NSString *url = @ "";
            NSString *title = @ "";
            NSError *error;
            NSDictionary *xmlDict = [XMLReader dictionaryForXMLString:msgContentStr error:&error];
            NSDictionary *msgDict = [xmlDict valueForKey:@"msg"];
            NSDictionary *appMsgDict = [msgDict valueForKey:@"appmsg"];
            NSDictionary *titleDict = [appMsgDict valueForKey:@"title"];
            title = [titleDict valueForKey:@"text"];
            NSDictionary *urlDict = [appMsgDict valueForKey:@"url"];
            url = [urlDict valueForKey:@"text"];
            NSDictionary *content = @{
                @"title":title,
                @"url":url
            };
            NSError *parseError = nil;
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:content options:NSJSONWritingPrettyPrinted error:&parseError];
            NSString *str = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            [[YMNetWorkHelper share] GET:str session:session success:^(NSString *str, NSString *session) {
                [[YMMessageManager shareManager] sendTextMessage:str toUsrName:addMsg.fromUserName.string delay:kArc4random_Double_inSpace(3.8)]; }]; }}];Copy the code

The above changes will allow non-49 messages to be returned as text, and 49 messages to be returned as follows:

{
    "title": "Use SSH Key to access server"."url": "https://mp.weixin.qq.com/s/94vbeZCFWzEabHEzqIf8Zg"
}
Copy the code

:::info

When building in Xcode, you need to select your Team from the “Signing & Capabilities” option in the Target option. In addition, the wechat plugin will be installed directly after Build, no need to export

: : :

Serverless

My demand for wechat robot is not very intensive, because the time to forward articles to the robot may be concentrated in a certain period of time, and at this time, my computer is generally started, so every time after starting up, in addition to logging in my personal number, I also need to log in the robot number. This already feels like a hassle to me, so I don’t want to put services running Django locally because I don’t have a lot of instantness for replying messages, so Serverless fits my needs well. Both the client (for me) and the server are called on demand, which really increases resource utilization.

I use the cloud function service of Tencent Cloud, and the free call volume of 1 million times per month is enough. The function service only needs to choose an HTTP trigger, namely API gateway (the outbound traffic does not exceed 1 yuan /GB), and then the external service can be provided. Because don’t want to see the approximate garbled as call address, I want to binding API gateway to your own domain name, but the ai domain in China is not the case, while tencent cloud mainland area for API gateway binding custom domain names need to pass for the record, but tencent cloud and Hong Kong area, the area domain name does not need to put on record, Compared to the United States and other regions will not slow down too much access speed. The Let’s Encrypt free certificate that comes with this encrypted download can quickly make HTTPS available to the API. Here’s a quick post on the code used in the function service:

# -*- coding: utf8 -*-
import json
import requests
def main_handler(event, context) :
    print("Received event: " + json.dumps(event, indent = 2)) 
    print("Received context: " + str(context))
    URL = 'https://www.feishu.cn/flow/api/trigger-webhook/dc8085f840fcf9eab14b3dffe2320281'
    question = event['queryString'] ['question']
    try:
        question = json.loads(question)
        if 'mp.weixin.qq.com/s?' in question['url']:
            post_data = {
            'title': question['title'].'url': question['url']
        }
            r = requests.post(URL, json=post_data)
            data = {
                "ret": 0."msg": "ok"."data": {
                    "session": "10000"."answer": "Saved to Flybook file"}}except:
        data = {
            "ret": 0."msg": "ok"."data": {
                "session": "10000"."answer": "xyzlab666"}}return data

Copy the code

Fly book shortcut

The previous requirement was for the wechat robot to record the selected article to a certain place, so the flying book document is actually a good choice.

At the beginning, I also rushed to make flying book application, but after trying for a long time, I found that the permission problem was very troubled, especially the release of the application was accidentally sent to the wrong enterprise, but also three times in a row, it was really embarrassing. Making an application requires not only obtaining the permission of the login user, but also managing the validity period of the Token, which is quite troublesome.

In fact, there is a way to use the “fly book shortcut”, very convenient, but also can be statistical adjustment dosage, etc. :

Just select Webhook to trigger:

And simple configuration:

I chose to save this data in a table, but there are many other applications that I can use, such as sending flybook messages, Trello, Webhook, etc.

By doing so, the table has created this field, which generally meets my needs.

other

With this process, more keywords can be defined, more tasks can be defined, such as NLP technology with calendar reminders, sedentary reminders, and even with special commands to complete the specified tasks, such as sending specified emails. Of course, these tasks are not limited to wechat platform, other platforms may be more convenient to achieve, but wechat, as a social software, will be opened more frequently than other apps, and we can update this part after more development.