puppet-padchat.ts 23.9 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
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
134
     * Use bridge's heartbeat to feed dog
135 136 137 138 139 140 141
     */
    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
  protected onPadchatMessage(rawPayload: PadchatMessagePayload) {
    log.verbose('PuppetPadchat', 'onPadchatMessage({id=%s, type=%s(%s)})',
                                rawPayload.msg_id,
                                PadchatMessageType[rawPayload.sub_type],
                                rawPayload.msg_type,
              )
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235 236
    console.log('rawPayload:', rawPayload)

237 238
    switch (rawPayload.sub_type) {
      case PadchatMessageType.VerifyMsg:
239 240 241 242
        this.cachePadchatFriendRequestPayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
243 244 245
        this.emit('friend', rawPayload.msg_id)
        break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
246 247 248 249 250 251 252 253
      case PadchatMessageType.Sys:
        console.log('sys message:', rawPayload)
        // this.emit('room-join',   roomId, inviteeIdList,  inviterId)
        // this.emit('room-leave',  roomId, leaverIdList, remover)
        // this.emit('room-topic',  roomId, topic, oldTopic, changerId)

        break

254 255 256 257 258 259 260 261 262 263
      default:
        this.cachePadchatMessagePayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
        this.emit('message', rawPayload.msg_id)
        break
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
264
  public async stop(): Promise<void> {
265 266 267 268 269
    log.verbose('PuppetPadchat', 'stop()')

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

    if (this.state.off()) {
272
      log.warn('PuppetPadchat', 'stop() is called on a OFF puppet. await ready(off) and return.')
ruiruibupt's avatar
init  
ruiruibupt 已提交
273 274 275 276 277
      await this.state.ready('off')
      return
    }

    this.state.off('pending')
ruiruibupt's avatar
ruiruibupt 已提交
278 279 280

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

282
    setImmediate(() => this.bridge && this.bridge.removeAllListeners())
ruiruibupt's avatar
ruiruibupt 已提交
283
    await this.bridge.stop()
ruiruibupt's avatar
ruiruibupt 已提交
284

ruiruibupt's avatar
init  
ruiruibupt 已提交
285 286
    // await some tasks...
    this.state.off(true)
287
    this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
288 289
  }

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

293
    if (!this.id) {
294 295 296 297 298 299 300
      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 已提交
301 302
    }

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

306 307 308 309
    // if (!passive) {
    //   await this.bridge.WXLogout()
    // }

310
    await this.bridge.logout()
ruiruibupt's avatar
init  
ruiruibupt 已提交
311 312 313 314 315 316 317 318 319 320 321 322 323 324
  }

  /**
   *
   * 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 已提交
325
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
326
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
327
    }
ruiruibupt's avatar
ruiruibupt 已提交
328

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

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
335 336 337
    return
  }

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

341 342 343 344
    if (!this.bridge) {
      throw new Error('no bridge')
    }

ruiruibupt's avatar
ruiruibupt 已提交
345
    const contactIdList = this.bridge.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
346 347 348 349

    return contactIdList
  }

350 351
  public async contactAvatar(contactId: string)                : Promise<FileBox>
  public async contactAvatar(contactId: string, file: FileBox) : Promise<void>
ruiruibupt's avatar
init  
ruiruibupt 已提交
352

353 354 355 356 357 358 359 360 361 362
  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')
      }
363 364 365
      if (!this.bridge) {
        throw new Error('no bridge')
      }
366 367 368 369 370 371 372
      await this.bridge.WXSetHeadImage(await file.toBase64())
      return
    }

    /**
     * 2. get avatar
     */
ruiruibupt's avatar
ruiruibupt 已提交
373 374 375 376 377 378
    const payload = await this.contactPayload(contactId)

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

379 380 381 382
    const fileBox = FileBox.fromUrl(payload.avatar)
    return fileBox
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
383
  public async contactQrcode(contactId: string): Promise<string> {
384 385 386
    if (contactId !== this.selfId()) {
      throw new Error('can not set avatar for others')
    }
387 388 389
    if (!this.bridge) {
      throw new Error('no bridge')
    }
390
    const base64 = await this.bridge.WXGetUserQRCode(contactId, 0)
391
    const qrcode = await pfHelper.imageBase64ToQrcode(base64)
392
    return qrcode
ruiruibupt's avatar
init  
ruiruibupt 已提交
393 394
  }

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

398 399 400
    if (!this.bridge) {
      throw new Error('no bridge')
    }
401
    const rawPayload = await this.bridge.contactRawPayload(contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
402 403 404
    return rawPayload
  }

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

408
    const payload: ContactPayload = pfHelper.contactRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
409 410 411 412 413 414 415 416
    return payload
  }

  /**
   *
   * Message
   *
   */
ruiruibupt's avatar
ruiruibupt 已提交
417

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
418 419
  public async messageFile(messageId: string): Promise<FileBox> {
    log.warn('PuppetPadchat', 'messageFile(%s) not implemented yet', messageId)
420

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

ruiruibupt's avatar
ruiruibupt 已提交
423 424
    // TODO

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    if (!this.bridge) {
      throw new Error('no bridge')
    }

    const rawPayload = await this.messageRawPayload(messageId)
    const payload    = await this.messagePayload(messageId)

    const rawText = JSON.stringify(rawPayload)

    let result

    switch (payload.type) {
      case MessageType.Attachment:
        break

      case MessageType.Audio:
        result = await this.bridge.WXGetMsgVoice(rawText)
        console.log(result)
        return FileBox.fromBase64(result.data.image, 'test.slk')

      case MessageType.Emoticon:
        result = await this.bridge.WXGetMsgImage(rawText)
        console.log(result)
        return FileBox.fromBase64(result.data.image, 'test.gif')

      case MessageType.Image:
        result = await this.bridge.WXGetMsgImage(rawText)
        console.log(result)
        return FileBox.fromBase64(result.data.image, 'test.jpg')

      case MessageType.Video:
        result = await this.bridge.WXGetMsgVideo(rawText)
        console.log(result)
        return FileBox.fromBase64(result.data.image, 'test.mp4')

      default:
        throw new Error('unsupport type: ' + PadchatMessageType[rawPayload.sub_type] + ':' + rawPayload.sub_type)
    }

ruiruibupt's avatar
ruiruibupt 已提交
464
    const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
465
    const filename = 'test-' + messageId + '.txt'
ruiruibupt's avatar
ruiruibupt 已提交
466

467
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
468 469 470 471 472 473 474
      base64,
      filename,
    )

    return file
  }

475
  public async messageRawPayload(id: string): Promise<PadchatMessagePayload> {
476 477 478 479 480
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
481 482 483 484 485

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

487
    const rawPayload = this.cachePadchatMessagePayload.get(id)
488 489 490 491 492 493

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

    return rawPayload
ruiruibupt's avatar
init  
ruiruibupt 已提交
494 495
  }

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
501
    log.silly('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
502 503 504 505 506 507 508
    return payload
  }

  public async messageSendText(
    receiver : Receiver,
    text     : string,
  ): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
509
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', JSON.stringify(receiver), text)
ruiruibupt's avatar
ruiruibupt 已提交
510 511
    const id = receiver.contactId || receiver.roomId
    if (!id) {
512
      throw Error('no id')
ruiruibupt's avatar
ruiruibupt 已提交
513
    }
514 515 516
    if (!this.bridge) {
      throw new Error('no bridge')
    }
ruiruibupt's avatar
ruiruibupt 已提交
517
    await this.bridge.WXSendMsg(id, text)
ruiruibupt's avatar
init  
ruiruibupt 已提交
518 519 520 521 522 523
  }

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
524
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), file)
ruiruibupt's avatar
ruiruibupt 已提交
525 526 527 528 529 530

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

531 532 533 534
    if (!this.bridge) {
      throw new Error('no bridge')
    }

535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    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 已提交
553 554
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  public async messageSendContact(
    receiver  : Receiver,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), contactId)

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

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

    const payload = await this.contactPayload(contactId)
    const title = payload.name + '名片'
    await this.bridge.WXShareCard(id, contactId, title)
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
575 576 577 578 579
  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
580
                              JSON.stringify(receiver),
ruiruibupt's avatar
init  
ruiruibupt 已提交
581 582
                              messageId,
              )
583
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
584

585 586 587 588
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
589 590
      await this.messageSendText(
        receiver,
591
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
592 593 594 595
      )
    } else {
      await this.messageSendFile(
        receiver,
596
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
597 598
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
599 600 601 602 603 604 605
  }

  /**
   *
   * Room
   *
   */
606 607 608 609 610
  public async roomMemberRawPayload(
    roomId    : string,
    contactId : string,
  ): Promise<PadchatRoomMemberPayload> {
    log.silly('PuppetPadchat', 'roomMemberRawPayload(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
611

612 613 614 615
    if (!this.bridge) {
      throw new Error('no bridge')
    }

616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
    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)

637 638 639 640
    if (!this.bridge) {
      throw new Error('no bridge')
    }

641
    const rawPayload = await this.bridge.roomRawPayload(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
642 643 644
    return rawPayload
  }

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

648 649
    // const memberIdList = await this.bridge.getRoomMemberIdList()
    //  WXGetChatRoomMember(rawPayload.user_name)
650

651
    const payload: RoomPayload = pfHelper.roomRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
652 653 654 655

    return payload
  }

656 657 658
  public async roomMemberList(roomId: string): Promise<string[]> {
    log.verbose('PuppetPadchat', 'roomMemberList(%s)', roomId)

659 660 661 662
    if (!this.bridge) {
      throw new Error('no bridge')
    }

663 664 665 666 667 668
    const memberIdList = await this.bridge.getRoomMemberIdList(roomId)
    log.silly('PuppetPadchat', 'roomMemberList()=%d', memberIdList.length)

    return memberIdList
  }

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

672 673 674 675
    if (!this.bridge) {
      throw new Error('no bridge')
    }

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

ruiruibupt's avatar
ruiruibupt 已提交
679
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
680 681 682 683 684 685 686
  }

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
696
  public async roomQrcode(roomId: string): Promise<string> {
697 698 699 700 701 702 703 704
    log.verbose('PuppetPadchat', 'roomQrCode(%s)', roomId)

    // TODO

    throw new Error('not support')

  }

705 706 707 708 709 710 711 712 713 714 715 716 717
  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 已提交
718 719 720 721 722
  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
723

724 725 726 727
    if (!this.bridge) {
      throw new Error('no bridge')
    }

728 729 730 731 732 733 734 735 736 737 738 739
    // 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 已提交
740 741
  }

742 743 744
  public async roomTopic(roomId: string)                : Promise<string>
  public async roomTopic(roomId: string, topic: string) : Promise<void>

ruiruibupt's avatar
init  
ruiruibupt 已提交
745 746 747 748 749 750 751
  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 已提交
752
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
753
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
754
    }
ruiruibupt's avatar
ruiruibupt 已提交
755

756 757 758 759
    if (!this.bridge) {
      throw new Error('no bridge')
    }

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
762 763 764 765 766 767 768 769 770
    return
  }

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

771 772 773 774
    if (!this.bridge) {
      throw new Error('no bridge')
    }

775 776 777 778 779
    // FIXME:
    const roomId = this.bridge.WXCreateChatRoom(contactIdList)
    console.log('roomCreate returl:', roomId)

    return roomId
ruiruibupt's avatar
init  
ruiruibupt 已提交
780 781 782 783
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
784 785 786 787 788

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

ruiruibupt's avatar
ruiruibupt 已提交
789
    await this.bridge.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
790 791
  }

792 793 794 795 796
  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 : '')
797 798 799 800 801

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

802 803 804 805 806 807 808
    if (text) {
      await this.bridge.WXSetChatroomAnnouncement(roomId, text)
    } else {
      return await this.bridge.WXGetChatroomAnnouncement(roomId)
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
809 810 811 812 813 814 815 816 817 818
  /**
   *
   * FriendRequest
   *
   */
  public async friendRequestSend(
    contactId : string,
    hello     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'friendRequestSend(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
819 820 821

    const rawPayload = await this.contactRawPayload(contactId)

822 823 824 825 826 827 828 829 830 831 832 833
    // 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 已提交
834

835 836 837 838
    if (!this.bridge) {
      throw new Error('no bridge')
    }

839 840
    // Issue #1252 : what's wrong here?

ruiruibupt's avatar
ruiruibupt 已提交
841
    await this.bridge.WXAddUser(
842 843
      rawPayload.stranger || '',
      rawPayload.ticket   || '',
ruiruibupt's avatar
ruiruibupt 已提交
844 845 846
      '14',
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
847 848 849
  }

  public async friendRequestAccept(
850
    friendRequestId : string,
ruiruibupt's avatar
init  
ruiruibupt 已提交
851
  ): Promise<void> {
852
    log.verbose('PuppetPadchat', 'friendRequestAccept(%s)', friendRequestId)
ruiruibupt's avatar
ruiruibupt 已提交
853

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

856 857
    console.log('friendRequestAccept: ', payload)

858 859 860
    if (!payload.ticket) {
      throw new Error('no ticket')
    }
861 862 863
    if (!payload.stranger) {
      throw new Error('no stranger')
    }
ruiruibupt's avatar
ruiruibupt 已提交
864

865 866 867 868
    if (!this.bridge) {
      throw new Error('no bridge')
    }

869
    await this.bridge.WXAcceptUser(
870
      payload.stranger,
871 872
      payload.ticket,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
873 874
  }

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

878
    const payload: FriendRequestPayload = await pfHelper.friendRequestRawPayloadParser(rawPayload)
879
    return payload
ruiruibupt's avatar
ruiruibupt 已提交
880 881
  }

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

885 886
    const rawPayload = this.cachePadchatFriendRequestPayload.get(friendRequestId)
    if (!rawPayload) {
887
      throw new Error('no rawPayload for id ' + friendRequestId)
888
    }
ruiruibupt's avatar
ruiruibupt 已提交
889

890
    return rawPayload
ruiruibupt's avatar
ruiruibupt 已提交
891 892
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
893 894 895
}

export default PuppetPadchat