puppet-padchat.ts 21.8 KB
Newer Older
ruiruibupt's avatar
init  
ruiruibupt 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/**
 *   Wechaty - https://github.com/chatie/wechaty
 *
 *   @copyright 2016-2018 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.
 *
 */

20
import path  from 'path'
21 22
// import fs    from 'fs'
// import cuid from 'cuid'
ruiruibupt's avatar
init  
ruiruibupt 已提交
23

24
import LRU from 'lru-cache'
25

ruiruibupt's avatar
init  
ruiruibupt 已提交
26 27
import {
  FileBox,
28
}               from 'file-box'
ruiruibupt's avatar
init  
ruiruibupt 已提交
29 30

import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
31 32
  ContactPayload,

33 34
  MessagePayload,
  MessageType,
ruiruibupt's avatar
init  
ruiruibupt 已提交
35 36

  RoomPayload,
37
  RoomMemberPayload,
ruiruibupt's avatar
init  
ruiruibupt 已提交
38 39 40 41

  Puppet,
  PuppetOptions,
  Receiver,
ruiruibupt's avatar
ruiruibupt 已提交
42
  FriendRequestPayload,
43 44
  FriendRequestPayloadReceive,
}                                 from '../puppet/'
ruiruibupt's avatar
init  
ruiruibupt 已提交
45

46
import {
47 48
  PadchatPureFunctionHelper as pfHelper,
}                                         from './pure-function-helper'
49

ruiruibupt's avatar
init  
ruiruibupt 已提交
50 51
import {
  log,
52 53
  qrCodeForChatie,
}                   from '../config'
ruiruibupt's avatar
init  
ruiruibupt 已提交
54

55
import {
56
  padchatToken,
57 58 59
  WECHATY_PUPPET_PADCHAT_ENDPOINT,
}                                   from './config'

ruiruibupt's avatar
init  
ruiruibupt 已提交
60 61
import {
  Bridge,
62
  // resolverDict,
ruiruibupt's avatar
ruiruibupt 已提交
63
  // AutoDataType,
ruiruibupt's avatar
init  
ruiruibupt 已提交
64 65 66
}                       from './bridge'

import {
67
  // PadchatPayload,
68
  PadchatContactPayload,
69
  PadchatMessagePayload,
70
  PadchatRoomPayload,
71 72
  // PadchatRoomMemberListPayload,
  PadchatRoomMemberPayload,
73
  PadchatMessageType,
74

75
  // PadchatMessageType,
76 77 78 79
  // PadchatContinue,
  // PadchatMsgType,
  // PadchatStatus,
  // PadchatPayloadType,
80
  // PadchatRoomRawMember,
81
}                           from './padchat-schemas'
ruiruibupt's avatar
init  
ruiruibupt 已提交
82 83 84 85 86

export type PuppetFoodType = 'scan' | 'ding'
export type ScanFoodType   = 'scan' | 'login' | 'logout'

export class PuppetPadchat extends Puppet {
87

88
  // private readonly cachePadchatContactPayload       : LRU.Cache<string, PadchatContactRawPayload>
89
  private readonly cachePadchatFriendRequestPayload : LRU.Cache<string, PadchatMessagePayload>
90 91
  private readonly cachePadchatMessagePayload       : LRU.Cache<string, PadchatMessagePayload>
  // private readonly cachePadchatRoomPayload          : LRU.Cache<string, PadchatRoomRawPayload>
92

93
  public bridge?:  Bridge
ruiruibupt's avatar
init  
ruiruibupt 已提交
94 95 96 97 98 99

  constructor(
    public options: PuppetOptions,
  ) {
    super(options)

100 101 102
    const lruOptions: LRU.Options = {
      max: 1000,
      // length: function (n) { return n * 2},
103
      dispose: function (key: string, val: any) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104
        log.silly('PuppetPadchat', 'constructor() lruOptions.dispose(%s, %s)', key, JSON.stringify(val))
105 106 107 108
      },
      maxAge: 1000 * 60 * 60,
    }

109
    // this.cachePadchatContactPayload       = new LRU<string, PadchatContactRawPayload>(lruOptions)
110
    this.cachePadchatFriendRequestPayload = new LRU<string, PadchatMessagePayload>(lruOptions)
111
    this.cachePadchatMessagePayload       = new LRU<string, PadchatMessagePayload>(lruOptions)
112
    // this.cachePadchatRoomPayload          = new LRU<string, PadchatRoomRawPayload>(lruOptions)
ruiruibupt's avatar
init  
ruiruibupt 已提交
113 114 115
  }

  public toString() {
116
    return `PuppetPadchat<${this.options.memory.name}>`
ruiruibupt's avatar
init  
ruiruibupt 已提交
117 118 119 120 121 122
  }

  public ding(data?: any): Promise<string> {
    return data
  }

ruiruibupt's avatar
ruiruibupt 已提交
123
  public startWatchdog(): void {
ruiruibupt's avatar
ruiruibupt 已提交
124
    log.verbose('PuppetPadchat', 'initWatchdogForPuppet()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
125

126 127 128
    if (!this.bridge) {
      throw new Error('no bridge')
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
129

ruiruibupt's avatar
ruiruibupt 已提交
130 131 132
    // clean the dog because this could be re-inited
    this.watchdog.removeAllListeners()

133 134 135 136 137 138 139 140 141
    /**
     * Use puppet's heartbeat to feed dog
     */
    this.bridge.on('heartbeat', (data: string) => {
      log.silly('PuppetPadchat', 'startWatchdog() bridge.on(heartbeat)')
      this.watchdog.feed({
        data,
      })
    })
ruiruibupt's avatar
ruiruibupt 已提交
142
    this.watchdog.on('feed', async food => {
143
      log.silly('PuppetPadchat', 'startWatchdog() watchdog.on(feed, food={type=%s, data=%s})', food.type, food.data)
ruiruibupt's avatar
ruiruibupt 已提交
144 145
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
146
    this.watchdog.on('reset', async (food, timeout) => {
147 148 149 150 151
      log.warn('PuppetPadchat', 'startWatchdog() dog.on(reset) last food:%s, timeout:%s',
                                food.data,
                                timeout,
              )
      await this.restart('watchdog.on(reset)')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
152
    })
ruiruibupt's avatar
ruiruibupt 已提交
153 154 155 156 157

    this.emit('watchdog', {
      data: 'inited',
    })

ruiruibupt's avatar
ruiruibupt 已提交
158
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
159 160

  public async start(): Promise<void> {
ruiruibupt's avatar
ruiruibupt 已提交
161
    log.verbose('PuppetPadchat', `start() with ${this.options.memory.name}`)
ruiruibupt's avatar
init  
ruiruibupt 已提交
162

163 164 165 166 167 168
    if (this.state.on()) {
      log.warn('PuppetPadchat', 'start() already on(pending)?')
      await this.state.ready('on')
      return
    }

ruiruibupt's avatar
ruiruibupt 已提交
169 170 171 172 173 174 175
    /**
     * state has two main state: ON / OFF
     * ON (pending)
     * OFF (pending)
     */
    this.state.on('pending')

176 177 178 179 180 181 182
    const bridge = this.bridge = new Bridge({
      memory   : this.options.memory,
      token    : padchatToken(),
      endpoint : WECHATY_PUPPET_PADCHAT_ENDPOINT,
    })

    await this.startBridge(bridge)
ruiruibupt's avatar
ruiruibupt 已提交
183
    await this.startWatchdog()
ruiruibupt's avatar
ruiruibupt 已提交
184

ruiruibupt's avatar
ruiruibupt 已提交
185
    this.state.on(true)
186
    this.emit('start')
187
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
188

189
  protected async login(selfId: string): Promise<void> {
190 191 192
    if (!this.bridge) {
      throw new Error('no bridge')
    }
193 194
    await super.login(selfId)
    this.bridge.syncContactsAndRooms()
ruiruibupt's avatar
init  
ruiruibupt 已提交
195 196
  }

197
  public async startBridge(bridge: Bridge): Promise<void> {
ruiruibupt's avatar
ruiruibupt 已提交
198
    log.verbose('PuppetPadchat', 'startBridge()')
ruiruibupt's avatar
ruiruibupt 已提交
199 200

    if (this.state.off()) {
201
      throw new Error('startBridge() state is off')
ruiruibupt's avatar
ruiruibupt 已提交
202 203
    }

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    bridge.removeAllListeners()
    // bridge.on('ding'     , Event.onDing.bind(this))
    // bridge.on('error'    , e => this.emit('error', e))
    // bridge.on('log'      , Event.onLog.bind(this))
    bridge.on('scan',    (qrcode: string, status: number, data?: string) => this.emit('scan', qrcode, status, data))
    bridge.on('login',   (userId: string)                                => this.login(userId))
    bridge.on('message', (rawPayload: PadchatMessagePayload)             => this.onPadchatMessage(rawPayload))
    bridge.on('logout',  ()                                              => this.logout())

    bridge.on('destroy', reason => {
      log.warn('PuppetPadchat', 'startBridge() bridge.on(destroy) for %s. Restarting PuppetPadchat ... ', reason)
      this.restart(reason)
    })

    await bridge.start()
  }

  protected async restart(reason: string): Promise<void> {
    log.verbose('PuppetPadchat', 'restart(%s)', reason)

    await this.stop()
    await this.start()
ruiruibupt's avatar
init  
ruiruibupt 已提交
226 227 228

  }

229 230 231 232 233 234 235 236
  protected onPadchatMessage(rawPayload: PadchatMessagePayload) {
    log.verbose('PuppetPadchat', 'onPadchatMessage({id=%s, type=%s(%s)})',
                                rawPayload.msg_id,
                                PadchatMessageType[rawPayload.sub_type],
                                rawPayload.msg_type,
              )
    switch (rawPayload.sub_type) {
      case PadchatMessageType.VerifyMsg:
237 238 239 240
        this.cachePadchatFriendRequestPayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
241 242 243 244 245 246 247 248 249 250 251 252 253
        this.emit('friend', rawPayload.msg_id)
        break

      default:
        this.cachePadchatMessagePayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
        this.emit('message', rawPayload.msg_id)
        break
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
254
  public async stop(): Promise<void> {
255 256 257 258 259
    log.verbose('PuppetPadchat', 'stop()')

    if (!this.bridge) {
      throw new Error('no bridge')
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
260 261

    if (this.state.off()) {
262
      log.warn('PuppetPadchat', 'stop() is called on a OFF puppet. await ready(off) and return.')
ruiruibupt's avatar
init  
ruiruibupt 已提交
263 264 265 266 267
      await this.state.ready('off')
      return
    }

    this.state.off('pending')
ruiruibupt's avatar
ruiruibupt 已提交
268 269 270

    this.watchdog.sleep()
    await this.logout()
ruiruibupt's avatar
ruiruibupt 已提交
271

272
    setImmediate(() => this.bridge && this.bridge.removeAllListeners())
ruiruibupt's avatar
ruiruibupt 已提交
273
    await this.bridge.stop()
ruiruibupt's avatar
ruiruibupt 已提交
274

ruiruibupt's avatar
init  
ruiruibupt 已提交
275 276
    // await some tasks...
    this.state.off(true)
277
    this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
278 279
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
280
  public async logout(): Promise<void> {
ruiruibupt's avatar
init  
ruiruibupt 已提交
281 282
    log.verbose('PuppetPadchat', 'logout()')

283
    if (!this.id) {
284 285 286 287 288 289 290
      log.warn('PuppetPadchat', 'logout() this.id not exist')
      // throw new Error('logout before login?')
      return
    }

    if (!this.bridge) {
      throw new Error('no bridge')
ruiruibupt's avatar
init  
ruiruibupt 已提交
291 292
    }

293 294
    this.emit('logout', this.id) // becore we will throw above by logonoff() when this.user===undefined
    this.id = undefined
ruiruibupt's avatar
init  
ruiruibupt 已提交
295

296 297 298 299
    // if (!passive) {
    //   await this.bridge.WXLogout()
    // }

300
    await this.bridge.logout()
ruiruibupt's avatar
init  
ruiruibupt 已提交
301 302 303 304 305 306 307 308 309 310 311 312 313 314
  }

  /**
   *
   * Contact
   *
   */
  public contactAlias(contactId: string)                      : Promise<string>
  public contactAlias(contactId: string, alias: string | null): Promise<void>

  public async contactAlias(contactId: string, alias?: string|null): Promise<void | string> {
    log.verbose('PuppetPadchat', 'contactAlias(%s, %s)', contactId, alias)

    if (typeof alias === 'undefined') {
ruiruibupt's avatar
ruiruibupt 已提交
315
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
316
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
317
    }
ruiruibupt's avatar
ruiruibupt 已提交
318

319 320 321 322
    if (!this.bridge) {
      throw new Error('no bridge')
    }

ruiruibupt's avatar
ruiruibupt 已提交
323 324
    await this.bridge.WXSetUserRemark(contactId, alias || '')

ruiruibupt's avatar
init  
ruiruibupt 已提交
325 326 327
    return
  }

328 329
  public async contactList(): Promise<string[]> {
    log.verbose('PuppetPadchat', 'contactList()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
330

331 332 333 334
    if (!this.bridge) {
      throw new Error('no bridge')
    }

ruiruibupt's avatar
ruiruibupt 已提交
335
    const contactIdList = this.bridge.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
336 337 338 339

    return contactIdList
  }

340 341
  public async contactAvatar(contactId: string)                : Promise<FileBox>
  public async contactAvatar(contactId: string, file: FileBox) : Promise<void>
ruiruibupt's avatar
init  
ruiruibupt 已提交
342

343 344 345 346 347 348 349 350 351 352
  public async contactAvatar(contactId: string, file?: FileBox): Promise<void | FileBox> {
    log.verbose('PuppetPadchat', 'contactAvatar(%s, %s)', contactId, file ? file.name : '')

    /**
     * 1. set avatar for user self
     */
    if (file) {
      if (contactId !== this.selfId()) {
        throw new Error('can not set avatar for others')
      }
353 354 355
      if (!this.bridge) {
        throw new Error('no bridge')
      }
356 357 358 359 360 361 362
      await this.bridge.WXSetHeadImage(await file.toBase64())
      return
    }

    /**
     * 2. get avatar
     */
ruiruibupt's avatar
ruiruibupt 已提交
363 364 365 366 367 368
    const payload = await this.contactPayload(contactId)

    if (!payload.avatar) {
      throw new Error('no avatar')
    }

369 370 371 372
    const fileBox = FileBox.fromUrl(payload.avatar)
    return fileBox
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
373
  public async contactQrcode(contactId: string): Promise<string> {
374 375 376
    if (contactId !== this.selfId()) {
      throw new Error('can not set avatar for others')
    }
377 378 379
    if (!this.bridge) {
      throw new Error('no bridge')
    }
380
    const base64 = await this.bridge.WXGetUserQRCode(contactId, 0)
381
    const qrcode = await pfHelper.imageBase64ToQrcode(base64)
382
    return qrcode
ruiruibupt's avatar
init  
ruiruibupt 已提交
383 384
  }

385
  public async contactRawPayload(contactId: string): Promise<PadchatContactPayload> {
386
    log.silly('PuppetPadchat', 'contactRawPayload(%s)', contactId)
ruiruibupt's avatar
ruiruibupt 已提交
387

388 389 390
    if (!this.bridge) {
      throw new Error('no bridge')
    }
391
    const rawPayload = await this.bridge.contactRawPayload(contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
392 393 394
    return rawPayload
  }

395
  public async contactRawPayloadParser(rawPayload: PadchatContactPayload): Promise<ContactPayload> {
396
    log.silly('PuppetPadchat', 'contactRawPayloadParser({user_name="%s"})', rawPayload.user_name)
ruiruibupt's avatar
init  
ruiruibupt 已提交
397

398
    const payload: ContactPayload = pfHelper.contactRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
399 400 401 402 403 404 405 406
    return payload
  }

  /**
   *
   * Message
   *
   */
ruiruibupt's avatar
ruiruibupt 已提交
407 408

  public async messageFile(id: string): Promise<FileBox> {
409 410
    log.warn('PuppetPadchat', 'messageFile(%s) not implemented yet', id)

ruiruibupt's avatar
ruiruibupt 已提交
411 412
    // const rawPayload = await this.messageRawPayload(id)

ruiruibupt's avatar
ruiruibupt 已提交
413 414
    // TODO

ruiruibupt's avatar
ruiruibupt 已提交
415
    const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
416
    const filename = 'test-' + id + '.txt'
ruiruibupt's avatar
ruiruibupt 已提交
417

418
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
419 420 421 422 423 424 425
      base64,
      filename,
    )

    return file
  }

426
  public async messageRawPayload(id: string): Promise<PadchatMessagePayload> {
427 428 429 430 431
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
432 433 434 435 436

    // this.cachePadchatMessageRawPayload.set(id, {
    //   id: 'xxx',
    //   data: 'xxx',
    // } as any)
437

438
    const rawPayload = this.cachePadchatMessagePayload.get(id)
439 440 441 442 443 444

    if (!rawPayload) {
      throw new Error('no rawPayload')
    }

    return rawPayload
ruiruibupt's avatar
init  
ruiruibupt 已提交
445 446
  }

447
  public async messageRawPayloadParser(rawPayload: PadchatMessagePayload): Promise<MessagePayload> {
448
    log.verbose('PuppetPadChat', 'messageRawPayloadParser({msg_id="%s"})', rawPayload.msg_id)
449

450
    const payload: MessagePayload = pfHelper.messageRawPayloadParser(rawPayload)
ruiruibupt's avatar
ruiruibupt 已提交
451

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
452
    log.silly('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
453 454 455 456 457 458 459 460
    return payload
  }

  public async messageSendText(
    receiver : Receiver,
    text     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', receiver, text)
ruiruibupt's avatar
ruiruibupt 已提交
461 462
    const id = receiver.contactId || receiver.roomId
    if (!id) {
463
      throw Error('no id')
ruiruibupt's avatar
ruiruibupt 已提交
464
    }
465 466 467
    if (!this.bridge) {
      throw new Error('no bridge')
    }
ruiruibupt's avatar
ruiruibupt 已提交
468
    await this.bridge.WXSendMsg(id, text)
ruiruibupt's avatar
init  
ruiruibupt 已提交
469 470 471 472 473 474
  }

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
475
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), file)
ruiruibupt's avatar
ruiruibupt 已提交
476 477 478 479 480 481

    const id = receiver.contactId || receiver.roomId
    if (!id) {
      throw new Error('no id!')
    }

482 483 484 485
    if (!this.bridge) {
      throw new Error('no bridge')
    }

486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
    const type = file.mimeType || path.extname(file.name)
    switch (type) {
      case '.slk':
        // 发送语音消息(微信silk格式语音)
        await this.bridge.WXSendVoice(
          id,
          await file.toBase64(),
          60,
        )
        break

      default:
        await this.bridge.WXSendImage(
          id,
          await file.toBase64(),
        )
        break
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
504 505 506 507 508 509 510
  }

  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
511
                              JSON.stringify(receiver),
ruiruibupt's avatar
init  
ruiruibupt 已提交
512 513
                              messageId,
              )
514
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
515

516 517 518 519
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
520 521
      await this.messageSendText(
        receiver,
522
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
523 524 525 526
      )
    } else {
      await this.messageSendFile(
        receiver,
527
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
528 529
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
530 531 532 533 534 535 536
  }

  /**
   *
   * Room
   *
   */
537 538 539 540 541
  public async roomMemberRawPayload(
    roomId    : string,
    contactId : string,
  ): Promise<PadchatRoomMemberPayload> {
    log.silly('PuppetPadchat', 'roomMemberRawPayload(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
542

543 544 545 546
    if (!this.bridge) {
      throw new Error('no bridge')
    }

547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
    const rawPayload = await this.bridge.roomMemberRawPayload(roomId, contactId)
    return rawPayload
  }

  public async roomMemberRawPayloadParser(
    rawPayload: PadchatRoomMemberPayload,
  ): Promise<RoomMemberPayload> {
    log.silly('PuppetPadchat', 'roomMemberRawPayloadParser(%s)', rawPayload)

    const payload: RoomMemberPayload = {
      id        : rawPayload.user_name,
      inviterId : rawPayload.invited_by,
      roomAlias : rawPayload.chatroom_nick_name,
    }

    return payload
  }

  public async roomRawPayload(roomId: string): Promise<PadchatRoomPayload> {
    log.verbose('PuppetPadchat', 'roomRawPayload(%s)', roomId)

568 569 570 571
    if (!this.bridge) {
      throw new Error('no bridge')
    }

572
    const rawPayload = await this.bridge.roomRawPayload(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
573 574 575
    return rawPayload
  }

576
  public async roomRawPayloadParser(rawPayload: PadchatRoomPayload): Promise<RoomPayload> {
577
    log.verbose('PuppetPadchat', 'roomRawPayloadParser(rawPayload.user_name="%s")', rawPayload.user_name)
ruiruibupt's avatar
init  
ruiruibupt 已提交
578

579 580
    // const memberIdList = await this.bridge.getRoomMemberIdList()
    //  WXGetChatRoomMember(rawPayload.user_name)
581

582
    const payload: RoomPayload = pfHelper.roomRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
583 584 585 586

    return payload
  }

587 588 589
  public async roomMemberList(roomId: string): Promise<string[]> {
    log.verbose('PuppetPadchat', 'roomMemberList(%s)', roomId)

590 591 592 593
    if (!this.bridge) {
      throw new Error('no bridge')
    }

594 595 596 597 598 599
    const memberIdList = await this.bridge.getRoomMemberIdList(roomId)
    log.silly('PuppetPadchat', 'roomMemberList()=%d', memberIdList.length)

    return memberIdList
  }

600
  public async roomList(): Promise<string[]> {
ruiruibupt's avatar
ruiruibupt 已提交
601
    log.verbose('PuppetPadchat', 'roomList()')
602

603 604 605 606
    if (!this.bridge) {
      throw new Error('no bridge')
    }

607 608
    const roomIdList = await this.bridge.getRoomIdList()
    log.silly('PuppetPadchat', 'roomList()=%d', roomIdList.length)
ruiruibupt's avatar
ruiruibupt 已提交
609

ruiruibupt's avatar
ruiruibupt 已提交
610
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
611 612 613 614 615 616 617
  }

  public async roomDel(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomDel(%s, %s)', roomId, contactId)
ruiruibupt's avatar
ruiruibupt 已提交
618

619 620 621 622
    if (!this.bridge) {
      throw new Error('no bridge')
    }

623
    // Should check whether user is in the room. WXDeleteChatRoomMember won't check if user in the room automatically
ruiruibupt's avatar
ruiruibupt 已提交
624
    await this.bridge.WXDeleteChatRoomMember(roomId, contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
625 626
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
627
  public async roomQrcode(roomId: string): Promise<string> {
628 629 630 631 632 633 634 635
    log.verbose('PuppetPadchat', 'roomQrCode(%s)', roomId)

    // TODO

    throw new Error('not support')

  }

636 637 638 639 640 641 642 643 644 645 646 647 648
  public async roomAvatar(roomId: string): Promise<FileBox> {
    log.verbose('PuppetPadchat', 'roomAvatar(%s)', roomId)

    const payload = await this.roomPayload(roomId)

    if (payload.avatar) {
      return FileBox.fromUrl(payload.avatar)
    }
    log.warn('PuppetPadchat', 'roomAvatar() avatar not found, use the chatie default.')

    return qrCodeForChatie()
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
649 650 651 652 653
  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
654

655 656 657 658
    if (!this.bridge) {
      throw new Error('no bridge')
    }

659 660 661 662 663 664 665 666 667 668 669 670
    // XXX: did there need to calc the total number of the members in this room?
    // if n <= 40 then add() else invite() ?
    try {
      log.verbose('PuppetPadchat', 'roomAdd(%s, %s) try to Add', roomId, contactId)
      await this.bridge.WXAddChatRoomMember(roomId, contactId)
    } catch (e) {
      // FIXME
      console.error(e)
      log.warn('PuppetPadchat', 'roomAdd(%s, %s) Add exception: %s', e)
      log.verbose('PuppetPadchat', 'roomAdd(%s, %s) try to Invite', roomId, contactId)
      await this.bridge.WXInviteChatRoomMember(roomId, contactId)
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
671 672
  }

673 674 675
  public async roomTopic(roomId: string)                : Promise<string>
  public async roomTopic(roomId: string, topic: string) : Promise<void>

ruiruibupt's avatar
init  
ruiruibupt 已提交
676 677 678 679 680 681 682
  public async roomTopic(
    roomId: string,
    topic?: string,
  ): Promise<void | string> {
    log.verbose('PuppetPadchat', 'roomTopic(%s, %s)', roomId, topic)

    if (typeof topic === 'undefined') {
ruiruibupt's avatar
ruiruibupt 已提交
683
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
684
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
685
    }
ruiruibupt's avatar
ruiruibupt 已提交
686

687 688 689 690
    if (!this.bridge) {
      throw new Error('no bridge')
    }

ruiruibupt's avatar
ruiruibupt 已提交
691 692
    await this.bridge.WXSetChatroomName(roomId, topic)

ruiruibupt's avatar
init  
ruiruibupt 已提交
693 694 695 696 697 698 699 700 701
    return
  }

  public async roomCreate(
    contactIdList : string[],
    topic         : string,
  ): Promise<string> {
    log.verbose('PuppetPadchat', 'roomCreate(%s, %s)', contactIdList, topic)

702 703 704 705
    if (!this.bridge) {
      throw new Error('no bridge')
    }

706 707 708 709 710
    // FIXME:
    const roomId = this.bridge.WXCreateChatRoom(contactIdList)
    console.log('roomCreate returl:', roomId)

    return roomId
ruiruibupt's avatar
init  
ruiruibupt 已提交
711 712 713 714
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
715 716 717 718 719

    if (!this.bridge) {
      throw new Error('no bridge')
    }

ruiruibupt's avatar
ruiruibupt 已提交
720
    await this.bridge.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
721 722
  }

723 724 725 726 727
  public async roomAnnounce(roomId: string)             : Promise<string>
  public async roomAnnounce(roomId: string, text: string) : Promise<void>

  public async roomAnnounce(roomId: string, text?: string): Promise<void | string> {
    log.verbose('PuppetPadchat', 'roomAnnounce(%s, %s)', roomId, text ? text : '')
728 729 730 731 732

    if (!this.bridge) {
      throw new Error('no bridge')
    }

733 734 735 736 737 738 739
    if (text) {
      await this.bridge.WXSetChatroomAnnouncement(roomId, text)
    } else {
      return await this.bridge.WXGetChatroomAnnouncement(roomId)
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
740 741 742 743 744 745 746 747 748 749
  /**
   *
   * FriendRequest
   *
   */
  public async friendRequestSend(
    contactId : string,
    hello     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'friendRequestSend(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
750 751 752

    const rawPayload = await this.contactRawPayload(contactId)

753 754 755 756 757 758 759 760 761 762 763 764
    // XXX
    console.log('rawPayload.stranger: ', rawPayload)

    // let strangerV1
    // let strangerV2
    // if (pfHelper.isStrangerV1(rawPayload.stranger)) {
    //   strangerV1 = rawPayload.stranger
    // } else if (pfHelper.isStrangerV2(rawPayload.stranger)) {
    //   strangerV2 = rawPayload.stranger
    // } else {
    //   throw new Error('stranger neither v1 nor v2!')
    // }
ruiruibupt's avatar
ruiruibupt 已提交
765

766 767 768 769
    if (!this.bridge) {
      throw new Error('no bridge')
    }

770 771
    // Issue #1252 : what's wrong here?

ruiruibupt's avatar
ruiruibupt 已提交
772
    await this.bridge.WXAddUser(
773 774
      rawPayload.stranger || '',
      rawPayload.ticket   || '',
ruiruibupt's avatar
ruiruibupt 已提交
775 776 777
      '14',
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
778 779 780
  }

  public async friendRequestAccept(
781
    friendRequestId : string,
ruiruibupt's avatar
init  
ruiruibupt 已提交
782
  ): Promise<void> {
783
    log.verbose('PuppetPadchat', 'friendRequestAccept(%s)', friendRequestId)
ruiruibupt's avatar
ruiruibupt 已提交
784

785
    const payload = await this.friendRequestPayload(friendRequestId) as FriendRequestPayloadReceive
ruiruibupt's avatar
ruiruibupt 已提交
786

787 788
    console.log('friendRequestAccept: ', payload)

789 790 791
    if (!payload.ticket) {
      throw new Error('no ticket')
    }
792 793 794
    if (!payload.stranger) {
      throw new Error('no stranger')
    }
ruiruibupt's avatar
ruiruibupt 已提交
795

796 797 798 799
    if (!this.bridge) {
      throw new Error('no bridge')
    }

800
    await this.bridge.WXAcceptUser(
801
      payload.stranger,
802 803
      payload.ticket,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
804 805
  }

806
  public async friendRequestRawPayloadParser(rawPayload: PadchatMessagePayload) : Promise<FriendRequestPayload> {
807
    log.verbose('PuppetPadchat', 'friendRequestRawPayloadParser({id=%s})', rawPayload.msg_id)
ruiruibupt's avatar
ruiruibupt 已提交
808

809
    const payload: FriendRequestPayload = await pfHelper.friendRequestRawPayloadParser(rawPayload)
810
    return payload
ruiruibupt's avatar
ruiruibupt 已提交
811 812
  }

813 814
  public async friendRequestRawPayload(friendRequestId: string): Promise<PadchatMessagePayload> {
    log.verbose('PuppetPadchat', 'friendRequestRawPayload(%s)', friendRequestId)
ruiruibupt's avatar
ruiruibupt 已提交
815

816 817
    const rawPayload = this.cachePadchatFriendRequestPayload.get(friendRequestId)
    if (!rawPayload) {
818
      throw new Error('no rawPayload for id ' + friendRequestId)
819
    }
ruiruibupt's avatar
ruiruibupt 已提交
820

821
    return rawPayload
ruiruibupt's avatar
ruiruibupt 已提交
822 823
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
824 825 826
}

export default PuppetPadchat