message.ts 7.3 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
import Contact from './contact'
import Room    from './room'
import UtilLib from './util-lib'
12

13
import log     from './brolog-env'
14

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
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
}

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 (李卓桓) 已提交
44
class Message {
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
  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>{}

  constructor(private rawObj?: MessageRawObj) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
73
    Message.counter++
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
74

75
    if (typeof rawObj === 'string') {
76
      this.rawObj = JSON.parse(rawObj)
77 78
    }

79
    this.rawObj = rawObj = rawObj || <MessageRawObj>{}
80
    this.obj = this.parse(rawObj)
81
    this.id = this.obj.id
82
  }
83

84
  // Transform rawObj to local m
85 86
  private parse(rawObj): MessageObj {
    const obj: MessageObj = {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87 88
      id:             rawObj.MsgId
      , type:         rawObj.MsgType
89 90
      , from:         rawObj.MMActualSender
      , to:           rawObj.ToUserName
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
91 92 93
      , content:      rawObj.MMActualContent // Content has @id prefix added by wx
      , status:       rawObj.Status
      , digest:       rawObj.MMDigest
94
      , date:         rawObj.MMDisplayTime  // Javascript timestamp of milliseconds
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
95
    }
96 97

    // FIXME: has ther any better method to know the room ID?
98 99 100 101 102 103 104 105 106 107 108 109 110
    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
111
  }
112
  public toString() {
113
    return UtilLib.plainText(this.obj.content)
114
  }
115
  public toStringDigest() {
116
    const text = UtilLib.digestEmoji(this.obj.digest)
117
    return '{' + this.typeEx() + '}' + text
118 119
  }

120 121
  public toStringEx() {
    let s = `${this.constructor.name}#${Message.counter}`
122 123 124
    s += '(' + this.getSenderString()
    s += ':' + this.getContentString() + ')'
    return s
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
125
  }
126
  public getSenderString() {
127
    const name  = Contact.load(this.obj.from).toStringEx()
128 129
    const room = this.obj.room ? Room.load(this.obj.room).toStringEx() : null
    return '<' + name + (room ? `@${room}` : '') + '>'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
130
  }
131
  public getContentString() {
132
    let content = UtilLib.plainText(this.obj.content)
133
    if (content.length > 20) { content = content.substring(0, 17) + '...' }
134 135
    return '{' + this.type() + '}' + content
  }
136

137
  public from(contact?: Contact): Contact {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
138 139 140 141 142 143
    if (contact) {
      if (contact instanceof Contact) {
        this.obj.from = contact.id
      } else if (typeof contact === 'string') {
        this.obj.from = contact
      } else {
144
        throw new Error('from neither Contact nor UserName: ' + contact + ' ' + typeof contact)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
145 146 147 148 149
      }
    }
    return this.obj.from ? Contact.load(this.obj.from) : null
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
150
  public to(contact?: Contact|Room|string): Contact {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
151
    if (contact) {
152
      if (contact instanceof Contact || contact instanceof Room) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
153 154 155 156
        this.obj.to = contact.id
      } else if (typeof contact === 'string') {
        this.obj.to = contact
      } else {
157
        throw new Error('to neither Contact nor UserName: ' + contact + ' ' + typeof contact)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
158 159 160 161 162
      }
    }
    return this.obj.to ? Contact.load(this.obj.to) : null
  }

163
  public content(content?) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
164 165 166 167 168 169
    if (content) {
      this.obj.content = content
    }
    return this.obj.content
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
170
  public room(room?: Room): Room {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
171 172 173 174 175 176 177 178 179 180 181
    if (room) {
      if (room instanceof Room) {
        this.obj.room = room.id
      } else if (typeof room === 'string') {
        this.obj.room = room
      } else {
        throw new Error('neither Room nor UserName')
      }
    }
    return this.obj.room ? Room.load(this.obj.room) : null
  }
182

183
  public type()    { return this.obj.type }
184
  public typeEx()  { return Message.TYPE[this.obj.type] }
185
  public count()   { return Message.counter }
186

187
  public async ready(): Promise<Message> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
188
    log.silly('Message', 'ready()')
189

190 191
    // return co.call(this, function* () {
    try {
192 193 194 195
      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

196 197 198
      await from.ready()                // Contact from
      await to.ready()                  // Contact to
      if (room) { await room.ready() }  // Room member list
199

200
      return this         // return this for chain
201 202
    // }).catch(e => { // Exception
    } catch (e) {
203
        log.error('Message', 'ready() exception: %s', e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
204 205 206
        // console.log(e)
        // this.dump()
        // this.dumpRaw()
207
        throw e
208
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
209 210
  }

211
  public get(prop): string {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
212 213
    if (!prop || !(prop in this.obj)) {
      const s = '[' + Object.keys(this.obj).join(',') + ']'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
214 215
      throw new Error(`Message.get(${prop}) must be in: ${s}`)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
216
    return this.obj[prop]
217 218
  }

219
  public set(prop, value) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220
    this.obj[prop] = value
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
221
    return this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
222
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223

224
  public dump() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
225 226
    console.error('======= dump message =======')
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227
  }
228
  public dumpRaw() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
229
    console.error('======= dump raw message =======')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231 232
  }

233 234
  public static find(selector, option) {
    // return new Message({MsgId: '-1'})
235 236
  }

237
  public static findAll(selector, option) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
238
    return [
239 240
      // new Message   ({MsgId: '-2'})
      // , new Message ({MsgId: '-3'})
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
241
    ]
242 243
  }

244 245 246 247 248 249
  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 (李卓桓) 已提交
250
}
251 252

Message.initType()
253

254 255 256 257
// Message.attach = function(puppet) {
//   log.verbose('Message', 'attach() to %s', puppet && puppet.constructor.name)
//   Message.puppet = puppet
// }
258

259 260
// module.exports = Message.default = Message.Message = Message
export default Message
Huan (李卓桓)'s avatar
doc  
Huan (李卓桓) 已提交
261 262

/*
263 264 265 266 267
 * 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 (李卓桓) 已提交
268
 */