UNPKG

wechaty-puppet-service

Version:
1,524 lines (1,152 loc) 45.4 kB
/** * 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. * */ /* eslint-disable sort-keys */ import type { Writable } from 'stream' import { chunkDecoder, chunkEncoder, StringValue, grpc, puppet as grpcPuppet, } from 'wechaty-grpc' import type { FileBoxInterface, FileBox, } from 'file-box' import * as PUPPET from 'wechaty-puppet' import { timeoutPromise } from 'gerror' import { packFileBoxToPb, unpackConversationIdFileBoxArgsFromPb, } from '../deprecated/mod.js' import { timestampFromMilliseconds, } from '../pure-functions/timestamp.js' import { normalizeFileBoxUuid, } from '../file-box-helper/mod.js' import { log } from '../config.js' import { grpcError } from './grpc-error.js' import { EventStreamManager } from './event-stream-manager.js' function puppetImplementation ( puppet : PUPPET.impls.PuppetInterface, FileBoxUuid : typeof FileBox, ): grpcPuppet.IPuppetServer { /** * Save scan payload to send it to the puppet-service right after connected (if needed) * * TODO: clean the listeners if necessary */ let scanPayload: undefined | PUPPET.payloads.EventScan let readyPayload: undefined | PUPPET.payloads.EventReady let readyTimeout: undefined | ReturnType<typeof setTimeout> puppet.on('scan', payload => { scanPayload = payload }) puppet.on('ready', payload => { readyPayload = payload }) puppet.on('logout', _ => { readyPayload = undefined if (readyTimeout) { clearTimeout(readyTimeout) } }) puppet.on('login', _ => { scanPayload = undefined readyTimeout = setTimeout(() => { // Huan(202110): should we emit ready event here? readyPayload && eventStreamManager.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_READY, readyPayload) }, 5 * 1000) }) const eventStreamManager = new EventStreamManager(puppet) const serializeFileBox = async (fileBox: FileBoxInterface) => { /** * 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(FileBoxUuid)(fileBox) return JSON.stringify(normalizedFileBox) } const puppetServerImpl: grpcPuppet.IPuppetServer = { contactAlias: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactAlias()') const id = call.request.getId() /** * Set */ if (call.request.hasAlias()) { try { await puppet.contactAlias(id, call.request.getAlias()) return callback(null, new grpcPuppet.ContactAliasResponse()) } catch (e) { return grpcError('contactAlias', e, callback) } } /** * Get */ try { const alias = await puppet.contactAlias(id) const response = new grpcPuppet.ContactAliasResponse() response.setAlias(alias) return callback(null, response) } catch (e) { return grpcError('contactAlias', e, callback) } }, contactAvatar: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactAvatar()') const id = call.request.getId() /** * Set */ try { if (call.request.hasFileBox()) { const fileBox = FileBoxUuid.fromJSON( call.request.getFileBox(), ) await puppet.contactAvatar(id, fileBox) return callback(null, new grpcPuppet.ContactAvatarResponse()) } } catch (e) { return grpcError('contactAvatar', e, callback) } /** * Get */ try { const fileBox = await puppet.contactAvatar(id) const serializedFileBox = await serializeFileBox(fileBox) const response = new grpcPuppet.ContactAvatarResponse() response.setFileBox(serializedFileBox) return callback(null, response) } catch (e) { return grpcError('contactAvatar', e, callback) } }, contactCorporationRemark: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactCorporationRemark()') const contactId = call.request.getContactId() try { await puppet.contactCorporationRemark( contactId, call.request.getCorporationRemark() || null, ) return callback(null, new grpcPuppet.ContactCorporationRemarkResponse()) } catch (e) { return grpcError('contactCorporationRemark', e, callback) } }, contactDescription: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactDescription()') const contactId = call.request.getContactId() try { const description = call.request.getDescription() await puppet.contactDescription(contactId, description || null) return callback(null, new grpcPuppet.ContactDescriptionResponse()) } catch (e) { return grpcError('contactDescription', e, callback) } }, contactList: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactList()') void call // empty request try { const idList = await puppet.contactList() const response = new grpcPuppet.ContactListResponse() response.setIdsList(idList) return callback(null, response) } catch (e) { return grpcError('contactList', e, callback) } }, contactPayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactPayload()') const id = call.request.getId() try { const payload = await puppet.contactPayload(id) const response = new grpcPuppet.ContactPayloadResponse() response.setAddress(payload.address || '') response.setAlias(payload.alias || '') response.setAvatar(payload.avatar) response.setCity(payload.city || '') response.setFriend(payload.friend || false) response.setGender(payload.gender) response.setId(payload.id) response.setName(payload.name) response.setProvince(payload.province || '') response.setSignature(payload.signature || '') response.setStar(payload.star || false) response.setType(payload.type) response.setWeixin(payload.weixin || '') response.setPhonesList(payload.phone) response.setCoworker(payload.coworker || false) response.setCorporation(payload.corporation || '') response.setTitle(payload.title || '') response.setDescription(payload.description || '') return callback(null, response) } catch (e) { return grpcError('contactPayload', e, callback) } }, contactPhone: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactPhone()') try { const contactId = call.request.getContactId() const phoneList = call.request.getPhonesList() await puppet.contactPhone(contactId, phoneList) return callback(null, new grpcPuppet.ContactPhoneResponse()) } catch (e) { return grpcError('contactPhone', e, callback) } }, contactSelfName: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactSelfName()') try { const name = call.request.getName() await puppet.contactSelfName(name) return callback(null, new grpcPuppet.ContactSelfNameResponse()) } catch (e) { return grpcError('contactSelfName', e, callback) } }, contactSelfQRCode: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactSelfName()') void call try { const qrcode = await puppet.contactSelfQRCode() const response = new grpcPuppet.ContactSelfQRCodeResponse() response.setQrcode(qrcode) return callback(null, response) } catch (e) { return grpcError('contactSelfQRCode', e, callback) } }, contactSelfSignature: async (call, callback) => { log.verbose('PuppetServiceImpl', 'contactSelfSignature()') try { const signature = call.request.getSignature() await puppet.contactSelfSignature(signature) return callback(null, new grpcPuppet.ContactSelfSignatureResponse()) } catch (e) { return grpcError('contactSelfSignature', e, callback) } }, ding: async (call, callback) => { log.verbose('PuppetServiceImpl', 'ding()') try { const data = call.request.getData() await puppet.ding(data) return callback(null, new grpcPuppet.DingResponse()) } catch (e) { return grpcError('ding', e, callback) } }, dirtyPayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'dirtyPayload()') try { const id = call.request.getId() const type: PUPPET.types.Dirty = call.request.getType() await puppet.dirtyPayload(type, id) return callback(null, new grpcPuppet.DirtyPayloadResponse()) } catch (e) { return grpcError('puppet.dirtyPayload() rejection: ', e, callback) } }, /** * * Bridge Event Emitter Events * */ event: (streamingCall) => { log.verbose('PuppetServiceImpl', 'event()') if (eventStreamManager.busy()) { log.error('PuppetServiceImpl', 'event() there is another event() call not end when receiving a new one.') const error: grpc.ServiceError = { ...new Error('GrpcServerImpl.event() can not call twice.'), code: grpc.status.ALREADY_EXISTS, details: 'GrpcServerImpl.event() can not call twice.', metadata: streamingCall.metadata, } /** * Send error from gRPC server stream: * https://github.com/grpc/grpc-node/issues/287#issuecomment-383218225 * * Streaming RPCs * - https://grpc.io/docs/tutorials/basic/node/ * Only one of 'error' or 'end' will be emitted. Finally, the 'status' event fires when the server sends the status. */ streamingCall.emit('error', error) return } eventStreamManager.start(streamingCall) /** * If `scanPayload` is not undefined, then we emit it to downstream immediatelly */ if (scanPayload) { eventStreamManager.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_SCAN, scanPayload) } }, friendshipAccept: async (call, callback) => { log.verbose('PuppetServiceImpl', 'friendshipAccept()') try { const id = call.request.getId() await puppet.friendshipAccept(id) return callback(null, new grpcPuppet.FriendshipAcceptResponse()) } catch (e) { return grpcError('friendshipAccept', e, callback) } }, friendshipAdd: async (call, callback) => { log.verbose('PuppetServiceImpl', 'friendshipAdd()') try { const contactId = call.request.getContactId() // FIXME: for backward compatibility, need to be removed after all puppet has updated. const hello = call.request.getHello() const referrer = call.request.getReferrer() const friendshipAddOptions: PUPPET.types.FriendshipAddOptions = { hello, ...referrer, } { // Deprecated: will be removed after Dec 31, 2022 const sourceContactId = call.request.getSourceContactIdStringValueDeprecated()?.getValue() const sourceRoomId = call.request.getSourceRoomIdStringValueDeprecated()?.getValue() if (sourceContactId) { friendshipAddOptions['contactId'] = sourceContactId } if (sourceRoomId) { friendshipAddOptions['roomId'] = sourceRoomId } } await puppet.friendshipAdd(contactId, friendshipAddOptions) return callback(null, new grpcPuppet.FriendshipAddResponse()) } catch (e) { return grpcError('friendshipAdd', e, callback) } }, friendshipPayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'friendshipPayload()') try { const id = call.request.getId() const payload = await puppet.friendshipPayload(id) const payloadReceive = payload as PUPPET.payloads.FriendshipReceive const response = new grpcPuppet.FriendshipPayloadResponse() response.setContactId(payload.contactId) response.setHello(payload.hello || '') response.setId(payload.id) response.setScene(payloadReceive.scene || PUPPET.types.FriendshipScene.Unknown) response.setStranger(payloadReceive.stranger || '') response.setTicket(payloadReceive.ticket) response.setType(payload.type) return callback(null, response) } catch (e) { return grpcError('friendshipPayload', e, callback) } }, friendshipSearchPhone: async (call, callback) => { log.verbose('PuppetServiceImpl', 'friendshipSearchPhone()') try { const phone = call.request.getPhone() const contactId = await puppet.friendshipSearchPhone(phone) const response = new grpcPuppet.FriendshipSearchPhoneResponse() if (contactId) { response.setContactId(contactId) } return callback(null, response) } catch (e) { return grpcError('friendshipSearchPhone', e, callback) } }, friendshipSearchWeixin: async (call, callback) => { log.verbose('PuppetServiceImpl', 'friendshipSearchWeixin()') try { const weixin = call.request.getWeixin() const contactId = await puppet.friendshipSearchWeixin(weixin) const response = new grpcPuppet.FriendshipSearchWeixinResponse() if (contactId) { response.setContactId(contactId) } return callback(null, response) } catch (e) { return grpcError('friendshipSearchWeixin', e, callback) } }, logout: async (call, callback) => { log.verbose('PuppetServiceImpl', 'logout()') void call // empty arguments try { await puppet.logout() return callback(null, new grpcPuppet.LogoutResponse()) } catch (e) { return grpcError('logout', e, callback) } }, messageContact: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageContact()') try { const id = call.request.getId() const contactId = await puppet.messageContact(id) const response = new grpcPuppet.MessageContactResponse() response.setId(contactId) return callback(null, response) } catch (e) { return grpcError('messageContact', e, callback) } }, messageFile: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageFile()') try { const id = call.request.getId() const fileBox = await puppet.messageFile(id) const serializedFileBox = await serializeFileBox(fileBox) const response = new grpcPuppet.MessageFileResponse() response.setFileBox(serializedFileBox) return callback(null, response) } catch (e) { return grpcError('messageFile', e, callback) } }, /** * @deprecated will be removed after Dec 31, 2022 */ messageFileStream: async (call) => { log.verbose('PuppetServiceImpl', 'messageFileStream()') try { const id = call.request.getId() const fileBox = await puppet.messageFile(id) const response = await packFileBoxToPb(grpcPuppet.MessageFileStreamResponse)(fileBox) response.on('error', e => call.destroy(e as Error)) response.pipe(call as unknown as Writable) // Huan(202203): FIXME: as unknown as } catch (e) { log.error('PuppetServiceImpl', 'grpcError() messageFileStream() rejection: %s', e && (e as Error).message) call.destroy(e as Error) } }, messageForward: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageForward()') try { const conversationId = call.request.getConversationId() const messageId = call.request.getMessageId() const id = await puppet.messageForward(conversationId, messageId) const response = new grpcPuppet.MessageForwardResponse() if (id) { response.setId(id) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(id) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageForward', e, callback) } }, messageImage: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageImage()') try { const id = call.request.getId() const type = call.request.getType() const fileBox = await puppet.messageImage(id, type) const serializedFileBox = await serializeFileBox(fileBox) const response = new grpcPuppet.MessageImageResponse() response.setFileBox(serializedFileBox) return callback(null, response) } catch (e) { return grpcError('messageImage', e, callback) } }, /** * @deprecated will be removed after Dec 31, 2022 */ messageImageStream: async (call) => { log.verbose('PuppetServiceImpl', 'messageImageStream()') try { const id = call.request.getId() const type = call.request.getType() const fileBox = await puppet.messageImage(id, type) // as number as PUPPET.types.Image const response = await packFileBoxToPb(grpcPuppet.MessageImageStreamResponse)(fileBox) response.on('error', e => call.destroy(e as Error)) response.pipe(call as unknown as Writable) // Huan(202203) FIXME: as unknown as } catch (e) { log.error('PuppetServiceImpl', 'grpcError() messageImageStream() rejection: %s', (e as Error).message) call.destroy(e as Error) } }, messageLocation: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageLocation()') try { const id = call.request.getId() const payload = await puppet.messageLocation(id) const response = new grpcPuppet.MessageLocationResponse() const pbLocationPayload = new grpcPuppet.LocationPayload() pbLocationPayload.setLatitude(payload.latitude) pbLocationPayload.setLongitude(payload.longitude) pbLocationPayload.setAccuracy(payload.accuracy) pbLocationPayload.setAddress(payload.address) pbLocationPayload.setName(payload.name) response.setLocation(pbLocationPayload) return callback(null, response) } catch (e) { return grpcError('messageLocation', e, callback) } }, messageMiniProgram: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageMiniProgram()') try { const id = call.request.getId() const payload = await puppet.messageMiniProgram(id) const response = new grpcPuppet.MessageMiniProgramResponse() const pbMiniProgramPayload = new grpcPuppet.MiniProgramPayload() if (payload.appid) { pbMiniProgramPayload.setAppid(payload.appid) } if (payload.description) { pbMiniProgramPayload.setDescription(payload.description) } if (payload.iconUrl) { pbMiniProgramPayload.setIconUrl(payload.iconUrl) } if (payload.pagePath) { pbMiniProgramPayload.setPagePath(payload.pagePath) } if (payload.shareId) { pbMiniProgramPayload.setShareId(payload.shareId) } if (payload.thumbKey) { pbMiniProgramPayload.setThumbKey(payload.thumbKey) } if (payload.thumbUrl) { pbMiniProgramPayload.setThumbUrl(payload.thumbUrl) } if (payload.title) { pbMiniProgramPayload.setTitle(payload.title) } if (payload.username) { pbMiniProgramPayload.setUsername(payload.username) } response.setMiniProgram(pbMiniProgramPayload) // Deprecated after Dec 31, 2022 response.setMiniProgramDeprecated(JSON.stringify(payload)) return callback(null, response) } catch (e) { return grpcError('messageMiniProgram', e, callback) } }, messagePayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messagePayload()') try { const id = call.request.getId() const payload = await puppet.messagePayload(id) const mentionIdList = ('mentionIdList' in payload) ? payload.mentionIdList || [] : [] const response = new grpcPuppet.MessagePayloadResponse() response.setFilename(payload.filename || '') /** * Huan(202203):`payload.fromId` is deprecated, will be removed in v2.0 */ response.setTalkerId(payload.talkerId || payload.fromId || '') response.setId(payload.id) response.setMentionIdsList(mentionIdList) response.setRoomId(payload.roomId || '') response.setText(payload.text || '') response.setReceiveTime(timestampFromMilliseconds(payload.timestamp)) // Deprecated: will be removed after Dec 31, 2022 response.setTimestampDeprecated(Math.floor(payload.timestamp)) /** * Huan(202203):`payload.toId` is deprecated, will be removed in v2.0 */ response.setListenerId(payload.listenerId || payload.toId || '') response.setType(payload.type as grpcPuppet.MessageTypeMap[keyof grpcPuppet.MessageTypeMap]) return callback(null, response) } catch (e) { return grpcError('messagePayload', e, callback) } }, messageRecall: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageRecall()') try { const id = call.request.getId() const success = await puppet.messageRecall(id) const response = new grpcPuppet.MessageRecallResponse() response.setSuccess(success) return callback(null, response) } catch (e) { return grpcError('messageRecall', e, callback) } }, messageSendContact: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendContact()') try { const conversationId = call.request.getConversationId() const contactId = call.request.getContactId() const messageId = await puppet.messageSendContact(conversationId, contactId) const response = new grpcPuppet.MessageSendContactResponse() if (messageId) { response.setId(messageId) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(messageId) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageSendContact', e, callback) } }, messageSendFile: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendFile()') try { const conversationId = call.request.getConversationId() const jsonText = call.request.getFileBox() const fileBox = FileBoxUuid.fromJSON(jsonText) const messageId = await puppet.messageSendFile(conversationId, fileBox) const response = new grpcPuppet.MessageSendFileResponse() if (messageId) { response.setId(messageId) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(messageId) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageSendFile', e, callback) } }, /** * @deprecated will be removed after Dec 31, 2022 */ messageSendFileStream: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendFileStream()') try { const requestArgs = await unpackConversationIdFileBoxArgsFromPb(call) const conversationId = requestArgs.conversationId const fileBox = requestArgs.fileBox const messageId = await puppet.messageSendFile(conversationId, fileBox) const response = new grpcPuppet.MessageSendFileStreamResponse() if (messageId) { response.setId(messageId) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(messageId) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageSendFileStream', e, callback) } }, messageSendLocation: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendLocation()') try { const conversationId = call.request.getConversationId() const pbLocationPayload = call.request.getLocation() const payload: PUPPET.payloads.Location = { accuracy : 0, address : 'NOADDRESS', latitude : 0, longitude : 0, name : 'NONAME', ...pbLocationPayload, } const messageId = await puppet.messageSendLocation(conversationId, payload) const response = new grpcPuppet.MessageSendLocationResponse() if (messageId) { response.setId(messageId) } return callback(null, response) } catch (e) { return grpcError('messageSendLocation', e, callback) } }, messageSendMiniProgram: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendMiniProgram()') try { const conversationId = call.request.getConversationId() let pbMiniProgramPayload = call.request.getMiniProgram()?.toObject() if (!pbMiniProgramPayload) { // Deprecated: will be removed after Dec 31, 2022 const jsonText = call.request.getMiniProgramDeprecated() pbMiniProgramPayload = JSON.parse(jsonText) } const payload: PUPPET.payloads.MiniProgram = { ...pbMiniProgramPayload, } const messageId = await puppet.messageSendMiniProgram(conversationId, payload) const response = new grpcPuppet.MessageSendMiniProgramResponse() if (messageId) { response.setId(messageId) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(messageId) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageSendMiniProgram', e, callback) } }, messageSendText: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendText()') try { const conversationId = call.request.getConversationId() const text = call.request.getText() const mentionIdList = call.request.getMentionalIdsList() const messageId = await puppet.messageSendText(conversationId, text, mentionIdList) const response = new grpcPuppet.MessageSendTextResponse() if (messageId) { response.setId(messageId) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(messageId) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageSendText', e, callback) } }, messageSendUrl: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageSendUrl()') try { const conversationId = call.request.getConversationId() let pbUrlLinkPayload = call.request.getUrlLink()?.toObject() if (!pbUrlLinkPayload) { // Deprecated: will be removed after Dec 31, 2022 const jsonText = call.request.getUrlLinkDeprecated() pbUrlLinkPayload = JSON.parse(jsonText) } const payload: PUPPET.payloads.UrlLink = { title : 'NOTITLE', url : 'NOURL', ...pbUrlLinkPayload, } const messageId = await puppet.messageSendUrl(conversationId, payload) const response = new grpcPuppet.MessageSendUrlResponse() if (messageId) { response.setId(messageId) { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const idWrapper = new StringValue() idWrapper.setValue(messageId) response.setIdStringValueDeprecated(idWrapper) } } return callback(null, response) } catch (e) { return grpcError('messageSendUrl', e, callback) } }, messageUrl: async (call, callback) => { log.verbose('PuppetServiceImpl', 'messageUrl()') try { const id = call.request.getId() const payload = await puppet.messageUrl(id) const response = new grpcPuppet.MessageUrlResponse() const pbUrlLinkPayload = new grpcPuppet.UrlLinkPayload() pbUrlLinkPayload.setTitle(payload.title) pbUrlLinkPayload.setUrl(payload.url) if (payload.thumbnailUrl) { pbUrlLinkPayload.setThumbnailUrl(payload.thumbnailUrl) } if (payload.description) { pbUrlLinkPayload.setDescription(payload.description) } response.setUrlLink(pbUrlLinkPayload) // Deprecated: will be removed after Dec 31, 2022 response.setUrlLinkDeprecated(JSON.stringify(payload)) return callback(null, response) } catch (e) { return grpcError('messageUrl', e, callback) } }, roomAdd: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomAdd()') try { const roomId = call.request.getId() const contactId = call.request.getContactId() const inviteOnly = call.request.getInviteOnly() await puppet.roomAdd(roomId, contactId, inviteOnly) return callback(null, new grpcPuppet.RoomAddResponse()) } catch (e) { return grpcError('roomAdd', e, callback) } }, roomAnnounce: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomAnnounce()') try { const roomId = call.request.getId() /** * Set */ if (call.request.hasText()) { await puppet.roomAnnounce(roomId, call.request.getText()) return callback(null, new grpcPuppet.RoomAnnounceResponse()) } /** * Get */ const text = await puppet.roomAnnounce(roomId) const response = new grpcPuppet.RoomAnnounceResponse() response.setText(text) return callback(null, response) } catch (e) { return grpcError('roomAnnounce', e, callback) } }, roomAvatar: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomAvatar()') try { const roomId = call.request.getId() const fileBox = await puppet.roomAvatar(roomId) const serializedFileBox = await serializeFileBox(fileBox) const response = new grpcPuppet.RoomAvatarResponse() response.setFileBox(serializedFileBox) return callback(null, response) } catch (e) { return grpcError('roomAvatar', e, callback) } }, roomCreate: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomCreate()') try { const contactIdList = call.request.getContactIdsList() const topic = call.request.getTopic() const roomId = await puppet.roomCreate(contactIdList, topic) const response = new grpcPuppet.RoomCreateResponse() response.setId(roomId) return callback(null, response) } catch (e) { return grpcError('roomCreate', e, callback) } }, roomDel: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomDel()') try { const roomId = call.request.getId() const contactId = call.request.getContactId() await puppet.roomDel(roomId, contactId) return callback(null, new grpcPuppet.RoomDelResponse()) } catch (e) { return grpcError('roomDel', e, callback) } }, roomInvitationAccept: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomInvitationAccept()') try { const id = call.request.getId() await puppet.roomInvitationAccept(id) return callback(null, new grpcPuppet.RoomInvitationAcceptResponse()) } catch (e) { return grpcError('roomInvitationAccept', e, callback) } }, roomInvitationPayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomInvitationPayload()') try { const roomInvitationId = call.request.getId() /** * Set */ { const jsonText = call.request.getPayload() if (jsonText) { const payload = JSON.parse(jsonText) as PUPPET.payloads.RoomInvitation await puppet.roomInvitationPayload(roomInvitationId, payload) return callback(null, new grpcPuppet.RoomInvitationPayloadResponse()) } { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const payloadWrapper = call.request.getPayloadStringValueDeprecated() if (payloadWrapper) { const jsonText = payloadWrapper.getValue() const payload = JSON.parse(jsonText) as PUPPET.payloads.RoomInvitation await puppet.roomInvitationPayload(roomInvitationId, payload) return callback(null, new grpcPuppet.RoomInvitationPayloadResponse()) } } } /** * Get */ const payload = await puppet.roomInvitationPayload(roomInvitationId) const response = new grpcPuppet.RoomInvitationPayloadResponse() response.setAvatar(payload.avatar) response.setId(payload.id) response.setInvitation(payload.invitation) response.setInviterId(payload.inviterId) response.setReceiverId(payload.receiverId) response.setMemberCount(payload.memberCount) response.setMemberIdsList(payload.memberIdList) response.setReceiveTime(timestampFromMilliseconds(payload.timestamp)) { // Deprecated: will be removed after Dec 31, 2022 const deprecated = true void deprecated response.setTimestampUint64Deprecated(Math.floor(payload.timestamp)) } response.setTopic(payload.topic) return callback(null, response) } catch (e) { return grpcError('roomInvitationPayload', e, callback) } }, roomList: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomList()') void call try { const roomIdList = await puppet.roomList() const response = new grpcPuppet.RoomListResponse() response.setIdsList(roomIdList) return callback(null, response) } catch (e) { return grpcError('roomList', e, callback) } }, roomMemberList: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomMemberList()') try { const roomId = call.request.getId() const roomMemberIdList = await puppet.roomMemberList(roomId) const response = new grpcPuppet.RoomMemberListResponse() response.setMemberIdsList(roomMemberIdList) return callback(null, response) } catch (e) { return grpcError('roomMemberList', e, callback) } }, roomMemberPayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomMemberPayload()') try { const roomId = call.request.getId() const memberId = call.request.getMemberId() const payload = await puppet.roomMemberPayload(roomId, memberId) const response = new grpcPuppet.RoomMemberPayloadResponse() response.setAvatar(payload.avatar) response.setId(payload.id) response.setInviterId(payload.inviterId || '') response.setName(payload.name) response.setRoomAlias(payload.roomAlias || '') return callback(null, response) } catch (e) { return grpcError('roomMemberPayload', e, callback) } }, roomPayload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomPayload()') try { const roomId = call.request.getId() const payload = await puppet.roomPayload(roomId) const response = new grpcPuppet.RoomPayloadResponse() response.setAdminIdsList(payload.adminIdList) response.setAvatar(payload.avatar || '') response.setId(payload.id) response.setMemberIdsList(payload.memberIdList) response.setOwnerId(payload.ownerId || '') response.setTopic(payload.topic) return callback(null, response) } catch (e) { return grpcError('roomPayload', e, callback) } }, roomQRCode: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomQRCode()') try { const roomId = call.request.getId() const qrcode = await puppet.roomQRCode(roomId) const response = new grpcPuppet.RoomQRCodeResponse() response.setQrcode(qrcode) return callback(null, response) } catch (e) { return grpcError('roomQRCode', e, callback) } }, roomQuit: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomQuit()') try { const roomId = call.request.getId() await puppet.roomQuit(roomId) return callback(null, new grpcPuppet.RoomQuitResponse()) } catch (e) { return grpcError('roomQuit', e, callback) } }, roomTopic: async (call, callback) => { log.verbose('PuppetServiceImpl', 'roomTopic()') try { const roomId = call.request.getId() /** * Set */ if (call.request.hasTopic()) { await puppet.roomTopic(roomId, call.request.getTopic()) return callback(null, new grpcPuppet.RoomTopicResponse()) } /** * Get */ const topic = await puppet.roomTopic(roomId) const response = new grpcPuppet.RoomTopicResponse() response.setTopic(topic) return callback(null, response) } catch (e) { return grpcError('roomTopic', e, callback) } }, start: async (call, callback) => { log.verbose('PuppetServiceImpl', 'start()') void call try { await timeoutPromise( puppet.start(), 15 * 1000, // 15 seconds timeout ) return callback(null, new grpcPuppet.StartResponse()) } catch (e) { return grpcError('start', e, callback) } }, stop: async (call, callback) => { log.verbose('PuppetServiceImpl', 'stop()') void call try { if (eventStreamManager.busy()) { eventStreamManager.stop() } else { log.error('PuppetServiceImpl', 'stop() eventStreamManager is not busy?') } readyPayload = undefined await timeoutPromise( puppet.stop(), 15 * 1000, // 15 seconds timeout ) return callback(null, new grpcPuppet.StopResponse()) } catch (e) { return grpcError('stop', e, callback) } }, tagContactAdd: async (call, callback) => { log.verbose('PuppetServiceImpl', 'tagContactAdd()') try { const tagId = call.request.getId() const contactId = call.request.getContactId() await puppet.tagContactAdd(tagId, contactId) return callback(null, new grpcPuppet.TagContactAddResponse()) } catch (e) { return grpcError('tagContactAdd', e, callback) } }, tagContactDelete: async (call, callback) => { log.verbose('PuppetServiceImpl', 'tagContactDelete()') try { const tagId = call.request.getId() await puppet.tagContactDelete(tagId) return callback(null, new grpcPuppet.TagContactDeleteResponse()) } catch (e) { return grpcError('tagContactDelete', e, callback) } }, tagContactList: async (call, callback) => { log.verbose('PuppetServiceImpl', 'tagContactList()') try { const contactId = call.request.getContactId() /** * for a specific contact */ if (contactId) { const tagIdList = await puppet.tagContactList(contactId) const response = new grpcPuppet.TagContactListResponse() response.setIdsList(tagIdList) return callback(null, new grpcPuppet.TagContactListResponse()) } { /** * Huan(202110): Deprecated: will be removed after Dec 31, 2022 */ const contactIdWrapper = call.request.getContactIdStringValueDeprecated() if (contactIdWrapper) { const contactId = contactIdWrapper.getValue() const tagIdList = await puppet.tagContactList(contactId) const response = new grpcPuppet.TagContactListResponse() response.setIdsList(tagIdList) return callback(null, new grpcPuppet.TagContactListResponse()) } } /** * get all tags for all contact */ const tagIdList = await puppet.tagContactList() const response = new grpcPuppet.TagContactListResponse() response.setIdsList(tagIdList) return callback(null, response) } catch (e) { return grpcError('tagContactList', e, callback) } }, tagContactRemove: async (call, callback) => { log.verbose('PuppetServiceImpl', 'tagContactRemove()') try { const tagId = call.request.getId() const contactId = call.request.getContactId() await puppet.tagContactRemove(tagId, contactId) return callback(null, new grpcPuppet.TagContactRemoveResponse()) } catch (e) { return grpcError('tagContactRemove', e, callback) } }, version: async (call, callback) => { log.verbose('PuppetServiceImpl', 'version() v%s', puppet.version()) void call try { const version = puppet.version() const response = new grpcPuppet.VersionResponse() response.setVersion(version) return callback(null, response) } catch (e) { return grpcError('version', e, callback) } }, download: async (call) => { log.verbose('PuppetServiceImpl', 'download()') const uuid = call.request.getId() const fileBox = FileBoxUuid.fromUuid(uuid, { name: 'uuid.dat' }) fileBox .pipe(chunkEncoder(grpcPuppet.DownloadResponse)) .pipe(call as unknown as Writable) // Huan(202203) FIXME: as unknown as }, upload: async (call, callback) => { log.verbose('PuppetServiceImpl', 'upload()') const fileBox = FileBoxUuid.fromStream( call.pipe(chunkDecoder()), 'uuid.dat', ) const uuid = await fileBox.toUuid() const response = new grpcPuppet.UploadResponse() response.setId(uuid) return callback(null, response) }, } return puppetServerImpl } export { puppetImplementation }