message.ts 16.1 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/**
 *   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.
 *   @ignore
 */
19 20
// import path from 'path'
// import cuid from 'cuid'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
21

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22
import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
23
  FileBox,
24 25 26 27
}                     from 'file-box'
import {
  instanceToClass,
}                     from 'clone-class'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
28

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
29
import {
30
  log,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
31
  Sayable,
32 33 34 35
}                 from './config'
import {
  Accessory,
}                 from './accessory'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
36

37 38 39 40 41 42
import {
  Contact,
}                 from './contact'
import {
  Room,
}                 from './room'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
43

44 45 46 47
import {
  MessagePayload,
  MessageType,
}                 from './puppet/'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
48

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
49 50 51 52 53 54
/**
 * All wechat messages will be encapsulated as a Message.
 *
 * `Message` is `Sayable`,
 * [Examples/Ding-Dong-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/ding-dong-bot.ts}
 */
55
export class Message extends Accessory implements Sayable {
56 57 58 59 60 61 62

  /**
   *
   * Static Properties
   *
   */

63
  // tslint:disable-next-line:variable-name
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64
  public static readonly Type = MessageType
65

66 67 68 69 70
  /**
   * @todo add function
   */
  public static async find<T extends typeof Message>(
    this: T,
71
    query: any,
72
  ): Promise<T['prototype'] | null> {
73
    return (await this.findAll(query))[0]
74 75 76 77 78 79 80
  }

  /**
   * @todo add function
   */
  public static async findAll<T extends typeof Message>(
    this: T,
81
    query: any,
82
  ): Promise<T['prototype'][]> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
83
    log.verbose('Message', 'findAll(%s)', query)
84
    return [
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
85 86
      new (this as any)({ MsgId: 'id1' }),
      new (this as any)({ MsdId: 'id2' }),
87
    ]
88 89
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90
 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
91 92
  * Create a Mobile Terminated Message
  *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
93 94 95
  * "mobile originated" or "mobile terminated"
  * https://www.tatango.com/resources/video-lessons/video-mo-mt-sms-messaging/
  */
96
  // TODO: rename create to load ??? Huan 201806
97 98 99
  public static create(id: string): Message {
    log.verbose('Message', 'static create(%s)', id)

100 101 102 103 104 105 106
    /**
     * Must NOT use `Message` at here
     * MUST use `this` at here
     *
     * because the class will be `cloneClass`-ed
     */
    const msg = new this(id)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107

108
    // msg.payload = this.puppet.cacheMessagePayload.get(id)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
109

110
    return msg
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
111 112
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
113 114 115 116 117
  /**
   *
   * Instance Properties
   *
   */
118
  private get payload(): undefined | MessagePayload {
119 120 121 122
    if (!this.id) {
      return undefined
    }

123 124
    return this.puppet.messagePayloadCache(this.id)
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
125

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
126 127 128
  /**
   * @private
   */
129 130
  constructor(
    public readonly id: string,
131 132
  ) {
    super()
133
    log.verbose('Message', 'constructor(%s) for class %s',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
134
                          id || '',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
135 136
                          this.constructor.name,
              )
137

138 139 140 141 142 143 144 145 146 147
    // tslint:disable-next-line:variable-name
    const MyClass = instanceToClass(this, Message)

    if (MyClass === Message) {
      throw new Error('Message class can not be instanciated directly! See: https://github.com/Chatie/wechaty/issues/1217')
    }

    if (!this.puppet) {
      throw new Error('Message class can not be instanciated without a puppet!')
    }
148
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
149 150 151 152 153

  /**
   * @private
   */
  public toString() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
154 155 156 157
    if (!this.isReady()) {
      return this.constructor.name
    }

158 159 160
    const msgStrList = [
      'Message',
      `#${MessageType[this.type()]}`,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
161
      '(',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
162
        this.room() ? (this.room() + '') : '',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
163
        this.from() || '',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
164
        '',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
165
        this.to() || '',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
166
      ')',
167
    ]
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
168 169 170
    if (   this.type() === Message.Type.Text
        || this.type() === Message.Type.Unknown
    ) {
171
      msgStrList.push(`<${this.text().substr(0, 70)}>`)
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
172
    } else {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
173
      log.silly('Message', 'toString() for message type: %s(%s)', Message.Type[this.type()], this.type())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
174

175 176 177
      if (!this.payload) {
        throw new Error('no payload')
      }
178
      const filename = this.payload.filename
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
179 180 181 182 183 184 185 186
      // if (!filename) {
      //   throw new Error(
      //     'no file for message id: ' + this.id
      //     + ' with type: ' + Message.Type[this.payload.type]
      //     + '(' + this.payload.type + ')',
      //   )
      // }
      msgStrList.push(`<${filename || 'unknown file name'}>`)
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
187
    }
188 189

    return msgStrList.join('')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
190
  }
191 192 193 194
  /**
   * Get the sender from a message.
   * @returns {Contact}
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
195
  public from(): null | Contact {
196 197 198
    if (!this.payload) {
      throw new Error('no payload')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
199

200 201 202 203
    // if (contact) {
    //   this.payload.from = contact
    //   return
    // }
204

205 206
    const fromId = this.payload.fromId
    if (!fromId) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
207
      return null
208
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
209

210
    const from = this.wechaty.Contact.load(fromId)
211
    return from
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
212 213
  }

214 215 216 217 218
  /**
   * Get the destination of the message
   * Message.to() will return null if a message is in a room, use Message.room() to get the room.
   * @returns {(Contact|null)}
   */
219 220 221 222 223
  public to(): null | Contact {
    if (!this.payload) {
      throw new Error('no payload')
    }

224 225 226 227 228
    const toId = this.payload.toId
    if (!toId) {
      return null
    }

229
    const to = this.wechaty.Contact.load(toId)
230
    return to
231 232
  }

233 234 235 236 237 238
  /**
   * Get the room from the message.
   * If the message is not in a room, then will return `null`
   *
   * @returns {(Room | null)}
   */
239 240 241 242
  public room(): null | Room {
    if (!this.payload) {
      throw new Error('no payload')
    }
243 244 245 246
    const roomId = this.payload.roomId
    if (!roomId) {
      return null
    }
247

248
    const room = this.wechaty.Room.load(roomId)
249
    return room
250 251
  }

252 253 254 255 256
  /**
   * Get the text content of the message
   *
   * @returns {string}
   */
257 258 259 260 261
  public text(): string {
    if (!this.payload) {
      throw new Error('no payload')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
262
    return this.payload.text || ''
263 264
  }

265 266 267
  public async say(text:    string, mention?: Contact | Contact[]) : Promise<void>
  public async say(contact: Contact)                               : Promise<void>
  public async say(file:    FileBox)                               : Promise<void>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
268

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
269 270 271 272
  /**
   * Reply a Text or Media File message to the sender.
   *
   * @see {@link https://github.com/Chatie/wechaty/blob/master/examples/ding-dong-bot.ts|Examples/ding-dong-bot}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
273
   * @param {(string | FileBox)} textOrContactOrFile
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
274
   * @param {(Contact|Contact[])} [mention]
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
275 276 277 278 279 280
   * @returns {Promise<void>}
   *
   * @example
   * const bot = new Wechaty()
   * bot
   * .on('message', async m => {
281
   *   if (/^ding$/i.test(m.text())) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
282 283
   *     await m.say('hello world')
   *     console.log('Bot REPLY: hello world')
284
   *     await m.say(new bot.Message(__dirname + '/wechaty.png'))
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
285 286 287 288
   *     console.log('Bot REPLY: Image')
   *   }
   * })
   */
289
  public async say(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
290
    textOrContactOrFile : string | Contact | FileBox,
291 292
    mention?   : Contact | Contact[],
  ): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
293
    log.verbose('Message', 'say(%s%s)',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
294
                            textOrContactOrFile.toString(),
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
295 296 297
                            mention
                              ? ', ' + mention
                              : '',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
298
                )
299 300 301 302 303 304 305 306 307 308 309 310

    // const user = this.puppet.userSelf()
    const from = this.from()
    // const to   = this.to()
    const room = this.room()

    const mentionList = mention
                          ? Array.isArray(mention)
                            ? mention
                            : [mention]
                          : []

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
    if (typeof textOrContactOrFile === 'string') {
      await this.sayText(
        textOrContactOrFile,
        from || undefined,
        room || undefined,
        mentionList,
      )
    } else if (textOrContactOrFile instanceof Contact) {
      /**
       * Contact Card
       */
      await this.puppet.messageSendContact({
        roomId    : room && room.id || undefined,
        contactId : from && from.id || undefined,
      }, textOrContactOrFile.id)
326 327 328 329
    } else {
      /**
       * File Message
       */
330 331
      await this.puppet.messageSendFile({
        roomId    : room && room.id || undefined,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
332 333
        contactId : from && from.id || undefined,
      }, textOrContactOrFile)
334 335 336 337
    }
  }

  private async sayText(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
338 339 340 341
    text         : string,
    to?          : Contact,
    room?        : Room,
    mentionList? : Contact[],
342
  ): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
343
    if (room && mentionList && mentionList.length > 0) {
344
      /**
345
       * 1 had mentioned someone
346
       */
347 348 349 350 351 352
      const mentionContact = mentionList[0]
      const textMentionList = mentionList.map(c => '@' + c.name()).join(' ')
      await this.puppet.messageSendText({
        contactId: mentionContact.id,
        roomId: room.id,
      }, textMentionList + ' ' + text)
353 354
    } else {
      /**
355
       * 2 did not mention anyone
356
       */
357
      await this.puppet.messageSendText({
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
358 359
        contactId : to && to.id,
        roomId    : room && room.id,
360
      }, text)
361 362 363
    }
  }

364 365 366
  public async file(): Promise<FileBox> {
    if (this.type() === Message.Type.Text) {
      throw new Error('text message no file')
367
    }
368 369
    const fileBox = await this.puppet.messageFile(this.id)
    return fileBox
370
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
371 372 373 374 375 376

  /**
   * Get the type from the message.
   *
   * If type is equal to `MsgType.RECALLED`, {@link Message#id} is the msgId of the recalled message.
   * @see {@link MsgType}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
377
   * @returns {WebMsgType}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
378
   */
379 380 381 382 383 384 385
  public type(): MessageType {
    if (!this.payload) {
      throw new Error('no payload')
    }
    return this.payload.type || MessageType.Unknown
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
386 387 388 389 390 391 392 393 394
  /**
   * Check if a message is sent by self.
   *
   * @returns {boolean} - Return `true` for send from self, `false` for send from others.
   * @example
   * if (message.self()) {
   *  console.log('this message is sent by myself!')
   * }
   */
395
  public self(): boolean {
396
    const userId = this.puppet.selfId()
397 398
    const from = this.from()

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
399
    return !!from && from.id === userId
400
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420

  /**
   *
   * Get message mentioned contactList.
   *
   * Message event table as follows
   *
   * |                                                                            | Web  |  Mac PC Client | iOS Mobile |  android Mobile |
   * | :---                                                                       | :--: |     :----:     |   :---:    |     :---:       |
   * | [You were mentioned] tip ([有人@我]的提示)                                   |  ✘   |        √       |     √      |       √         |
   * | Identify magic code (8197) by copy & paste in mobile                       |  ✘   |        √       |     √      |       ✘         |
   * | Identify magic code (8197) by programming                                  |  ✘   |        ✘       |     ✘      |       ✘         |
   * | Identify two contacts with the same roomAlias by [You were  mentioned] tip |  ✘   |        ✘       |     √      |       √         |
   *
   * @returns {Contact[]} - Return message mentioned contactList
   *
   * @example
   * const contactList = message.mentioned()
   * console.log(contactList)
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
421 422
  public async mention(): Promise<Contact[]> {
    log.verbose('Message', 'mention()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
423

424 425
    const room = this.room()
    if (this.type() !== MessageType.Text || !room ) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
426
      return []
427 428 429 430 431 432
    }

    // define magic code `8197` to identify @xxx
    const AT_SEPRATOR = String.fromCharCode(8197)

    const atList = this.text().split(AT_SEPRATOR)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
433 434
    // console.log('atList: ', atList)
    if (atList.length === 0) return []
435 436

    // Using `filter(e => e.indexOf('@') > -1)` to filter the string without `@`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
437
    const rawMentionList = atList
438 439 440 441 442 443 444 445 446 447 448 449
      .filter(str => str.includes('@'))
      .map(str => multipleAt(str))

    // convert 'hello@a@b@c' to [ 'c', 'b@c', 'a@b@c' ]
    function multipleAt(str: string) {
      str = str.replace(/^.*?@/, '@')
      let name = ''
      const nameList: string[] = []
      str.split('@')
        .filter(mentionName => !!mentionName)
        .reverse()
        .forEach(mentionName => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
450
          // console.log('mentionName: ', mentionName)
451 452 453 454 455 456
          name = mentionName + '@' + name
          nameList.push(name.slice(0, -1)) // get rid of the `@` at beginning
        })
      return nameList
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
457 458 459
    let mentionNameList: string[] = []
    // Flatten Array
    // see http://stackoverflow.com/a/10865042/1123955
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
460
    mentionNameList = mentionNameList.concat.apply([], rawMentionList)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
461 462 463
    // filter blank string
    mentionNameList = mentionNameList.filter(s => !!s)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
464
    log.verbose('Message', 'mention() text = "%s", mentionNameList = "%s"',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
465 466 467
                            this.text(),
                            JSON.stringify(mentionNameList),
                )
468

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
469 470 471 472
    const contactListNested = await Promise.all(
      mentionNameList.map(
        name => room.memberAll(name),
      ),
473 474
    )

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
475 476 477
    let contactList: Contact[] = []
    contactList = contactList.concat.apply([], contactListNested)

478
    if (contactList.length === 0) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
479
      log.silly('Message', `message.mention() can not found member using room.member() from mentionList, metion string: ${JSON.stringify(mentionNameList)}`)
480 481 482 483
    }
    return contactList
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
484 485 486 487 488 489 490 491
  /**
   * @deprecated: use mention() instead
   */
  public async mentioned(): Promise<Contact[]> {
    log.warn('Message', 'mentioned() DEPRECATED. use mention() instead.')
    return this.mention()
  }

492 493 494 495 496
  /**
   * @private
   */
  public isReady(): boolean {
    return !!this.payload
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
497
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
498 499 500 501

  /**
   * @private
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
502 503 504 505 506 507 508
  public async ready(): Promise<void> {
    log.verbose('Message', 'ready()')

    if (this.isReady()) {
      return
    }

509 510 511 512 513
    await this.puppet.messagePayload(this.id)

    if (!this.payload) {
      throw new Error('no payload')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
514

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
515 516 517 518 519
    const fromId = this.payload.fromId
    const roomId = this.payload.roomId
    const toId   = this.payload.toId

    if (fromId) {
520
      await this.wechaty.Contact.load(fromId).ready()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
521 522
    }
    if (roomId) {
523
      await this.wechaty.Room.load(roomId).ready()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
524 525
    }
    if (toId) {
526
      await this.wechaty.Contact.load(toId).ready()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
527
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
528
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
529

530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
  //       case WebMsgType.APP:
  //         if (!this.rawObj) {
  //           throw new Error('no rawObj')
  //         }
  //         switch (this.typeApp()) {
  //           case WebAppMsgType.ATTACH:
  //             if (!this.rawObj.MMAppMsgDownloadUrl) {
  //               throw new Error('no MMAppMsgDownloadUrl')
  //             }
  //             // had set in Message
  //             // url = this.rawObj.MMAppMsgDownloadUrl
  //             break

  //           case WebAppMsgType.URL:
  //           case WebAppMsgType.READER_TYPE:
  //             if (!this.rawObj.Url) {
  //               throw new Error('no Url')
  //             }
  //             // had set in Message
  //             // url = this.rawObj.Url
  //             break

  //           default:
  //             const e = new Error('ready() unsupported typeApp(): ' + this.typeApp())
  //             log.warn('PuppeteerMessage', e.message)
  //             throw e
  //         }
  //         break

  //       case WebMsgType.TEXT:
  //         if (this.typeSub() === WebMsgType.LOCATION) {
  //           url = await puppet.bridge.getMsgPublicLinkImg(this.id)
  //         }
  //         break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
565 566 567 568 569
  /**
   * Forward the received message.
   *
   * @param {(Sayable | Sayable[])} to Room or Contact
   * The recipient of the message, the room, or the contact
570
   * @returns {Promise<void>}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
571
   */
572 573
  public async forward(to: Room | Contact): Promise<void> {
    log.verbose('Message', 'forward(%s)', to)
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
574

575 576 577 578
    let roomId, contactId

    if (to instanceof Room) {
      roomId = to.id
579
    } else if (to instanceof Contact) {
580 581
      contactId = to.id
    }
582

583
    try {
584 585 586 587 588 589 590
      await this.puppet.messageForward(
        {
          contactId,
          roomId,
        },
        this.id,
      )
591 592 593 594 595
    } catch (e) {
      log.error('Message', 'forward(%s) exception: %s', to, e)
      throw e
    }
  }
596 597 598 599 600 601 602 603 604 605

  public date(): Date {
    if (!this.payload) {
      throw new Error('no payload')
    }

    // convert the unit timestamp to milliseconds
    // (from seconds to milliseconds)
    return new Date(1000 * this.payload.timestamp)
  }
606 607 608 609 610 611 612 613 614 615 616

  /**
   * Message Age:
   *  in seconds.
   */
  public age(): number {
    const ageMilliseconds = Date.now() - this.date().getTime()
    const ageSeconds = Math.floor(ageMilliseconds / 1000)
    return ageSeconds
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
617 618 619
}

export default Message