puppet-padchat.ts 24.0 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

  Puppet,
  PuppetOptions,
41

ruiruibupt's avatar
init  
ruiruibupt 已提交
42
  Receiver,
43

ruiruibupt's avatar
ruiruibupt 已提交
44
  FriendRequestPayload,
45
  FriendRequestPayloadReceive,
46 47

  WATCHDOG_TIMEOUT,
48
}                                 from '../puppet/'
ruiruibupt's avatar
init  
ruiruibupt 已提交
49

50
import {
51 52
  PadchatPureFunctionHelper as pfHelper,
}                                         from './pure-function-helper'
53

ruiruibupt's avatar
init  
ruiruibupt 已提交
54 55
import {
  log,
56 57
  qrCodeForChatie,
}                   from '../config'
ruiruibupt's avatar
init  
ruiruibupt 已提交
58

59
import {
60
  padchatToken,
61 62 63
  WECHATY_PUPPET_PADCHAT_ENDPOINT,
}                                   from './config'

ruiruibupt's avatar
init  
ruiruibupt 已提交
64 65
import {
  Bridge,
66
  // resolverDict,
ruiruibupt's avatar
ruiruibupt 已提交
67
  // AutoDataType,
ruiruibupt's avatar
init  
ruiruibupt 已提交
68 69 70
}                       from './bridge'

import {
71
  // PadchatPayload,
72
  PadchatContactPayload,
73
  PadchatMessagePayload,
74
  PadchatRoomPayload,
75 76
  // PadchatRoomMemberListPayload,
  PadchatRoomMemberPayload,
77
  PadchatMessageType,
78

79
  // PadchatMessageType,
80 81 82 83
  // PadchatContinue,
  // PadchatMsgType,
  // PadchatStatus,
  // PadchatPayloadType,
84
  // PadchatRoomRawMember,
85
}                           from './padchat-schemas'
ruiruibupt's avatar
init  
ruiruibupt 已提交
86 87 88 89 90

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

export class PuppetPadchat extends Puppet {
91

92 93 94
  // in seconds, 4 minute for padchat
  protected [WATCHDOG_TIMEOUT] = 4 * 60

95
  // private readonly cachePadchatContactPayload       : LRU.Cache<string, PadchatContactRawPayload>
96
  private readonly cachePadchatFriendRequestPayload : LRU.Cache<string, PadchatMessagePayload>
97 98
  private readonly cachePadchatMessagePayload       : LRU.Cache<string, PadchatMessagePayload>
  // private readonly cachePadchatRoomPayload          : LRU.Cache<string, PadchatRoomRawPayload>
99

100
  public bridge?:  Bridge
ruiruibupt's avatar
init  
ruiruibupt 已提交
101 102 103 104 105 106

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

107 108 109
    const lruOptions: LRU.Options = {
      max: 1000,
      // length: function (n) { return n * 2},
110
      dispose: function (key: string, val: any) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
111
        log.silly('PuppetPadchat', 'constructor() lruOptions.dispose(%s, %s)', key, JSON.stringify(val))
112 113 114 115
      },
      maxAge: 1000 * 60 * 60,
    }

116
    // this.cachePadchatContactPayload       = new LRU<string, PadchatContactRawPayload>(lruOptions)
117
    this.cachePadchatFriendRequestPayload = new LRU<string, PadchatMessagePayload>(lruOptions)
118
    this.cachePadchatMessagePayload       = new LRU<string, PadchatMessagePayload>(lruOptions)
119
    // this.cachePadchatRoomPayload          = new LRU<string, PadchatRoomRawPayload>(lruOptions)
ruiruibupt's avatar
init  
ruiruibupt 已提交
120 121 122
  }

  public toString() {
123
    return `PuppetPadchat<${this.options.memory.name}>`
ruiruibupt's avatar
init  
ruiruibupt 已提交
124 125 126 127 128 129
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
130
  public startWatchdog(): void {
ruiruibupt's avatar
ruiruibupt 已提交
131
    log.verbose('PuppetPadchat', 'initWatchdogForPuppet()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
132

133 134 135
    if (!this.bridge) {
      throw new Error('no bridge')
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
136

ruiruibupt's avatar
ruiruibupt 已提交
137 138 139
    // clean the dog because this could be re-inited
    this.watchdog.removeAllListeners()

140
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
141
     * Use bridge's heartbeat to feed dog
142 143 144 145 146 147 148
     */
    this.bridge.on('heartbeat', (data: string) => {
      log.silly('PuppetPadchat', 'startWatchdog() bridge.on(heartbeat)')
      this.watchdog.feed({
        data,
      })
    })
ruiruibupt's avatar
ruiruibupt 已提交
149
    this.watchdog.on('feed', async food => {
150
      log.silly('PuppetPadchat', 'startWatchdog() watchdog.on(feed, food={type=%s, data=%s})', food.type, food.data)
ruiruibupt's avatar
ruiruibupt 已提交
151 152
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
153
    this.watchdog.on('reset', async (food, timeout) => {
154 155 156 157 158
      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 (李卓桓) 已提交
159
    })
ruiruibupt's avatar
ruiruibupt 已提交
160 161 162 163 164

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

ruiruibupt's avatar
ruiruibupt 已提交
165
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
166 167

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

170 171 172 173 174 175
    if (this.state.on()) {
      log.warn('PuppetPadchat', 'start() already on(pending)?')
      await this.state.ready('on')
      return
    }

ruiruibupt's avatar
ruiruibupt 已提交
176 177 178 179 180 181 182
    /**
     * state has two main state: ON / OFF
     * ON (pending)
     * OFF (pending)
     */
    this.state.on('pending')

183 184 185 186 187 188 189
    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 已提交
190
    await this.startWatchdog()
ruiruibupt's avatar
ruiruibupt 已提交
191

ruiruibupt's avatar
ruiruibupt 已提交
192
    this.state.on(true)
193
    this.emit('start')
194
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
195

196
  protected async login(selfId: string): Promise<void> {
197 198 199
    if (!this.bridge) {
      throw new Error('no bridge')
    }
200 201
    await super.login(selfId)
    this.bridge.syncContactsAndRooms()
ruiruibupt's avatar
init  
ruiruibupt 已提交
202 203
  }

204
  public async startBridge(bridge: Bridge): Promise<void> {
ruiruibupt's avatar
ruiruibupt 已提交
205
    log.verbose('PuppetPadchat', 'startBridge()')
ruiruibupt's avatar
ruiruibupt 已提交
206 207

    if (this.state.off()) {
208
      throw new Error('startBridge() state is off')
ruiruibupt's avatar
ruiruibupt 已提交
209 210
    }

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    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 已提交
233 234 235

  }

236 237 238 239 240 241
  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 (李卓桓) 已提交
242 243
    console.log('rawPayload:', rawPayload)

244 245
    switch (rawPayload.sub_type) {
      case PadchatMessageType.VerifyMsg:
246 247 248 249
        this.cachePadchatFriendRequestPayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
250 251 252
        this.emit('friend', rawPayload.msg_id)
        break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
253 254 255 256 257 258 259 260
      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

261 262 263 264 265 266 267 268 269 270
      default:
        this.cachePadchatMessagePayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
        this.emit('message', rawPayload.msg_id)
        break
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
271
  public async stop(): Promise<void> {
272 273 274 275 276
    log.verbose('PuppetPadchat', 'stop()')

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

    if (this.state.off()) {
279
      log.warn('PuppetPadchat', 'stop() is called on a OFF puppet. await ready(off) and return.')
ruiruibupt's avatar
init  
ruiruibupt 已提交
280 281 282 283 284
      await this.state.ready('off')
      return
    }

    this.state.off('pending')
ruiruibupt's avatar
ruiruibupt 已提交
285 286 287

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

289
    setImmediate(() => this.bridge && this.bridge.removeAllListeners())
ruiruibupt's avatar
ruiruibupt 已提交
290
    await this.bridge.stop()
ruiruibupt's avatar
ruiruibupt 已提交
291

ruiruibupt's avatar
init  
ruiruibupt 已提交
292 293
    // await some tasks...
    this.state.off(true)
294
    this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
295 296
  }

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

300
    if (!this.id) {
301 302 303 304 305 306 307
      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 已提交
308 309
    }

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

313 314 315 316
    // if (!passive) {
    //   await this.bridge.WXLogout()
    // }

317
    await this.bridge.logout()
ruiruibupt's avatar
init  
ruiruibupt 已提交
318 319 320 321 322 323 324 325 326 327 328 329 330 331
  }

  /**
   *
   * 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 已提交
332
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
333
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
334
    }
ruiruibupt's avatar
ruiruibupt 已提交
335

336 337 338 339
    if (!this.bridge) {
      throw new Error('no bridge')
    }

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
342 343 344
    return
  }

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

348 349 350 351
    if (!this.bridge) {
      throw new Error('no bridge')
    }

ruiruibupt's avatar
ruiruibupt 已提交
352
    const contactIdList = this.bridge.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
353 354 355 356

    return contactIdList
  }

357 358
  public async contactAvatar(contactId: string)                : Promise<FileBox>
  public async contactAvatar(contactId: string, file: FileBox) : Promise<void>
ruiruibupt's avatar
init  
ruiruibupt 已提交
359

360 361 362 363 364 365 366 367 368 369
  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')
      }
370 371 372
      if (!this.bridge) {
        throw new Error('no bridge')
      }
373 374 375 376 377 378 379
      await this.bridge.WXSetHeadImage(await file.toBase64())
      return
    }

    /**
     * 2. get avatar
     */
ruiruibupt's avatar
ruiruibupt 已提交
380 381 382 383 384 385
    const payload = await this.contactPayload(contactId)

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

386 387 388 389
    const fileBox = FileBox.fromUrl(payload.avatar)
    return fileBox
  }

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

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

405 406 407
    if (!this.bridge) {
      throw new Error('no bridge')
    }
408
    const rawPayload = await this.bridge.contactRawPayload(contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
409 410 411
    return rawPayload
  }

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

415
    const payload: ContactPayload = pfHelper.contactRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
416 417 418 419 420 421 422 423
    return payload
  }

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

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

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

ruiruibupt's avatar
ruiruibupt 已提交
430 431
    // TODO

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
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 464 465 466 467 468 469 470
    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 已提交
471
    const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
472
    const filename = 'test-' + messageId + '.txt'
ruiruibupt's avatar
ruiruibupt 已提交
473

474
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
475 476 477 478 479 480 481
      base64,
      filename,
    )

    return file
  }

482
  public async messageRawPayload(id: string): Promise<PadchatMessagePayload> {
483 484 485 486 487
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
488 489 490 491 492

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

494
    const rawPayload = this.cachePadchatMessagePayload.get(id)
495 496 497 498 499 500

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

    return rawPayload
ruiruibupt's avatar
init  
ruiruibupt 已提交
501 502
  }

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
508
    log.silly('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
509 510 511 512 513 514 515
    return payload
  }

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

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
531
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), file)
ruiruibupt's avatar
ruiruibupt 已提交
532 533 534 535 536 537

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

538 539 540 541
    if (!this.bridge) {
      throw new Error('no bridge')
    }

542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
    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 已提交
560 561
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
  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 已提交
582 583 584 585 586
  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
587
                              JSON.stringify(receiver),
ruiruibupt's avatar
init  
ruiruibupt 已提交
588 589
                              messageId,
              )
590
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
591

592 593 594 595
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
596 597
      await this.messageSendText(
        receiver,
598
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
599 600 601 602
      )
    } else {
      await this.messageSendFile(
        receiver,
603
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
604 605
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
606 607 608 609 610 611 612
  }

  /**
   *
   * Room
   *
   */
613 614 615 616 617
  public async roomMemberRawPayload(
    roomId    : string,
    contactId : string,
  ): Promise<PadchatRoomMemberPayload> {
    log.silly('PuppetPadchat', 'roomMemberRawPayload(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
618

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

623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
    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)

644 645 646 647
    if (!this.bridge) {
      throw new Error('no bridge')
    }

648
    const rawPayload = await this.bridge.roomRawPayload(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
649 650 651
    return rawPayload
  }

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

655 656
    // const memberIdList = await this.bridge.getRoomMemberIdList()
    //  WXGetChatRoomMember(rawPayload.user_name)
657

658
    const payload: RoomPayload = pfHelper.roomRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
659 660 661 662

    return payload
  }

663 664 665
  public async roomMemberList(roomId: string): Promise<string[]> {
    log.verbose('PuppetPadchat', 'roomMemberList(%s)', roomId)

666 667 668 669
    if (!this.bridge) {
      throw new Error('no bridge')
    }

670 671 672 673 674 675
    const memberIdList = await this.bridge.getRoomMemberIdList(roomId)
    log.silly('PuppetPadchat', 'roomMemberList()=%d', memberIdList.length)

    return memberIdList
  }

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

679 680 681 682
    if (!this.bridge) {
      throw new Error('no bridge')
    }

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

ruiruibupt's avatar
ruiruibupt 已提交
686
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
687 688 689 690 691 692 693
  }

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

695 696 697 698
    if (!this.bridge) {
      throw new Error('no bridge')
    }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
703
  public async roomQrcode(roomId: string): Promise<string> {
704 705 706 707 708 709 710 711
    log.verbose('PuppetPadchat', 'roomQrCode(%s)', roomId)

    // TODO

    throw new Error('not support')

  }

712 713 714 715 716 717 718 719 720 721 722 723 724
  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 已提交
725 726 727 728 729
  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
730

731 732 733 734
    if (!this.bridge) {
      throw new Error('no bridge')
    }

735 736 737 738 739 740 741 742 743 744 745 746
    // 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 已提交
747 748
  }

749 750 751
  public async roomTopic(roomId: string)                : Promise<string>
  public async roomTopic(roomId: string, topic: string) : Promise<void>

ruiruibupt's avatar
init  
ruiruibupt 已提交
752 753 754 755 756 757 758
  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 已提交
759
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
760
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
761
    }
ruiruibupt's avatar
ruiruibupt 已提交
762

763 764 765 766
    if (!this.bridge) {
      throw new Error('no bridge')
    }

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
769 770 771 772 773 774 775 776 777
    return
  }

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

778 779 780 781
    if (!this.bridge) {
      throw new Error('no bridge')
    }

782 783 784 785 786
    // FIXME:
    const roomId = this.bridge.WXCreateChatRoom(contactIdList)
    console.log('roomCreate returl:', roomId)

    return roomId
ruiruibupt's avatar
init  
ruiruibupt 已提交
787 788 789 790
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
791 792 793 794 795

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

ruiruibupt's avatar
ruiruibupt 已提交
796
    await this.bridge.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
797 798
  }

799 800 801 802 803
  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 : '')
804 805 806 807 808

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

809 810 811 812 813 814 815
    if (text) {
      await this.bridge.WXSetChatroomAnnouncement(roomId, text)
    } else {
      return await this.bridge.WXGetChatroomAnnouncement(roomId)
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
816 817 818 819 820 821 822 823 824 825
  /**
   *
   * FriendRequest
   *
   */
  public async friendRequestSend(
    contactId : string,
    hello     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'friendRequestSend(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
826 827 828

    const rawPayload = await this.contactRawPayload(contactId)

829 830 831 832 833 834 835 836 837 838 839 840
    // 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 已提交
841

842 843 844 845
    if (!this.bridge) {
      throw new Error('no bridge')
    }

846 847
    // Issue #1252 : what's wrong here?

ruiruibupt's avatar
ruiruibupt 已提交
848
    await this.bridge.WXAddUser(
849 850
      rawPayload.stranger || '',
      rawPayload.ticket   || '',
ruiruibupt's avatar
ruiruibupt 已提交
851 852 853
      '14',
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
854 855 856
  }

  public async friendRequestAccept(
857
    friendRequestId : string,
ruiruibupt's avatar
init  
ruiruibupt 已提交
858
  ): Promise<void> {
859
    log.verbose('PuppetPadchat', 'friendRequestAccept(%s)', friendRequestId)
ruiruibupt's avatar
ruiruibupt 已提交
860

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

863 864
    console.log('friendRequestAccept: ', payload)

865 866 867
    if (!payload.ticket) {
      throw new Error('no ticket')
    }
868 869 870
    if (!payload.stranger) {
      throw new Error('no stranger')
    }
ruiruibupt's avatar
ruiruibupt 已提交
871

872 873 874 875
    if (!this.bridge) {
      throw new Error('no bridge')
    }

876
    await this.bridge.WXAcceptUser(
877
      payload.stranger,
878 879
      payload.ticket,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
880 881
  }

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

885
    const payload: FriendRequestPayload = await pfHelper.friendRequestRawPayloadParser(rawPayload)
886
    return payload
ruiruibupt's avatar
ruiruibupt 已提交
887 888
  }

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

892 893
    const rawPayload = this.cachePadchatFriendRequestPayload.get(friendRequestId)
    if (!rawPayload) {
894
      throw new Error('no rawPayload for id ' + friendRequestId)
895
    }
ruiruibupt's avatar
ruiruibupt 已提交
896

897
    return rawPayload
ruiruibupt's avatar
ruiruibupt 已提交
898 899
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
900 901 902
}

export default PuppetPadchat