message.ts 7.9 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   from './config'
import Contact  from './contact'
import Room     from './room'
import UtilLib  from './util-lib'
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
  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>{}

72
  public readyStream(): Promise<NodeJS.ReadableStream> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
73 74 75
    throw Error('abstract method')
  }

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

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

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

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

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

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

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

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

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179
  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
  }

180
  public content(content?) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
181 182 183 184 185 186
    if (content) {
      this.obj.content = content
    }
    return this.obj.content
  }

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

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

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

200 201 202
      await from.ready()                // Contact from
      await to.ready()                  // Contact to
      if (room) { await room.ready() }  // Room member list
203

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

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

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

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

237 238
  public static async find(query) {
    return Promise.resolve(new Message(<MessageRawObj>{MsgId: '-1'}))
239 240
  }

241 242 243 244 245
  public static async findAll(query) {
    return Promise.resolve([
      new Message   (<MessageRawObj>{MsgId: '-2'})
      , new Message (<MessageRawObj>{MsgId: '-3'})
    ])
246 247
  }

248 249 250 251 252 253
  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 (李卓桓) 已提交
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276

  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)

    }
277
    return Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278 279 280
                  .send(m)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281
}
282 283

Message.initType()
284

285 286
// module.exports = Message.default = Message.Message = Message
export default Message
Huan (李卓桓)'s avatar
doc  
Huan (李卓桓) 已提交
287 288

/*
289 290 291 292 293
 * 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 (李卓桓) 已提交
294
 */