message.ts 7.2 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
// const co = require('co')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
10

11
// import Config  from './config'
12 13 14
import Contact from './contact'
import Room    from './room'
import UtilLib from './util-lib'
15

16
import log     from './brolog-env'
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 44 45 46
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 (李卓桓) 已提交
47
class Message {
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 73 74 75
  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 (李卓桓) 已提交
76
    Message.counter++
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
77

78
    if (typeof rawObj === 'string') {
79
      this.rawObj = JSON.parse(rawObj)
80 81
    }

82
    this.rawObj = rawObj = rawObj || <MessageRawObj>{}
83
    this.obj = this.parse(rawObj)
84
    this.id = this.obj.id
85
  }
86

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

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

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

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

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

166
  public content(content) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
167 168 169 170 171 172
    if (content) {
      this.obj.content = content
    }
    return this.obj.content
  }

173
  public room(room) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
174 175 176 177 178 179 180 181 182 183 184
    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
  }
185

186 187 188
  public type()    { return this.obj.type }
  public typeEx()  { return Message.Type[this.obj.type] }
  public count()   { return Message.counter }
189

190
  public ready(): Promise<Message> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
191
    log.silly('Message', 'ready()')
192

193 194 195 196 197
    return co.call(this, function* () {
      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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
198 199
      yield from.ready()                // Contact from
      yield to.ready()                  // Contact to
200 201
      if (room) { yield room.ready() }  // Room member list

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

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

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

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

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

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

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

Message.initType()
254

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

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

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