puppet-padchat.ts 34.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'
ruiruibupt's avatar
init  
ruiruibupt 已提交
21

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22 23
import LRU      from 'lru-cache'
import flatten  from 'array-flatten'
24

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

29 30
import Misc from '../misc'

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

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

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

  Puppet,
  PuppetOptions,
42

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

45 46
  FriendshipPayload,
  FriendshipPayloadReceive,
47
}                                 from '../puppet/'
ruiruibupt's avatar
init  
ruiruibupt 已提交
48

49
import {
50
  contactRawPayloadParser,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
51

52
  fileBoxToQrcode,
53
  // friendRequestEventMessageParser,
54
  friendshipRawPayloadParser,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
55 56 57 58

  isStrangerV1,
  isStrangerV2,

59
  messageRawPayloadParser,
60 61 62

  roomRawPayloadParser,

63 64 65
  roomJoinEventMessageParser,
  roomLeaveEventMessageParser,
  roomTopicEventMessageParser,
66 67 68 69

  friendshipConfirmEventMessageParser,
  friendshipReceiveEventMessageParser,
  friendshipVerifyEventMessageParser,
70
}                                         from './pure-function-helpers'
71

ruiruibupt's avatar
init  
ruiruibupt 已提交
72 73
import {
  log,
74 75
  qrCodeForChatie,
}                   from '../config'
ruiruibupt's avatar
init  
ruiruibupt 已提交
76

77
import {
78
  padchatToken,
79 80 81
  WECHATY_PUPPET_PADCHAT_ENDPOINT,
}                                   from './config'

ruiruibupt's avatar
init  
ruiruibupt 已提交
82
import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
83 84
  PadchatManager,
}                       from './padchat-manager'
ruiruibupt's avatar
init  
ruiruibupt 已提交
85 86

import {
87
  PadchatContactPayload,
88
  PadchatMessagePayload,
89
  PadchatRoomPayload,
90
  PadchatRoomMemberPayload,
91
  PadchatMessageType,
92
}                           from './padchat-schemas'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
93

E
Egg 已提交
94 95 96 97
import {
  WXSearchContactType,
  WXSearchContactTypeStatus,
}                           from './padchat-rpc.type'
ruiruibupt's avatar
init  
ruiruibupt 已提交
98 99

export class PuppetPadchat extends Puppet {
100

101
  // private readonly cachePadchatContactPayload       : LRU.Cache<string, PadchatContactRawPayload>
102
  // private readonly cachePadchatFriendshipPayload : LRU.Cache<string, PadchatMessagePayload>
103
  private readonly cachePadchatMessagePayload    : LRU.Cache<string, PadchatMessagePayload>
104
  // private readonly cachePadchatRoomPayload          : LRU.Cache<string, PadchatRoomRawPayload>
105

106
  public padchatManager?:  PadchatManager
ruiruibupt's avatar
init  
ruiruibupt 已提交
107 108 109 110

  constructor(
    public options: PuppetOptions,
  ) {
111 112 113 114
    super({
      timeout: 60 * 4,  // Default set timeout to 4 minutes
      ...options,
    })
ruiruibupt's avatar
init  
ruiruibupt 已提交
115

116 117 118
    const lruOptions: LRU.Options = {
      max: 1000,
      // length: function (n) { return n * 2},
119
      dispose: function (key: string, val: any) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
120
        log.silly('PuppetPadchat', 'constructor() lruOptions.dispose(%s, %s)', key, JSON.stringify(val))
121 122 123 124
      },
      maxAge: 1000 * 60 * 60,
    }

125
    // this.cachePadchatContactPayload       = new LRU<string, PadchatContactRawPayload>(lruOptions)
126
    // this.cachePadchatFriendshipPayload = new LRU<string, PadchatMessagePayload>(lruOptions)
127
    this.cachePadchatMessagePayload       = new LRU<string, PadchatMessagePayload>(lruOptions)
128
    // this.cachePadchatRoomPayload          = new LRU<string, PadchatRoomRawPayload>(lruOptions)
ruiruibupt's avatar
init  
ruiruibupt 已提交
129 130 131
  }

  public toString() {
132
    return `PuppetPadchat<${this.options.memory.name}>`
ruiruibupt's avatar
init  
ruiruibupt 已提交
133 134 135 136 137 138
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
139
  public startWatchdog(): void {
140
    log.verbose('PuppetPadchat', 'startWatchdog()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
141

142
    if (!this.padchatManager) {
143 144
      throw new Error('no bridge')
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
145

ruiruibupt's avatar
ruiruibupt 已提交
146 147 148
    // clean the dog because this could be re-inited
    this.watchdog.removeAllListeners()

149
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
150
     * Use bridge's heartbeat to feed dog
151
     */
152
    this.padchatManager.on('heartbeat', (data: string) => {
153 154 155 156 157
      log.silly('PuppetPadchat', 'startWatchdog() bridge.on(heartbeat)')
      this.watchdog.feed({
        data,
      })
    })
ruiruibupt's avatar
ruiruibupt 已提交
158
    this.watchdog.on('feed', async food => {
159
      log.silly('PuppetPadchat', 'startWatchdog() watchdog.on(feed, food={type=%s, data=%s})', food.type, food.data)
ruiruibupt's avatar
ruiruibupt 已提交
160 161
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
162
    this.watchdog.on('reset', async (food, timeout) => {
163 164 165 166 167
      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 (李卓桓) 已提交
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')

192
    const bridge = this.padchatManager = new PadchatManager({
193 194 195 196 197 198
      memory   : this.options.memory,
      token    : padchatToken(),
      endpoint : WECHATY_PUPPET_PADCHAT_ENDPOINT,
    })

    await this.startBridge(bridge)
ruiruibupt's avatar
ruiruibupt 已提交
199
    await this.startWatchdog()
ruiruibupt's avatar
ruiruibupt 已提交
200

ruiruibupt's avatar
ruiruibupt 已提交
201
    this.state.on(true)
202
    this.emit('start')
203
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
204

205
  protected async login(selfId: string): Promise<void> {
206
    if (!this.padchatManager) {
207 208
      throw new Error('no bridge')
    }
209
    await super.login(selfId)
210
    this.padchatManager.syncContactsAndRooms()
ruiruibupt's avatar
init  
ruiruibupt 已提交
211 212
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
213
  public async startBridge(bridge: PadchatManager): Promise<void> {
ruiruibupt's avatar
ruiruibupt 已提交
214
    log.verbose('PuppetPadchat', 'startBridge()')
ruiruibupt's avatar
ruiruibupt 已提交
215 216

    if (this.state.off()) {
217
      throw new Error('startBridge() state is off')
ruiruibupt's avatar
ruiruibupt 已提交
218 219
    }

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    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 已提交
242 243 244

  }

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
245
  protected async onPadchatMessage(rawPayload: PadchatMessagePayload): Promise<void> {
246 247 248 249 250
    log.verbose('PuppetPadchat', 'onPadchatMessage({id=%s, type=%s(%s)})',
                                rawPayload.msg_id,
                                PadchatMessageType[rawPayload.sub_type],
                                rawPayload.msg_type,
              )
251 252 253 254 255 256 257 258
    /**
     * 0. Discard messages when not logged in
     */
    if (!this.id) {
      log.warn('PuppetPadchat', 'onPadchatMessage({id=%s, type=%s(%s)}) discarded message because puppet is not logged-in')
      return
    }

ruiruibupt's avatar
ruiruibupt 已提交
259
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
260
     * 1. Sometimes will get duplicated same messages from rpc, drop the same message from here.
ruiruibupt's avatar
ruiruibupt 已提交
261 262
     */
    if (this.cachePadchatMessagePayload.has(rawPayload.msg_id)) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
263
      log.silly('PuppetPadchat', 'onPadchatMessage(id=%s) duplicate message: %s',
264 265 266
                                rawPayload.msg_id,
                                JSON.stringify(rawPayload).substr(0, 500),
              )
ruiruibupt's avatar
ruiruibupt 已提交
267 268 269
      return
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
270 271 272 273 274 275 276 277
    /**
     * 2. Save message for future usage
     */
    this.cachePadchatMessagePayload.set(
      rawPayload.msg_id,
      rawPayload,
    )

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278 279
    console.log('rawPayload:', rawPayload)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
280 281 282
    /**
     * 3. Check for Different Message Types
     */
283 284
    switch (rawPayload.sub_type) {
      case PadchatMessageType.VerifyMsg:
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
285
        this.emit('friendship', rawPayload.msg_id)
286 287
        break

288 289 290 291 292 293 294
      case PadchatMessageType.Recalled:
        /**
         * When someone joined the room invited by Bot,
         * the bot will receive a `recall-able` message for room event
         *
         * { content: '12740017638@chatroom:\n<sysmsg type="delchatroommember">\n\t<delchatroommember>\n\t\t<plain>
         *            <![CDATA[You invited 卓桓、Zhuohuan, 太阁_传话助手, 桔小秘 to the group chat.   ]]></plain>...,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
295
         *  sub_type: 10002}
296 297
         */
        await Promise.all([
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
298
          this.onPadchatMessageRoomEventJoin(rawPayload),
299 300
        ])
        break
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
301
      case PadchatMessageType.Sys:
302 303
        await Promise.all([
          this.onPadchatMessageFriendshipEvent(rawPayload),
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
304 305 306 307
          ////////////////////////////////////////////////
          this.onPadchatMessageRoomEventJoin(rawPayload),
          this.onPadchatMessageRoomEventLeave(rawPayload),
          this.onPadchatMessageRoomEventTopic(rawPayload),
308
        ])
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
309 310
        break

311 312 313 314 315 316
      case PadchatMessageType.App:
      case PadchatMessageType.Emoticon:
      case PadchatMessageType.Image:
      case PadchatMessageType.MicroVideo:
      case PadchatMessageType.Video:
      case PadchatMessageType.Voice:
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
317
        // TODO: the above types are filel type
318

319 320 321 322 323 324
      default:
        this.emit('message', rawPayload.msg_id)
        break
    }
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
325 326 327 328 329 330
  /**
   * Look for room join event
   */
  protected async onPadchatMessageRoomEventJoin(rawPayload: PadchatMessagePayload): Promise<void> {
    log.verbose('PuppetPadchat', 'onPadchatMessageRoomEventJoin({id=%s})', rawPayload.msg_id)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
331 332 333 334 335 336
    const roomJoinEvent = roomJoinEventMessageParser(rawPayload)

    if (roomJoinEvent) {
      const inviteeNameList = roomJoinEvent.inviteeNameList
      const inviterName     = roomJoinEvent.inviterName
      const roomId          = roomJoinEvent.roomId
337
      log.silly('PuppetPadchat', 'onPadchatMessageRoomEventJoin() roomJoinEvent="%s"', JSON.stringify(roomJoinEvent))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
338

339 340 341 342 343 344 345 346
      const inviteeIdList = await Misc.retry(async (retry, attempt) => {
        log.verbose('PuppetPadchat', 'onPadchatMessageRoomEvent({id=%s}) roomJoin retry(attempt=%d)', attempt)

        const tryIdList = flatten<string>(
          await Promise.all(
            inviteeNameList.map(
              inviteeName => this.roomMemberSearch(roomId, inviteeName),
            ),
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
347
          ),
348 349 350 351 352 353
        )

        if (tryIdList.length) {
          return tryIdList
        }

354
        if (!this.padchatManager) {
355 356 357 358
          throw new Error('no manager')
        }

        /**
359
         * Dirty Cache
360
         */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
361
        await this.roomMemberPayloadDirty(roomId)
362 363 364 365 366 367 368 369

        return retry(new Error('roomMemberSearch() not found'))

      }).catch(e => {
        log.warn('PuppetPadchat', 'onPadchatMessageRoomEvent({id=%s}) roomJoin retry() fail: %s', e.message)
        return [] as string[]
      })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
370
      const inviterIdList = await this.roomMemberSearch(roomId, inviterName)
371

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
372 373 374
      if (inviterIdList.length < 1) {
        throw new Error('no inviterId found')
      } else if (inviterIdList.length > 1) {
375
        log.warn('PuppetPadchat', 'onPadchatMessageRoomEvent() case PadchatMesssageSys: inviterId found more than 1, use the first one.')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
376
      }
377

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
378 379
      const inviterId = inviterIdList[0]

380
      this.emit('room-join', roomId, inviteeIdList,  inviterId)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
381
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
382 383 384 385 386 387 388 389
  }

  /**
   * Look for room leave event
   */
  protected async onPadchatMessageRoomEventLeave(rawPayload: PadchatMessagePayload): Promise<void> {
    log.verbose('PuppetPadchat', 'onPadchatMessageRoomEventLeave({id=%s})', rawPayload.msg_id)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
390 391 392 393 394 395
    const roomLeaveEvent = roomLeaveEventMessageParser(rawPayload)

    if (roomLeaveEvent) {
      const leaverNameList = roomLeaveEvent.leaverNameList
      const removerName    = roomLeaveEvent.removerName
      const roomId         = roomLeaveEvent.roomId
396
      log.silly('PuppetPadchat', 'onPadchatMessageRoomEventLeave() roomLeaveEvent="%s"', JSON.stringify(roomLeaveEvent))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

      const leaverIdList = flatten<string>(
        await Promise.all(
          leaverNameList.map(
            leaverName => this.roomMemberSearch(roomId, leaverName),
          ),
        ),
      )
      const removerIdList = await this.roomMemberSearch(roomId, removerName)
      if (removerIdList.length < 1) {
        throw new Error('no removerId found')
      } else if (removerIdList.length > 1) {
        log.warn('PuppetPadchat', 'onPadchatMessage() case PadchatMesssageSys: removerId found more than 1, use the first one.')
      }
      const removerId = removerIdList[0]

413 414 415 416 417
      if (!this.padchatManager) {
        throw new Error('no padchatManager')
      }

      /**
418
       * Dirty Cache
419
       */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
420 421
      await this.roomMemberPayloadDirty(roomId)
      await this.roomPayloadDirty(roomId)
422

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
423 424
      this.emit('room-leave',  roomId, leaverIdList, removerId)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
425 426 427 428 429 430 431 432
  }

  /**
   * Look for room topic event
   */
  protected async onPadchatMessageRoomEventTopic(rawPayload: PadchatMessagePayload): Promise<void> {
    log.verbose('PuppetPadchat', 'onPadchatMessageRoomEventTopic({id=%s})', rawPayload.msg_id)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
433 434 435 436 437 438
    const roomTopicEvent = roomTopicEventMessageParser(rawPayload)

    if (roomTopicEvent) {
      const changerName = roomTopicEvent.changerName
      const newTopic    = roomTopicEvent.topic
      const roomId      = roomTopicEvent.roomId
439
      log.silly('PuppetPadchat', 'onPadchatMessageRoomEventTopic() roomTopicEvent="%s"', JSON.stringify(roomTopicEvent))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
440

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
441 442
      const roomOldPayload = await this.roomPayload(roomId)
      const oldTopic       = roomOldPayload.topic
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
443 444 445 446 447 448 449 450 451

      const changerIdList = await this.roomMemberSearch(roomId, changerName)
      if (changerIdList.length < 1) {
        throw new Error('no changerId found')
      } else if (changerIdList.length > 1) {
        log.warn('PuppetPadchat', 'onPadchatMessage() case PadchatMesssageSys: changerId found more than 1, use the first one.')
      }
      const changerId = changerIdList[0]

452 453 454 455 456 457
      if (!this.padchatManager) {
        throw new Error('no padchatManager')
      }
      /**
       * Update Room Payload to new Topic
       */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
458
      await this.roomPayloadDirty(roomId)
459

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
460 461
      this.emit('room-topic',  roomId, newTopic, oldTopic, changerId)
    }
462 463 464
  }

  protected async onPadchatMessageFriendshipEvent(rawPayload: PadchatMessagePayload): Promise<void> {
465
    log.verbose('PuppetPadchat', 'onPadchatMessageFriendshipEvent({id=%s})', rawPayload.msg_id)
466
    /**
467
     * 1. Look for friendship confirm event
468
     */
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
    const friendshipConfirmContactId = friendshipConfirmEventMessageParser(rawPayload)
    /**
     * 2. Look for friendship receive event
     */
    const friendshipReceiveContactId = friendshipReceiveEventMessageParser(rawPayload)
    /**
     * 3. Look for friendship verify event
     */
    const friendshipVerifyContactId = friendshipVerifyEventMessageParser(rawPayload)

    if (   friendshipConfirmContactId
        || friendshipReceiveContactId
        || friendshipVerifyContactId
    ) {
      this.emit('friendship', rawPayload.msg_id)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
485 486
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
487
  public async stop(): Promise<void> {
488 489
    log.verbose('PuppetPadchat', 'stop()')

490
    if (!this.padchatManager) {
491 492
      throw new Error('no bridge')
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
493 494

    if (this.state.off()) {
495
      log.warn('PuppetPadchat', 'stop() is called on a OFF puppet. await ready(off) and return.')
ruiruibupt's avatar
init  
ruiruibupt 已提交
496 497 498 499 500
      await this.state.ready('off')
      return
    }

    this.state.off('pending')
ruiruibupt's avatar
ruiruibupt 已提交
501 502 503

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

505 506
    setImmediate(() => this.padchatManager && this.padchatManager.removeAllListeners())
    await this.padchatManager.stop()
ruiruibupt's avatar
ruiruibupt 已提交
507

ruiruibupt's avatar
init  
ruiruibupt 已提交
508 509
    // await some tasks...
    this.state.off(true)
510
    this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
511 512
  }

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

516
    if (!this.id) {
517 518 519 520 521
      log.warn('PuppetPadchat', 'logout() this.id not exist')
      // throw new Error('logout before login?')
      return
    }

522
    if (!this.padchatManager) {
523
      throw new Error('no bridge')
ruiruibupt's avatar
init  
ruiruibupt 已提交
524 525
    }

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

529 530 531 532
    // if (!passive) {
    //   await this.bridge.WXLogout()
    // }

533
    await this.padchatManager.logout()
ruiruibupt's avatar
init  
ruiruibupt 已提交
534 535 536 537 538 539 540 541 542 543 544 545 546 547
  }

  /**
   *
   * 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 已提交
548
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
549
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
550
    }
ruiruibupt's avatar
ruiruibupt 已提交
551

552
    if (!this.padchatManager) {
553 554 555
      throw new Error('no bridge')
    }

556
    await this.padchatManager.WXSetUserRemark(contactId, alias || '')
ruiruibupt's avatar
ruiruibupt 已提交
557

ruiruibupt's avatar
init  
ruiruibupt 已提交
558 559 560
    return
  }

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

564
    if (!this.padchatManager) {
565 566 567
      throw new Error('no bridge')
    }

568
    const contactIdList = this.padchatManager.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
569 570 571 572

    return contactIdList
  }

573 574
  public async contactAvatar(contactId: string)                : Promise<FileBox>
  public async contactAvatar(contactId: string, file: FileBox) : Promise<void>
ruiruibupt's avatar
init  
ruiruibupt 已提交
575

576
  public async contactAvatar(contactId: string, file?: FileBox): Promise<void | FileBox> {
577 578 579 580
    log.verbose('PuppetPadchat', 'contactAvatar(%s%s)',
                                  contactId,
                                  file ? (', ' + file.name) : '',
                )
581 582 583 584 585 586 587 588

    /**
     * 1. set avatar for user self
     */
    if (file) {
      if (contactId !== this.selfId()) {
        throw new Error('can not set avatar for others')
      }
589
      if (!this.padchatManager) {
590 591
        throw new Error('no bridge')
      }
592
      await this.padchatManager.WXSetHeadImage(await file.toBase64())
593 594 595 596 597 598
      return
    }

    /**
     * 2. get avatar
     */
ruiruibupt's avatar
ruiruibupt 已提交
599 600 601 602 603 604
    const payload = await this.contactPayload(contactId)

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

605 606 607 608
    const fileBox = FileBox.fromUrl(
      payload.avatar,
      `wechaty-contact-avatar-${payload.name}.jpg`,
    )
609 610 611
    return fileBox
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
612
  public async contactQrcode(contactId: string): Promise<string> {
613 614 615
    if (contactId !== this.selfId()) {
      throw new Error('can not set avatar for others')
    }
616
    if (!this.padchatManager) {
617 618
      throw new Error('no bridge')
    }
619
    const base64 = await this.padchatManager.WXGetUserQRCode(contactId, 0)
620
    const qrcode = await fileBoxToQrcode(base64)
621
    return qrcode
ruiruibupt's avatar
init  
ruiruibupt 已提交
622 623
  }

624 625 626 627 628 629 630 631 632 633
  public async contactPayloadDirty(contactId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'contactPayloadDirty(%s)', contactId)

    if (this.padchatManager) {
      this.padchatManager.contactRawPayloadDirty(contactId)
    }

    super.contactPayloadDirty(contactId)
  }

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

637
    if (!this.padchatManager) {
638 639
      throw new Error('no bridge')
    }
640
    const rawPayload = await this.padchatManager.contactRawPayload(contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
641 642 643
    return rawPayload
  }

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

647
    const payload: ContactPayload = contactRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
648 649 650 651 652 653 654 655
    return payload
  }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
657
  public async messageFile(messageId: string): Promise<FileBox> {
658
    log.warn('PuppetPadchat', 'messageFile(%s) not fully implemented yet, PR is welcome!', messageId)
ruiruibupt's avatar
ruiruibupt 已提交
659

ruiruibupt's avatar
ruiruibupt 已提交
660 661
    // TODO

662
    if (!this.padchatManager) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
663 664 665 666 667 668
      throw new Error('no bridge')
    }

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

669 670
    const rawText        = JSON.stringify(rawPayload)
    const attachmentName = payload.filename || payload.id
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
671 672 673 674 675 676 677 678

    let result

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

      case MessageType.Audio:
679
        result = await this.padchatManager.WXGetMsgVoice(rawText)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
680
        console.log(result)
681
        return FileBox.fromBase64(result.voice, `${attachmentName}.slk`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
682 683

      case MessageType.Emoticon:
684
        result = await this.padchatManager.WXGetMsgEmoticon(rawText)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
685
        console.log(result)
686
        return FileBox.fromBase64(result.image, `${attachmentName}.gif`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
687 688

      case MessageType.Image:
689
        result = await this.padchatManager.WXGetMsgImage(rawText)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
690
        console.log(result)
691
        return FileBox.fromBase64(result.image, `${attachmentName}.jpg`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
692 693

      case MessageType.Video:
694
        result = await this.padchatManager.WXGetMsgVideo(rawText)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
695
        console.log(result)
696
        return FileBox.fromBase64(result.video, `${attachmentName}.mp4`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
697 698 699 700 701

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

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

705
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
706 707 708 709 710 711 712
      base64,
      filename,
    )

    return file
  }

713 714 715 716 717 718 719 720 721 722
  public async messagePayloadDirty(messageId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'messagePayloadDirty(%s)', messageId)

    if (this.padchatManager) {
      // this.padchatManager.messageRawPayloadDirty(messageId)
    }

    super.messagePayloadDirty(messageId)
  }

723
  public async messageRawPayload(id: string): Promise<PadchatMessagePayload> {
724 725 726 727 728
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
729 730 731 732 733

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

735
    const rawPayload = this.cachePadchatMessagePayload.get(id)
736 737 738 739 740 741

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

    return rawPayload
ruiruibupt's avatar
init  
ruiruibupt 已提交
742 743
  }

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

747
    const payload: MessagePayload = messageRawPayloadParser(rawPayload)
ruiruibupt's avatar
ruiruibupt 已提交
748

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
749
    log.silly('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
750 751 752 753 754 755 756
    return payload
  }

  public async messageSendText(
    receiver : Receiver,
    text     : string,
  ): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
757
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', JSON.stringify(receiver), text)
758 759 760 761

    // roomId first, contactId second.
    const id = receiver.roomId || receiver.contactId

ruiruibupt's avatar
ruiruibupt 已提交
762
    if (!id) {
763
      throw Error('no id')
ruiruibupt's avatar
ruiruibupt 已提交
764
    }
765
    if (!this.padchatManager) {
766 767
      throw new Error('no bridge')
    }
768
    await this.padchatManager.WXSendMsg(id, text)
ruiruibupt's avatar
init  
ruiruibupt 已提交
769 770 771 772 773 774
  }

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
775
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), file)
ruiruibupt's avatar
ruiruibupt 已提交
776

777 778 779
    // roomId first, contactId second.
    const id = receiver.roomId || receiver.contactId

ruiruibupt's avatar
ruiruibupt 已提交
780 781 782 783
    if (!id) {
      throw new Error('no id!')
    }

784
    if (!this.padchatManager) {
785 786 787
      throw new Error('no bridge')
    }

788 789 790 791
    const type = file.mimeType || path.extname(file.name)
    switch (type) {
      case '.slk':
        // 发送语音消息(微信silk格式语音)
792
        await this.padchatManager.WXSendVoice(
793 794 795 796 797 798 799
          id,
          await file.toBase64(),
          60,
        )
        break

      default:
800
        await this.padchatManager.WXSendImage(
801 802 803 804 805
          id,
          await file.toBase64(),
        )
        break
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
806 807
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
808 809 810 811 812 813
  public async messageSendContact(
    receiver  : Receiver,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), contactId)

814
    if (!this.padchatManager) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
815 816 817
      throw new Error('no bridge')
    }

818 819 820
    // roomId first, contactId second.
    const id = receiver.roomId || receiver.contactId

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
821 822 823 824 825 826
    if (!id) {
      throw Error('no id')
    }

    const payload = await this.contactPayload(contactId)
    const title = payload.name + '名片'
827
    await this.padchatManager.WXShareCard(id, contactId, title)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
828 829
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
830 831 832 833 834
  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
835
                              JSON.stringify(receiver),
ruiruibupt's avatar
init  
ruiruibupt 已提交
836 837
                              messageId,
              )
838
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
839

840 841 842 843
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
844 845
      await this.messageSendText(
        receiver,
846
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
847 848 849 850
      )
    } else {
      await this.messageSendFile(
        receiver,
851
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
852 853
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
854 855 856 857 858 859 860
  }

  /**
   *
   * Room
   *
   */
861 862 863 864 865 866 867 868 869 870
  public async roomMemberPayloadDirty(roomId: string) {
    log.silly('PuppetPadchat', 'roomMemberRawPayloadDirty(%s)', roomId)

    if (this.padchatManager) {
      await this.padchatManager.roomMemberRawPayloadDirty(roomId)
    }

    super.roomMemberPayloadDirty(roomId)
  }

871 872 873 874 875
  public async roomMemberRawPayload(
    roomId    : string,
    contactId : string,
  ): Promise<PadchatRoomMemberPayload> {
    log.silly('PuppetPadchat', 'roomMemberRawPayload(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
876

877
    if (!this.padchatManager) {
878 879 880
      throw new Error('no bridge')
    }

881 882 883
    const memberDictRawPayload = await this.padchatManager.roomMemberRawPayload(roomId)

    return memberDictRawPayload[contactId]
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
  }

  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
  }

900 901 902 903 904 905 906 907 908 909
  public async roomPayloadDirty(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomPayloadDirty(%s)', roomId)

    if (this.padchatManager) {
      this.padchatManager.roomRawPayloadDirty(roomId)
    }

    super.roomPayloadDirty(roomId)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
910 911 912
  public async roomRawPayload(
    roomId: string,
  ): Promise<PadchatRoomPayload> {
913 914
    log.verbose('PuppetPadchat', 'roomRawPayload(%s)', roomId)

915
    if (!this.padchatManager) {
916 917 918
      throw new Error('no bridge')
    }

919
    const rawPayload = await this.padchatManager.roomRawPayload(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
920 921 922
    return rawPayload
  }

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

926
    const payload: RoomPayload = roomRawPayloadParser(rawPayload)
ruiruibupt's avatar
init  
ruiruibupt 已提交
927 928 929
    return payload
  }

930 931 932
  public async roomMemberList(roomId: string): Promise<string[]> {
    log.verbose('PuppetPadchat', 'roomMemberList(%s)', roomId)

933
    if (!this.padchatManager) {
934 935 936
      throw new Error('no bridge')
    }

937
    const memberIdList = await this.padchatManager.getRoomMemberIdList(roomId)
938 939 940 941 942
    log.silly('PuppetPadchat', 'roomMemberList()=%d', memberIdList.length)

    return memberIdList
  }

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

946
    if (!this.padchatManager) {
947 948 949
      throw new Error('no bridge')
    }

950
    const roomIdList = await this.padchatManager.getRoomIdList()
951
    log.silly('PuppetPadchat', 'roomList()=%d', roomIdList.length)
ruiruibupt's avatar
ruiruibupt 已提交
952

ruiruibupt's avatar
ruiruibupt 已提交
953
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
954 955 956 957 958 959 960
  }

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

962
    if (!this.padchatManager) {
963 964 965
      throw new Error('no bridge')
    }

966
    // Should check whether user is in the room. WXDeleteChatRoomMember won't check if user in the room automatically
967
    await this.padchatManager.WXDeleteChatRoomMember(roomId, contactId)
968
    await this.roomMemberPayloadDirty(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
969 970
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
971
  public async roomQrcode(roomId: string): Promise<string> {
972 973 974 975 976 977 978 979
    log.verbose('PuppetPadchat', 'roomQrCode(%s)', roomId)

    // TODO

    throw new Error('not support')

  }

980 981 982 983 984 985 986 987 988 989 990 991 992
  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 已提交
993 994 995 996 997
  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
998

999
    if (!this.padchatManager) {
1000 1001 1002
      throw new Error('no bridge')
    }

1003 1004 1005 1006
    // 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)
1007
      await this.padchatManager.WXAddChatRoomMember(roomId, contactId)
1008 1009 1010 1011 1012
    } 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)
1013
      await this.padchatManager.WXInviteChatRoomMember(roomId, contactId)
1014
    }
1015
    await this.roomMemberPayloadDirty(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
1016 1017
  }

1018 1019 1020
  public async roomTopic(roomId: string)                : Promise<string>
  public async roomTopic(roomId: string, topic: string) : Promise<void>

ruiruibupt's avatar
init  
ruiruibupt 已提交
1021 1022 1023 1024 1025 1026 1027
  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 已提交
1028
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
1029
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
1030
    }
ruiruibupt's avatar
ruiruibupt 已提交
1031

1032
    if (!this.padchatManager) {
1033 1034 1035
      throw new Error('no bridge')
    }

1036
    await this.padchatManager.WXSetChatroomName(roomId, topic)
1037
    await this.roomPayloadDirty(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
1038

ruiruibupt's avatar
init  
ruiruibupt 已提交
1039 1040 1041 1042 1043 1044 1045 1046 1047
    return
  }

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

1048
    if (!this.padchatManager) {
1049 1050 1051
      throw new Error('no bridge')
    }

1052
    const roomId = await this.padchatManager.WXCreateChatRoom(contactIdList)
1053

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1054 1055
    // Load new created room payload
    await this.roomPayload(roomId)
1056

1057
    return roomId
ruiruibupt's avatar
init  
ruiruibupt 已提交
1058 1059 1060 1061
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
1062

1063
    if (!this.padchatManager) {
1064 1065 1066
      throw new Error('no bridge')
    }

1067
    await this.padchatManager.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
1068 1069
  }

1070 1071 1072 1073 1074
  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 : '')
1075

1076
    if (!this.padchatManager) {
1077 1078 1079
      throw new Error('no bridge')
    }

1080
    if (text) {
1081
      await this.padchatManager.WXSetChatroomAnnouncement(roomId, text)
1082
    } else {
1083
      return await this.padchatManager.WXGetChatroomAnnouncement(roomId)
1084 1085 1086
    }
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
1087 1088
  /**
   *
1089
   * Friendship
ruiruibupt's avatar
init  
ruiruibupt 已提交
1090 1091
   *
   */
1092
  public async friendshipVerify(
ruiruibupt's avatar
init  
ruiruibupt 已提交
1093 1094 1095
    contactId : string,
    hello     : string,
  ): Promise<void> {
1096
    log.verbose('PuppetPadchat', 'friendshipVerify(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
1097

1098
    if (!this.padchatManager) {
E
Egg 已提交
1099 1100
      throw new Error('no bridge')
    }
ruiruibupt's avatar
ruiruibupt 已提交
1101

1102
    const rawSearchPayload: WXSearchContactType = await this.padchatManager.WXSearchContact(contactId)
1103

E
Egg 已提交
1104 1105 1106
    /**
     * If the contact is not stranger, than ussing WXSearchContact can get user_name
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1107
    if (rawSearchPayload.user_name !== '' && !isStrangerV1(rawSearchPayload.user_name) && !isStrangerV2(rawSearchPayload.user_name)) {
E
Egg 已提交
1108 1109 1110
      log.warn('PuppetPadchat', 'friendRequestSend %s has been friend with bot, no need to send friend request!', contactId)
      return
    }
ruiruibupt's avatar
ruiruibupt 已提交
1111

E
Egg 已提交
1112 1113
    let strangerV1
    let strangerV2
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1114
    if (isStrangerV1(rawSearchPayload.stranger)) {
E
Egg 已提交
1115 1116
      strangerV1 = rawSearchPayload.stranger
      strangerV2 = rawSearchPayload.user_name
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1117
    } else if (isStrangerV2(rawSearchPayload.stranger)) {
E
Egg 已提交
1118 1119 1120 1121
      strangerV2 = rawSearchPayload.stranger
      strangerV1 = rawSearchPayload.user_name
    } else {
      throw new Error('stranger neither v1 nor v2!')
1122 1123
    }

E
Egg 已提交
1124
    // Issue #1252 : what's wrong here?, Trying to fix now...
1125

1126
    await this.padchatManager.WXAddUser(
E
Egg 已提交
1127 1128 1129
      strangerV1 || '',
      strangerV2 || '',
      WXSearchContactTypeStatus.WXID, // default
ruiruibupt's avatar
ruiruibupt 已提交
1130 1131
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
1132 1133
  }

1134 1135
  public async friendshipAccept(
    friendshipId : string,
ruiruibupt's avatar
init  
ruiruibupt 已提交
1136
  ): Promise<void> {
1137
    log.verbose('PuppetPadchat', 'friendshipAccept(%s)', friendshipId)
ruiruibupt's avatar
ruiruibupt 已提交
1138

1139
    const payload = await this.friendshipPayload(friendshipId) as FriendshipPayloadReceive
ruiruibupt's avatar
ruiruibupt 已提交
1140

1141
    console.log('friendshipAccept: ', payload)
1142

1143 1144 1145
    if (!payload.ticket) {
      throw new Error('no ticket')
    }
1146 1147 1148
    if (!payload.stranger) {
      throw new Error('no stranger')
    }
ruiruibupt's avatar
ruiruibupt 已提交
1149

1150
    if (!this.padchatManager) {
1151 1152 1153
      throw new Error('no bridge')
    }

1154
    await this.padchatManager.WXAcceptUser(
1155
      payload.stranger,
1156 1157
      payload.ticket,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
1158 1159
  }

1160 1161
  public async friendshipRawPayloadParser(rawPayload: PadchatMessagePayload) : Promise<FriendshipPayload> {
    log.verbose('PuppetPadchat', 'friendshipRawPayloadParser({id=%s})', rawPayload.msg_id)
ruiruibupt's avatar
ruiruibupt 已提交
1162

1163
    const payload: FriendshipPayload = await friendshipRawPayloadParser(rawPayload)
1164
    return payload
ruiruibupt's avatar
ruiruibupt 已提交
1165 1166
  }

1167 1168 1169 1170 1171 1172 1173
  public async friendshipPayloadDirty(friendshipId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'friendshipPayloadDirty(%s)', friendshipId)

    if (this.padchatManager) {
      // this.padchatManager.friendshipRawPayloadDirty(friendshipId)
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1174
    await super.friendshipPayloadDirty(friendshipId)
1175 1176
  }

1177 1178
  public async friendshipRawPayload(friendshipId: string): Promise<PadchatMessagePayload> {
    log.verbose('PuppetPadchat', 'friendshipRawPayload(%s)', friendshipId)
ruiruibupt's avatar
ruiruibupt 已提交
1179

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1180 1181 1182
    /**
     * Friendship shares Cache with the Message RawPayload
     */
1183
    const rawPayload = this.cachePadchatMessagePayload.get(friendshipId)
1184
    if (!rawPayload) {
1185
      throw new Error('no rawPayload for id ' + friendshipId)
1186
    }
ruiruibupt's avatar
ruiruibupt 已提交
1187

1188
    return rawPayload
ruiruibupt's avatar
ruiruibupt 已提交
1189 1190
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
1191 1192 1193
}

export default PuppetPadchat