room.ts 9.7 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2 3 4 5 6 7
/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
8 9
 * Add/Del/Topic: https://github.com/wechaty/wechaty/issues/32
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
10
 */
11 12
const EventEmitter = require('events')

13 14 15 16 17 18 19
const Config        = require('./config')
const Contact       = require('./contact')
const Message       = require('./message')
const UtilLib       = require('./util-lib')
const WechatyEvent  = require('./wechaty-event')

const log           = require('./brolog-env')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
20

21
class Room extends EventEmitter{
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22
  constructor(id) {
23
    super()
24
    log.silly('Room', `constructor(${id})`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
25
    this.id = id
26
    this.obj = {}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
27
    this.dirtyObj = {} // when refresh, use this to save dirty data for query
28 29
    if (!Config.puppetInstance()) {
      throw new Error('Config.puppetInstance() not found')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
30
    }
31
  }
32

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33
  toString()    { return this.id }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
34
  toStringEx()  { return `Room(${this.obj.topic}[${this.id}])` }
35

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
36
  // @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
37
  isReady() {
38
    return this.obj.memberList && this.obj.memberList.length
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
39 40 41
  }

  refresh() {
42 43 44
    if (this.isReady()) {
      this.dirtyObj = this.obj
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
45 46 47 48
    this.obj = {}
    return this.ready()
  }

49
  ready(contactGetter) {
50
    log.silly('Room', 'ready(%s)', contactGetter ? contactGetter.constructor.name : '')
51
    if (!this.id) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
52 53 54 55
      const e = new Error('ready() on a un-inited Room')
      log.warn('Room', e.message)
      return Promise.reject(e)
    } else if (this.isReady()) {
56
      return Promise.resolve(this)
57
    } else if (this.obj.id) {
58
      log.warn('Room', 'ready() has obj.id but memberList empty in room %s. reloading', this.obj.topic)
59
    }
60

61 62
    contactGetter = contactGetter || Config.puppetInstance()
                                            .getContact.bind(Config.puppetInstance())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
63
    return contactGetter(this.id)
64
    .then(data => {
65
      log.silly('Room', `contactGetter(${this.id}) resolved`)
66 67
      this.rawObj = data
      this.obj    = this.parse(data)
68
      return this
69 70 71 72 73 74
    })
    .then(_ => {
      return Promise.all(this.obj.memberList.map(c => c.ready()))
                    .then(_ => this)
    })
    .catch(e => {
75 76
      log.error('Room', 'contactGetter(%s) exception: %s', this.id, e.message)
      throw e
77 78 79
    })
  }

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
  on(event, callback) {
    log.verbose('Room', 'on(%s, %s)', event, typeof callback)

    /**
     * every room event must can be mapped to a global event.
     * such as: `join` to `room-join`
     */
    const wrapCallback = WechatyEvent.wrap.call(this, 'room-' + event, callback)

    // bind(this1, this2): the second this is for simulate the global room-* event
    return super.on(event, wrapCallback.bind(this, this))
  }

  say(content, replyTo = null) {
    log.verbose('Room', 'say(%s, %s)', content, replyTo)

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

    if (!replyTo) {
      m.content(content)
      m.to(this)
      return Config.puppetInstance()
                    .send(m)
    }

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

    m.content(mentionList + ' ' + content)
    return Config.puppetInstance()
                  .send(m)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
120
  get(prop) { return this.obj[prop] || this.dirtyObj[prop] }
121

122
  parse(rawObj) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
123 124 125 126
    if (!rawObj) {
      return {}
    }
    return {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
127 128 129 130 131
      id:           rawObj.UserName
      , encryId:    rawObj.EncryChatRoomId // ???
      , topic:      rawObj.NickName
      , ownerUin:   rawObj.OwnerUin

132 133
      , memberList: this.parseMemberList(rawObj.MemberList)
      , nickMap:    this.parseNickMap(rawObj.MemberList)
134 135 136
    }
  }

137
  parseMemberList(memberList) {
138 139 140
    if (!memberList || !memberList.map) {
      return []
    }
141
    return memberList.map(m => Contact.load(m.UserName))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
142 143 144 145
  }

  parseNickMap(memberList) {
    const nickMap = {}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
146
    let contact, remark
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
147
    if (memberList && memberList.map) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
148
      memberList.forEach(m => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
149 150 151 152 153 154 155
        contact = Contact.load(m.UserName)
        if (contact) {
          remark = contact.remark()
        } else {
          remark = null
        }
        nickMap[m.UserName] = remark || m.DisplayName || m.NickName
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
157 158
    }
    return nickMap
159 160
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
161
  dumpRaw() {
162
    console.error('======= dump raw Room =======')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
163
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
164
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
165
  dump()    {
166
    console.error('======= dump Room =======')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
167
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
168 169
  }

170
  add(contact) {
171
    log.verbose('Room', 'add(%s)', contact)
172 173 174 175 176 177 178 179 180

    if (!contact) {
      throw new Error('contact not found')
    }

    return Config.puppetInstance()
                  .roomAdd(this, contact)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
181
  del(contact) {
182
    log.verbose('Room', 'del(%s)', contact)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
183 184 185 186

    if (!contact) {
      throw new Error('contact not found')
    }
187 188
    return Config.puppetInstance()
                  .roomDel(this, contact)
189
                  // .then(_ => this.delLocal(contact))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
190 191
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
192
  // @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
193 194 195
  delLocal(contact) {
    log.verbose('Room', 'delLocal(%s)', contact)

196 197
    const memberList = this.obj.memberList
    if (!memberList || memberList.length === 0) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
198 199 200 201
      return true // already in refreshing
    }

    let i
202 203
    for (i=0; i<memberList.length; i++) {
      if (memberList[i].id === contact.get('id')) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
204 205 206
        break
      }
    }
207 208
    if (i < memberList.length) {
      memberList.splice(i, 1)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
209 210 211
      return true
    }
    return false
212
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
213 214 215 216

  quit() {
    throw new Error('wx web not implement yet')
    // WechatyBro.glue.chatroomFactory.quit("@@1c066dfcab4ef467cd0a8da8bec90880035aa46526c44f504a83172a9086a5f7"
217
  }
218

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
219
  topic(newTopic) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220 221 222
    if (newTopic) {
      log.verbose('Room', 'topic(%s)', newTopic)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224
    if (newTopic) {
225
      Config.puppetInstance().roomTopic(this, newTopic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
226 227
      return newTopic
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
228 229
    // return this.get('topic')
    return UtilLib.plainText(this.obj.topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230 231
  }

232
  nick(contact) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233 234 235
    if (!this.obj.nickMap) {
      return ''
    }
236 237 238 239 240 241 242 243 244 245 246 247
    return this.obj.nickMap[contact.id]
  }

  has(contact) {
    if (!this.obj.memberList) {
      return false
    }
    return this.obj.memberList
                    .filter(c => c.id === contact.id)
                    .length > 0
  }

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
  owner() {
    const ownerUin = this.obj.ownerUin
    let memberList = this.obj.memberList || []

    let user = Config.puppetInstance()
                      .user

    if (user && user.get('uin') === ownerUin) {
      return user
    }

    memberList = memberList.filter(m => m.Uin === ownerUin)
    if (memberList.length > 0) {
      return memberList[0]
    } else {
      return null
    }
  }

267
  member(name) {
268 269
    log.verbose('Room', 'member(%s)', name)

270
    if (!this.obj.memberList) {
271
      log.warn('Room', 'member() not ready')
272 273 274 275 276
      return null
    }
    const nickMap = this.obj.nickMap
    const idList = Object.keys(nickMap)
                          .filter(k => nickMap[k] === name)
277 278 279

    log.silly('Room', 'member() check nickMap: %s', JSON.stringify(nickMap))

280 281 282 283 284
    if (idList.length) {
      return Contact.load(idList[0])
    } else {
      return null
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
285 286
  }

287 288
  static create(contactList, topic) {
    log.verbose('Room', 'create(%s, %s)', contactList.join(','), topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
289

290
    if (!contactList || !typeof contactList === 'array') {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
291 292
      throw new Error('contactList not found')
    }
293

294
    return Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
295
                  .roomCreate(contactList, topic)
296 297 298 299 300
                  .then(roomId => {
                    if (typeof roomId === 'object') {
                      // It is a Error Object send back by callback in browser(WechatyBro)
                      throw roomId
                    }
301
                    return Room.load(roomId)
302
                  })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
303 304 305 306
  }

  // private
  static _find({
307
    topic
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
308
  }) {
309
    log.silly('Room', '_find(%s)', topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
310

311 312
    if (!topic) {
      throw new Error('topic not found')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
313 314 315
    }

    let filterFunction
316 317 318 319
    if (topic instanceof RegExp) {
      filterFunction = `c => ${topic.toString()}.test(c)`
    } else if (typeof topic === 'string') {
      filterFunction = `c => c === '${topic}'`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
320
    } else {
321
      throw new Error('unsupport topic type')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
322 323
    }

324
    return Config.puppetInstance().roomFind(filterFunction)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
325 326 327 328 329 330 331 332 333 334
              .then(idList => {
                return idList
              })
              .catch(e => {
                log.error('Room', '_find() rejected: %s', e.message)
                throw e
              })
  }

  static find({
335
    topic
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
336
  }) {
337
    log.verbose('Room', 'find(%s)', topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
338

339
    return Room._find({topic})
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
340 341 342 343 344 345 346 347 348 349 350 351
              .then(idList => {
                if (!idList || !Array.isArray(idList)){
                  throw new Error('_find return error')
                }
                if (idList.length < 1) {
                  return null
                }
                const id = idList[0]
                return Room.load(id)
              })
              .catch(e => {
                log.error('Room', 'find() rejected: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
352
                return null // fail safe
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
353
                // throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
354 355 356 357
              })
  }

  static findAll({
358
    topic
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
359
  }) {
360
    log.verbose('Room', 'findAll(%s)', topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
361

362
    return Room._find({topic})
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
363 364 365 366 367 368 369 370 371 372 373
              .then(idList => {
                // console.log(idList)
                if (!idList || !Array.isArray(idList)){
                  throw new Error('_find return error')
                }
                if (idList.length < 1) {
                  return []
                }
                return idList.map(i => Room.load(i))
              })
              .catch(e => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
374
                // log.error('Room', 'findAll() rejected: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
375
                return [] // fail safe
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
376 377 378 379 380 381 382 383 384 385 386 387 388 389
              })
  }

  static init() { Room.pool = {} }

  static load(id) {
    if (!id) { return null }

    if (id in Room.pool) {
      return Room.pool[id]
    }
    return Room.pool[id] = new Room(id)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
390
}
391

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
392 393
Room.init()

394
module.exports = Room