wechaty-puppet-wechat
Version:
Puppet WeChat for Wechaty
436 lines • 17.3 kB
JavaScript
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { log, } from './config.js';
// import {
// // FriendRequestPayload,
// FriendRequestType,
// FriendRequestPayloadReceive,
// FriendRequestPayloadConfirm,
// } from 'wechaty-puppet'
const REGEX_CONFIG = {
friendConfirm: [
/^You have added (.+) as your WeChat contact. Start chatting!$/,
/^你已添加了(.+),现在可以开始聊天了。$/,
/^(.+) just added you to his\/her contacts list. Send a message to him\/her now!$/,
/^(.+)刚刚把你添加到通讯录,现在可以开始聊天了。$/,
],
roomJoinInvite: [
// There are 3 blank(charCode is 32) here. eg: You invited 管理员 to the group chat.
/^(.+?) invited (.+) to the group chat.\s+$/,
// There no no blank or punctuation here. eg: 管理员 invited 小桔建群助手 to the group chat
/^(.+?) invited (.+) to the group chat$/,
// There are 2 blank(charCode is 32) here. eg: 你邀请"管理员"加入了群聊
/^(.+?)邀请"(.+)"加入了群聊\s+$/,
// There no no blank or punctuation here. eg: "管理员"邀请"宁锐锋"加入了群聊
/^"(.+?)"邀请"(.+)"加入了群聊$/,
],
roomJoinQrcode: [
// Wechat change this, should desperate. See more in pr#651
// /^" (.+)" joined the group chat via the QR Code shared by "?(.+?)".$/,
// There are 2 blank(charCode is 32) here. Qrcode is shared by bot.
// eg: "管理员" joined group chat via the QR code you shared.
/^"(.+)" joined group chat via the QR code "?(.+?)"? shared.\s+$/,
// There are no blank(charCode is 32) here. Qrcode isn't shared by bot.
// eg: "宁锐锋" joined the group chat via the QR Code shared by "管理员".
/^"(.+)" joined the group chat via the QR Code shared by "?(.+?)".$/,
// There are 2 blank(charCode is 32) here. Qrcode is shared by bot. eg: "管理员"通过扫描你分享的二维码加入群聊
/^"(.+)"通过扫描(.+?)分享的二维码加入群聊\s+$/,
// There are 1 blank(charCode is 32) here. Qrode isn't shared by bot. eg: " 苏轼"通过扫描"管理员"分享的二维码加入群聊
/^"\s+(.+)"通过扫描"(.+?)"分享的二维码加入群聊$/,
],
// no list
roomLeaveIKickOther: [
/^(You) removed "(.+)" from the group chat$/,
/^(你)将"(.+)"移出了群聊$/,
],
roomLeaveOtherKickMe: [
/^(You) were removed from the group chat by "(.+)"$/,
/^(你)被"(.+)"移出群聊$/,
],
roomTopic: [
/^"?(.+?)"? changed the group name to "(.+)"$/,
/^"?(.+?)"?修改群名为“(.+)”$/,
],
};
export class Firer {
puppet;
constructor(puppet) {
this.puppet = puppet;
//
}
// public async checkFriendRequest(
// rawPayload : WebMessageRawPayload,
// ): Promise<void> {
// if (!rawPayload.RecommendInfo) {
// throw new Error('no RecommendInfo')
// }
// const recommendInfo: WebRecomendInfo = rawPayload.RecommendInfo
// log.verbose('PuppetWeChatFirer', 'fireFriendRequest(%s)', recommendInfo)
// if (!recommendInfo) {
// throw new Error('no recommendInfo')
// }
// const contactId = recommendInfo.UserName
// const hello = recommendInfo.Content
// const ticket = recommendInfo.Ticket
// const type = FriendRequestType.Receive
// const id = cuid()
// const payloadReceive: FriendRequestPayloadReceive = {
// id,
// contactId,
// hello,
// ticket,
// type,
// }
// this.puppet.cacheFriendRequestPayload.set(id, payloadReceive)
// this.puppet.emit('friend', id)
// }
async checkFriendConfirm(rawPayload) {
const content = rawPayload.Content;
log.silly('PuppetWeChatFirer', 'fireFriendConfirm(%s)', content);
if (!this.parseFriendConfirm(content)) {
return;
}
// const contactId = rawPayload.FromUserName
// const type = FriendRequestType.Confirm
// const id = cuid()
// const payloadConfirm: FriendRequestPayloadConfirm = {
// id,
// contactId,
// type,
// }
// this.puppet.cacheFriendRequestPayload.set(id, payloadConfirm)
this.puppet.emit('friendship', { friendshipId: rawPayload.MsgId });
}
async checkRoomJoin(rawPayload) {
const text = rawPayload.Content;
const roomId = rawPayload.FromUserName;
/**
* Get the display names of invitee & inviter
*/
let inviteeNameList;
let inviterName;
try {
[inviteeNameList, inviterName] = this.parseRoomJoin(text);
}
catch (e) {
log.silly('PuppetWeChatFirer', 'checkRoomJoin() "%s" is not a join message', text);
return false; // not a room join message
}
log.silly('PuppetWeChatFirer', 'checkRoomJoin() inviteeList: %s, inviter: %s', inviteeNameList.join(','), inviterName);
/**
* Convert the display name to Contact ID
*/
let inviterContactId;
const inviteeContactIdList = [];
if (/^You|你$/i.test(inviterName)) { // === 'You' || inviter === '你' || inviter === 'you'
inviterContactId = this.puppet.currentUserId;
}
const sleep = 1000;
const timeout = 60 * 1000;
let ttl = timeout / sleep;
let ready = true;
while (ttl-- > 0) {
log.silly('PuppetWeChatFirer', 'fireRoomJoin() retry() ttl %d', ttl);
if (!ready) {
await new Promise(resolve => setTimeout(resolve, timeout));
ready = true;
}
/**
* loop inviteeNameList
* set inviteeContactIdList
*/
for (let i = 0; i < inviteeNameList.length; i++) {
const inviteeName = inviteeNameList[i];
const inviteeContactId = inviteeContactIdList[i];
if (inviteeContactId) {
/**
* had already got ContactId for Room Member
* try to resolve the ContactPayload
*/
try {
await this.puppet.contactPayload(inviteeContactId);
}
catch (e) {
log.warn('PuppetWeChatFirer', 'fireRoomJoin() contactPayload(%s) exception: %s', inviteeContactId, e.message);
ready = false;
}
}
else {
/**
* only had Name of RoomMember
* try to resolve the ContactId & ContactPayload
*/
const memberIdList = await this.puppet.roomMemberSearch(roomId, inviteeName);
if (memberIdList.length <= 0) {
ready = false;
}
const contactId = memberIdList[0];
// XXX: Take out the first one if we have matched many contact.
inviteeContactIdList[i] = contactId;
if (!contactId) {
ready = false;
}
else {
try {
await this.puppet.contactPayload(contactId);
}
catch (e) {
ready = false;
}
}
}
}
if (!inviterContactId) {
const contactIdList = await this.puppet.roomMemberSearch(roomId, inviterName);
if (contactIdList.length > 0) {
inviterContactId = contactIdList[0];
}
else {
ready = false;
}
}
if (ready) {
log.silly('PuppetWeChatFirer', 'fireRoomJoin() resolve() inviteeContactIdList: %s, inviterContactId: %s', inviteeContactIdList.join(','), inviterContactId);
/**
* Resolve All Payload again to make sure the data is ready.
*/
await Promise.all(inviteeContactIdList
.filter(id => !!id)
.map(id => this.puppet.contactPayload(id)));
if (!inviterContactId) {
throw new Error('no inviterContactId');
}
await this.puppet.contactPayload(inviterContactId);
await this.puppet.roomPayload(roomId);
const timestamp = Math.floor(Date.now() / 1000); // in seconds
this.puppet.emit('room-join', {
inviteeIdList: inviteeContactIdList,
inviterId: inviterContactId,
roomId,
timestamp,
});
return true;
}
}
log.warn('PuppetWeChatFier', 'fireRoomJoin() resolve payload fail.');
return false;
}
/**
* You removed "Bruce LEE" from the group chat
*/
async checkRoomLeave(rawPayload) {
log.verbose('PuppetWeChatFirer', 'fireRoomLeave(%s)', rawPayload.Content);
const roomId = rawPayload.FromUserName;
let leaverName;
let removerName;
try {
[leaverName, removerName] = this.parseRoomLeave(rawPayload.Content);
}
catch (e) {
log.silly('PuppetWeChatFirer', 'fireRoomLeave() %s', e.message);
return false;
}
log.silly('PuppetWeChatFirer', 'fireRoomLeave() got leaverName: %s', leaverName);
/**
* FIXME: leaver maybe is a list
* @lijiarui: I have checked, leaver will never be a list.
* If the bot remove 2 leavers at the same time,
* it will be 2 sys message, instead of 1 sys message contains 2 leavers.
*/
let leaverContactId;
let removerContactId;
if (/^(You|你)$/i.test(leaverName)) {
leaverContactId = this.puppet.currentUserId;
}
else if (/^(You|你)$/i.test(removerName)) {
removerContactId = this.puppet.currentUserId;
}
if (!leaverContactId) {
const idList = await this.puppet.roomMemberSearch(roomId, leaverName);
leaverContactId = idList[0];
}
if (!removerContactId) {
const idList = await this.puppet.roomMemberSearch(roomId, removerName);
removerContactId = idList[0];
}
if (!leaverContactId || !removerContactId) {
throw new Error('no id');
}
/**
* FIXME: leaver maybe is a list
* @lijiarui 2017: I have checked, leaver will never be a list. If the bot remove 2 leavers at the same time,
* it will be 2 sys message, instead of 1 sys message contains 2 leavers.
* @huan 2018 May: we need to generilize the pattern for future usage.
*/
const timestamp = Math.floor(Date.now() / 1000); // in seconds
this.puppet.emit('room-leave', {
removeeIdList: [leaverContactId],
removerId: removerContactId,
roomId,
timestamp,
});
setTimeout(() => {
this.puppet.roomPayloadDirty(roomId)
.then(() => this.puppet.roomPayload(roomId))
.catch(console.error);
}, 10 * 1000); // reload the room data, especially for memberList
return true;
}
async checkRoomTopic(rawPayload) {
let topic;
let changer;
try {
[topic, changer] = this.parseRoomTopic(rawPayload.Content);
}
catch (e) { // not found
return false;
}
const roomId = rawPayload.FromUserName;
const roomPayload = await this.puppet.roomPayload(roomId);
const oldTopic = roomPayload.topic;
let changerContactId;
if (/^(You|你)$/.test(changer)) {
changerContactId = this.puppet.currentUserId;
}
else {
changerContactId = (await this.puppet.roomMemberSearch(roomId, changer))[0];
}
if (!changerContactId) {
log.error('PuppetWeChatFirer', 'fireRoomTopic() changer contact not found for %s', changer);
return false;
}
try {
const timestamp = Math.floor(Date.now() / 1000); // in seconds
this.puppet.emit('room-topic', {
changerId: changerContactId,
newTopic: topic,
oldTopic,
roomId,
timestamp,
});
return true;
}
catch (e) {
log.error('PuppetWeChatFirer', 'fireRoomTopic() co exception: %s', e.stack);
return false;
}
}
/**
* try to find FriendRequest Confirmation Message
*/
parseFriendConfirm(content) {
const reList = REGEX_CONFIG.friendConfirm;
const found = reList.some(re => re.test(content));
if (found) {
return true;
}
else {
return false;
}
}
/**
* try to find 'join' event for Room
*
* 1.
* You invited 管理员 to the group chat.
* You invited 李卓桓.PreAngel、Bruce LEE to the group chat.
* 2.
* 管理员 invited 小桔建群助手 to the group chat
* 管理员 invited 庆次、小桔妹 to the group chat
*/
parseRoomJoin(content) {
log.verbose('PuppetWeChatFirer', 'parseRoomJoin(%s)', content);
const reListInvite = REGEX_CONFIG.roomJoinInvite;
const reListQrcode = REGEX_CONFIG.roomJoinQrcode;
let foundInvite = null;
for (const re of reListInvite) {
foundInvite = content.match(re);
if (foundInvite) {
break;
}
}
// reListInvite.some(re => !!(foundInvite = content.match(re)))
let foundQrcode = [];
for (const re of reListQrcode) {
foundQrcode = content.match(re);
if (foundQrcode) {
break;
}
}
// reListQrcode.some(re => !!(foundQrcode = content.match(re)))
if ((!foundInvite || !foundInvite.length) && (!foundQrcode || !foundQrcode.length)) {
throw new Error('parseRoomJoin() not found matched re of ' + content);
}
/**
* 管理员 invited 庆次、小桔妹 to the group chat
* "管理员"通过扫描你分享的二维码加入群聊
*/
const [inviter, inviteeStr] = foundInvite ? [foundInvite[1], foundInvite[2]] : [foundQrcode[2], foundQrcode[1]];
// FIXME: should also compatible english split
const inviteeList = inviteeStr?.split(/、/) || [];
return [inviteeList, inviter]; // put invitee at first place
}
parseRoomLeave(content) {
let matchIKickOther = null;
for (const re of REGEX_CONFIG.roomLeaveIKickOther) {
matchIKickOther = content.match(re);
if (matchIKickOther) {
break;
}
}
let matchOtherKickMe = null;
for (const re of REGEX_CONFIG.roomLeaveOtherKickMe) {
matchOtherKickMe = content.match(re);
if (matchOtherKickMe) {
break;
}
}
let leaverName;
let removerName;
if (matchIKickOther && matchIKickOther.length) {
leaverName = matchIKickOther[2];
removerName = matchIKickOther[1];
}
else if (matchOtherKickMe && matchOtherKickMe.length) {
leaverName = matchOtherKickMe[1];
removerName = matchOtherKickMe[2];
}
else {
throw new Error('no match');
}
return [leaverName, removerName];
}
parseRoomTopic(content) {
const reList = REGEX_CONFIG.roomTopic;
let found = null;
for (const re of reList) {
found = content.match(re);
if (found) {
break;
}
}
if (!found || !found.length) {
throw new Error('checkRoomTopic() not found');
}
const [, changer, topic] = found;
return [topic, changer];
}
}
export default Firer;
//# sourceMappingURL=firer.js.map