message.ts 15.2 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2
/**
 *
3
 * Wechaty: * * Wechaty - Wechat for Bot. Connecting ChatBots
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4 5
 *
 * Licenst: ISC
6
 * https://github.com/wechaty/wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
7 8
 *
 */
9
import {
L
lijiarui 已提交
10 11 12 13 14
  Config,
  RecommendInfo,
  Sayable,
  log,
}  from './config'
15

16 17 18
import { Contact }  from './contact'
import { Room }     from './room'
import { UtilLib }  from './util-lib'
19

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
20
export type MsgRawObj = {
L
lijiarui 已提交
21
  MsgId:            string,
22

L
lijiarui 已提交
23 24 25 26
  MMActualSender:   string, // getUserContact(message.MMActualSender,message.MMPeerUserName).isContact()
  MMPeerUserName:   string, // message.MsgType == CONF.MSGTYPE_TEXT && message.MMPeerUserName == 'newsapp'
  ToUserName:       string,
  MMActualContent:  string, // Content has @id prefix added by wx
27

L
lijiarui 已提交
28 29
  MMDigest:         string,
  MMDisplayTime:    number,  // Javascript timestamp of milliseconds
30 31 32 33 34

  /**
   * MsgType == MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_URL
   * class="cover" mm-src="{{getMsgImg(message.MsgId,'slave')}}"
   */
L
lijiarui 已提交
35 36
  Url:              string,
  MMAppMsgDesc:     string,  // class="desc" ng-bind="message.MMAppMsgDesc"
37 38

  /**
39 40
   * Attachment
   *
41 42
   * MsgType == MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_ATTACH
   */
L
lijiarui 已提交
43 44 45 46 47 48 49 50 51 52 53
  FileName:         string,  // FileName: '钢甲互联项目BP1108.pdf',
  FileSize:         number,  // FileSize: '2845701',
  MediaId:          string,  // MediaId: '@crypt_b1a45e3f_c21dceb3ac01349...

  MMAppMsgFileExt:      string,  // doc, docx ... 'undefined'?
  MMAppMsgFileSize:     string,  // '2.7MB',
  MMAppMsgDownloadUrl:  string,  // 'https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia?sender=@4f549c2dafd5ad731afa4d857bf03c10&mediaid=@crypt_b1a45e3f
                                 // <a download ng-if="message.MMFileStatus == CONF.MM_SEND_FILE_STATUS_SUCCESS
                                 // && (massage.MMStatus == CONF.MSG_SEND_STATUS_SUCC || massage.MMStatus === undefined)
                                 // " href="{{message.MMAppMsgDownloadUrl}}">下载</a>
  MMUploadProgress: number,  // < 100
54 55 56 57 58 59 60 61 62 63

  /**
   * 模板消息
   * MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_READER_TYPE
   *  item.url
   *  item.title
   *  item.pub_time
   *  item.cover
   *  item.digest
   */
L
lijiarui 已提交
64
  MMCategory:       any[],  //  item in message.MMCategory
65 66 67 68 69 70

  /**
   * Type
   *
   * MsgType == CONF.MSGTYPE_VOICE : ng-style="{'width':40 + 7*message.VoiceLength/1000}
   */
L
lijiarui 已提交
71 72 73
  MsgType:          number,
  AppMsgType:       AppMsgType,  // message.MsgType == CONF.MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_URL
                                 // message.MsgType == CONF.MSGTYPE_TEXT && message.SubMsgType != CONF.MSGTYPE_LOCATION
74

L
lijiarui 已提交
75
  SubMsgType:       MsgType, // "msgType":"{{message.MsgType}}","subType":{{message.SubMsgType||0}},"msgId":"{{message.MsgId}}"
76 77 78 79

  /**
   * Status-es
   */
L
lijiarui 已提交
80 81 82 83 84
  Status:           string,
  MMStatus:         number,  // img ng-show="message.MMStatus == 1" class="ico_loading"
                             // ng-click="resendMsg(message)" ng-show="message.MMStatus == 5" title="重新发送"
  MMFileStatus:     number,  // <p class="loading" ng-show="message.MMStatus == 1 || message.MMFileStatus == CONF.MM_SEND_FILE_STATUS_FAIL">
                             // CONF.MM_SEND_FILE_STATUS_QUEUED, MM_SEND_FILE_STATUS_SENDING
85

86 87 88
  /**
   * Location
   */
L
lijiarui 已提交
89 90 91 92
  MMLocationUrl:    string,  // ng-if="message.MsgType == CONF.MSGTYPE_TEXT && message.SubMsgType == CONF.MSGTYPE_LOCATION"
                             // <a href="{{message.MMLocationUrl}}" target="_blank">
                             // 'http://apis.map.qq.com/uri/v1/geocoder?coord=40.075041,116.338994'
  MMLocationDesc:   string,  // MMLocationDesc: '北京市昌平区回龙观龙腾苑(五区)内(龙腾街南)',
93 94 95 96 97 98 99 100 101 102 103 104

  /**
   * MsgType == CONF.MSGTYPE_EMOTICON
   *
   * getMsgImg(message.MsgId,'big',message)
   */

  /**
   * Image
   *
   *  getMsgImg(message.MsgId,'slave')
   */
L
lijiarui 已提交
105 106 107
  MMImgStyle:       string,  // ng-style="message.MMImgStyle"
  MMPreviewSrc:     string,  // message.MMPreviewSrc || message.MMThumbSrc || getMsgImg(message.MsgId,'slave')
  MMThumbSrc:       string,
108 109 110 111 112 113 114

  /**
   * Friend Request & ShareCard ?
   *
   * MsgType == CONF.MSGTYPE_SHARECARD" ng-click="showProfile($event,message.RecommendInfo.UserName)
   * MsgType == CONF.MSGTYPE_VERIFYMSG
   */
L
lijiarui 已提交
115
  RecommendInfo?:   RecommendInfo,
116 117
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
118
export type MsgObj = {
L
lijiarui 已提交
119 120 121 122 123 124 125 126 127 128 129
  id:       string,
  type:     MsgType,
  from:     string,
  to?:      string,  // if to is not set, then room must be set
  room?:    string,
  content:  string,
  status:   string,
  digest:   string,
  date:     string,

  url?:     string,  // for MessageMedia class
130 131
}

132 133
// export type MessageTypeName = 'TEXT' | 'IMAGE' | 'VOICE' | 'VERIFYMSG' | 'POSSIBLEFRIEND_MSG'
// | 'SHARECARD' | 'VIDEO' | 'EMOTICON' | 'LOCATION' | 'APP' | 'VOIPMSG' | 'STATUSNOTIFY'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
134
// | 'VOIPNOTIFY' | 'VOIPINVITE' | 'MICROVIDEO' | 'SYSNOTICE' | 'SYS' | 'RECALLED'
135 136 137

// export type MessageTypeValue = 1 | 3 | 34 | 37 | 40 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 62 | 9999 | 10000 | 10002

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
138
export type MsgTypeMap = {
L
lijiarui 已提交
139
  [index: string]: string|number,
140 141 142 143
  //   MessageTypeName:  MessageTypeValue
  // , MessageTypeValue: MessageTypeName
}

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
export const enum AppMsgType {
  TEXT                     = 1,
  IMG                      = 2,
  AUDIO                    = 3,
  VIDEO                    = 4,
  URL                      = 5,
  ATTACH                   = 6,
  OPEN                     = 7,
  EMOJI                    = 8,
  VOICE_REMIND             = 9,
  SCAN_GOOD                = 10,
  GOOD                     = 13,
  EMOTION                  = 15,
  CARD_TICKET              = 16,
  REALTIME_SHARE_LOCATION  = 17,
  TRANSFERS                = 2e3,
  RED_ENVELOPES            = 2001,
  READER_TYPE              = 100001,
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
164
export const enum MsgType {
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  TEXT                = 1,
  IMAGE               = 3,
  VOICE               = 34,
  VERIFYMSG           = 37,
  POSSIBLEFRIEND_MSG  = 40,
  SHARECARD           = 42,
  VIDEO               = 43,
  EMOTICON            = 47,
  LOCATION            = 48,
  APP                 = 49,
  VOIPMSG             = 50,
  STATUSNOTIFY        = 51,
  VOIPNOTIFY          = 52,
  VOIPINVITE          = 53,
  MICROVIDEO          = 62,
  SYSNOTICE           = 9999,
  SYS                 = 10000,
182
  RECALLED            = 10002,
183 184
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
185
export class Message implements Sayable {
186
  public static counter = 0
187
  public _counter: number
188

189 190 191 192 193
  /**
   * a map for:
   *   1. name to id
   *   2. id to name
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
194
  public static TYPE: MsgTypeMap = {
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    TEXT:               1,
    IMAGE:              3,
    VOICE:              34,
    VERIFYMSG:          37,
    POSSIBLEFRIEND_MSG: 40,
    SHARECARD:          42,
    VIDEO:              43,
    EMOTICON:           47,
    LOCATION:           48,
    APP:                49,
    VOIPMSG:            50,
    STATUSNOTIFY:       51,
    VOIPNOTIFY:         52,
    VOIPINVITE:         53,
    MICROVIDEO:         62,
    SYSNOTICE:          9999,
    SYS:                10000,
L
lijiarui 已提交
212
    RECALLED:           10002,
213 214 215 216
  }

  public readonly id: string

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
217
  protected obj = <MsgObj>{}
218

219
  public readyStream(): Promise<NodeJS.ReadableStream> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220 221 222
    throw Error('abstract method')
  }

223 224
  public filename(): string {
    throw Error('not a media message')
225 226
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227
  constructor(public rawObj?: MsgRawObj) {
228
    this._counter = Message.counter++
229
    log.silly('Message', 'constructor() SN:%d', this._counter)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230

231
    if (typeof rawObj === 'string') {
232
      this.rawObj = JSON.parse(rawObj)
233 234
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235
    this.rawObj = rawObj = rawObj || <MsgRawObj>{}
236
    this.obj = this.parse(rawObj)
237
    this.id = this.obj.id
238
  }
239

240
  // Transform rawObj to local m
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
241 242
  private parse(rawObj): MsgObj {
    const obj: MsgObj = {
243
      id:           rawObj.MsgId,
244 245 246 247 248 249 250
      type:         rawObj.MsgType,
      from:         rawObj.MMActualSender, // MMPeerUserName
      to:           rawObj.ToUserName,
      content:      rawObj.MMActualContent, // Content has @id prefix added by wx
      status:       rawObj.Status,
      digest:       rawObj.MMDigest,
      date:         rawObj.MMDisplayTime,  // Javascript timestamp of milliseconds
L
lijiarui 已提交
251
      url:          rawObj.Url || rawObj.MMAppMsgDownloadUrl || rawObj.MMLocationUrl,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
252
    }
253 254

    // FIXME: has ther any better method to know the room ID?
255 256 257 258 259 260 261
    if (rawObj.MMIsChatRoom) {
      if (/^@@/.test(rawObj.FromUserName)) {
        obj.room =  rawObj.FromUserName // MMPeerUserName always eq FromUserName ?
      } else if (/^@@/.test(rawObj.ToUserName)) {
        obj.room = rawObj.ToUserName
      } else {
        log.error('Message', 'parse found a room message, but neither FromUserName nor ToUserName is a room(/^@@/)')
262
        // obj.room = undefined // bug compatible
263
      }
264 265 266
      if (obj.to && /^@@/.test(obj.to)) { // if a message in room without any specific receiver, then it will set to be `undefined`
        obj.to = undefined
      }
267
    }
268

269
    return obj
270
  }
271
  public toString() {
272
    return UtilLib.plainText(this.obj.content)
273
  }
274
  public toStringDigest() {
275
    const text = UtilLib.digestEmoji(this.obj.digest)
276
    return '{' + this.typeEx() + '}' + text
277 278
  }

279
  public toStringEx() {
280
    let s = `${this.constructor.name}#${this._counter}`
281 282 283
    s += '(' + this.getSenderString()
    s += ':' + this.getContentString() + ')'
    return s
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
284
  }
285
  public getSenderString() {
286 287
    const fromName  = Contact.load(this.obj.from).name()
    const roomTopic = this.obj.room
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
288
                  ? (':' + Room.load(this.obj.room).topic())
289
                  : ''
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
290
    return `<${fromName}${roomTopic}>`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
291
  }
292
  public getContentString() {
293
    let content = UtilLib.plainText(this.obj.content)
294
    if (content.length > 20) { content = content.substring(0, 17) + '...' }
295 296
    return '{' + this.type() + '}' + content
  }
297

298 299
  public from(contact: Contact): void
  public from(id: string): void
300
  public from(): Contact
301
  public from(contact?: Contact|string): Contact|void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
302
    if (contact) {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
303 304
      if (contact instanceof Contact) {
        this.obj.from = contact.id
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
305
      } else if (typeof contact === 'string') {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
306 307 308 309
        this.obj.from = contact
      } else {
        throw new Error('unsupport from param: ' + typeof contact)
      }
310
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
311
    }
312 313 314 315 316 317

    const loadedContact = Contact.load(this.obj.from)
    if (!loadedContact) {
      throw new Error('no from')
    }
    return loadedContact
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
318 319
  }

320 321 322
  // public to(room: Room): void
  // public to(): Contact|Room
  // public to(contact?: Contact|Room|string): Contact|Room|void {
323 324
  public to(contact: Contact): void
  public to(id: string): void
325 326 327
  public to(): Contact|null // if to is not set, then room must had set

  public to(contact?: Contact|string): Contact|Room|null|void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
328
    if (contact) {
329
      if (contact instanceof Contact) {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
330
        this.obj.to = contact.id
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
331
      } else if (typeof contact === 'string') {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
332 333 334 335
        this.obj.to = contact
      } else {
        throw new Error('unsupport to param ' + typeof contact)
      }
336
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
337
    }
338

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
339 340
    // no parameter

341 342
    if (!this.obj.to) {
      return null
343
    }
344
    return Contact.load(this.obj.to)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
345 346
  }

347 348
  public room(room: Room): void
  public room(id: string): void
349
  public room(): Room|null
350
  public room(room?: Room|string): Room|null|void {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
351 352 353 354 355 356 357 358
    if (room) {
      if (room instanceof Room) {
        this.obj.room = room.id
      } else if (typeof room === 'string') {
        this.obj.room = room
      } else {
        throw new Error('unsupport room param ' + typeof room)
      }
359
      return
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
360
    }
361 362
    if (this.obj.room) {
      return Room.load(this.obj.room)
363
    }
364
    return null
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
365 366
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
367 368 369 370
  public content(): string
  public content(content: string): void

  public content(content?: string): string|void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
371 372
    if (content) {
      this.obj.content = content
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
373
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
374 375 376 377
    }
    return this.obj.content
  }

378
  public type(): MsgType {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
379
    return this.obj.type
380 381 382 383 384 385 386 387 388 389 390 391 392 393
  }

  public typeSub(): MsgType {
    if (!this.rawObj) {
      throw new Error('no rawObj')
    }
    return this.rawObj.SubMsgType
  }

  public typeApp(): AppMsgType {
    if (!this.rawObj) {
      throw new Error('no rawObj')
    }
    return this.rawObj.AppMsgType
394 395
  }

396
  public typeEx()  { return Message.TYPE[this.obj.type] }
397
  public count()   { return this._counter }
398

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
399 400 401 402
  public self(): boolean {
    const userId = Config.puppetInstance()
                        .userId

403
    const fromId = this.obj.from
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
404 405 406 407 408 409 410
    if (!userId || !fromId) {
      throw new Error('no user or no from')
    }

    return fromId === userId
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
411 412 413 414 415
  // public ready() {
  //   log.warn('Message', 'ready() DEPRECATED. use load() instead.')
  //   return this.ready()
  // }

416
  public async ready(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
417
    log.silly('Message', 'ready()')
418

419
    try {
420
      const from  = Contact.load(this.obj.from)
421
      await from.ready()                // Contact from
422

423 424 425
      if (this.obj.to) {
        const to = Contact.load(this.obj.to)
        await to.ready()
426
      }
427

428 429
      if (this.obj.room) {
        const room  = Room.load(this.obj.room)
430
        await room.ready()  // Room member list
431
      }
432

433
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
434
        log.error('Message', 'ready() exception: %s', e.stack)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
435 436 437
        // console.log(e)
        // this.dump()
        // this.dumpRaw()
438
        throw e
439
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
440 441
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
442 443 444
  /**
   * @deprecated
   */
445
  public get(prop: string): string {
446
    log.warn('Message', 'DEPRECATED get() at %s', new Error('stack').stack)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
447

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
448 449
    if (!prop || !(prop in this.obj)) {
      const s = '[' + Object.keys(this.obj).join(',') + ']'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
450 451
      throw new Error(`Message.get(${prop}) must be in: ${s}`)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
452
    return this.obj[prop]
453 454
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
455 456 457
  /**
   * @deprecated
   */
458
  public set(prop: string, value: string): this {
459
    log.warn('Message', 'DEPRECATED set() at %s', new Error('stack').stack)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
460

461 462 463
    if (typeof value !== 'string') {
      throw new Error('value must be string, we got: ' + typeof value)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
464
    this.obj[prop] = value
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
465
    return this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
466
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
467

468
  public dump() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
469 470
    console.error('======= dump message =======')
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
471
  }
472
  public dumpRaw() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
473
    console.error('======= dump raw message =======')
474
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj && this.rawObj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
475 476
  }

477
  public static async find(query) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
478
    return Promise.resolve(new Message(<MsgRawObj>{MsgId: '-1'}))
479 480
  }

481 482
  public static async findAll(query) {
    return Promise.resolve([
L
lijiarui 已提交
483 484
      new Message   (<MsgRawObj>{MsgId: '-2'}),
      new Message (<MsgRawObj>{MsgId: '-3'}),
485
    ])
486 487
  }

488 489 490 491 492 493
  public static initType() {
    Object.keys(Message.TYPE).forEach(k => {
      const v = Message.TYPE[k]
      Message.TYPE[v] = k // Message.Type[1] = 'TEXT'
    })
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
494 495 496 497 498

  public say(content: string, replyTo?: Contact|Contact[]): Promise<any> {
    log.verbose('Message', 'say(%s, %s)', content, replyTo)

    const m = new Message()
499 500 501 502
    const room = this.room()
    if (room) {
      m.room(room)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519

    if (!replyTo) {
      m.to(this.from())
      m.content(content)

    } else if (this.room()) {
      let mentionList
      if (Array.isArray(replyTo)) {
        m.to(replyTo[0])
        mentionList = replyTo.map(c => '@' + c.name()).join(' ')
      } else {
        m.to(replyTo)
        mentionList = '@' + replyTo.name()
      }
      m.content(mentionList + ' ' + content)

    }
520
    return Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
521 522 523
                  .send(m)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
524
}
525 526

Message.initType()
527

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
528
export * from './message-media'
529

Huan (李卓桓)'s avatar
doc  
Huan (李卓桓) 已提交
530
/*
531 532 533 534 535
 * join room in mac client: https://support.weixin.qq.com/cgi-bin/
 * mmsupport-bin/addchatroombyinvite
 * ?ticket=AUbv%2B4GQA1Oo65ozlIqRNw%3D%3D&exportkey=AS9GWEg4L82fl3Y8e2OeDbA%3D
 * &lang=en&pass_ticket=T6dAZXE27Y6R29%2FFppQPqaBlNwZzw9DAN5RJzzzqeBA%3D
 * &wechat_real_lang=en
Huan (李卓桓)'s avatar
doc  
Huan (李卓桓) 已提交
536
 */