wechaty-puppet-official-account
Version:
Wechaty Puppet for WeChat Official Accounts
627 lines • 23.7 kB
JavaScript
"use strict";
/**
* 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.
*
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PuppetOA = void 0;
const PUPPET = __importStar(require("wechaty-puppet"));
const file_box_1 = require("file-box");
const config_js_1 = require("./config.js");
const official_account_js_1 = require("./official-account/official-account.js");
class PuppetOA extends PUPPET.Puppet {
messageLocation(_messageId) {
throw new Error('Method not implemented.');
}
messageSendLocation(_conversationId, _locationPayload) {
throw new Error('Method not implemented.');
}
contactPhone(contactId, phoneList) {
config_js_1.log.info('contactPhone(%s, %s)', contactId, phoneList);
throw new Error('Method not implemented.');
}
contactCorporationRemark(contactId, corporationRemark) {
config_js_1.log.info('contactCorporationRemark(%s, %s)', contactId, corporationRemark);
throw new Error('Method not implemented.');
}
contactDescription(contactId, description) {
config_js_1.log.info('contactDescription(%s, %s)', contactId, description);
throw new Error('Method not implemented.');
}
static VERSION = config_js_1.VERSION;
appId;
appSecret;
port;
token;
webhookProxyUrl;
personalMode;
accessTokenProxyUrl;
oa;
_heartBeatTimer;
constructor(options = {}) {
super();
config_js_1.log.verbose('PuppetOA', 'constructor()');
options = {
...(0, config_js_1.envOptions)(),
...options,
};
if (options.appId) {
this.appId = options.appId;
}
else {
throw new Error(`
PuppetOA: appId not found. Please either set the WECHATY_PUPPET_OA_APP_ID environment variable, or set 'appId' optoins for PuppetOA.
`);
}
if (options.appSecret) {
this.appSecret = options.appSecret;
}
else {
throw new Error(`
PuppetOA: appSecret not found. Please either set the WECHATY_PUPPET_OA_APP_SECRET environment variable, or set 'appSecret' options for PuppetOA.
`);
}
if (options.token) {
this.token = options.token;
}
else {
throw new Error(`
PuppetOA: token not found. Please either set WECHATY_PUPPET_OA_TOKEN environment variabnle, or set 'token' options for PuppetOA.
`);
}
if (options.personalMode) {
this.personalMode = options.personalMode;
}
else {
this.personalMode = false;
}
this.port = options.port;
this.webhookProxyUrl = options.webhookProxyUrl;
/**
* NOTE: if the ip address of server is dynamic, it can't fetch the accessToken from tencent server.
* So, the accessTokenProxyUrl configuration is needed to fetch the accessToken from the specific endpoint.
*
* eg: accessTokenProxyUrl = 'http://your-endpoint/'
* puppet-oa will fetch accessToken from: http://your-endpoint/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}
*/
if (options.accessTokenProxyUrl) {
if (options.accessTokenProxyUrl.endsWith('/')) {
options.accessTokenProxyUrl = options.accessTokenProxyUrl.substring(0, options.accessTokenProxyUrl.length - 1);
}
this.accessTokenProxyUrl = options.accessTokenProxyUrl;
}
}
version() {
return config_js_1.VERSION;
}
async onStart() {
config_js_1.log.verbose('PuppetOA', 'onStart()');
this.oa = new official_account_js_1.OfficialAccount({
appId: this.appId,
appSecret: this.appSecret,
personalMode: this.personalMode,
port: this.port,
token: this.token,
webhookProxyUrl: this.webhookProxyUrl,
});
this.bridgeEvents(this.oa);
await this.oa.start();
await this._startPuppetHeart(true);
// FIXME: Huan(202008) find a way to get the bot user information
// Official Account Info can be customized by user, so It should be
// configured by environment variable.
// set gh_ prefix to identify the official-account
const currentUserId = `gh_${this.appId}`;
await this.oa.payloadStore.setContactPayload(currentUserId, { openid: currentUserId });
this.login(currentUserId);
this.emit('ready', { data: 'ready' });
}
async _startPuppetHeart(firstTime = true) {
if (firstTime && this._heartBeatTimer) {
return;
}
this.emit('heartbeat', { data: 'heartbeat@office: live' });
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this._heartBeatTimer = setTimeout(async () => {
await this._startPuppetHeart(false);
return undefined;
}, 15 * 1000); // 15s
}
// 停止监听心跳
_stopPuppetHeart() {
if (!this._heartBeatTimer) {
return;
}
clearTimeout(this._heartBeatTimer);
this._heartBeatTimer = undefined;
}
bridgeEvents(oa) {
oa.on('message', msg => this.emit('message', { messageId: msg.MsgId }));
oa.on('login', _ => this.login(this.currentUserId));
oa.on('ready', _ => this.emit('ready', { data: 'ready' }));
oa.on('logout', _ => this.wrapAsync(this.logout('oa.on(logout)')));
}
async onStop() {
config_js_1.log.verbose('PuppetOA', 'onStop()');
if (this.oa) {
this.oa.removeAllListeners();
await this.oa.stop();
this.oa = undefined;
await this.logout('oa.on(logout)');
}
this._stopPuppetHeart();
}
ding(data) {
config_js_1.log.silly('PuppetOA', 'ding(%s)', data || '');
// FIXME: do the real job
setTimeout(() => this.emit('dong', { data: data || '' }), 1000);
}
/**
*
* ContactSelf
*
*
*/
async contactSelfQRCode() {
config_js_1.log.verbose('PuppetOA', 'contactSelfQRCode()');
return 'qrcode in the future ;^)';
}
async contactSelfName(name) {
config_js_1.log.verbose('PuppetOA', 'contactSelfName(%s)', name);
}
async contactSelfSignature(signature) {
config_js_1.log.verbose('PuppetOA', 'contactSelfSignature(%s)', signature);
}
async contactAlias(contactId, alias) {
config_js_1.log.verbose('PuppetOA', 'contactAlias(%s, %s)', contactId, alias);
/**
* 1. set
*/
if (alias) {
await this.oa?.updateContactRemark(contactId, alias);
return alias;
}
/**
* 2. get
*/
const contactPayload = await this.contactPayload(contactId);
if (!contactPayload.alias) {
config_js_1.log.warn('Contact<%s> has no alias', contactId);
}
return contactPayload.alias;
}
async contactList() {
config_js_1.log.verbose('PuppetOA', 'contactList()');
const contactIdList = await this.oa?.getContactList();
if (!contactIdList) {
throw new Error('contactIdList found from oa store');
}
return contactIdList;
}
async contactAvatar(contactId, file) {
config_js_1.log.verbose('PuppetOA', 'contactAvatar(%s)', contactId);
/**
* 1. set
*/
if (file) {
return;
}
/**
* 2. get
*/
const contactPayload = await this.contactPayload(contactId);
const fileBox = contactPayload.avatar ? file_box_1.FileBox.fromUrl(contactPayload.avatar) : undefined;
if (fileBox) {
return fileBox;
}
}
async contactRawPayloadParser(oaPayload) {
const payload = {
alias: oaPayload.remark,
avatar: oaPayload.headimgurl,
city: oaPayload.city,
friend: true,
gender: oaPayload.sex,
id: oaPayload.openid,
name: oaPayload.nickname,
phone: [],
province: oaPayload.province,
signature: '',
star: false,
type: PUPPET.types.Contact.Individual,
weixin: oaPayload.unionid,
};
return payload;
}
async contactRawPayload(id) {
config_js_1.log.verbose('PuppetOA', 'contactRawPayload(%s)', id);
const contactInfoPayload = await this.oa?.getContactPayload(id);
if (!contactInfoPayload) {
throw new Error(`can not get PUPPET.payloads.Contact(${id})`);
}
return contactInfoPayload;
}
/**
*
* Message
*
*/
async messageContact(messageId) {
config_js_1.log.verbose('PuppetOA', 'messageContact(%s)', messageId);
// const attachment = this.mocker.MockMessage.loadAttachment(messageId)
// if (attachment instanceof ContactMock) {
// return attachment.id
// }
return '';
}
async messageImage(messageId, imageType) {
config_js_1.log.verbose('PuppetOA', 'messageImage(%s, %s[%s])', messageId, imageType, PUPPET.types.Image[imageType]);
// const attachment = this.mocker.MockMessage.loadAttachment(messageId)
// if (attachment instanceof FileBox) {
// return attachment
// }
const payload = await this.messagePayload(messageId);
let fileBox;
if (payload.type === PUPPET.types.Message.Image) {
if (!payload.filename) {
throw Error(`image message type must have filename file. <${payload}>`);
}
fileBox = file_box_1.FileBox.fromUrl(payload.filename);
}
else {
throw Error('can"t get file from the message');
}
return fileBox;
}
async messageRecall(messageId) {
config_js_1.log.verbose('PuppetOA', 'messageRecall(%s)', messageId);
return false;
}
async messageFile(id) {
config_js_1.log.verbose('PuppetOA', 'messageFile(%s)', id);
const payload = await this.messagePayload(id);
switch (payload.type) {
case PUPPET.types.Message.Image:
if (!payload.filename) {
throw Error(`Image message must have filename. <${payload}>`);
}
return file_box_1.FileBox.fromUrl(payload.filename);
case PUPPET.types.Message.Audio:
if (!payload.filename) {
throw Error(`Audio message must have filename. <${payload}>`);
}
// payload.filename is an URL to the audio file. The name of the file is not in the URL.
// Setting a filename with expected extension is necessary for inference of mime type in
// FileBox.
return file_box_1.FileBox.fromUrl(payload.filename, 'message.amr');
default:
throw Error('can"t get file from the message');
}
}
async messageUrl(messageId) {
config_js_1.log.verbose('PuppetOA', 'messageUrl(%s)', messageId);
// const attachment = this.mocker.MockMessage.loadAttachment(messageId)
// if (attachment instanceof UrlLink) {
// return attachment.payload
// }
return {
title: 'mock title for ' + messageId,
url: 'https://mock.url',
};
}
async messageMiniProgram(messageId) {
config_js_1.log.verbose('PuppetOA', 'messageMiniProgram(%s)', messageId);
// const attachment = this.mocker.MockMessage.loadAttachment(messageId)
// if (attachment instanceof MiniProgram) {
// return attachment.payload
// }
return {
title: 'mock title for ' + messageId,
};
}
async messageRawPayloadParser(rawPayload) {
const payload = {
id: rawPayload.MsgId,
listenerId: rawPayload.ToUserName,
talkerId: rawPayload.FromUserName,
timestamp: parseInt(rawPayload.CreateTime),
type: PUPPET.types.Message.Text,
};
if (rawPayload.MsgType === 'image') {
payload.type = PUPPET.types.Message.Image;
if (!rawPayload.PicUrl) {
throw Error(`Image Payload must has PicUrl field :<${JSON.stringify(rawPayload)}>`);
}
payload.filename = rawPayload.PicUrl;
}
else if (rawPayload.MsgType === 'video') {
payload.type = PUPPET.types.Message.Video;
}
else if (rawPayload.MsgType === 'location') {
payload.type = PUPPET.types.Message.Location;
}
else if (rawPayload.MsgType === 'text') {
payload.text = rawPayload.Content;
}
else if (rawPayload.MsgType === 'event') {
payload.text = rawPayload.EventKey;
}
else if (rawPayload.MsgType === 'voice') {
payload.type = PUPPET.types.Message.Audio;
payload.filename = await this.oa?.getAudioUrl(rawPayload.MediaId);
}
return payload;
}
async messageRawPayload(id) {
config_js_1.log.verbose('PuppetOA', 'messageRawPayload(%s)', id);
const payload = await this.oa?.payloadStore.getMessagePayload(id);
if (!payload) {
throw new Error('payload not found from oa store');
}
return payload;
}
async _messageSend(conversationId, something, // | Attachment
mediatype = 'image') {
config_js_1.log.verbose('PuppetOA', 'messageSend(%s, %s)', conversationId, something);
let msgId = null;
if (typeof something === 'string') {
const payload = {
content: something,
msgtype: 'text',
touser: conversationId,
};
if (this.personalMode) {
msgId = await this.oa?.sendCustomMessagePersonal(payload);
if (!msgId) {
throw new Error('can"t send personal CustomeMessage');
}
}
else {
msgId = await this.oa?.sendCustomMessage(payload);
}
}
else if (file_box_1.FileBox.valid(something)) {
await something.ready();
msgId = await this.oa?.sendFile({ file: something, msgtype: mediatype, touser: conversationId });
}
if (!msgId) {
throw new Error('PuppetOA messageSend() can"t get msgId response');
}
return msgId;
}
async messageSendText(conversationId, text) {
return this._messageSend(conversationId, text);
}
async messageSendFile(conversationId, file) {
let msgtype;
const mimeType = file.mediaType;
switch (mimeType) {
case 'image/jpeg':
case 'image/jpg':
case 'image/png':
case 'image/gif':
msgtype = 'image';
break;
case 'audio/amr':
case 'audio/mpeg':
msgtype = 'voice';
break;
case 'video/mp4':
msgtype = 'video';
break;
default:
throw new Error(`unsupported media type: ${file.mimeType}`);
}
return this._messageSend(conversationId, file, msgtype);
}
async messageSendContact(conversationId, contactId) {
config_js_1.log.verbose('PuppetOA', 'messageSendUrl(%s, %s)', conversationId, contactId);
// const contact = this.mocker.MockContact.load(contactId)
// return this.messageSend(conversationId, contact)
}
async messageSendUrl(conversationId, urlLinkPayload) {
config_js_1.log.verbose('PuppetOA', 'messageSendUrl(%s, %s)', conversationId, urlLinkPayload);
let msgId = null;
msgId = await this.oa?.sendCustomLink({ touser: conversationId, urlLinkPayload });
if (!msgId) {
throw new Error('PuppetOA messageSendUrl() can"t get msgId response');
}
return msgId;
}
async messageSendMiniProgram(conversationId, miniProgramPayload) {
config_js_1.log.verbose('PuppetOA', 'messageSendMiniProgram(%s, %s)', conversationId, JSON.stringify(miniProgramPayload));
let msgId = null;
msgId = await this.oa?.sendCustomMiniProgram({ miniProgram: miniProgramPayload, touser: conversationId });
if (!msgId) {
throw new Error('PuppetOA messageSendMiniProgram() can"t get msgId response');
}
return msgId;
}
async messageForward(conversationId, messageId) {
config_js_1.log.verbose('PuppetOA', 'messageForward(%s, %s)', conversationId, messageId);
}
async conversationReadMark(conversationId, hasRead) {
config_js_1.log.verbose('PuppetOA', 'conversationReadMark(%s, %s)', conversationId, hasRead);
}
/**
*
* Room
*
*/
async roomRawPayloadParser(payload) { return payload; }
async roomRawPayload(id) {
config_js_1.log.verbose('PuppetOA', 'roomRawPayload(%s)', id);
return {};
}
async roomList() {
config_js_1.log.verbose('PuppetOA', 'roomList()');
return [];
}
async roomDel(roomId, contactId) {
config_js_1.log.verbose('PuppetOA', 'roomDel(%s, %s)', roomId, contactId);
}
async roomAvatar(roomId) {
config_js_1.log.verbose('PuppetOA', 'roomAvatar(%s)', roomId);
const payload = await this.roomPayload(roomId);
if (payload.avatar) {
return file_box_1.FileBox.fromUrl(payload.avatar);
}
config_js_1.log.warn('PuppetOA', 'roomAvatar() avatar not found, use the chatie default.');
return (0, config_js_1.qrCodeForChatie)();
}
async roomAdd(roomId, contactId) {
config_js_1.log.verbose('PuppetOA', 'roomAdd(%s, %s)', roomId, contactId);
}
async roomTopic(roomId, topic) {
config_js_1.log.verbose('PuppetOA', 'roomTopic(%s, %s)', roomId, topic);
if (typeof topic === 'undefined') {
return 'mock room topic';
}
await this.dirtyPayload(PUPPET.types.Payload.Room, roomId);
}
async roomCreate(contactIdList, topic) {
config_js_1.log.verbose('PuppetOA', 'roomCreate(%s, %s)', contactIdList, topic);
return 'mock_room_id';
}
async roomQuit(roomId) {
config_js_1.log.verbose('PuppetOA', 'roomQuit(%s)', roomId);
}
async roomQRCode(roomId) {
config_js_1.log.verbose('PuppetOA', 'roomQRCode(%s)', roomId);
return roomId + ' mock qrcode';
}
async roomMemberList(roomId) {
config_js_1.log.verbose('PuppetOA', 'roomMemberList(%s)', roomId);
return [];
}
async roomMemberRawPayload(roomId, contactId) {
config_js_1.log.verbose('PuppetOA', 'roomMemberRawPayload(%s, %s)', roomId, contactId);
return {
avatar: 'mock-avatar-data',
id: 'xx',
name: 'mock-name',
roomAlias: 'yy',
};
}
async roomMemberRawPayloadParser(rawPayload) {
config_js_1.log.verbose('PuppetOA', 'roomMemberRawPayloadParser(%s)', rawPayload);
return rawPayload;
}
async roomAnnounce(roomId, text) {
if (text) {
return;
}
return 'mock announcement for ' + roomId;
}
/**
*
* Room Invitation
*
*/
async roomInvitationAccept(roomInvitationId) {
config_js_1.log.verbose('PuppetOA', 'roomInvitationAccept(%s)', roomInvitationId);
}
async roomInvitationRawPayload(roomInvitationId) {
config_js_1.log.verbose('PuppetOA', 'roomInvitationRawPayload(%s)', roomInvitationId);
}
async roomInvitationRawPayloadParser(rawPayload) {
config_js_1.log.verbose('PuppetOA', 'roomInvitationRawPayloadParser(%s)', JSON.stringify(rawPayload));
return rawPayload;
}
/**
*
* Friendship
*
*/
async friendshipRawPayload(id) {
return { id };
}
async friendshipRawPayloadParser(rawPayload) {
return rawPayload;
}
async friendshipSearchPhone(phone) {
config_js_1.log.verbose('PuppetOA', 'friendshipSearchPhone(%s)', phone);
return null;
}
async friendshipSearchWeixin(weixin) {
config_js_1.log.verbose('PuppetOA', 'friendshipSearchWeixin(%s)', weixin);
return null;
}
async friendshipAdd(contactId, hello) {
config_js_1.log.verbose('PuppetOA', 'friendshipAdd(%s, %s)', contactId, hello);
}
async friendshipAccept(friendshipId) {
config_js_1.log.verbose('PuppetOA', 'friendshipAccept(%s)', friendshipId);
}
/**
*
* Tag
*
*/
async tagContactAdd(tagId, contactId) {
config_js_1.log.verbose('PuppetOA', 'tagContactAdd(%s)', tagId, contactId);
await this.oa?.addTagToMembers(tagId, [contactId]);
}
async tagContactRemove(tagId, contactId) {
config_js_1.log.verbose('PuppetOA', 'tagContactRemove(%s)', tagId, contactId);
await this.oa?.removeTagFromMembers(tagId, [contactId]);
}
async tagContactDelete(tagId) {
config_js_1.log.verbose('PuppetOA', 'tagContactDelete(%s)', tagId);
await this.oa?.deleteTag(tagId);
}
async tagContactList(contactId) {
config_js_1.log.verbose('PuppetOA', 'tagContactList(%s)', contactId);
if (!this.oa) {
throw new Error('can not find oa object');
}
// 1. get all of the tags
if (!contactId) {
const tagList = await this.oa.getContactList();
return tagList;
}
// 2. get the member tags
const tagList = await this.oa.getMemberTags(contactId);
return tagList;
}
}
exports.PuppetOA = PuppetOA;
exports.default = PuppetOA;
//# sourceMappingURL=puppet-oa.js.map