message.ts 8.0 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2
/**
 *
3
 * 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 10 11 12 13
import {
    Config
  , RecommendInfo
}               from './config'

14 15 16 17
import Contact  from './contact'
import Room     from './room'
import UtilLib  from './util-lib'
import log      from './brolog-env'
18

19 20 21 22 23 24 25 26 27
type MessageRawObj = {
  MsgId:            string
  MsgType:          string
  MMActualSender:   string
  ToUserName:       string
  MMActualContent:  string // Content has @id prefix added by wx
  Status:           string
  MMDigest:         string
  MMDisplayTime:    string  // Javascript timestamp of milliseconds
28 29

  RecommendInfo?:   RecommendInfo
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
}

type MessageObj = {
  id:       string
  type:     string
  from:     string
  to:       string
  room?:    string
  content:  string
  status:   string
  digest:   string
  date:     string

  url?:     string  // for MessageMedia class
}

type MessageType = {
  [index: string]: number|string
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
50
class Message {
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  public static counter = 0

  public static TYPE: MessageType = {
    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,
    RECALLED:           10002
  }

  public readonly id: string

  protected obj = <MessageObj>{}

78
  public readyStream(): Promise<NodeJS.ReadableStream> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
79 80 81
    throw Error('abstract method')
  }

82
  constructor(public rawObj?: MessageRawObj) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
83
    Message.counter++
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
84

85
    if (typeof rawObj === 'string') {
86
      this.rawObj = JSON.parse(rawObj)
87 88
    }

89
    this.rawObj = rawObj = rawObj || <MessageRawObj>{}
90
    this.obj = this.parse(rawObj)
91
    this.id = this.obj.id
92
  }
93

94
  // Transform rawObj to local m
95 96
  private parse(rawObj): MessageObj {
    const obj: MessageObj = {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
97 98
      id:             rawObj.MsgId
      , type:         rawObj.MsgType
99 100
      , from:         rawObj.MMActualSender
      , to:           rawObj.ToUserName
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
101 102 103
      , content:      rawObj.MMActualContent // Content has @id prefix added by wx
      , status:       rawObj.Status
      , digest:       rawObj.MMDigest
104
      , date:         rawObj.MMDisplayTime  // Javascript timestamp of milliseconds
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
105
    }
106 107

    // FIXME: has ther any better method to know the room ID?
108 109 110 111 112 113 114 115 116 117 118 119 120
    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(/^@@/)')
        obj.room = null // bug compatible
      }
    } else {
      obj.room = null
    }
    return obj
121
  }
122
  public toString() {
123
    return UtilLib.plainText(this.obj.content)
124
  }
125
  public toStringDigest() {
126
    const text = UtilLib.digestEmoji(this.obj.digest)
127
    return '{' + this.typeEx() + '}' + text
128 129
  }

130 131
  public toStringEx() {
    let s = `${this.constructor.name}#${Message.counter}`
132 133 134
    s += '(' + this.getSenderString()
    s += ':' + this.getContentString() + ')'
    return s
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
135
  }
136
  public getSenderString() {
137
    const name  = Contact.load(this.obj.from).toStringEx()
138 139
    const room = this.obj.room ? Room.load(this.obj.room).toStringEx() : null
    return '<' + name + (room ? `@${room}` : '') + '>'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
140
  }
141
  public getContentString() {
142
    let content = UtilLib.plainText(this.obj.content)
143
    if (content.length > 20) { content = content.substring(0, 17) + '...' }
144 145
    return '{' + this.type() + '}' + content
  }
146

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
147
  public from(contact?: Contact|string): Contact {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
148
    if (contact) {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
149 150
      if (contact instanceof Contact) {
        this.obj.from = contact.id
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
151
      } else if (typeof contact === 'string') {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
152 153 154 155
        this.obj.from = contact
      } else {
        throw new Error('unsupport from param: ' + typeof contact)
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156 157 158 159
    }
    return this.obj.from ? Contact.load(this.obj.from) : null
  }

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
160
  public to(contact?: Contact|Room|string): Contact {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
161
    if (contact) {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
162 163
      if (contact instanceof Contact || contact instanceof Room) {
        this.obj.to = contact.id
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
164
      } else if (typeof contact === 'string') {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
165 166 167 168
        this.obj.to = contact
      } else {
        throw new Error('unsupport to param ' + typeof contact)
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
169 170 171 172
    }
    return this.obj.to ? Contact.load(this.obj.to) : null
  }

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185
  public room(room?: Room|string): Room {
    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)
      }
    }
    return this.obj.room ? Room.load(this.obj.room) : null
  }

186
  public content(content?) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
187 188 189 190 191 192
    if (content) {
      this.obj.content = content
    }
    return this.obj.content
  }

193
  public type()    { return this.obj.type }
194
  public typeEx()  { return Message.TYPE[this.obj.type] }
195
  public count()   { return Message.counter }
196

197
  public async ready(): Promise<this> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
198
    log.silly('Message', 'ready()')
199

200 201
    // return co.call(this, function* () {
    try {
202 203 204 205
      const from  = Contact.load(this.obj.from)
      const to    = Contact.load(this.obj.to)
      const room  = this.obj.room ? Room.load(this.obj.room) : null

206 207 208
      await from.ready()                // Contact from
      await to.ready()                  // Contact to
      if (room) { await room.ready() }  // Room member list
209

210
      return this         // return this for chain
211 212
    // }).catch(e => { // Exception
    } catch (e) {
213
        log.error('Message', 'ready() exception: %s', e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
214 215 216
        // console.log(e)
        // this.dump()
        // this.dumpRaw()
217
        throw e
218
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
219 220
  }

221
  public get(prop): string {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
222 223
    if (!prop || !(prop in this.obj)) {
      const s = '[' + Object.keys(this.obj).join(',') + ']'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224 225
      throw new Error(`Message.get(${prop}) must be in: ${s}`)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
226
    return this.obj[prop]
227 228
  }

229
  public set(prop, value) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230
    this.obj[prop] = value
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231
    return this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
232
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233

234
  public dump() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235 236
    console.error('======= dump message =======')
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
237
  }
238
  public dumpRaw() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
239
    console.error('======= dump raw message =======')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
240
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
241 242
  }

243 244
  public static async find(query) {
    return Promise.resolve(new Message(<MessageRawObj>{MsgId: '-1'}))
245 246
  }

247 248 249 250 251
  public static async findAll(query) {
    return Promise.resolve([
      new Message   (<MessageRawObj>{MsgId: '-2'})
      , new Message (<MessageRawObj>{MsgId: '-3'})
    ])
252 253
  }

254 255 256 257 258 259
  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 (李卓桓) 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

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

    const m = new Message()
    m.room(this.room())

    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)

    }
283
    return Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
284 285 286
                  .send(m)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
287
}
288 289

Message.initType()
290

291
export default Message
292 293
export { Message }
export { MediaMessage } from './message-media'
294

Huan (李卓桓)'s avatar
doc  
Huan (李卓桓) 已提交
295
/*
296 297 298 299 300
 * 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 (李卓桓) 已提交
301
 */