wechaty-puppet-service
Version:
Puppet Service for Wechaty
1,164 lines • 57.4 kB
JavaScript
/**
* Wechaty Open Source Software - https://github.com/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* 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 util from 'util';
import * as PUPPET from 'wechaty-puppet';
import { StringValue, puppet as grpcPuppet, } from 'wechaty-grpc';
import { puppet$, Duck as PuppetDuck, } from 'wechaty-redux';
import { Ducks,
// Bundle,
} from 'ducks';
// import type { Subscription } from 'rxjs'
/**
* Deprecated. Will be removed after Dec 31, 2022
*/
import { packConversationIdFileBoxToPb, unpackFileBoxFromPb, } from '../deprecated/mod.js';
import { serializeFileBox } from '../deprecated/serialize-file-box.js';
import { millisecondsFromTimestamp } from '../pure-functions/timestamp.js';
import { uuidifyFileBoxGrpc, normalizeFileBoxUuid, } from '../file-box-helper/mod.js';
import { envVars, log, VERSION, } from '../config.js';
import { EventTypeRev, } from '../event-type-rev.js';
import { packageJson } from '../package-json.js';
import { GrpcManager } from './grpc-manager.js';
import { PayloadStore } from './payload-store.js';
class PuppetService extends PUPPET.Puppet {
options;
static VERSION = VERSION;
_cleanupCallbackList;
_payloadStore;
/**
* Wechaty Redux
*/
_ducks;
_store;
_grpcManager;
get grpcManager() {
if (!this._grpcManager) {
throw new Error('no grpc manager');
}
return this._grpcManager;
}
/**
* UUIDify:
* We need to clone a FileBox
* to set uuid loader/saver with this grpc client
*/
FileBoxUuid;
constructor(options = {}) {
super(options);
this.options = options;
this._payloadStore = new PayloadStore({
token: envVars.WECHATY_PUPPET_SERVICE_TOKEN(this.options.token),
});
this.hookPayloadStore();
this._ducks = new Ducks({ puppet: PuppetDuck });
this._store = this._ducks.configureStore();
this._cleanupCallbackList = [];
this.FileBoxUuid = uuidifyFileBoxGrpc(() => this.grpcManager.client);
}
async serializeFileBox(fileBox) {
/**
* 1. if the fileBox is one of type `Url`, `QRCode`, `Uuid`, etc,
* then it can be serialized by `fileBox.toString()`
* 2. if the fileBox is one of type `Stream`, `Buffer`, `File`, etc,
* then it need to be convert to type `Uuid`
* before serialized by `fileBox.toString()`
*/
const normalizedFileBox = await normalizeFileBoxUuid(this.FileBoxUuid)(fileBox);
return JSON.stringify(normalizedFileBox);
}
name() {
return packageJson.name || 'wechaty-puppet-service';
}
version() {
return packageJson.version || '0.0.0';
}
async onStart() {
log.verbose('PuppetService', 'onStart()');
if (this._grpcManager) {
log.warn('PuppetService', 'onStart() found this.grpc is already existed. dropped.');
this._grpcManager = undefined;
}
log.verbose('PuppetService', 'start() instanciating GrpcManager ...');
const grpcManager = new GrpcManager(this.options);
log.verbose('PuppetService', 'start() instanciating GrpcManager ... done');
/**
* Huan(202108): when we started the event stream,
* the `this.grpc` need to be available for all listeners.
*/
this._grpcManager = grpcManager;
log.verbose('PuppetService', 'start() setting up bridge grpc event stream ...');
this.bridgeGrpcEventStream(grpcManager);
log.verbose('PuppetService', 'start() setting up bridge grpc event stream ... done');
log.verbose('PuppetService', 'start() starting grpc manager...');
await grpcManager.start();
log.verbose('PuppetService', 'start() starting grpc manager... done');
/**
* Ducks management
*/
const subscription = puppet$(this)
.subscribe(this._store.dispatch);
this._cleanupCallbackList.push(() => subscription.unsubscribe());
log.verbose('PuppetService', 'onStart() ... done');
}
async onStop() {
log.verbose('PuppetService', 'onStop()');
this._cleanupCallbackList.map(setImmediate);
this._cleanupCallbackList.length = 0;
if (this._grpcManager) {
log.verbose('PuppetService', 'onStop() stopping grpc manager ...');
const grpcManager = this._grpcManager;
this._grpcManager = undefined;
await grpcManager.stop();
log.verbose('PuppetService', 'onStop() stopping grpc manager ... done');
}
log.verbose('PuppetService', 'onStop() ... done');
}
hookPayloadStore() {
log.verbose('PuppetService', 'hookPayloadStore()');
this.on('login', async ({ contactId }) => {
try {
log.verbose('PuppetService', 'hookPayloadStore() this.on(login) contactId: "%s"', contactId);
await this._payloadStore.start(contactId);
}
catch (e) {
log.verbose('PuppetService', 'hookPayloadStore() this.on(login) rejection "%s"', e.message);
}
});
this.on('logout', async ({ contactId }) => {
log.verbose('PuppetService', 'hookPayloadStore() this.on(logout) contactId: "%s"', contactId);
try {
await this._payloadStore.stop();
}
catch (e) {
log.verbose('PuppetService', 'hookPayloadStore() this.on(logout) rejection "%s"', e.message);
}
});
}
bridgeGrpcEventStream(client) {
log.verbose('PuppetService', 'bridgeGrpcEventStream(client)');
client
.on('data', this.onGrpcStreamEvent.bind(this))
.on('end', () => {
log.verbose('PuppetService', 'bridgeGrpcEventStream() eventStream.on(end)');
})
.on('error', (e) => {
this.emit('error', e);
// https://github.com/wechaty/wechaty-puppet-service/issues/16
// log.verbose('PuppetService', 'bridgeGrpcEventStream() eventStream.on(error) %s', e)
// const reason = 'bridgeGrpcEventStream() eventStream.on(error) ' + e
/**
* Huan(202110): simple reset puppet when grpc client has error? (or not?)
*/
// this.wrapAsync(this.reset())
// /**
// * The `Puppet` class have a throttleQueue for receiving the `reset` events
// * and it's the `Puppet` class's duty for call the `puppet.reset()` to reset the puppet.
// */
// if (this.state.on()) {
// this.emit('reset', { data: reason })
// }
})
.on('cancel', (...args) => {
log.verbose('PuppetService', 'bridgeGrpcEventStream() eventStream.on(cancel), %s', JSON.stringify(args));
});
}
onGrpcStreamEvent(event) {
const type = event.getType();
const payload = event.getPayload();
log.verbose('PuppetService', 'onGrpcStreamEvent({type:%s(%s), payload(len:%s)})', EventTypeRev[type], type, payload.length);
log.silly('PuppetService', 'onGrpcStreamEvent({type:%s(%s), payload:"%s"})', EventTypeRev[type], type, payload);
if (type !== grpcPuppet.EventType.EVENT_TYPE_HEARTBEAT) {
this.emit('heartbeat', {
data: `onGrpcStreamEvent(${EventTypeRev[type]})`,
});
}
switch (type) {
case grpcPuppet.EventType.EVENT_TYPE_DONG:
this.emit('dong', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_ERROR:
this.emit('error', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_HEARTBEAT:
this.emit('heartbeat', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_FRIENDSHIP:
this.emit('friendship', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_LOGIN:
{
const loginPayload = JSON.parse(payload);
(async () => this.login(loginPayload.contactId))().catch(e => log.error('PuppetService', 'onGrpcStreamEvent() this.login() rejection %s', e.message));
}
break;
case grpcPuppet.EventType.EVENT_TYPE_LOGOUT:
{
const logoutPayload = JSON.parse(payload);
(async () => this.logout(logoutPayload.data))().catch(e => log.error('PuppetService', 'onGrpcStreamEvent() this.logout() rejection %s', e.message));
}
break;
case grpcPuppet.EventType.EVENT_TYPE_DIRTY:
this.emit('dirty', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_MESSAGE:
this.emit('message', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_POST:
this.emit('post', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_READY:
this.emit('ready', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_ROOM_INVITE:
this.emit('room-invite', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_ROOM_JOIN:
this.emit('room-join', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_ROOM_LEAVE:
this.emit('room-leave', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_ROOM_TOPIC:
this.emit('room-topic', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_SCAN:
this.emit('scan', JSON.parse(payload));
break;
case grpcPuppet.EventType.EVENT_TYPE_RESET:
log.warn('PuppetService', 'onGrpcStreamEvent() got an EventType.EVENT_TYPE_RESET ?');
// the `reset` event should be dealed not send out
break;
case grpcPuppet.EventType.EVENT_TYPE_UNSPECIFIED:
log.error('PuppetService', 'onGrpcStreamEvent() got an EventType.EVENT_TYPE_UNSPECIFIED ?');
break;
default:
// Huan(202003): in default, the `type` type should be `never`, please check.
throw new Error('eventType ' + type + ' unsupported! (code should not reach here)');
}
}
async logout(reason) {
log.verbose('PuppetService', 'logout(%s)', reason ? `"${reason}"` : '');
await super.logout(reason);
try {
await util.promisify(this.grpcManager.client.logout
.bind(this.grpcManager.client))(new grpcPuppet.LogoutRequest());
}
catch (e) {
log.silly('PuppetService', 'logout() no grpc client');
}
}
ding(data) {
log.silly('PuppetService', 'ding(%s)', data);
const request = new grpcPuppet.DingRequest();
request.setData(data || '');
this.grpcManager.client.ding(request, (error, _response) => {
if (error) {
log.error('PuppetService', 'ding() rejection: %s', error);
}
});
}
/**
*
* Huan(202111) Issue #158 - Refactoring the 'dirty' event, dirtyPayload(),
* and XXXPayloadDirty() methods logic & spec
*
* @see https://github.com/wechaty/puppet/issues/158
*
*/
async dirtyPayload(type, id) {
log.verbose('PuppetService', 'dirtyPayload(%s, %s)', type, id);
const request = new grpcPuppet.DirtyPayloadRequest();
request.setId(id);
request.setType(type);
try {
await util.promisify(this.grpcManager.client.dirtyPayload
.bind(this.grpcManager.client))(request);
}
catch (e) {
log.error('PuppetService', 'dirtyPayload() rejection: %s', e && e.message);
throw e;
}
}
/**
* `onDirty()` is called when the puppet emit `dirty` event.
* the event listener will be registered in `start()` from the `PuppetAbstract` class
*/
onDirty({ payloadType, payloadId, }) {
log.verbose('PuppetService', 'onDirty(%s<%s>, %s)', PUPPET.types.Dirty[payloadType], payloadType, payloadId);
const dirtyMap = {
[PUPPET.types.Dirty.Contact]: async (id) => this._payloadStore.contact?.delete(id),
[PUPPET.types.Dirty.Friendship]: async (_) => { },
[PUPPET.types.Dirty.Message]: async (_) => { },
[PUPPET.types.Dirty.Post]: async (_) => { },
[PUPPET.types.Dirty.Room]: async (id) => this._payloadStore.room?.delete(id),
[PUPPET.types.Dirty.RoomMember]: async (id) => this._payloadStore.roomMember?.delete(id),
[PUPPET.types.Dirty.Unspecified]: async (id) => { throw new Error('Unspecified type with id: ' + id); },
};
const dirtyFuncSync = this.wrapAsync(dirtyMap[payloadType]);
dirtyFuncSync(payloadId);
/**
* We need to call `super.onDirty()` to clean the `PuppetAbstract` LRUCache
*/
super.onDirty({ payloadId, payloadType });
}
async contactAlias(contactId, alias) {
log.verbose('PuppetService', 'contactAlias(%s, %s)', contactId, alias);
/**
* Get alias
*/
if (typeof alias === 'undefined') {
const request = new grpcPuppet.ContactAliasRequest();
request.setId(contactId);
const response = await util.promisify(this.grpcManager.client.contactAlias
.bind(this.grpcManager.client))(request);
const result = response.getAlias();
if (result) {
return result;
}
{
// DEPRECATED, will be removed after Dec 31, 2022
const aliasWrapper = response.getAliasStringValueDeprecated();
if (!aliasWrapper) {
throw new Error('can not get aliasWrapper');
}
return aliasWrapper.getValue();
}
}
/**
* Set alias
*/
const request = new grpcPuppet.ContactAliasRequest();
request.setId(contactId);
request.setAlias(alias || ''); // null -> '', in server, we treat '' as null
{
// DEPRECATED, will be removed after Dec 31, 2022
const aliasWrapper = new StringValue();
aliasWrapper.setValue(alias || ''); // null -> '', in server, we treat '' as null
request.setAliasStringValueDeprecated(aliasWrapper);
}
await util.promisify(this.grpcManager.client.contactAlias
.bind(this.grpcManager.client))(request);
}
async contactPhone(contactId, phoneList) {
log.verbose('PuppetService', 'contactPhone(%s, %s)', contactId, phoneList);
const request = new grpcPuppet.ContactPhoneRequest();
request.setContactId(contactId);
request.setPhonesList(phoneList);
await util.promisify(this.grpcManager.client.contactPhone
.bind(this.grpcManager.client))(request);
}
async contactCorporationRemark(contactId, corporationRemark) {
log.verbose('PuppetService', 'contactCorporationRemark(%s, %s)', contactId, corporationRemark);
const request = new grpcPuppet.ContactCorporationRemarkRequest();
request.setContactId(contactId);
if (corporationRemark) {
request.setCorporationRemark(corporationRemark);
}
{
// DEPRECATED, will be removed after Dec 31, 2022
const corporationRemarkWrapper = new StringValue();
if (corporationRemark) {
corporationRemarkWrapper.setValue(corporationRemark);
request.setCorporationRemarkStringValueDeprecated(corporationRemarkWrapper);
}
}
await util.promisify(this.grpcManager.client.contactCorporationRemark
.bind(this.grpcManager.client))(request);
}
async contactDescription(contactId, description) {
log.verbose('PuppetService', 'contactDescription(%s, %s)', contactId, description);
const request = new grpcPuppet.ContactDescriptionRequest();
request.setContactId(contactId);
if (description) {
request.setDescription(description);
}
{
// DEPRECATED, will be removed after Dec 31, 2022
const descriptionWrapper = new StringValue();
if (description) {
descriptionWrapper.setValue(description);
request.setDescriptionStringValueDeprecated(descriptionWrapper);
}
}
await util.promisify(this.grpcManager.client.contactDescription
.bind(this.grpcManager.client))(request);
}
async contactList() {
log.verbose('PuppetService', 'contactList()');
const response = await util.promisify(this.grpcManager.client.contactList
.bind(this.grpcManager.client))(new grpcPuppet.ContactListRequest());
return response.getIdsList();
}
async contactAvatar(contactId, fileBox) {
log.verbose('PuppetService', 'contactAvatar(%s)', contactId);
/**
* 1. set
*/
if (fileBox) {
const request = new grpcPuppet.ContactAvatarRequest();
request.setId(contactId);
const serializedFileBox = await this.serializeFileBox(fileBox);
request.setFileBox(serializedFileBox);
{
// DEPRECATED, will be removed after Dec 31, 2022
const fileboxWrapper = new StringValue();
fileboxWrapper.setValue(await serializeFileBox(fileBox));
request.setFileboxStringValueDeprecated(fileboxWrapper);
}
await util.promisify(this.grpcManager.client.contactAvatar
.bind(this.grpcManager.client))(request);
return;
}
/**
* 2. get
*/
const request = new grpcPuppet.ContactAvatarRequest();
request.setId(contactId);
const response = await util.promisify(this.grpcManager.client.contactAvatar
.bind(this.grpcManager.client))(request);
let jsonText;
jsonText = response.getFileBox();
{
// DEPRECATED, will be removed after Dec 31, 2022
const deprecated = true;
void deprecated;
if (!jsonText) {
const textWrapper = response.getFileboxStringValueDeprecated();
if (!textWrapper) {
throw new Error('can not get textWrapper');
}
jsonText = textWrapper.getValue();
}
}
return this.FileBoxUuid.fromJSON(jsonText);
}
async contactRawPayload(id) {
log.verbose('PuppetService', 'contactRawPayload(%s)', id);
const cachedPayload = await this._payloadStore.contact?.get(id);
if (cachedPayload) {
log.silly('PuppetService', 'contactRawPayload(%s) cache HIT', id);
return cachedPayload;
}
const request = new grpcPuppet.ContactPayloadRequest();
request.setId(id);
const response = await util.promisify(this.grpcManager.client.contactPayload
.bind(this.grpcManager.client))(request);
const payload = {
address: response.getAddress(),
alias: response.getAlias(),
avatar: response.getAvatar(),
city: response.getCity(),
corporation: response.getCorporation(),
coworker: response.getCoworker(),
description: response.getDescription(),
friend: response.getFriend(),
gender: response.getGender(),
id: response.getId(),
name: response.getName(),
phone: response.getPhonesList(),
province: response.getProvince(),
signature: response.getSignature(),
star: response.getStar(),
title: response.getTitle(),
type: response.getType(),
weixin: response.getWeixin(),
};
await this._payloadStore.contact?.set(id, payload);
log.silly('PuppetService', 'contactRawPayload(%s) cache SET', id);
return payload;
}
async contactRawPayloadParser(payload) {
// log.silly('PuppetService', 'contactRawPayloadParser({id:%s})', payload.id)
// passthrough
return payload;
}
async contactSelfName(name) {
log.verbose('PuppetService', 'contactSelfName(%s)', name);
const request = new grpcPuppet.ContactSelfNameRequest();
request.setName(name);
await util.promisify(this.grpcManager.client.contactSelfName
.bind(this.grpcManager.client))(request);
}
async contactSelfQRCode() {
log.verbose('PuppetService', 'contactSelfQRCode()');
const response = await util.promisify(this.grpcManager.client.contactSelfQRCode
.bind(this.grpcManager.client))(new grpcPuppet.ContactSelfQRCodeRequest());
return response.getQrcode();
}
async contactSelfSignature(signature) {
log.verbose('PuppetService', 'contactSelfSignature(%s)', signature);
const request = new grpcPuppet.ContactSelfSignatureRequest();
request.setSignature(signature);
await util.promisify(this.grpcManager.client.contactSelfSignature
.bind(this.grpcManager.client))(request);
}
/**
*
* Conversation
*
*/
conversationReadMark(conversationId, hasRead = true) {
log.verbose('PuppetService', 'conversationMarkRead(%s, %s)', conversationId, hasRead);
return PUPPET.throwUnsupportedError('not implemented. See https://github.com/wechaty/wechaty-puppet/pull/132');
}
/**
*
* Message
*
*/
async messageMiniProgram(messageId) {
log.verbose('PuppetService', 'messageMiniProgram(%s)', messageId);
const request = new grpcPuppet.MessageMiniProgramRequest();
request.setId(messageId);
const response = await util.promisify(this.grpcManager.client.messageMiniProgram
.bind(this.grpcManager.client))(request);
let miniProgramPayload = response.getMiniProgram()?.toObject();
if (!miniProgramPayload) {
/**
* Deprecated: will be removed after Dec 22, 2022
*/
const jsonText = response.getMiniProgramDeprecated();
miniProgramPayload = JSON.parse(jsonText);
}
const payload = {
...miniProgramPayload,
};
return payload;
}
async messageLocation(messageId) {
log.verbose('PuppetService', 'messageLocation(%s)', messageId);
const request = new grpcPuppet.MessageLocationRequest();
request.setId(messageId);
const response = await util.promisify(this.grpcManager.client.messageLocation
.bind(this.grpcManager.client))(request);
const locationPayload = response.getLocation();
const payload = {
accuracy: 0,
address: 'NOADDRESS',
latitude: 0,
longitude: 0,
name: 'NONAME',
...locationPayload,
};
return payload;
}
async messageImage(messageId, imageType) {
log.verbose('PuppetService', 'messageImage(%s, %s[%s])', messageId, imageType, PUPPET.types.Image[imageType]);
try {
const request = new grpcPuppet.MessageImageRequest();
request.setId(messageId);
request.setType(imageType);
const response = await util.promisify(this.grpcManager.client.messageImage
.bind(this.grpcManager.client))(request);
const jsonText = response.getFileBox();
if (jsonText) {
return this.FileBoxUuid.fromJSON(jsonText);
}
}
catch (e) {
log.verbose('PuppetService', 'messageImage() rejection %s', e.message);
}
{
// Deprecated. Will be removed after Dec 31, 2022
const request = new grpcPuppet.MessageImageStreamRequest();
request.setId(messageId);
request.setType(imageType);
const pbStream = this.grpcManager.client.messageImageStream(request);
const fileBox = await unpackFileBoxFromPb(pbStream);
// const fileBoxChunkStream = unpackFileBoxChunk(stream)
// return unpackFileBox(fileBoxChunkStream)
return fileBox;
}
}
async messageContact(messageId) {
log.verbose('PuppetService', 'messageContact(%s)', messageId);
const request = new grpcPuppet.MessageContactRequest();
request.setId(messageId);
const response = await util.promisify(this.grpcManager.client.messageContact
.bind(this.grpcManager.client))(request);
const contactId = response.getId();
return contactId;
}
async messageSendMiniProgram(conversationId, miniProgramPayload) {
log.verbose('PuppetService', 'messageSendMiniProgram(%s, "%s")', conversationId, JSON.stringify(miniProgramPayload));
const request = new grpcPuppet.MessageSendMiniProgramRequest();
request.setConversationId(conversationId);
const pbMiniProgramPayload = new grpcPuppet.MiniProgramPayload();
if (miniProgramPayload.appid) {
pbMiniProgramPayload.setAppid(miniProgramPayload.appid);
}
if (miniProgramPayload.description) {
pbMiniProgramPayload.setDescription(miniProgramPayload.description);
}
if (miniProgramPayload.iconUrl) {
pbMiniProgramPayload.setIconUrl(miniProgramPayload.iconUrl);
}
if (miniProgramPayload.pagePath) {
pbMiniProgramPayload.setPagePath(miniProgramPayload.pagePath);
}
if (miniProgramPayload.shareId) {
pbMiniProgramPayload.setShareId(miniProgramPayload.shareId);
}
if (miniProgramPayload.thumbKey) {
pbMiniProgramPayload.setThumbKey(miniProgramPayload.thumbKey);
}
if (miniProgramPayload.thumbUrl) {
pbMiniProgramPayload.setThumbUrl(miniProgramPayload.thumbUrl);
}
if (miniProgramPayload.title) {
pbMiniProgramPayload.setTitle(miniProgramPayload.title);
}
if (miniProgramPayload.username) {
pbMiniProgramPayload.setUsername(miniProgramPayload.username);
}
request.setMiniProgram(pbMiniProgramPayload);
/**
* Deprecated: will be removed after Dec 31, 2022
*/
request.setMiniProgramDeprecated(JSON.stringify(miniProgramPayload));
const response = await util.promisify(this.grpcManager.client.messageSendMiniProgram
.bind(this.grpcManager.client))(request);
const messageId = response.getId();
if (messageId) {
return messageId;
}
{
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
*/
const messageIdWrapper = response.getIdStringValueDeprecated();
if (messageIdWrapper) {
return messageIdWrapper.getValue();
}
}
}
async messageSendLocation(conversationId, locationPayload) {
log.verbose('PuppetService', 'messageSendLocation(%s)', conversationId, JSON.stringify(locationPayload));
const request = new grpcPuppet.MessageSendLocationRequest();
request.setConversationId(conversationId);
const pbLocationPayload = new grpcPuppet.LocationPayload();
pbLocationPayload.setAccuracy(locationPayload.accuracy);
pbLocationPayload.setAddress(locationPayload.address);
pbLocationPayload.setLatitude(locationPayload.latitude);
pbLocationPayload.setLongitude(locationPayload.longitude);
pbLocationPayload.setName(locationPayload.name);
request.setLocation(pbLocationPayload);
const response = await util.promisify(this.grpcManager.client.messageSendLocation
.bind(this.grpcManager.client))(request);
const id = response.getId();
if (id) {
return id;
}
}
async messageRecall(messageId) {
log.verbose('PuppetService', 'messageRecall(%s)', messageId);
const request = new grpcPuppet.MessageRecallRequest();
request.setId(messageId);
const response = await util.promisify(this.grpcManager.client.messageRecall
.bind(this.grpcManager.client))(request);
return response.getSuccess();
}
async messageFile(id) {
log.verbose('PuppetService', 'messageFile(%s)', id);
try {
const request = new grpcPuppet.MessageFileRequest();
request.setId(id);
const response = await util.promisify(this.grpcManager.client.messageFile
.bind(this.grpcManager.client))(request);
const jsonText = response.getFileBox();
if (jsonText) {
return this.FileBoxUuid.fromJSON(jsonText);
}
}
catch (e) {
log.warn('PuppetService', 'messageFile() rejection: %s', e.message);
log.warn('PuppetService', [
'This might because you are using Wechaty v1.x with a Puppet Service v0.x',
'Contact your Wechaty Puppet Service provided to report this problem',
'Related issues:',
' - https://github.com/wechaty/puppet-service/issues/179',
' - https://github.com/wechaty/puppet-service/pull/170',
].join('\n'));
}
{
// Deprecated. `MessageFileStream` Will be removed after Dec 31, 2022
const request = new grpcPuppet.MessageFileStreamRequest();
request.setId(id);
const pbStream = this.grpcManager.client.messageFileStream(request);
// const fileBoxChunkStream = unpackFileBoxChunk(pbStream)
// return unpackFileBox(fileBoxChunkStream)
const fileBox = await unpackFileBoxFromPb(pbStream);
return fileBox;
}
}
async messageForward(conversationId, messageId) {
log.verbose('PuppetService', 'messageForward(%s, %s)', conversationId, messageId);
const request = new grpcPuppet.MessageForwardRequest();
request.setConversationId(conversationId);
request.setMessageId(messageId);
const response = await util.promisify(this.grpcManager.client.messageForward
.bind(this.grpcManager.client))(request);
const forwardedMessageId = response.getId();
if (forwardedMessageId) {
return forwardedMessageId;
}
{
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
*/
const messageIdWrapper = response.getIdStringValueDeprecated();
if (messageIdWrapper) {
return messageIdWrapper.getValue();
}
}
}
async messageRawPayload(id) {
log.verbose('PuppetService', 'messageRawPayload(%s)', id);
// const cachedPayload = await this.payloadStore.message?.get(id)
// if (cachedPayload) {
// log.silly('PuppetService', 'messageRawPayload(%s) cache HIT', id)
// return cachedPayload
// }
const request = new grpcPuppet.MessagePayloadRequest();
request.setId(id);
const response = await util.promisify(this.grpcManager.client.messagePayload
.bind(this.grpcManager.client))(request);
let timestamp;
const receiveTime = response.getReceiveTime();
if (receiveTime) {
timestamp = millisecondsFromTimestamp(receiveTime);
}
else {
// Deprecated: will be removed after Dec 31, 2022
timestamp = response.getTimestampDeprecated();
}
const payload = {
filename: response.getFilename(),
id: response.getId(),
listenerId: response.getListenerId(),
mentionIdList: response.getMentionIdsList(),
roomId: response.getRoomId(),
talkerId: response.getTalkerId(),
text: response.getText(),
timestamp,
type: response.getType(),
};
// log.silly('PuppetService', 'messageRawPayload(%s) cache SET', id)
// await this.payloadStore.message?.set(id, payload)
return payload;
}
async messageRawPayloadParser(payload) {
// log.silly('PuppetService', 'messagePayload({id:%s})', payload.id)
// passthrough
return payload;
}
async messageSendText(conversationId, text, mentionIdList) {
log.verbose('PuppetService', 'messageSend(%s, %s)', conversationId, text);
const request = new grpcPuppet.MessageSendTextRequest();
request.setConversationId(conversationId);
request.setText(text);
if (typeof mentionIdList !== 'undefined') {
request.setMentionalIdsList(mentionIdList);
}
const response = await util.promisify(this.grpcManager.client.messageSendText
.bind(this.grpcManager.client))(request);
const messageId = response.getId();
if (messageId) {
return messageId;
}
{
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
*/
const messageIdWrapper = response.getIdStringValueDeprecated();
if (messageIdWrapper) {
return messageIdWrapper.getValue();
}
}
}
async messageSendFile(conversationId, fileBox) {
log.verbose('PuppetService', 'messageSendFile(%s, %s)', conversationId, fileBox);
try {
const request = new grpcPuppet.MessageSendFileRequest();
request.setConversationId(conversationId);
const serializedFileBox = await this.serializeFileBox(fileBox);
request.setFileBox(serializedFileBox);
const response = await util.promisify(this.grpcManager.client.messageSendFile
.bind(this.grpcManager.client))(request);
const messageId = response.getId();
if (messageId) {
return messageId;
}
else {
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
*/
const messageIdWrapper = response.getIdStringValueDeprecated();
if (messageIdWrapper) {
return messageIdWrapper.getValue();
}
}
return; // void
}
catch (e) {
log.verbose('PuppetService', 'messageSendFile() rejection: %s', e.message);
}
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
* The old server will not support `Upload` gRPC method,
* which I'm expecting the above code will throw a exception,
* then the below code will be executed.
*/
return this.messageSendFileStream(conversationId, fileBox);
}
async messageSendContact(conversationId, contactId) {
log.verbose('PuppetService', 'messageSend("%s", %s)', conversationId, contactId);
const request = new grpcPuppet.MessageSendContactRequest();
request.setConversationId(conversationId);
request.setContactId(contactId);
const response = await util.promisify(this.grpcManager.client.messageSendContact
.bind(this.grpcManager.client))(request);
const messageId = response.getId();
if (messageId) {
return messageId;
}
{
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
*/
const messageIdWrapper = response.getIdStringValueDeprecated();
if (messageIdWrapper) {
return messageIdWrapper.getValue();
}
}
}
async messageSendUrl(conversationId, urlLinkPayload) {
log.verbose('PuppetService', 'messageSendUrl("%s", %s)', conversationId, JSON.stringify(urlLinkPayload));
const request = new grpcPuppet.MessageSendUrlRequest();
request.setConversationId(conversationId);
const pbUrlLinkPayload = new grpcPuppet.UrlLinkPayload();
pbUrlLinkPayload.setUrl(urlLinkPayload.url);
pbUrlLinkPayload.setTitle(urlLinkPayload.title);
if (urlLinkPayload.description) {
pbUrlLinkPayload.setDescription(urlLinkPayload.description);
}
if (urlLinkPayload.thumbnailUrl) {
pbUrlLinkPayload.setThumbnailUrl(urlLinkPayload.thumbnailUrl);
}
request.setUrlLink(pbUrlLinkPayload);
// Deprecated: will be removed after Dec 31, 2022
request.setUrlLinkDeprecated(JSON.stringify(urlLinkPayload));
const response = await util.promisify(this.grpcManager.client.messageSendUrl
.bind(this.grpcManager.client))(request);
const messageId = response.getId();
if (messageId) {
return messageId;
}
{
/**
* Huan(202110): Deprecated: will be removed after Dec 31, 2022
*/
const messageIdWrapper = response.getIdStringValueDeprecated();
if (messageIdWrapper) {
return messageIdWrapper.getValue();
}
}
}
async messageUrl(messageId) {
log.verbose('PuppetService', 'messageUrl(%s)', messageId);
const request = new grpcPuppet.MessageUrlRequest();
request.setId(messageId);
const response = await util.promisify(this.grpcManager.client.messageUrl
.bind(this.grpcManager.client))(request);
let pbUrlLinkPayload = response.getUrlLink()?.toObject();
if (!pbUrlLinkPayload) {
// Deprecated: will be removed after Dec 31, 2022
const jsonText = response.getUrlLinkDeprecated();
pbUrlLinkPayload = JSON.parse(jsonText);
}
const payload = {
title: 'NOTITLE',
url: 'NOURL',
...pbUrlLinkPayload,
};
return payload;
}
/**
*
* Room
*
*/
async roomRawPayload(id) {
log.verbose('PuppetService', 'roomRawPayload(%s)', id);
const cachedPayload = await this._payloadStore.room?.get(id);
if (cachedPayload) {
log.silly('PuppetService', 'roomRawPayload(%s) cache HIT', id);
return cachedPayload;
}
const request = new grpcPuppet.RoomPayloadRequest();
request.setId(id);
const response = await util.promisify(this.grpcManager.client.roomPayload
.bind(this.grpcManager.client))(request);
const payload = {
adminIdList: response.getAdminIdsList(),
avatar: response.getAvatar(),
id: response.getId(),
memberIdList: response.getMemberIdsList(),
ownerId: response.getOwnerId(),
topic: response.getTopic(),
};
await this._payloadStore.room?.set(id, payload);
log.silly('PuppetService', 'roomRawPayload(%s) cache SET', id);
return payload;
}
async roomRawPayloadParser(payload) {
// log.silly('PuppetService', 'roomRawPayloadParser({id:%s})', payload.id)
// passthrough
return payload;
}
async roomList() {
log.verbose('PuppetService', 'roomList()');
const response = await util.promisify(this.grpcManager.client.roomList
.bind(this.grpcManager.client))(new grpcPuppet.RoomListRequest());
return response.getIdsList();
}
async roomDel(roomId, contactId) {
log.verbose('PuppetService', 'roomDel(%s, %s)', roomId, contactId);
const request = new grpcPuppet.RoomDelRequest();
request.setId(roomId);
request.setContactId(contactId);
await util.promisify(this.grpcManager.client.roomDel
.bind(this.grpcManager.client))(request);
}
async roomAvatar(roomId) {
log.verbose('PuppetService', 'roomAvatar(%s)', roomId);
const request = new grpcPuppet.RoomAvatarRequest();
request.setId(roomId);
const response = await util.promisify(this.grpcManager.client.roomAvatar
.bind(this.grpcManager.client))(request);
const jsonText = response.getFileBox();
return this.FileBoxUuid.fromJSON(jsonText);
}
async roomAdd(roomId, contactId, inviteOnly) {
log.verbose('PuppetService', 'roomAdd(%s, %s)', roomId, contactId);
const request = new grpcPuppet.RoomAddRequest();
request.setId(roomId);
request.setContactId(contactId);
request.setInviteOnly(inviteOnly);
await util.promisify(this.grpcManager.client.roomAdd
.bind(this.grpcManager.client))(request);
}
async roomTopic(roomId, topic) {
log.verbose('PuppetService', 'roomTopic(%s, %s)', roomId, topic);
/**
* Get
*/
if (typeof topic === 'undefined') {
const request = new grpcPuppet.RoomTopicRequest();
request.setId(roomId);
const response = await util.promisify(this.grpcManager.client.roomTopic
.bind(this.grpcManager.client))(request);
const result = response.getTopic();
if (result) {
return result;
}
{
// DEPRECATED, will be removed after Dec 31, 2022
const topicWrapper = response.getTopicStringValueDeprecated();
if (topicWrapper) {
return topicWrapper.getValue();
}
}
return '';
}
/**
* Set
*/
const request = new grpcPuppet.RoomTopicRequest();
request.setId(roomId);
request.setTopic(topic);
{
// DEPRECATED, will be removed after Dec 31, 2022
const topicWrapper = new StringValue();
topicWrapper.setValue(topic);
request.setTopicStringValueDeprecated(topicWrapper);
}
await util.promisify(this.grpcManager.client.roomTopic
.bind(this.grpcManager.client))(request);
}
async roomCreate(contactIdList, topic) {
log.verbose('PuppetService', 'roomCreate(%s, %s)', contactIdList, topic);
const request = new grpcPuppet.RoomCreateRequest();
request.setContactIdsList(contactIdList);
request.setTopic(topic);
const response = await util.promisify(this.grpcManager.client.roomCreate
.bind(this.grpcManager.client))(request);
return response.getId();
}
async roomQuit(roomId) {
log.verbose('PuppetService', 'roomQuit(%s)', roomId);
const request = new grpcPuppet.RoomQuitRequest();
request.setId(roomId);
await util.promisify(this.grpcManager.client.roomQuit
.bind(this.grpcManager.client))(request);
}
async roomQRCode(roomId) {
log.verbose('PuppetService', 'roomQRCode(%s)', roomId);
const request = new grpcPuppet.RoomQRCodeRequest();
request.setId(roomId);
const response = await util.promisify(this.grpcManager.client.roomQRCode
.bind(this.grpcManager.client))(request);
return response.getQrcode();
}
async roomMemberList(roomId) {
log.verbose('PuppetService', 'roomMemberList(%s)', roomId);
const request = new grpcPuppet.RoomMemberListRequest();
request.setId(roomId);
const response = await util.promisify(this.grpcManager.client.roomMemberList
.bind(this.grpcManager.client))(request);
return response.getMemberIdsList();
}
async roomMemberRawPayload(roomId, contactId) {
log.verbose('PuppetService', 'roomMemberRawPayload(%s, %s)', roomId, contactId);
const cachedPayload = await this._payloadStore.roomMember?.get(roomId);
const cachedRoomMemberPayload = cachedPayload && cachedPayload[contactId];
if (cachedRoomMemberPayload) {
log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache HIT', roomId, contactId);
return cachedRoomMemberPayload;
}
const request = new grpcPuppet.RoomMemberPayloadRequest();
request.setId(roomId);
request.setMemberId(contactId);
const response = await util.promisify(this.grpcManager.client.roomMemberPayload
.bind(this.grpcManager.client))(request);
const payload = {
avatar: response.getAvatar(),
id: response.getId(),
inviterId: response.getInviterId(),
name: response.getName(),
roomAlias: response.getRoomAlias(),
};
await this._payloadStore.roomMember?.set(roomId, {
...cachedPayload,
contactId: payload,
});
log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache SET', roomId, contactId);
return payload;
}
async roomMemberRawPayloadParser(payload) {
// log.silly('PuppetService', 'roomMemberRawPayloadParser({id:%s})', payload.id)
// passthrough
return payload;
}
async roomAnnounce(roomId, text) {
log.verbose('PuppetService', 'roomAnnounce(%s%s)', roomId, typeof text === 'undefined'
? ''
: `, ${text}`);
/**
* Set
*/
if (text) {
const request = new grpcPuppet.RoomAnnounceRequest();
request.setId(roomId);
request.setText(text);
{
// DEPRECATED, will be removed after Dec 31, 2022
const textWrapper = new StringValue();
textWrapper.setValue(text);
request.setTextStringValueDeprecated(textWrapper);
}
await util.promisify(this.grpcManager.client.roomAnnounce
.bind(this.grpcManager.client))(request);
return;
}
/**
* Get
*/
const request = new grpcPuppet.RoomAnnounceRequest();
request.setId(roomId);
const response = await util.promisify(this.grpcManager.client.roomAnnounce
.bind(this.grpcManager.client))(request);
const result = response.getText();
if (result) {
return result;
}
{
// DEPRECATED, will be removed after Dec 31, 2022
const textWrapper = response.getTextStringValueDeprecated();
if (textWrapper) {
return textWrapper.getValue();
}
}
return '';
}
async roomInvitationAccept(roomInvitationId) {
log.verbose('PuppetService', 'roomInvitationAccept(%s)', roomInvitationId);
const request = new grpcPuppet.RoomInvitationAcceptRequest();
request.setId(roomInvitationId);
await util.promisify(this.grpcManager.client.roomInvitationAccept
.bind(this.grpcManager.client))(request);
}
async roomInvitationRawPayload(id) {
log.verbose('PuppetService', 'roomInvitationRawPayload(%s)', id);
const request = new grpcPuppet.RoomInvitationPayloadRequest();
request.setId(id);
const response = await util.promisify(this.grpcManager.client.roomInvitationPayload
.bind(this.grpcManager.client))(request);
let timestamp;
const receiveTime = response.getReceiveTime();
if (receiveTime) {
timestamp = millisecondsFromTimestamp(receiveTime);
}
{
// Deprecated: will be removed after Dec 31, 2022
const deprecated = true;
void deprecated;
if (!receiveTime) {
timestamp = response.getTimestampUint64Deprecated();
}
}
// FIXME: how to set it better?
timestamp ??= 0;
const payload = {
avatar: response.getAvatar(),
id: response.getId(),
invitation: response.getInvitation(),
inviterId: response.getInviterId(),
memberCount: response.getMemberCount(),
memberIdList: response.getMemberIdsList(),
receiverId: response.getReceiverId(),
timestamp,
topic: response.getTopic(),
};
return payload;
}
async roomInvitationRawPayloadParser(payload) {
// log.silly('PuppetService', 'roomInvitationRawPayloadParser({id:%s})', payload.id)
// passthrough
return payload;
}
/**
*
* Friendship
*
*/
async friendshipSearchPhone(phone) {
log.verbose('PuppetService', 'friendshipSearchPhone(%s)', phone);
const request = new grpcPuppet.Friendsh