diff --git a/src/message.ts b/src/message.ts index d5f7a71e3d962e8fa35d4001550a4c14584acb60..c14bd08621414e902b7f5a7d3858bd01c81fb6d6 100644 --- a/src/message.ts +++ b/src/message.ts @@ -42,7 +42,9 @@ export interface MsgRawObj { MMActualSender: string, // getUserContact(message.MMActualSender,message.MMPeerUserName).isContact() MMPeerUserName: string, // message.MsgType == CONF.MSGTYPE_TEXT && message.MMPeerUserName == 'newsapp' ToUserName: string, + FromUserName: string, MMActualContent: string, // Content has @id prefix added by wx + Content: string, MMDigest: string, MMDisplayTime: number, // Javascript timestamp of milliseconds @@ -132,6 +134,18 @@ export interface MsgRawObj { * MsgType == CONF.MSGTYPE_VERIFYMSG */ RecommendInfo?: RecommendInfo, + + /** + * Transpond Message + */ + MsgIdBeforeTranspond?: string, // oldMsg.MsgIdBeforeTranspond || oldMsg.MsgId, + isTranspond?: boolean, + MMSourceMsgId?: string, + sendByLocal?: boolean, // If transpond file, it must is false, not need to upload. And, can't to call createMessage(), it set to true + MMSendContent?: string, + + MMIsChatRoom?: boolean, + } export interface MsgObj { @@ -1098,6 +1112,92 @@ export class MediaMessage extends Message { throw e } } + + public forward(room: Room): Promise + public forward(contact: Contact): Promise + /** + * Forward the received message. + * + * The types of messages that can be forwarded are as follows: + * + * The return value of {@link Message#type} matches one of the following types: + * ```json + * MsgType { + * TEXT = 1, + * IMAGE = 3, + * VIDEO = 43, + * EMOTICON = 47, + * LOCATION = 48, + * APP = 49, + * MICROVIDEO = 62, + * } + * ``` + * + * When the return value of {@link Message#type} is `MsgType.APP`, the return value of {@link Message#typeApp} matches one of the following types: + * ```json + * AppMsgType { + * TEXT = 1, + * IMG = 2, + * VIDEO = 4, + * ATTACH = 6, + * EMOJI = 8, + * } + * ``` + * But, it should be noted that when forwarding ATTACH type message, if the file size is greater than 25Mb, the forwarding will fail. + * The reason is that the server limits the forwarding of files above 25Mb. You need to download the file and use `new MediaMessage (file)` to send the file. + * + * @param {(Room | Contact)} sendTo + * The recipient of the message, the room, or the contact + * @returns {Promise} + * @memberof MediaMessage + */ + public forward(sendTo: Room|Contact): Promise { + if (!this.rawObj) { + throw new Error('no rawObj!') + } + let m = Object.assign({}, this.rawObj) + const newMsg = {} + const fileSizeLimit = 25 * 1024 * 1024 + let id = '' + // if you know roomId or userId, you can use `Room.load(roomId)` or `Contact.load(userId)` + if (sendTo instanceof Room || sendTo instanceof Contact) { + id = sendTo.id + } else { + throw new Error('param must be Room or Contact!') + } + + newMsg.ToUserName = id + newMsg.FromUserName = config.puppetInstance().userId || '' + newMsg.isTranspond = true + newMsg.MsgIdBeforeTranspond = m.MsgIdBeforeTranspond || m.MsgId + newMsg.MMSourceMsgId = m.MsgId + // In room msg, the content prefix sender:, need to be removed, otherwise the forwarded sender will display the source message sender, causing self () to determine the error + newMsg.Content = UtilLib.unescapeHtml(m.Content.replace(/^@\w+:/, '')).replace(/^[\w\-]+:/, '') + newMsg.MMIsChatRoom = sendTo instanceof Room ? true : false + + // The following parameters need to be overridden after calling createMessage() + + // If you want to forward the file, would like to skip the duplicate upload, sendByLocal must be false. + // But need to pay attention to file.size> 25Mb, due to the server policy restrictions, need to re-upload + if (m.FileSize >= fileSizeLimit) { + log.warn('Message', 'forward() file size >= 25Mb,the message may fail to be forwarded due to server policy restrictions.') + } + newMsg.sendByLocal = false + newMsg.MMActualSender = config.puppetInstance().userId || '' + if (m.MMSendContent) { + newMsg.MMSendContent = m.MMSendContent.replace(/^@\w+:\s/, '') + } + if (m.MMDigest) { + newMsg.MMDigest = m.MMDigest.replace(/^@\w+:/, '') + } + if (m.MMActualContent) { + newMsg.MMActualContent = UtilLib.stripHtml(m.MMActualContent.replace(/^@\w+:/, '')).replace(/^[\w\-]+:/, '') + } + m = Object.assign(m, newMsg) + + return config.puppetInstance() + .forward(m, newMsg) + } } /* diff --git a/src/puppet-web/bridge.ts b/src/puppet-web/bridge.ts index 2d37eb381b2028d7ef746fe3e053f05c9aafd897..061014bd71cdd547236313c1779f5163d8777425 100644 --- a/src/puppet-web/bridge.ts +++ b/src/puppet-web/bridge.ts @@ -21,11 +21,10 @@ import { parseString } from 'xml2js' /* tslint:disable:no-var-requires */ const retryPromise = require('retry-promise').default -import { log } from '../config' +import { log } from '../config' -import { - PuppetWeb, -} from './puppet-web' +import PuppetWeb from './puppet-web' +import { MsgRawObj } from '../message' export interface MediaData { ToUserName: string, @@ -424,6 +423,20 @@ export class Bridge { }) } + public forward(baseData: MsgRawObj, patchData: MsgRawObj): Promise { + if (!baseData.ToUserName) { + throw new Error('UserName not found') + } + if (!patchData.MMActualContent && !patchData.MMSendContent && !patchData.Content) { + throw new Error('cannot say nothing') + } + return this.proxyWechaty('forward', baseData, patchData) + .catch(e => { + log.error('PuppetWebBridge', 'forward() exception: %s', e.message) + throw e + }) + } + /** * Proxy Call to Wechaty in Bridge */ diff --git a/src/puppet-web/puppet-web.ts b/src/puppet-web/puppet-web.ts index 2a0e71333f56e9914b268ce905885dd6a78e0ceb..7d31ca5f4daf57403855c84d22f536e82c6ad667 100644 --- a/src/puppet-web/puppet-web.ts +++ b/src/puppet-web/puppet-web.ts @@ -29,6 +29,7 @@ import Contact from '../contact' import { Message, MediaMessage, + MsgRawObj, } from '../message' import { Puppet, @@ -522,6 +523,26 @@ export class PuppetWeb extends Puppet { return ret } + public async forward(baseData: MsgRawObj, patchData: MsgRawObj): Promise { + + log.silly('PuppetWeb', 'forward() destination: %s, content: %s)', + patchData.ToUserName, + patchData.MMActualContent, + ) + let ret = false + try { + // log.info('PuppetWeb', `forward() baseData: ${JSON.stringify(baseData)}\n`) + // log.info('PuppetWeb', `forward() patchData: ${JSON.stringify(patchData)}\n`) + + ret = await this.bridge.forward(baseData, patchData) + } catch (e) { + log.error('PuppetWeb', 'forward() exception: %s', e.message) + Raven.captureException(e) + throw e + } + return ret + } + public async send(message: Message | MediaMessage): Promise { const to = message.to() const room = message.room() diff --git a/src/puppet-web/wechaty-bro.js b/src/puppet-web/wechaty-bro.js index 9fcb6d9818c53e81984ad6599c5c66d49d7195dc..4e30064ffb7a2e434ce9acd0af05b15553ac9c4a 100644 --- a/src/puppet-web/wechaty-bro.js +++ b/src/puppet-web/wechaty-bro.js @@ -516,6 +516,31 @@ return true } + function forward(baseData, patchData) { + var chatFactory = WechatyBro.glue.chatFactory + var confFactory = WechatyBro.glue.confFactory + + if (!chatFactory || !confFactory) { + log('forward() chatFactory or confFactory not exist.') + return false + } + + try { + var m = chatFactory.createMessage(baseData) + + // Need to override the parametes after called createMessage() + m = Object.assign(m, patchData) + + chatFactory.appendMessage(m) + chatFactory.sendMessage(m) + } catch (e) { + log('forward() exception: ' + e.message) + return false + } + return true + } + + function send(ToUserName, Content) { var chatFactory = WechatyBro.glue.chatFactory var confFactory = WechatyBro.glue.confFactory @@ -917,6 +942,7 @@ , getPassticket: getPassticket , getUploadMediaUrl: getUploadMediaUrl , sendMedia: sendMedia + , forward: forward // for Wechaty Contact Class , contactFindAsync: contactFindAsync diff --git a/src/puppet.ts b/src/puppet.ts index 587fe84b0df124fce479e81846917cd5d16ab9cf..9fbeb54be96ec4e25978a64d222ebdbf876446b4 100644 --- a/src/puppet.ts +++ b/src/puppet.ts @@ -28,6 +28,7 @@ import Contact from './contact' import { Message, MediaMessage, + MsgRawObj, } from './message' import Room from './room' @@ -54,6 +55,7 @@ export abstract class Puppet extends EventEmitter implements Sayable { public abstract self(): Contact public abstract send(message: Message | MediaMessage): Promise + public abstract forward(baseData: MsgRawObj, patchData: MsgRawObj): Promise public abstract say(content: string): Promise public abstract reset(reason?: string): void