room.ts 14.7 KB
Newer Older
1
import { EventEmitter } from 'events'
2

3
import {
L
lijiarui 已提交
4 5 6
  Config,
  Sayable,
  log,
7
}                 from './config'
8 9 10
import { Contact }    from './contact'
import { Message }    from './message'
import { UtilLib }    from './util-lib'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
11

12
type RoomObj = {
L
lijiarui 已提交
13 14 15 16 17 18 19
  id:         string,
  encryId:    string,
  topic:      string,
  ownerUin:   number,
  memberList: Contact[],
  nameMap:    Map<string, string>,
  aliasMap:   Map<string, string>,
20 21
}

ruiruibupt's avatar
2  
ruiruibupt 已提交
22
type NameType = 'name' | 'alias'
23

24
export type RoomRawMember = {
L
lijiarui 已提交
25 26 27
  UserName:     string,
  NickName:     string,
  DisplayName:  string,
28 29
}

30
export type RoomRawObj = {
L
lijiarui 已提交
31 32 33 34 35
  UserName:         string,
  EncryChatRoomId:  string,
  NickName:         string,
  OwnerUin:         number,
  ChatRoomOwner:    string,
36
  MemberList?:      RoomRawMember[],
37 38
}

39 40 41
export type RoomEventName = 'join'
                          | 'leave'
                          | 'topic'
42 43
                          | 'EVENT_PARAM_ERROR'

44
export type RoomQueryFilter = {
L
lijiarui 已提交
45
  topic: string | RegExp,
46 47
}

48
export type MemberQueryFilter = {
L
lijiarui 已提交
49 50
  name?:  string,
  alias?: string,
51 52
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
53 54 55 56 57 58 59 60 61 62
/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 * Add/Del/Topic: https://github.com/wechaty/wechaty/issues/32
 *
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
63
export class Room extends EventEmitter implements Sayable {
64 65
  private static pool = new Map<string, Room>()

66 67
  private dirtyObj: RoomObj | null // when refresh, use this to save dirty data for query
  private obj:      RoomObj | null
68 69
  private rawObj:   RoomRawObj

70
  constructor(public id: string) {
71
    super()
72
    log.silly('Room', `constructor(${id})`)
73
  }
74

75
  public toString()    { return this.id }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
76
  public toStringEx()  { return `Room(${this.obj && this.obj.topic}[${this.id}])` }
77

78
  public isReady(): boolean {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
79
    return !!(this.obj && this.obj.memberList && this.obj.memberList.length)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
80 81
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82
  public async refresh(): Promise<void> {
83 84 85
    if (this.isReady()) {
      this.dirtyObj = this.obj
    }
86
    this.obj = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87 88
    await this.ready()
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
89 90
  }

91 92 93 94
  private async readyAllMembers(memberList: RoomRawMember[]): Promise<void> {
    for (let member of memberList) {
      let contact = Contact.load(member.UserName)
      await contact.ready()
95 96 97 98
    }
    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
99
  public async ready(contactGetter?: (id: string) => Promise<any>): Promise<void> {
100
    log.silly('Room', 'ready(%s)', contactGetter ? contactGetter.constructor.name : '')
101
    if (!this.id) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
102 103
      const e = new Error('ready() on a un-inited Room')
      log.warn('Room', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
105
    } else if (this.isReady()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
106
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107
    } else if (this.obj && this.obj.id) {
108
      log.warn('Room', 'ready() has obj.id but memberList empty in room %s. reloading', this.obj.topic)
109
    }
110

111 112 113 114 115 116 117 118
    if (!contactGetter) {
      contactGetter = Config.puppetInstance()
                            .getContact.bind(Config.puppetInstance())
    }
    if (!contactGetter) {
      throw new Error('no contactGetter')
    }

119 120
    try {
      const data = await contactGetter(this.id)
121
      log.silly('Room', `contactGetter(${this.id}) resolved`)
122
      this.rawObj = data
123
      await this.readyAllMembers(this.rawObj.MemberList || [])
124
      this.obj    = this.parse(this.rawObj)
125 126 127
      if (!this.obj) {
        throw new Error('no this.obj set after contactGetter')
      }
128
      await Promise.all(this.obj.memberList.map(c => c.ready(contactGetter)))
129

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
130
      return
131

132
    } catch (e) {
133 134
      log.error('Room', 'contactGetter(%s) exception: %s', this.id, e.message)
      throw e
135
    }
136 137
  }

138 139 140
  public on(event: 'leave', listener: (this: Room, leaver: Contact) => void): this
  public on(event: 'join' , listener: (this: Room, inviteeList: Contact[] , inviter: Contact)  => void): this
  public on(event: 'topic', listener: (this: Room, topic: string, oldTopic: string, changer: Contact) => void): this
141
  public on(event: 'EVENT_PARAM_ERROR', listener: () => void): this
142

143
  public on(event: RoomEventName, listener: Function): this {
144
    log.verbose('Room', 'on(%s, %s)', event, typeof listener)
145

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
146 147 148 149 150 151 152 153 154 155 156
    // const thisWithSay = {
    //   say: (content: string) => {
    //     return Config.puppetInstance()
    //                   .say(content)
    //   }
    // }
    // super.on(event, function() {
    //   return listener.apply(thisWithSay, arguments)
    // })

    super.on(event, listener) // Room is `Sayable`
157
    return this
158 159
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
160 161 162
  public say(content: string): Promise<any>
  public say(content: string, replyTo: Contact): Promise<void>
  public say(content: string, replyTo: Contact[]): Promise<void>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
163

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
164
  public say(content: string, replyTo?: Contact|Contact[]): Promise<void> {
L
lijiarui 已提交
165 166 167
    log.verbose('Room', 'say(%s, %s)',
                        content,
                        Array.isArray(replyTo)
168
                        ? replyTo.map(c => c.name()).join(', ')
L
lijiarui 已提交
169
                        : replyTo ? replyTo.name() : '',
170
    )
171 172 173 174

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
175
    const replyToList: Contact[] = [].concat(replyTo as any || [])
176

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
177 178 179 180 181 182 183
    if (replyToList.length > 0) {
      const mentionList = replyToList.map(c => '@' + c.name()).join(' ')
      m.content(mentionList + ' ' + content)
    } else {
      m.content(content)
    }
    // m.to(replyToList[0])
184 185 186 187 188

    return Config.puppetInstance()
                  .send(m)
  }

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

191
  private parse(rawObj: RoomRawObj): RoomObj | null {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
192
    if (!rawObj) {
193
      log.warn('Room', 'parse() on a empty rawObj?')
194
      return null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
195
    }
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
196

197
    const memberList = this.parseMemberList(rawObj.MemberList || [])
L
lijiarui 已提交
198 199
    const nameMap    = this.parseMap('name', rawObj.MemberList)
    const aliasMap   = this.parseMap('alias', rawObj.MemberList)
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
200

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
201
    return {
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
202 203 204 205 206
      id:         rawObj.UserName,
      encryId:    rawObj.EncryChatRoomId, // ???
      topic:      rawObj.NickName,
      ownerUin:   rawObj.OwnerUin,
      memberList,
207 208
      nameMap,
      aliasMap,
209 210 211
    }
  }

212
  private parseMemberList(rawMemberList: RoomRawMember[]): Contact[] {
213
    if (!rawMemberList || !rawMemberList.map) {
214 215
      return []
    }
216
    return rawMemberList.map(m => Contact.load(m.UserName))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
217
  }
L
lijiarui 已提交
218
  private parseMap(parseContent: NameType, memberList?: RoomRawMember[]): Map<string, string> {
219
    const mapList: Map<string, string> = new Map<string, string>()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220
    if (memberList && memberList.map) {
Huan (李卓桓)'s avatar
#104  
Huan (李卓桓) 已提交
221
      memberList.forEach(member => {
222 223 224
        let tmpName: string
        let contact = Contact.load(member.UserName)
        switch (parseContent) {
ruiruibupt's avatar
2  
ruiruibupt 已提交
225
          case 'name':
ruiruibupt's avatar
1  
ruiruibupt 已提交
226
            tmpName = contact.alias() || contact.name()
227
            break
228
          case 'alias':
ruiruibupt's avatar
1  
ruiruibupt 已提交
229
            tmpName = member.DisplayName || contact.name()
230 231 232 233
            break
          default:
            throw new Error('parseMap failed, member not found')
        }
234 235
        /**
         * ISSUE #64 emoji need to be striped
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
236
         * ISSUE #104 never use remark name because sys group message will never use that
ruiruibupt's avatar
#217  
ruiruibupt 已提交
237
         * @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 已提交
238 239
         * @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
240
         */
241
        mapList[member.UserName] = UtilLib.stripEmoji(tmpName)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
242
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
243
    }
244
    return mapList
245 246
  }

247
  public dumpRaw() {
248
    console.error('======= dump raw Room =======')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
249
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
250
  }
251
  public dump() {
252
    console.error('======= dump Room =======')
253
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj && this.obj[k]}`))
254 255
  }

Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
256
  public async add(contact: Contact): Promise<number> {
257
    log.verbose('Room', 'add(%s)', contact)
258 259 260 261 262

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

Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
263 264 265
    const n = Config.puppetInstance()
                      .roomAdd(this, contact)
    return n
266 267
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
268
  public async del(contact: Contact): Promise<number> {
269
    log.verbose('Room', 'del(%s)', contact.name())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
270 271 272 273

    if (!contact) {
      throw new Error('contact not found')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
274
    const n = await Config.puppetInstance()
Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
275 276
                            .roomDel(this, contact)
                            .then(_ => this.delLocal(contact))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
277
    return n
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278 279
  }

280
  private delLocal(contact: Contact): number {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281 282
    log.verbose('Room', 'delLocal(%s)', contact)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
283
    const memberList = this.obj && this.obj.memberList
284
    if (!memberList || memberList.length === 0) {
285
      return 0 // already in refreshing
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
286 287 288
    }

    let i
289 290
    for (i = 0; i < memberList.length; i++) {
      if (memberList[i].id === contact.id) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
291 292 293
        break
      }
    }
294 295
    if (i < memberList.length) {
      memberList.splice(i, 1)
296
      return 1
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
297
    }
298
    return 0
299
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
300

301
  public quit() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
302 303
    throw new Error('wx web not implement yet')
    // WechatyBro.glue.chatroomFactory.quit("@@1c066dfcab4ef467cd0a8da8bec90880035aa46526c44f504a83172a9086a5f7"
304
  }
305

306 307 308 309 310 311 312 313 314 315
  /**
   * get topic
   */
  public topic(): string
  /**
   * set topic
   */
  public topic(newTopic: string): void

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
320 321
    if (newTopic) {
      log.verbose('Room', 'topic(%s)', newTopic)
322
      Config.puppetInstance().roomTopic(this, newTopic)
323 324
                              .catch(e => {
                                log.warn('Room', 'topic(newTopic=%s) exception: %s',
L
lijiarui 已提交
325
                                                  newTopic, e && e.message || e,
326 327 328 329 330 331
                                )
                              })
      if (!this.obj) {
        this.obj = <RoomObj>{}
      }
      Object.assign(this.obj, { topic: newTopic })
332
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
333
    }
334
    return UtilLib.plainText(this.obj ? this.obj.topic : '')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
335 336
  }

337 338 339 340
  /**
   * should be deprecated
   * @deprecated
   */
ruiruibupt's avatar
2  
ruiruibupt 已提交
341
  public nick(contact: Contact): string | null {
ruiruibupt's avatar
1  
ruiruibupt 已提交
342
    log.warn('Room', 'nick(Contact) DEPRECATED, use alias(Contact) instead.')
ruiruibupt's avatar
#217  
ruiruibupt 已提交
343
    return this.alias(contact)
344 345
  }

ruiruibupt's avatar
2  
ruiruibupt 已提交
346
  public alias(contact: Contact): string | null {
L
lijiarui 已提交
347
    if (!this.obj || !this.obj.aliasMap) {
ruiruibupt's avatar
2  
ruiruibupt 已提交
348
      return null
349
    }
ruiruibupt's avatar
3  
ruiruibupt 已提交
350
    return this.obj.aliasMap[contact.id] || null
351 352
  }

353
  public has(contact: Contact): boolean {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
354
    if (!this.obj || !this.obj.memberList) {
355 356 357 358 359 360 361
      return false
    }
    return this.obj.memberList
                    .filter(c => c.id === contact.id)
                    .length > 0
  }

362
  public owner(): Contact | null {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
363 364
    const ownerUin = this.obj && this.obj.ownerUin
    let memberList = (this.obj && this.obj.memberList) || []
365 366 367 368 369 370 371 372

    let user = Config.puppetInstance()
                      .user

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

373
    memberList = memberList.filter(m => m.get('uin') === ownerUin)
374 375 376
    if (memberList.length > 0) {
      return memberList[0]
    }
J
jaslin 已提交
377 378

    if (this.rawObj.ChatRoomOwner) {
379 380 381 382
      return Contact.load(this.rawObj.ChatRoomOwner)
    }

    return null
383 384
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
385
  /**
ruiruibupt's avatar
1  
ruiruibupt 已提交
386 387
   * find member priority by `name`(contactAlias) / `alias`(roomAlias)
   * when use member(name:string), equals to member({name:string})
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
388
   */
389 390 391 392 393
  public member(filter: MemberQueryFilter): Contact | null
  public member(name: string): Contact | null

  public member(queryArg: MemberQueryFilter | string): Contact | null {
    if (typeof queryArg === 'string') {
ruiruibupt's avatar
1  
ruiruibupt 已提交
394
      return this.member({name: queryArg})
395 396
    }

L
lijiarui 已提交
397 398
    log.silly('Room', 'member({ %s })',
                         Object.keys(queryArg)
399
                                .map(k => `${k}: ${queryArg[k]}`)
L
lijiarui 已提交
400
                                .join(', '),
401 402 403
            )

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
407
    if (!this.obj || !this.obj.memberList) {
408
      log.warn('Room', 'member() not ready')
409 410
      return null
    }
411
    let filterKey            = Object.keys(queryArg)[0]
412 413 414
    /**
     * ISSUE #64 emoji need to be striped
     */
415 416 417
    let filterValue: string  = UtilLib.stripEmoji(queryArg[filterKey])

    const keyMap = {
418 419
      name:       'nameMap',
      alias:      'aliasMap',
420 421 422 423 424 425 426 427 428 429
    }

    filterKey = keyMap[filterKey]
    if (!filterKey) {
      throw new Error('unsupport filter key')
    }

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

431 432 433
    const filterMap = this.obj[filterKey]
    const idList = Object.keys(filterMap)
                          .filter(k => filterMap[k] === filterValue)
434

ruiruibupt's avatar
1  
ruiruibupt 已提交
435
    log.silly('Room', 'member() check %s from %s: %s', filterValue, filterKey, JSON.stringify(filterMap))
436

437 438 439 440 441
    if (idList.length) {
      return Contact.load(idList[0])
    } else {
      return null
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
442 443
  }

444
  public memberList(): Contact[] {
445
    log.verbose('Room', 'memberList')
446 447 448

    if (!this.obj || !this.obj.memberList || this.obj.memberList.length < 1) {
      log.warn('Room', 'memberList() not ready')
449
      return []
450 451 452 453
    }
    return this.obj.memberList
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
454
  public static create(contactList: Contact[], topic?: string): Promise<Room> {
455
    log.verbose('Room', 'create(%s, %s)', contactList.join(','), topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
456

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
457
    if (!contactList || !Array.isArray(contactList)) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
458 459
      throw new Error('contactList not found')
    }
460

461
    return Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
462
                  .roomCreate(contactList, topic)
463 464 465
                  .catch(e => {
                    log.error('Room', 'create() exception: %s', e && e.stack || e.message || e)
                    throw e
466
                  })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
467 468
  }

469 470 471 472
  public static async findAll(query?: RoomQueryFilter): Promise<Room[]> {
    if (!query) {
      query = { topic: /.*/ }
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
473
    log.verbose('Room', 'findAll({ topic: %s })', query.topic)
474

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

477 478
    if (!topicFilter) {
      throw new Error('topicFilter not found')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
479 480
    }

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

483
    if (topicFilter instanceof RegExp) {
484
      filterFunction = `(function (c) { return ${topicFilter.toString()}.test(c) })`
485
    } else if (typeof topicFilter === 'string') {
486
      topicFilter = topicFilter.replace(/'/g, '\\\'')
487
      filterFunction = `(function (c) { return c === '${topicFilter}' })`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
488
    } else {
489
      throw new Error('unsupport topic type')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
490 491
    }

492 493 494
    return Config.puppetInstance()
                  .roomFind(filterFunction)
                  .catch(e => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
495
                    log.verbose('Room', 'findAll() rejected: %s', e.message)
496 497
                    return [] // fail safe
                  })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
498 499
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
500
  public static async find(query: RoomQueryFilter): Promise<Room> {
501
    log.verbose('Room', 'find({ topic: %s })', query.topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
502

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
503 504 505 506
    const roomList = await Room.findAll(query)
    if (!roomList || roomList.length < 1) {
      throw new Error('no room found')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
507 508 509
    const room = roomList[0]
    await room.ready()
    return room
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
510 511
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
512 513 514
  /**
   * @todo document me
   */
515 516 517 518
  public static load(id: string): Room {
    if (!id) {
      throw new Error('Room.load() no id')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
519 520 521 522 523 524 525

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

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