room.ts 19.1 KB
Newer Older
1
/**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
2
 *   Wechaty - https://github.com/chatie/wechaty
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 *   Copyright 2016-2017 Huan LI <zixia@zixia.net>
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
19
import { EventEmitter } from 'events'
20

21
import {
22
  config,
23
  Raven,
L
lijiarui 已提交
24 25
  Sayable,
  log,
M
Mukaiu 已提交
26
}                     from './config'
Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
27
import Contact        from './contact'
M
Mukaiu 已提交
28 29 30 31
import {
  Message,
  MediaMessage,
}                     from './message'
Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
32
import UtilLib        from './util-lib'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33

34
interface RoomObj {
35 36 37 38 39 40 41 42
  id:               string,
  encryId:          string,
  topic:            string,
  ownerUin:         number,
  memberList:       Contact[],
  nameMap:          Map<string, string>,
  roomAliasMap:     Map<string, string>,
  contactAliasMap:  Map<string, string>,
43 44
}

45
type NameType = 'name' | 'alias' | 'roomAlias' | 'contactAlias'
46

47
export interface RoomRawMember {
L
lijiarui 已提交
48 49 50
  UserName:     string,
  NickName:     string,
  DisplayName:  string,
51 52
}

53
export interface RoomRawObj {
L
lijiarui 已提交
54 55 56 57 58
  UserName:         string,
  EncryChatRoomId:  string,
  NickName:         string,
  OwnerUin:         number,
  ChatRoomOwner:    string,
59
  MemberList?:      RoomRawMember[],
60 61
}

62 63 64
export type RoomEventName = 'join'
                          | 'leave'
                          | 'topic'
65 66
                          | 'EVENT_PARAM_ERROR'

67
export interface RoomQueryFilter {
L
lijiarui 已提交
68
  topic: string | RegExp,
69 70
}

71
export interface MemberQueryFilter {
72 73 74 75
  name?:         string,
  alias?:        string,
  roomAlias?:    string,
  contactAlias?: string,
76 77
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
78 79 80 81
/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82 83
 * Licenst: Apache-2.0
 * https://github.com/chatie/wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
84
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
85
 * Add/Del/Topic: https://github.com/chatie/wechaty/issues/32
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
86 87
 *
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
88
export class Room extends EventEmitter implements Sayable {
89 90
  private static pool = new Map<string, Room>()

91 92
  private dirtyObj: RoomObj | null // when refresh, use this to save dirty data for query
  private obj:      RoomObj | null
93 94
  private rawObj:   RoomRawObj

H
hcz 已提交
95 96 97
  /**
   * @private
   */
98
  constructor(public id: string) {
99
    super()
100
    log.silly('Room', `constructor(${id})`)
101
  }
102

H
hcz 已提交
103 104 105
  /**
   * @private
   */
106
  public toString()    { return this.id }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107
  public toStringEx()  { return `Room(${this.obj && this.obj.topic}[${this.id}])` }
108

109
  public isReady(): boolean {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
110
    return !!(this.obj && this.obj.memberList && this.obj.memberList.length)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
111 112
  }

H
hcz 已提交
113 114 115
  /**
   * @todo document me
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
116
  public async refresh(): Promise<void> {
117 118 119
    if (this.isReady()) {
      this.dirtyObj = this.obj
    }
120
    this.obj = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
121 122
    await this.ready()
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
123 124
  }

125
  private async readyAllMembers(memberList: RoomRawMember[]): Promise<void> {
126 127
    for (const member of memberList) {
      const contact = Contact.load(member.UserName)
128
      await contact.ready()
129 130 131 132
    }
    return
  }

133
  public async ready(contactGetter?: (id: string) => Promise<any>): Promise<Room> {
134
    log.silly('Room', 'ready(%s)', contactGetter ? contactGetter.constructor.name : '')
135
    if (!this.id) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
136 137
      const e = new Error('ready() on a un-inited Room')
      log.warn('Room', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
138
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
139
    } else if (this.isReady()) {
140
      return this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
141
    } else if (this.obj && this.obj.id) {
142
      log.warn('Room', 'ready() has obj.id but memberList empty in room %s. reloading', this.obj.topic)
143
    }
144

145
    if (!contactGetter) {
146 147
      contactGetter = config.puppetInstance()
                            .getContact.bind(config.puppetInstance())
148 149 150 151 152
    }
    if (!contactGetter) {
      throw new Error('no contactGetter')
    }

153 154
    try {
      const data = await contactGetter(this.id)
155
      log.silly('Room', `contactGetter(${this.id}) resolved`)
156
      this.rawObj = data
157
      await this.readyAllMembers(this.rawObj.MemberList || [])
158
      this.obj    = this.parse(this.rawObj)
159 160 161
      if (!this.obj) {
        throw new Error('no this.obj set after contactGetter')
      }
162
      await Promise.all(this.obj.memberList.map(c => c.ready(contactGetter)))
163

164
      return this
165

166
    } catch (e) {
167
      log.error('Room', 'contactGetter(%s) exception: %s', this.id, e.message)
168
      Raven.captureException(e)
169
      throw e
170
    }
171 172
  }

H
hcz 已提交
173 174 175
  /**
   * @todo document me
   */
176
  public on(event: 'leave', listener: (this: Room, leaver: Contact) => void): this
H
hcz 已提交
177 178 179
  /**
   * @todo document me
   */
180
  public on(event: 'join' , listener: (this: Room, inviteeList: Contact[] , inviter: Contact)  => void): this
H
hcz 已提交
181 182 183
  /**
   * @todo document me
   */
184
  public on(event: 'topic', listener: (this: Room, topic: string, oldTopic: string, changer: Contact) => void): this
H
hcz 已提交
185 186 187
  /**
   * @todo document me
   */
188
  public on(event: 'EVENT_PARAM_ERROR', listener: () => void): this
H
hcz 已提交
189 190 191
  /**
   * @todo document me
   */
192
  public on(event: RoomEventName, listener: (...args: any[]) => any): this {
193
    log.verbose('Room', 'on(%s, %s)', event, typeof listener)
194

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
195
    super.on(event, listener) // Room is `Sayable`
196
    return this
197 198
  }

H
hcz 已提交
199 200 201
  /**
   * @todo document me
   */
202 203 204 205
  public say(mediaMessage: MediaMessage)
  public say(content: string)
  public say(content: string, replyTo: Contact)
  public say(content: string, replyTo: Contact[])
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
206

207
  public say(textOrMedia: string | MediaMessage, replyTo?: Contact|Contact[]): Promise<boolean> {
M
Mukaiu 已提交
208
    const content = textOrMedia instanceof MediaMessage ? textOrMedia.filename() : textOrMedia
L
lijiarui 已提交
209 210 211
    log.verbose('Room', 'say(%s, %s)',
                        content,
                        Array.isArray(replyTo)
212
                        ? replyTo.map(c => c.name()).join(', ')
L
lijiarui 已提交
213
                        : replyTo ? replyTo.name() : '',
214
    )
215

M
Mukaiu 已提交
216 217 218
    let m
    if (typeof textOrMedia === 'string') {
      m = new Message()
219

M
Mukaiu 已提交
220
      const replyToList: Contact[] = [].concat(replyTo as any || [])
221

M
Mukaiu 已提交
222
      if (replyToList.length > 0) {
223 224
        const AT_SEPRATOR = String.fromCharCode(8197)
        const mentionList = replyToList.map(c => '@' + c.name()).join(AT_SEPRATOR)
M
Mukaiu 已提交
225 226 227 228 229 230 231 232 233
        m.content(mentionList + ' ' + content)
      } else {
        m.content(content)
      }
      // m.to(replyToList[0])
    } else
      m = textOrMedia

    m.room(this)
234

235
    return config.puppetInstance()
236 237 238
                  .send(m)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
239
  public get(prop): string { return (this.obj && this.obj[prop]) || (this.dirtyObj && this.dirtyObj[prop]) }
240

H
hcz 已提交
241 242 243
  /**
   * @private
   */
244
  private parse(rawObj: RoomRawObj): RoomObj | null {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245
    if (!rawObj) {
246
      log.warn('Room', 'parse() on a empty rawObj?')
247
      return null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
248
    }
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
249

250 251 252
    const memberList = (rawObj.MemberList || [])
                        .map(m => Contact.load(m.UserName))

L
lijiarui 已提交
253
    const nameMap    = this.parseMap('name', rawObj.MemberList)
254 255
    const roomAliasMap   = this.parseMap('roomAlias', rawObj.MemberList)
    const contactAliasMap   = this.parseMap('contactAlias', rawObj.MemberList)
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
256

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
257
    return {
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
258 259 260 261 262
      id:         rawObj.UserName,
      encryId:    rawObj.EncryChatRoomId, // ???
      topic:      rawObj.NickName,
      ownerUin:   rawObj.OwnerUin,
      memberList,
263
      nameMap,
264 265
      roomAliasMap,
      contactAliasMap,
266 267 268
    }
  }

L
lijiarui 已提交
269
  private parseMap(parseContent: NameType, memberList?: RoomRawMember[]): Map<string, string> {
270
    const mapList: Map<string, string> = new Map<string, string>()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
271
    if (memberList && memberList.map) {
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
272
      memberList.forEach(member => {
273
        let tmpName: string
274
        const contact = Contact.load(member.UserName)
275
        switch (parseContent) {
ruiruibupt's avatar
2  
ruiruibupt 已提交
276
          case 'name':
277
            tmpName = contact.name()
278
            break
279
          case 'roomAlias':
L
lijiarui 已提交
280
            tmpName = member.DisplayName
281
            break
282 283 284
          case 'contactAlias':
            tmpName = contact.alias() || ''
            break
285 286 287
          default:
            throw new Error('parseMap failed, member not found')
        }
288 289
        /**
         * ISSUE #64 emoji need to be striped
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
290
         * ISSUE #104 never use remark name because sys group message will never use that
ruiruibupt's avatar
#217  
ruiruibupt 已提交
291
         * @rui: Wrong for 'never use remark name because sys group message will never use that', see more in the latest comment in #104
ruiruibupt's avatar
1  
ruiruibupt 已提交
292 293
         * @rui: webwx's NickName here return contactAlias, if not set contactAlias, return name
         * @rui: 2017-7-2 webwx's NickName just ruturn name, no contactAlias
294
         */
295
        mapList[member.UserName] = UtilLib.stripEmoji(tmpName)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
296
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
297
    }
298
    return mapList
299 300
  }

301
  public dumpRaw() {
302
    console.error('======= dump raw Room =======')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
303
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
304
  }
305
  public dump() {
306
    console.error('======= dump Room =======')
307
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj && this.obj[k]}`))
308 309
  }

H
hcz 已提交
310 311 312
  /**
   * @todo document me
   */
Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
313
  public async add(contact: Contact): Promise<number> {
314
    log.verbose('Room', 'add(%s)', contact)
315 316 317 318 319

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

320
    const n = config.puppetInstance()
Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
321 322
                      .roomAdd(this, contact)
    return n
323 324
  }

H
hcz 已提交
325 326 327
  /**
   * @todo document me
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
328
  public async del(contact: Contact): Promise<number> {
329
    log.verbose('Room', 'del(%s)', contact.name())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
330 331 332 333

    if (!contact) {
      throw new Error('contact not found')
    }
334
    const n = await config.puppetInstance()
Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
335 336
                            .roomDel(this, contact)
                            .then(_ => this.delLocal(contact))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
337
    return n
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
338 339
  }

H
hcz 已提交
340 341 342
  /**
   * @todo document me
   */
343
  private delLocal(contact: Contact): number {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
344 345
    log.verbose('Room', 'delLocal(%s)', contact)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
346
    const memberList = this.obj && this.obj.memberList
347
    if (!memberList || memberList.length === 0) {
348
      return 0 // already in refreshing
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
349 350 351
    }

    let i
352 353
    for (i = 0; i < memberList.length; i++) {
      if (memberList[i].id === contact.id) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
354 355 356
        break
      }
    }
357 358
    if (i < memberList.length) {
      memberList.splice(i, 1)
359
      return 1
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
360
    }
361
    return 0
362
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
363

364
  public quit() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
365 366
    throw new Error('wx web not implement yet')
    // WechatyBro.glue.chatroomFactory.quit("@@1c066dfcab4ef467cd0a8da8bec90880035aa46526c44f504a83172a9086a5f7"
367
  }
368

369 370 371 372 373 374 375 376 377 378
  /**
   * get topic
   */
  public topic(): string
  /**
   * set topic
   */
  public topic(newTopic: string): void

  public topic(newTopic?: string): string | void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
379
    if (!this.isReady()) {
380
      log.warn('Room', 'topic() room not ready')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
381 382
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
383 384
    if (newTopic) {
      log.verbose('Room', 'topic(%s)', newTopic)
385
      config.puppetInstance()
386 387 388 389 390
            .roomTopic(this, newTopic)
            .catch(e => {
              log.warn('Room', 'topic(newTopic=%s) exception: %s',
                                newTopic, e && e.message || e,
                      )
391
              Raven.captureException(e)
392
            })
393 394 395 396
      if (!this.obj) {
        this.obj = <RoomObj>{}
      }
      Object.assign(this.obj, { topic: newTopic })
397
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
398
    }
399
    return UtilLib.plainText(this.obj ? this.obj.topic : '')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
400 401
  }

402 403 404 405
  /**
   * should be deprecated
   * @deprecated
   */
ruiruibupt's avatar
2  
ruiruibupt 已提交
406
  public nick(contact: Contact): string | null {
ruiruibupt's avatar
1  
ruiruibupt 已提交
407
    log.warn('Room', 'nick(Contact) DEPRECATED, use alias(Contact) instead.')
ruiruibupt's avatar
#217  
ruiruibupt 已提交
408
    return this.alias(contact)
409 410
  }

L
lijiarui 已提交
411
  /**
412
   * return contact's roomAlias in the room, the same as roomAlias
L
lijiarui 已提交
413
   * @param {Contact} contact
414
   * @returns {string | null} If a contact has an alias in room, return string, otherwise return null
L
lijiarui 已提交
415
   */
ruiruibupt's avatar
2  
ruiruibupt 已提交
416
  public alias(contact: Contact): string | null {
417 418 419
    return this.roomAlias(contact)
  }

H
hcz 已提交
420 421 422
  /**
   * @todo document me
   */
423 424
  public roomAlias(contact: Contact): string | null {
    if (!this.obj || !this.obj.roomAliasMap) {
ruiruibupt's avatar
2  
ruiruibupt 已提交
425
      return null
426
    }
427
    return this.obj.roomAliasMap[contact.id] || null
428 429
  }

H
hcz 已提交
430 431 432
  /**
   * @todo document me
   */
433
  public has(contact: Contact): boolean {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
434
    if (!this.obj || !this.obj.memberList) {
435 436 437 438 439 440 441
      return false
    }
    return this.obj.memberList
                    .filter(c => c.id === contact.id)
                    .length > 0
  }

H
hcz 已提交
442 443 444
  /**
   * @todo document me
   */
445
  public owner(): Contact | null {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
446
    const ownerUin = this.obj && this.obj.ownerUin
447

448
    const user = config.puppetInstance()
449 450 451 452 453 454
                      .user

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

J
jaslin 已提交
455
    if (this.rawObj.ChatRoomOwner) {
456 457 458
      return Contact.load(this.rawObj.ChatRoomOwner)
    }

459
    log.info('Room', 'owner() is limited by Tencent API, sometimes work sometimes not')
460
    return null
461 462
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
463
  /**
464 465
   * find member by name | roomAlias(alias) | contactAlias
   * when use memberAll(name:string), return all matched members, including name, roomAlias, contactAlias
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
466
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
467
  public memberAll(filter: MemberQueryFilter): Contact[]
468
  public memberAll(name: string): Contact[]
469

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
470
  public memberAll(queryArg: MemberQueryFilter | string): Contact[] {
471
    if (typeof queryArg === 'string') {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
      //
      // use the following `return` statement to do this job.
      //

      // const nameList = this.memberAll({name: queryArg})
      // const roomAliasList = this.memberAll({roomAlias: queryArg})
      // const contactAliasList = this.memberAll({contactAlias: queryArg})

      // if (nameList) {
      //   contactList = contactList.concat(nameList)
      // }
      // if (roomAliasList) {
      //   contactList = contactList.concat(roomAliasList)
      // }
      // if (contactAliasList) {
      //   contactList = contactList.concat(contactAliasList)
      // }

      return ([] as Contact[]).concat(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
491 492
        this.memberAll({name:         queryArg}),
        this.memberAll({roomAlias:    queryArg}),
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
493 494
        this.memberAll({contactAlias: queryArg}),
      )
495 496
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
497 498 499
    /**
     * We got filter parameter
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
500
    log.silly('Room', 'memberAll({ %s })',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
501 502 503
                      Object.keys(queryArg)
                            .map(k => `${k}: ${queryArg[k]}`)
                            .join(', '),
504 505 506
            )

    if (Object.keys(queryArg).length !== 1) {
ruiruibupt's avatar
1  
ruiruibupt 已提交
507
      throw new Error('Room member find queryArg only support one key. multi key support is not availble now.')
508
    }
509

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
510
    if (!this.obj || !this.obj.memberList) {
511
      log.warn('Room', 'member() not ready')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
512
      return []
513
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
514
    const filterKey            = Object.keys(queryArg)[0]
515 516 517
    /**
     * ISSUE #64 emoji need to be striped
     */
518
    const filterValue: string  = UtilLib.stripEmoji(UtilLib.plainText(queryArg[filterKey]))
519 520

    const keyMap = {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
521
      contactAlias: 'contactAliasMap',
522 523
      name:         'nameMap',
      alias:        'roomAliasMap',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
524
      roomAlias:    'roomAliasMap',
525 526
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
527 528 529
    const filterMapName = keyMap[filterKey]
    if (!filterMapName) {
      throw new Error('unsupport filter key: ' + filterKey)
530 531 532 533 534
    }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
536
    const filterMap = this.obj[filterMapName]
537
    const idList = Object.keys(filterMap)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
538
                          .filter(id => filterMap[id] === filterValue)
539

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
540
    log.silly('Room', 'memberAll() check %s from %s: %s', filterValue, filterKey, JSON.stringify(filterMap))
541

542
    if (idList.length) {
543
      return idList.map(id => Contact.load(id))
544
    } else {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
545
      return []
546
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
547 548
  }

H
hcz 已提交
549 550 551
  /**
   * @todo document me
   */
552
  public member(name: string): Contact | null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
553
  public member(filter: MemberQueryFilter): Contact | null
554 555 556 557

  public member(queryArg: MemberQueryFilter | string): Contact | null {
    log.verbose('Room', 'member(%s)', JSON.stringify(queryArg))

558 559 560 561 562 563 564 565 566
    let memberList: Contact[]
    // ISSUE #622
    // error TS2345: Argument of type 'string | MemberQueryFilter' is not assignable to parameter of type 'MemberQueryFilter' #622
    if (typeof queryArg === 'string') {
      memberList =  this.memberAll(queryArg)
    } else {
      memberList =  this.memberAll(queryArg)
    }

567 568 569 570 571
    if (!memberList || !memberList.length) {
      return null
    }

    if (memberList.length > 1) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
572
      log.warn('Room', 'member(%s) get %d contacts, use the first one by default', JSON.stringify(queryArg), memberList.length)
573 574 575 576
    }
    return memberList[0]
  }

H
hcz 已提交
577 578 579
  /**
   * @todo document me
   */
580
  public memberList(): Contact[] {
581
    log.verbose('Room', 'memberList')
582 583 584

    if (!this.obj || !this.obj.memberList || this.obj.memberList.length < 1) {
      log.warn('Room', 'memberList() not ready')
585 586 587 588
      log.verbose('Room', 'memberList() trying call refresh() to update')
      this.refresh().then(() => {
        log.verbose('Room', 'memberList() refresh() done')
      })
589
      return []
590 591 592 593
    }
    return this.obj.memberList
  }

H
hcz 已提交
594 595 596
  /**
   * @todo document me
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
597
  public static create(contactList: Contact[], topic?: string): Promise<Room> {
598
    log.verbose('Room', 'create(%s, %s)', contactList.join(','), topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
599

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
600
    if (!contactList || !Array.isArray(contactList)) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
601 602
      throw new Error('contactList not found')
    }
603

604
    return config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
605
                  .roomCreate(contactList, topic)
606 607
                  .catch(e => {
                    log.error('Room', 'create() exception: %s', e && e.stack || e.message || e)
608
                    Raven.captureException(e)
609
                    throw e
610
                  })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
611 612
  }

H
hcz 已提交
613 614 615
  /**
   * @todo document me
   */
616 617 618 619
  public static async findAll(query?: RoomQueryFilter): Promise<Room[]> {
    if (!query) {
      query = { topic: /.*/ }
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
620
    log.verbose('Room', 'findAll({ topic: %s })', query.topic)
621

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
622
    let topicFilter = query.topic
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
623

624 625
    if (!topicFilter) {
      throw new Error('topicFilter not found')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
626 627
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
628 629
    let filterFunction: string

630
    if (topicFilter instanceof RegExp) {
631
      filterFunction = `(function (c) { return ${topicFilter.toString()}.test(c) })`
632
    } else if (typeof topicFilter === 'string') {
633
      topicFilter = topicFilter.replace(/'/g, '\\\'')
634
      filterFunction = `(function (c) { return c === '${topicFilter}' })`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
635
    } else {
636
      throw new Error('unsupport topic type')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
637 638
    }

639
    const roomList = await config.puppetInstance()
640 641 642
                                  .roomFind(filterFunction)
                                  .catch(e => {
                                    log.verbose('Room', 'findAll() rejected: %s', e.message)
643
                                    Raven.captureException(e)
644 645 646
                                    return [] // fail safe
                                  })

647 648 649 650
    await Promise.all(roomList.map(room => room.ready()))
    // for (let i = 0; i < roomList.length; i++) {
    //   await roomList[i].ready()
    // }
651 652

    return roomList
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
653 654
  }

655 656 657 658 659 660
  /**
   * try to find a room by filter: {topic: string | RegExp}
   * @param {RoomQueryFilter} query
   * @returns {Promise<Room | null>} If can find the room, return Room, or return null
   */
  public static async find(query: RoomQueryFilter): Promise<Room | null> {
661
    log.verbose('Room', 'find({ topic: %s })', query.topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
662

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
663 664
    const roomList = await Room.findAll(query)
    if (!roomList || roomList.length < 1) {
665
      return null
666 667
    } else if (roomList.length > 1) {
      log.warn('Room', 'find() got more than one result, return the 1st one.')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
668
    }
669
    return roomList[0]
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
670 671
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
672 673 674
  /**
   * @todo document me
   */
675 676 677 678
  public static load(id: string): Room {
    if (!id) {
      throw new Error('Room.load() no id')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
679 680 681 682 683 684 685

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
686
}
Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
687 688

export default Room