Authoring team management + Wechaty robot = unlimited possibilities

Posted by common on Fri, 21 Jan 2022 16:06:17 +0100

User story

In fact, Authing team management + Wechaty robot can realize many functions to improve the efficiency of traditional personnel management. You can even do some data analysis and statistics to assist decision-making. Here I have listed a few simple realistic scenes, hoping to help you understand.

Authing as upstream data source

Synchronize data to other platforms, including Feishu and wechat in the early stage. After creating the user, the user can log in to the flybook directly through the mobile phone number. Wechaty Bot is added to personal wechat, and user association binding is carried out through message sending mobile phone number. Create an organizational structure in authoring and synchronize the corresponding departments and wechat groups that create flybooks. Synchronously delete flybook users and wechat group chat.

The original Flying Book Organization accesses wechat

Fly book as upstream data source. Wechat adds Wechat Bot to bind users by sending mobile phone numbers through messages.
Add or delete users in the flybook (first set the synchronization frequency, such as checking every 10 minutes), and invite them to join or remove them from the corresponding wechat group. Carry out department (Organization) management in flybook, synchronously create corresponding group chat in wechat (flat, no level), and synchronize group member management (add / remove).

Original wechat group and access to flybook

Select a full wechat group to access. First, Wechaty Bot will create authoring users for all members of the group. Wechat adds Wechaty Bot or @ mentioned methods to send messages to the mobile phone number for user association binding. Members bound to the mobile phone number can log in to the flying book through the mobile phone number.
Add or delete users in the original wechat group (first set the synchronization frequency, such as checking every 10 minutes), and add or remove corresponding flybook users.

A plug-in of wechaty authoring

You can also directly understand the instructions of the plug-in through the introduction page of the plug-in. The plug-in's open source code repository is located at: https://github.com/Authing/we...

Design ideas

  • The plug-in should encapsulate some common methods to connect Wechaty and authoring
  • It is equipped with some useful tool methods to improve the development efficiency
  • It has certain scalability to facilitate the realization of some non general requirements

realization

Before starting to develop this plug-in, I did two MVP projects with Wechaty as the upstream user and authoring user pool as the upstream user. Then complete the initial version of the plug-in through some methods and functions in the project.

List some methods that Wechaty needs to use as an upstream user:

  • Check whether the Authing user pool already exists for wechat friends or users in the group
  • Check whether wechat users are bound with Authing account (mainly mobile phone number)
  • When inviting or removing group members, you can create and delete authoring users accordingly

Here are some methods that need to be used when using authoring as the upstream of users:

  • Check the friend application and message of wechat to determine whether it is an Authing user (verified by the mobile phone number of message rules)
  • Associate and bind the micro signal with the Authing user (verify the mobile phone number through the message rules)

Then, in this process, two user lists are often compared, one is Wechaty Contact [] list and the other is authoring user [] list, to judge whether the contact is an authoring user, whether the contact is invited to the group, or whether the contact needs to be removed from group chat.

Finally, the general framework of the code looks like this:

export class WechatyAuthing {
  constructor(config: WechatyAuthingConfig);
  /**
   * Get Authing SDK client
   * Get authoring SDK instance
   */
  protected get client(): _ManagementClient1;
  /**
   * Get Authing User pool name
   * @returns {Promise<string>}
   */
  getPoolName(): Promise<string>;
  /** ********* AS UPSTREAM ************* */
  /**
   * Batch check users exists from Authing
   * Check whether you are registered as an authoring user
   * @param {Contact[]} contacts Wechaty Contact[]
   * @returns {ContactsFilterResult} { registered: Contact[]; unregistered: Contact[]; fail: Contact[] }
   */
  filterAuthingUsers<T = Contact>(
    contacts: T[]
  ): Promise<ContactsFilterResult<T>>;
  /**
   * Batch create users to Authing
   * Batch create users to the authoring user pool
   * @param {Contact[]} contacts Wechaty Contact[]
   * @returns {ContactsOperationResult} {success: Contact[], fail: Contact[]}
   */
  createAuthingUsers<T = Contact>(
    contacts: T[]
  ): Promise<ContactsOperationResult<T>>;
  /**
   * Batch delete users from Authing
   * Batch delete Wechaty users from authoring user pool
   * @param {Contact[]} contacts Wechaty Contact[]
   * @returns {ContactsOperationResult} {success: Contact[], fail: Contact[]}   */
  deleteAuthingUsers<T = Contact>(
    contacts: T[]
  ): Promise<ContactsOperationResult<T>>;
  /**
   * Create or update a authing user with Wechaty contact and phone
   * Bind Contact and phone number to authoring user (or create a user)
   * @param {Contact} contact
   * @param {string} phone
   * @returns {Promise<boolean>}
   */
  bindAuthingPhone<T = Contact>(contact: T, phone: string): Promise<boolean>;
  /** ********* AS DOWNSTREAM ************* */
  /**
   * Check if user with the phone number exists in Authing
   * Check whether the mobile phone number is registered as an Authing user
   * @param {string} phone
   * @returns {Promise<boolean>}
   */
  checkPhone(phone: string): Promise<boolean>;
  /**
   * Bind Wechaty contact to a Authing user by phone number
   * User who binds Wechaty Contact to Authing mobile number
   * @param {string} phone
   * @param {Contact} contact
   * @returns {Promise<boolean>}
   */
  bindPhoneContact<T = Contact>(phone: string, contact: T): Promise<boolean>;
  /** ********* PROTECTED ************* */
  /**
   * Create Authing user
   * Creating authoring users
   * @param {Contact} contact
   * @returns {User | null}
   */
  protected createAuthingUser<T = Contact>(contact: T): Promise<User | null>;
}

In addition, when I first started POC, I used a lot of inefficient code to realize this function, such as:

// Filter out friends in users
const friends = allFriends.filter(
  (contact) => ~users.findIndex((user) => user.externalId === contact.id)
);

// Delete members and alert uncertainty
const members2Delete = memberList.filter(
  (member) => ~deletedUsers.findIndex((user) => user.externalId === member.id)
);
const members2Warning = memberList.filter(
  (member) =>
    !~deletedUsers.findIndex((user) => user.externalId === member.id) &&
    !~users.findIndex((user) => user.externalId === member.id)
);
// Check friends who are not in the group
const members2Invite = friends.filter(
  (friend) => !~memberList.findIndex((member) => member.id === friend.id)
);

On this basis, several tools and methods are optimized:

  • Get the difference Set of two arrays through the Set property
  • Get the real unique ID of Wechaty user

In fact, there are many details to pay attention to in the integration process, which I will sort out in the last chapter of the article.

test

The current code quality is A, and the test coverage is 98%. If you are interested in this plug-in, the test case is not only the best start for you to participate in the contribution, but also A great case for the use of the plug-in.

It also includes example initialization, various method calls and return value expectations, the use of tool methods, and some details of extension development.

Take the test case of the extension plug-in as an example:

//https://github.com/Authing/wechaty-authing/blob/main/test/extends.spec.ts
import { WechatyAuthing } from '../src';

describe('extension', () => {
  it('client', async () => {
    class ExtendedWechatyAuthing extends WechatyAuthing {
      async totalUsers(): Promise<number> {
        const { totalCount } = await this.client.users.list();
        return totalCount;
      }
    }
    const client = new ExtendedWechatyAuthing({
      userPoolId: process.env.AUTHING_USER_POOL_ID,
      secret: process.env.AUTHING_USER_POOL_SECRET
    });
    const count = await client.totalUsers();
    expect(count).toBeGreaterThan(0);
  });
});

Of course, the specific plug-in usage details can be viewed through the project README file, which is available in Chinese and English.

Start making some robots

In the example (from POC project), the Wechaty version used is 0.75 and the puppety is padlocal (based on iPad wechat protocol). You can access the sample code repository to download: https://github.com/Authing/we...

Use wechat group as upstream user data

Administrator invites users to join the group (human operation)

Listen to the room join event and get the list of invitees (_inviteeList_). Check the Authing user pool, filter out the list of unregistered users, register users in batch, and send a message to bind the user's mobile phone number.

Administrator kicks out group chat users (human operation)

Listen to the room leave event and get the list of removed persons (_leaverList_). Batch delete from Authing user pool. Prompt to delete successful user names (list).

If the deletion fails (caused by uncertain reasons), prompt the user name (list) that failed to delete, and ask the administrator to delete it manually.

User @ Bot mentioned message

Listening to the message event triggers. If the message is not mentioned, or the message does not contain a mobile phone number, it will not be processed.

Check whether the user exists. If so, modify the bound mobile phone number to the one entered by the user (there may be duplicate mobile phone numbers, and binding fails); If the user does not exist, register a new user.

If the binding is successful, send a message prompt.

realization

The code is located in the upstream directory of the POC project.

First, check the group members when the Bot is started:

graph LR
    Start1(Start)
    --Bot start-up--> check1[Check for non Authing user]
    --> addUser[add to Authing The user sends a message reminding to bind the mobile phone number]
    --> End1(End)

Two Wechaty events were also listened to:

  • Room join: the administrator invites users to join the group (manual operation)
  • Room leave: administrator kicks out group chat users (manual operation)
graph TD
  Start1(Start)
  --> add1[Administrators add group members artificially]
  --Administration Bot Listen for incoming events--> add2[Bot towards Authing Registered user]
  --User mention Bot Send mobile number --> add3[Bot towards Authing Update bound user mobile number]
  --> End1(End)

  Start2(Start)
  --> del1[Administrators delete group members artificially]
  --Administration Bot Listen for out of group events-->  del2[Bot towards Authing delete user]
  --> End2(End)

Outside the code

What else can wechat users do after binding to the Authing user pool? For example, synchronize enterprise member information to flybook, connect to various SSO single sign on applications, etc. It's time to use your imagination. Changing productivity may not be so far away.

The Authing user is the upstream and the Wechaty user is the downstream

The user data source may also be the user data from flybook or other identity sources in the synchronization center. In one step, after adding a new user to the authoring user pool, the user is required to actively add Bot as a friend, which must be.

When Bot starts, check whether there is a full member group in wechat. If not, create one (but if the number of enterprise members is less than 3 and group chat cannot be created, the robot will wait for friends actively added). After all members of the group exist, start a regular task to check whether there are resigned members who need to be kicked out of the group chat, and new members are invited to join the group chat.

graph TD
  Start1(Start)
  --Bot start-up--> check1{Check whether the whole group exists}
  -->|T| start2[Start 30 s polling  CronJob]
  --> End1(End)
  check1-->|F| check2{Are there more than 2 friends in the user}
  -->|T| step1[Create Group ]
  --> start2
  check2-->|F Interval 30 s review| check2

  Start2(Start)
  --> step21[Query all group members]
  --> step22[Filter out not Authing Members of the user and kicked out]
  --> step23[Filter out Authing Friends in the user who are not in the group and invite]
  --> End2(End)
  -->|30s polling | Start2

Among them, the method of associating authoring users and Wechaty contacts is also very simple. This is to send a mobile phone number through a friend request or message to check whether the Authing user pool exists.

graph TD
  Start1(Start)
  --Bot start-up--> step11[Listen for friend requests and text messages]
  -->|Friend request event or text message| step12[Accept the request and invite to the group according to the mobile phone number]
  --> End1(End)

Extend the wechaty authing plug-in to implement more complex business

Extend the use of authoring SDK

Wechaty authing provides a protected client instance (corresponding to the ManagementClient in the authing sdk).

Therefore, you can refer to the official documents of authoring for in-depth development. Example:

class ExtendedWechatyAuthing extends WechatyAuthing {
  async totalUsers(): Promise<number> {
    const { totalCount } = await this.client.users.list();
    return totalCount;
  }
}

Reference documents: https://docs.authing.cn/v2/re...

Extended Wechaty plug-in

At the same time, it can encapsulate some Wechaty plug-in functions. Example:

class ExtendedWechatyAuthing extends WechatyAuthing {
  plugin(): WechatyPlugin {
    return (bot: Wechaty): void => {
      bot.on('ready', async () => {
        const { totalCount } = await this.client.users.list();
        log.info('total users', totalCount);
      });
    };
  }
}

const authing = new ExtendedWechatyAuthing({
  userPoolId: process.env.AUTHING_USER_POOL_ID,
  secret: process.env.AUTHING_USER_POOL_SECRET
});
const bot = createBot(process.env.WECHATY_PADLOCAL_TOKEN);
bot.use(authing.plugin());

For more information on the use and development of Wechaty plug-in, please visit: https://wechaty.js.org/docs/u...

matters needing attention

Wechat restrictions

  • Maximum number of wechat friends: 5000 https://kf.qq.com/faq/161223I...
  • Actively add friends: maximum of 30 requests per day
  • Passively add friends: 180 requests per day
  • Invite to join the group: https://kf.qq.com/faq/161223u...

    • More than 40 people need the other party's consent
    • More than 100 people need the other party to open wechat payment (real name, binding card)
    • Maximum number of ordinary group 500

Wechaty Contact unique ID description

Currently known id formats:

  • external

    • $search$-weixin
    • $search$-13212341234
    • weixin / wxid_xxxx
  • Payload internal

    • $search $- not friends v3_xxxxx@stranger
    • weixin / wxid_xxxx
    • I don't know what it is. For example, qq1111, but weixin field is micro signal xxxx

Some possible problems with Wechaty

edition

Wechaty 1. There is a gap between the X version and the previous version, such as type declaration and the way to create an instance. You need to pay attention to what version your puppet supports.

Active search

There are two search methods, through mobile phone number and through micro signal:

const contact = await this.Friendship.search({ phone: user.phone! });
const contact = await this.Friendship.search({ weixin: user.externalId! });

None of the obtained results can get a unique id:

Nickname? Willin,id:  $search$-132XXXXXXXX
 Nickname? XXXX,id:  $search$-186XXXXXXXX

Nickname? Willin,id:  $search$-weixinXXXX
 Nickname? XXX,id:  $search$-weixinXXXX

Solution: if you are a friend, you can start from contact payload. ID.

Exception request failed

For example, when searching for a mobile phone number or micro signal, the parameters filled in are correct. Friendship.search often appears:

================================================================================

VError: [tid:e05dff73] request has been cancelled for reason: SERVER_ERROR: 2 UNKNOWN: [tid:e05dff73] wechat bad request error
    at Request._failAllPendingRequest (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/padlocal-client-ts/src/Request.ts:334:15)
    at ClientDuplexStreamImpl.<anonymous> (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/padlocal-client-ts/src/Request.ts:82:12)
    at ClientDuplexStreamImpl.emit (node:events:390:28)
    at ClientDuplexStreamImpl.emit (node:domain:475:12)
    at Object.onReceiveStatus (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/@grpc/grpc-js/src/client.ts:673:18)
    at Object.onReceiveStatus (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/@grpc/grpc-js/src/client-interceptors.ts:424:48)
    at /Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/@grpc/grpc-js/src/call-stream.ts:323:24
    at processTicksAndRejections (node:internal/process/task_queues:78:11)
error Command failed with exit code 1.

The information obtained is not accurate

Such as room Memberall(), this method can sometimes obtain complete group member information. Sometimes only the id list can be returned.

The complete data should be as follows:

[
  {
    _events: {},
    _eventsCount: 0,
    id: 'xxxx',
    payload: {
      id: 'xxxx',
      gender: 1,
      type: 1,
      name: 'Willin',
      avatar:
        'https://wx.qlogo.cn/mmhead/ver_1/lY8XLaibGJAiajvtTPx5kdDs2T3qGToUm0mHFTYGRzcaVydJZwnibQKMNKDzv2WosXJu2aUU8lteT1R6FCKVK3PUg/0',
      alias: '',
      weixin: '',
      city: 'Lianyungang',
      friend: true,
      province: 'Jiangsu',
      signature: 'All go  \n All to go home',
      phone: []
    }
  }
];

Sometimes this happens:

[
  {
    _events: {},
    _eventsCount: 0,
    id: 'xxxx'
  }
];

Refs

Topics: node.js Programmer wechat sso auth