puppet-padchat.ts 20.4 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 readonly 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)
113

ruiruibupt's avatar
ruiruibupt 已提交
114
    this.bridge = new Bridge({
ruiruibupt's avatar
ruiruibupt 已提交
115
      memory   : this.options.memory,
116
      token    : padchatToken(),
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
117
      endpoint : WECHATY_PUPPET_PADCHAT_ENDPOINT,
ruiruibupt's avatar
ruiruibupt 已提交
118
    })
ruiruibupt's avatar
init  
ruiruibupt 已提交
119 120 121
  }

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

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

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

ruiruibupt's avatar
ruiruibupt 已提交
132
    const puppet = this
ruiruibupt's avatar
init  
ruiruibupt 已提交
133

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

    puppet.on('watchdog', food => this.watchdog.feed(food))
    this.watchdog.on('feed', async food => {
      log.silly('PuppetPadchat', 'initWatchdogForPuppet() dog.on(feed, food={type=%s, data=%s})', food.type, food.data)
      // feed the dog, heartbeat the puppet.
141
      // puppet.emit('heartbeat', food.data)
ruiruibupt's avatar
ruiruibupt 已提交
142

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155
      // const feedAfterTenSeconds = async () => {
      //   this.bridge.WXHeartBeat()
      //   .then(() => {
      //     this.emit('watchdog', {
      //       data: 'WXHeartBeat()',
      //     })
      //   })
      //   .catch(e => {
      //     log.warn('PuppetPadchat', 'initWatchdogForPuppet() feedAfterTenSeconds rejected: %s', e && e.message || '')
      //   })
      // }

      // setTimeout(feedAfterTenSeconds, 15 * 1000)
ruiruibupt's avatar
init  
ruiruibupt 已提交
156

ruiruibupt's avatar
ruiruibupt 已提交
157 158
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
159 160 161
    this.watchdog.on('reset', async (food, timeout) => {
      log.warn('PuppetPadchat', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s',
                            food.data, timeout)
162 163 164 165 166 167
    //   try {
    //     await this.stop()
    //     await this.start()
    //   } catch (e) {
    //     puppet.emit('error', e)
    //   }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
168
    })
ruiruibupt's avatar
ruiruibupt 已提交
169 170 171 172 173

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

ruiruibupt's avatar
ruiruibupt 已提交
174
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
175 176

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

179 180 181 182 183 184
    if (this.state.on()) {
      log.warn('PuppetPadchat', 'start() already on(pending)?')
      await this.state.ready('on')
      return
    }

ruiruibupt's avatar
ruiruibupt 已提交
185 186 187 188 189 190 191
    /**
     * state has two main state: ON / OFF
     * ON (pending)
     * OFF (pending)
     */
    this.state.on('pending')

ruiruibupt's avatar
ruiruibupt 已提交
192 193
    await this.startBridge()
    await this.startWatchdog()
ruiruibupt's avatar
ruiruibupt 已提交
194

ruiruibupt's avatar
ruiruibupt 已提交
195
    this.state.on(true)
196
    this.emit('start')
197
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
198

199 200 201
  protected async login(selfId: string): Promise<void> {
    await super.login(selfId)
    this.bridge.syncContactsAndRooms()
ruiruibupt's avatar
init  
ruiruibupt 已提交
202 203
  }

ruiruibupt's avatar
ruiruibupt 已提交
204 205
  public async startBridge(): Promise<void> {
    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
    }

ruiruibupt's avatar
ruiruibupt 已提交
211
    this.bridge.removeAllListeners()
ruiruibupt's avatar
init  
ruiruibupt 已提交
212 213 214
    // this.bridge.on('ding'     , Event.onDing.bind(this))
    // this.bridge.on('error'    , e => this.emit('error', e))
    // this.bridge.on('log'      , Event.onLog.bind(this))
215
    this.bridge.on('scan',    (qrcode: string, status: number, data?: string) => this.emit('scan', qrcode, status, data))
216 217 218
    this.bridge.on('login',   (userId: string)                                => this.login(userId))
    this.bridge.on('message', (rawPayload: PadchatMessagePayload)             => this.onPadchatMessage(rawPayload))
    this.bridge.on('logout',  ()                                              => this.logout())
ruiruibupt's avatar
init  
ruiruibupt 已提交
219

ruiruibupt's avatar
ruiruibupt 已提交
220
    await this.bridge.start()
ruiruibupt's avatar
init  
ruiruibupt 已提交
221 222
  }

223 224 225 226 227 228 229 230
  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:
231 232 233 234
        this.cachePadchatFriendRequestPayload.set(
          rawPayload.msg_id,
          rawPayload,
        )
235 236 237 238 239 240 241 242 243 244 245 246 247
        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 已提交
248 249 250 251
  public async stop(): Promise<void> {
    log.verbose('PuppetPadchat', 'quit()')

    if (this.state.off()) {
252
      log.warn('PuppetPadchat', 'stop() is called on a OFF puppet. await ready(off) and return.')
ruiruibupt's avatar
init  
ruiruibupt 已提交
253 254 255 256 257
      await this.state.ready('off')
      return
    }

    this.state.off('pending')
ruiruibupt's avatar
ruiruibupt 已提交
258 259 260

    this.watchdog.sleep()
    await this.logout()
ruiruibupt's avatar
ruiruibupt 已提交
261 262 263

    setImmediate(() => this.bridge.removeAllListeners())
    await this.bridge.stop()
ruiruibupt's avatar
ruiruibupt 已提交
264

ruiruibupt's avatar
init  
ruiruibupt 已提交
265 266
    // await some tasks...
    this.state.off(true)
267
    this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
268 269
  }

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

273
    if (!this.id) {
ruiruibupt's avatar
init  
ruiruibupt 已提交
274 275 276
      throw new Error('logout before login?')
    }

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

280 281 282 283
    // if (!passive) {
    //   await this.bridge.WXLogout()
    // }

284
    await this.bridge.logout()
ruiruibupt's avatar
init  
ruiruibupt 已提交
285 286 287 288 289 290 291 292 293 294 295 296 297 298
  }

  /**
   *
   * 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 已提交
299
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
300
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
301
    }
ruiruibupt's avatar
ruiruibupt 已提交
302 303 304

    await this.bridge.WXSetUserRemark(contactId, alias || '')

ruiruibupt's avatar
init  
ruiruibupt 已提交
305 306 307
    return
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
311
    const contactIdList = this.bridge.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
312 313 314 315

    return contactIdList
  }

316 317
  public async contactAvatar(contactId: string)                : Promise<FileBox>
  public async contactAvatar(contactId: string, file: FileBox) : Promise<void>
ruiruibupt's avatar
init  
ruiruibupt 已提交
318

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
  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')
      }
      await this.bridge.WXSetHeadImage(await file.toBase64())
      return
    }

    /**
     * 2. get avatar
     */
ruiruibupt's avatar
ruiruibupt 已提交
336 337 338 339 340 341
    const payload = await this.contactPayload(contactId)

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

342 343 344 345
    const fileBox = FileBox.fromUrl(payload.avatar)
    return fileBox
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
346
  public async contactQrcode(contactId: string): Promise<string> {
347 348 349 350
    if (contactId !== this.selfId()) {
      throw new Error('can not set avatar for others')
    }

351
    const base64 = await this.bridge.WXGetUserQRCode(contactId, 0)
352
    const qrcode = await pfHelper.imageBase64ToQrcode(base64)
353
    return qrcode
ruiruibupt's avatar
init  
ruiruibupt 已提交
354 355
  }

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

359
    const rawPayload = await this.bridge.contactRawPayload(contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
360 361 362
    return rawPayload
  }

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

366
    const payload: ContactPayload = pfHelper.contactRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
367 368 369 370 371 372 373 374
    return payload
  }

  /**
   *
   * Message
   *
   */
ruiruibupt's avatar
ruiruibupt 已提交
375 376

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

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

ruiruibupt's avatar
ruiruibupt 已提交
381 382
    // TODO

ruiruibupt's avatar
ruiruibupt 已提交
383
    const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
384
    const filename = 'test-' + id + '.txt'
ruiruibupt's avatar
ruiruibupt 已提交
385

386
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
387 388 389 390 391 392 393
      base64,
      filename,
    )

    return file
  }

394
  public async messageRawPayload(id: string): Promise<PadchatMessagePayload> {
395 396 397 398 399
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
400 401 402 403 404

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

406
    const rawPayload = this.cachePadchatMessagePayload.get(id)
407 408 409 410 411 412

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

    return rawPayload
ruiruibupt's avatar
init  
ruiruibupt 已提交
413 414
  }

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
420
    log.silly('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
421 422 423 424 425 426 427 428
    return payload
  }

  public async messageSendText(
    receiver : Receiver,
    text     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', receiver, text)
ruiruibupt's avatar
ruiruibupt 已提交
429 430
    const id = receiver.contactId || receiver.roomId
    if (!id) {
431
      throw Error('no id')
ruiruibupt's avatar
ruiruibupt 已提交
432 433
    }
    await this.bridge.WXSendMsg(id, text)
ruiruibupt's avatar
init  
ruiruibupt 已提交
434 435 436 437 438 439
  }

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
440
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), file)
ruiruibupt's avatar
ruiruibupt 已提交
441 442 443 444 445 446

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

447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
    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 已提交
465 466 467 468 469 470 471
  }

  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
472
                              JSON.stringify(receiver),
ruiruibupt's avatar
init  
ruiruibupt 已提交
473 474
                              messageId,
              )
475
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
476

477 478 479 480
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
481 482
      await this.messageSendText(
        receiver,
483
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
484 485 486 487
      )
    } else {
      await this.messageSendFile(
        receiver,
488
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
489 490
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
491 492 493 494 495 496 497
  }

  /**
   *
   * Room
   *
   */
498 499 500 501 502
  public async roomMemberRawPayload(
    roomId    : string,
    contactId : string,
  ): Promise<PadchatRoomMemberPayload> {
    log.silly('PuppetPadchat', 'roomMemberRawPayload(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
503

504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
    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)

    const rawPayload = await this.bridge.roomRawPayload(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
526 527 528
    return rawPayload
  }

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

532 533
    // const memberIdList = await this.bridge.getRoomMemberIdList()
    //  WXGetChatRoomMember(rawPayload.user_name)
534

535
    const payload: RoomPayload = pfHelper.roomRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
536 537 538 539

    return payload
  }

540 541 542 543 544 545 546 547 548
  public async roomMemberList(roomId: string): Promise<string[]> {
    log.verbose('PuppetPadchat', 'roomMemberList(%s)', roomId)

    const memberIdList = await this.bridge.getRoomMemberIdList(roomId)
    log.silly('PuppetPadchat', 'roomMemberList()=%d', memberIdList.length)

    return memberIdList
  }

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

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

ruiruibupt's avatar
ruiruibupt 已提交
555
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
556 557 558 559 560 561 562
  }

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
568
  public async roomQrcode(roomId: string): Promise<string> {
569 570 571 572 573 574 575 576
    log.verbose('PuppetPadchat', 'roomQrCode(%s)', roomId)

    // TODO

    throw new Error('not support')

  }

577 578 579 580 581 582 583 584 585 586 587 588 589
  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 已提交
590 591 592 593 594
  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
595 596 597 598 599 600 601 602 603 604 605 606 607 608

    // 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 已提交
609 610
  }

611 612 613
  public async roomTopic(roomId: string)                : Promise<string>
  public async roomTopic(roomId: string, topic: string) : Promise<void>

ruiruibupt's avatar
init  
ruiruibupt 已提交
614 615 616 617 618 619 620
  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 已提交
621
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
622
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
623
    }
ruiruibupt's avatar
ruiruibupt 已提交
624 625 626

    await this.bridge.WXSetChatroomName(roomId, topic)

ruiruibupt's avatar
init  
ruiruibupt 已提交
627 628 629 630 631 632 633 634 635
    return
  }

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

636 637 638 639 640
    // FIXME:
    const roomId = this.bridge.WXCreateChatRoom(contactIdList)
    console.log('roomCreate returl:', roomId)

    return roomId
ruiruibupt's avatar
init  
ruiruibupt 已提交
641 642 643 644
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
645
    await this.bridge.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
646 647
  }

648 649 650 651 652 653 654 655 656 657 658 659
  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 : '')
    if (text) {
      await this.bridge.WXSetChatroomAnnouncement(roomId, text)
    } else {
      return await this.bridge.WXGetChatroomAnnouncement(roomId)
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
660 661 662 663 664 665 666 667 668 669
  /**
   *
   * FriendRequest
   *
   */
  public async friendRequestSend(
    contactId : string,
    hello     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'friendRequestSend(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
670 671 672

    const rawPayload = await this.contactRawPayload(contactId)

673 674 675 676 677 678 679 680 681 682 683 684
    // 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 已提交
685

686 687
    // Issue #1252 : what's wrong here?

ruiruibupt's avatar
ruiruibupt 已提交
688
    await this.bridge.WXAddUser(
689 690
      rawPayload.stranger || '',
      rawPayload.ticket   || '',
ruiruibupt's avatar
ruiruibupt 已提交
691 692 693
      '14',
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
694 695 696
  }

  public async friendRequestAccept(
697
    friendRequestId : string,
ruiruibupt's avatar
init  
ruiruibupt 已提交
698
  ): Promise<void> {
699
    log.verbose('PuppetPadchat', 'friendRequestAccept(%s)', friendRequestId)
ruiruibupt's avatar
ruiruibupt 已提交
700

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

703 704
    console.log('friendRequestAccept: ', payload)

705 706 707
    if (!payload.ticket) {
      throw new Error('no ticket')
    }
708 709 710
    if (!payload.stranger) {
      throw new Error('no stranger')
    }
ruiruibupt's avatar
ruiruibupt 已提交
711

712
    await this.bridge.WXAcceptUser(
713
      payload.stranger,
714 715
      payload.ticket,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
716 717
  }

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

721
    const payload: FriendRequestPayload = await pfHelper.friendRequestRawPayloadParser(rawPayload)
722
    return payload
ruiruibupt's avatar
ruiruibupt 已提交
723 724
  }

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

728 729
    const rawPayload = this.cachePadchatFriendRequestPayload.get(friendRequestId)
    if (!rawPayload) {
730
      throw new Error('no rawPayload for id ' + friendRequestId)
731
    }
ruiruibupt's avatar
ruiruibupt 已提交
732

733
    return rawPayload
ruiruibupt's avatar
ruiruibupt 已提交
734 735
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
736 737 738
}

export default PuppetPadchat