puppet-padchat.ts 22.9 KB
Newer Older
ruiruibupt's avatar
init  
ruiruibupt 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/**
 *   Wechaty - https://github.com/chatie/wechaty
 *
 *   @copyright 2016-2018 Huan LI <zixia@zixia.net>
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

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

24 25
import * as LRU from 'lru-cache'

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

import {
31 32
  MessagePayload,
  MessageType,
ruiruibupt's avatar
init  
ruiruibupt 已提交
33

ruiruibupt's avatar
ruiruibupt 已提交
34
  // ContactQueryFilter,
35
  // ContactGender,
ruiruibupt's avatar
init  
ruiruibupt 已提交
36 37 38 39
  ContactType,
  ContactPayload,

  RoomPayload,
40 41
  // RoomQueryFilter,
  // RoomMemberQueryFilter,
ruiruibupt's avatar
init  
ruiruibupt 已提交
42 43 44 45

  Puppet,
  PuppetOptions,
  Receiver,
ruiruibupt's avatar
ruiruibupt 已提交
46
  FriendRequestPayload,
ruiruibupt's avatar
init  
ruiruibupt 已提交
47 48
}                       from '../puppet/'

49 50 51 52 53 54
import {
  isContactOfficialId,
  isRoomId,
}                       from './misc'

// import Misc           from '../misc'
55

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

61 62 63 64 65
import {
  WECHATY_PUPPET_PADCHAT_TOKEN,
  WECHATY_PUPPET_PADCHAT_ENDPOINT,
}                                   from './config'

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

import {
73 74
  // PadchatPayload,
  PadchatContactRawPayload,
75
  PadchatMessagePayload,
76
  PadchatRoomRawPayload,
77

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

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

export class PuppetPadchat extends Puppet {
90

91
  public readonly cachePadchatContactPayload       : LRU.Cache<string, PadchatContactRawPayload>
92
  // public readonly cachePadchatFriendRequestRawPayload : LRU.Cache<string, FriendRequestRawPayload>
93
  public readonly cachePadchatMessagePayload       : LRU.Cache<string, PadchatMessagePayload>
94
  public readonly cachePadchatRoomPayload          : LRU.Cache<string, PadchatRoomRawPayload>
95

ruiruibupt's avatar
ruiruibupt 已提交
96
  public bridge:  Bridge
ruiruibupt's avatar
ruiruibupt 已提交
97
  // public botWs:   WebSocket
ruiruibupt's avatar
init  
ruiruibupt 已提交
98 99 100 101 102 103

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

104 105 106
    const lruOptions: LRU.Options = {
      max: 1000,
      // length: function (n) { return n * 2},
107
      dispose: function (key: string, val: any) {
108 109 110 111 112
        log.silly('Puppet', 'constructor() lruOptions.dispose(%s, %s)', key, JSON.stringify(val))
      },
      maxAge: 1000 * 60 * 60,
    }

113
    this.cachePadchatContactPayload       = new LRU<string, PadchatContactRawPayload>(lruOptions)
114
    // this.cacheFriendRequestPayload = new LRU<string, FriendRequestPayload>(lruOptions)
115
    this.cachePadchatMessagePayload       = new LRU<string, PadchatMessagePayload>(lruOptions)
116
    this.cachePadchatRoomPayload          = new LRU<string, PadchatRoomRawPayload>(lruOptions)
117

ruiruibupt's avatar
ruiruibupt 已提交
118
    this.bridge = new Bridge({
ruiruibupt's avatar
ruiruibupt 已提交
119
      memory   : this.options.memory,
120 121
      token   : WECHATY_PUPPET_PADCHAT_TOKEN,
      endpoint: WECHATY_PUPPET_PADCHAT_ENDPOINT,
ruiruibupt's avatar
ruiruibupt 已提交
122 123 124
      autoData : {},
      // profile:  profile, // should be profile in the future
    })
ruiruibupt's avatar
init  
ruiruibupt 已提交
125 126 127
  }

  public toString() {
128
    return `PuppetPadchat<${this.options.memory.name}>`
ruiruibupt's avatar
init  
ruiruibupt 已提交
129 130 131 132 133 134
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
135
  public startWatchdog(): void {
ruiruibupt's avatar
ruiruibupt 已提交
136
    log.verbose('PuppetPadchat', 'initWatchdogForPuppet()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
137

ruiruibupt's avatar
ruiruibupt 已提交
138
    const puppet = this
ruiruibupt's avatar
init  
ruiruibupt 已提交
139

ruiruibupt's avatar
ruiruibupt 已提交
140 141 142 143 144 145 146
    // 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.
147
      // puppet.emit('heartbeat', food.data)
ruiruibupt's avatar
ruiruibupt 已提交
148 149 150 151 152 153 154 155 156 157 158 159

      const feedAfterTenSeconds = async () => {
        this.bridge.WXHeartBeat()
        .then(() => {
          this.emit('watchdog', {
            data: 'WXHeartBeat()',
          })
        })
        .catch(e => {
          log.warn('PuppetPadchat', 'initWatchdogForPuppet() feedAfterTenSeconds rejected: %s', e && e.message || '')
        })
      }
ruiruibupt's avatar
init  
ruiruibupt 已提交
160

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

ruiruibupt's avatar
ruiruibupt 已提交
163 164
    })

165 166 167 168 169 170 171 172 173 174
    // this.watchdog.on('reset', async (food, timeout) => {
    //   log.warn('PuppetPadchat', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s',
    //                         food.data, timeout)
    //   try {
    //     await this.stop()
    //     await this.start()
    //   } catch (e) {
    //     puppet.emit('error', e)
    //   }
    // })
ruiruibupt's avatar
ruiruibupt 已提交
175 176 177 178 179

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

ruiruibupt's avatar
ruiruibupt 已提交
180
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
181 182

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

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 196
    this.state.on(true)
    // this.emit('start')
ruiruibupt's avatar
init  
ruiruibupt 已提交
197 198 199

  }

ruiruibupt's avatar
ruiruibupt 已提交
200 201
  public async startBridge(): Promise<void> {
    log.verbose('PuppetPadchat', 'startBridge()')
ruiruibupt's avatar
ruiruibupt 已提交
202 203

    if (this.state.off()) {
204
      const e = new Error('startBridge() state is off')
ruiruibupt's avatar
ruiruibupt 已提交
205 206 207 208
      log.warn('PuppetPadchat', e.message)
      throw e
    }

ruiruibupt's avatar
ruiruibupt 已提交
209
    this.bridge.removeAllListeners()
ruiruibupt's avatar
init  
ruiruibupt 已提交
210 211 212
    // 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))
213
    this.bridge.on('login', (userId: string) => {
ruiruibupt's avatar
ruiruibupt 已提交
214
      this.bridge.syncContactsAndRooms()
215
      this.login(userId)
ruiruibupt's avatar
ruiruibupt 已提交
216
    })
217 218
    this.bridge.on('logout', () => {
      this.logout()
219
    })
220 221 222 223 224 225 226 227
    this.bridge.on('message', (messagePayload: PadchatMessagePayload) => {
      this.cachePadchatMessagePayload.set(
        messagePayload.msg_id,
        messagePayload,
      )
      this.emit('message', messagePayload.msg_id)
    })
    this.bridge.on('scan', (qrCode: string, statusCode: number, data?: string) => {
ruiruibupt's avatar
ruiruibupt 已提交
228 229
      this.emit('scan', qrCode, statusCode, data)
    })
ruiruibupt's avatar
init  
ruiruibupt 已提交
230

ruiruibupt's avatar
ruiruibupt 已提交
231
    await this.bridge.start()
ruiruibupt's avatar
init  
ruiruibupt 已提交
232 233 234 235 236 237 238 239 240 241 242 243
  }

  public async stop(): Promise<void> {
    log.verbose('PuppetPadchat', 'quit()')

    if (this.state.off()) {
      log.warn('PuppetPadchat', 'quit() is called on a OFF puppet. await ready(off) and return.')
      await this.state.ready('off')
      return
    }

    this.state.off('pending')
ruiruibupt's avatar
ruiruibupt 已提交
244 245 246

    this.watchdog.sleep()
    await this.logout()
ruiruibupt's avatar
ruiruibupt 已提交
247 248 249

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
251 252
    // await some tasks...
    this.state.off(true)
ruiruibupt's avatar
ruiruibupt 已提交
253 254

    // this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
255 256 257 258 259
  }

  public async logout(): Promise<void> {
    log.verbose('PuppetPadchat', 'logout()')

260
    if (!this.id) {
ruiruibupt's avatar
init  
ruiruibupt 已提交
261 262 263
      throw new Error('logout before login?')
    }

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

ruiruibupt's avatar
ruiruibupt 已提交
267
    // TODO: this.bridge.logout
ruiruibupt's avatar
init  
ruiruibupt 已提交
268 269 270 271 272 273 274 275 276 277 278 279 280 281
  }

  /**
   *
   * 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 已提交
282
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
283
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
284
    }
ruiruibupt's avatar
ruiruibupt 已提交
285 286 287

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
288 289 290
    return
  }

291 292 293
  // public async contactFindAll(query: ContactQueryFilter): Promise<string[]> {
  public async contactList(): Promise<string[]> {
    log.verbose('PuppetPadchat', 'contactList()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
294

ruiruibupt's avatar
ruiruibupt 已提交
295 296
    // const contactRawPayloadMap = (await this.bridge.checkSyncContactOrRoom()).contactMap

ruiruibupt's avatar
ruiruibupt 已提交
297
    const contactIdList = this.bridge.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
    // for (const contactRawPayload in contactRawPayloadMap) {

    // }

    // contactRawPayloadMap.forEach((value , id) => {
    //   contactIdList.push(id)
    //   this.Contact.load(
    //     id,
    //     await this.contactRawPayloadParser(value),
    //   )
    // })

    // // const payloadList = await Promise.all(
    // //   contactIdList.map(
    // //     id => this.contactPayload(id),
    // //   ),
    // // )

    // const contactList = contactIdList.filter(id => {
    //   await this.contactPayload(id)
    //   return true
    // })
    return contactIdList
  }

ruiruibupt's avatar
ruiruibupt 已提交
323 324 325 326 327 328 329 330
  // protected contactQueryFilterToFunction(
  //   query: ContactQueryFilter,
  // ): (payload: ContactPayload) => boolean {
  //   log.verbose('PuppetPadchat', 'contactQueryFilterToFunctionString({ %s })',
  //                           Object.keys(query)
  //                                 .map(k => `${k}: ${query[k as keyof ContactQueryFilter]}`)
  //                                 .join(', '),
  //             )
ruiruibupt's avatar
ruiruibupt 已提交
331

ruiruibupt's avatar
ruiruibupt 已提交
332 333 334
  //   if (Object.keys(query).length !== 1) {
  //     throw new Error('query only support one key. multi key support is not availble now.')
  //   }
ruiruibupt's avatar
ruiruibupt 已提交
335

ruiruibupt's avatar
ruiruibupt 已提交
336
  //   const filterKey = Object.keys(query)[0] as keyof ContactQueryFilter
ruiruibupt's avatar
ruiruibupt 已提交
337

ruiruibupt's avatar
ruiruibupt 已提交
338 339 340 341
  //   let filterValue: string | RegExp | undefined  = query[filterKey]
  //   if (!filterValue) {
  //     throw new Error('filterValue not found')
  //   }
ruiruibupt's avatar
ruiruibupt 已提交
342

ruiruibupt's avatar
ruiruibupt 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
  //   /**
  //    * must be string because we need inject variable value
  //    * into code as variable namespecialContactList
  //    */
  //   let filterFunction: (payload: ContactPayload) => boolean

  //   if (filterValue instanceof RegExp) {
  //     const regex = filterValue
  //     filterFunction = (payload: ContactPayload) => regex.test(payload[filterKey] || '')
  //   } else if (typeof filterValue === 'string') {
  //     filterValue = filterValue.replace(/'/g, '\\\'')
  //     filterFunction = (payload: ContactPayload) => payload[filterKey] === filterValue
  //   } else {
  //     throw new Error('unsupport name type')
  //   }
ruiruibupt's avatar
ruiruibupt 已提交
358

ruiruibupt's avatar
ruiruibupt 已提交
359 360
  //   return filterFunction
  // }
ruiruibupt's avatar
init  
ruiruibupt 已提交
361 362 363 364

  public async contactAvatar(contactId: string): Promise<FileBox> {
    log.verbose('PuppetPadchat', 'contactAvatar(%s)', contactId)

ruiruibupt's avatar
ruiruibupt 已提交
365 366 367 368 369 370
    const payload = await this.contactPayload(contactId)

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

371
    const file = FileBox.fromUrl(payload.avatar)
ruiruibupt's avatar
ruiruibupt 已提交
372
    return file
ruiruibupt's avatar
init  
ruiruibupt 已提交
373 374
  }

375
  public async contactRawPayload(id: string): Promise<PadchatContactRawPayload> {
ruiruibupt's avatar
init  
ruiruibupt 已提交
376
    log.verbose('PuppetPadchat', 'contactRawPayload(%s)', id)
ruiruibupt's avatar
ruiruibupt 已提交
377

378
    const rawPayload = await this.bridge.contactRawPayload(id)
ruiruibupt's avatar
init  
ruiruibupt 已提交
379 380 381
    return rawPayload
  }

382 383
  public async contactRawPayloadParser(rawPayload: PadchatContactRawPayload): Promise<ContactPayload> {
    log.verbose('PuppetPadchat', 'contactRawPayloadParser(rawPayload.user_name="%s")', rawPayload.user_name)
ruiruibupt's avatar
init  
ruiruibupt 已提交
384

385 386 387 388
    if (!rawPayload.user_name) {
      throw Error('cannot get user_name(wxid)!')
    }

389
    if (isRoomId(rawPayload.user_name)) {
ruiruibupt's avatar
ruiruibupt 已提交
390 391 392
      throw Error('Room Object instead of Contact!')
    }

393
    let contactType = ContactType.Unknown
394
    if (isContactOfficialId(rawPayload.user_name)) {
395
      contactType = ContactType.Official
ruiruibupt's avatar
ruiruibupt 已提交
396
    } else {
397
      contactType = ContactType.Personal
ruiruibupt's avatar
ruiruibupt 已提交
398 399
    }

ruiruibupt's avatar
init  
ruiruibupt 已提交
400
    const payload: ContactPayload = {
401
      id        : rawPayload.user_name,
402
      gender    : rawPayload.sex,
ruiruibupt's avatar
ruiruibupt 已提交
403 404 405 406 407 408
      type      : contactType,
      alias     : rawPayload.remark,
      avatar    : rawPayload.big_head,
      city      : rawPayload.city,
      name      : rawPayload.nick_name,
      province  : rawPayload.provincia,
ruiruibupt's avatar
ruiruibupt 已提交
409
      signature : (rawPayload.signature).replace('+', ' '),   // Stay+Foolish
ruiruibupt's avatar
init  
ruiruibupt 已提交
410 411 412 413 414 415 416 417 418
    }
    return payload
  }

  /**
   *
   * Message
   *
   */
ruiruibupt's avatar
ruiruibupt 已提交
419 420 421 422

  public async messageFile(id: string): Promise<FileBox> {
    // const rawPayload = await this.messageRawPayload(id)

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

ruiruibupt's avatar
ruiruibupt 已提交
425
    const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
426
    const filename = 'test-' + id + '.txt'
ruiruibupt's avatar
ruiruibupt 已提交
427

428
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
429 430 431 432 433 434 435
      base64,
      filename,
    )

    return file
  }

436
  public async messageRawPayload(id: string): Promise<PadchatMessagePayload> {
437 438 439 440 441
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
442 443 444 445 446

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

448
    const rawPayload = this.cachePadchatMessagePayload.get(id)
449 450 451 452 453 454 455

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

    return rawPayload

ruiruibupt's avatar
ruiruibupt 已提交
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    // log.verbose('PuppetPadchat', 'messageRawPayload(%s)', id)
    // const rawPayload: PadchatMessageRawPayload = {
    //   content:      '',
    //   data:         '',
    //   continue:     1,
    //   description:  '',
    //   from_user:    '',
    //   msg_id:       '',
    //   msg_source:   '',
    //   msg_type:     5,
    //   status:       1,
    //   sub_type:     PadchatMessageType.TEXT,
    //   timestamp:    11111111,
    //   to_user:      '',
    //   uin:          111111,

    //   // from : 'from_id',
    //   // text : 'padchat message text',
    //   // to   : 'to_id',
    // }
    // return rawPayload
ruiruibupt's avatar
init  
ruiruibupt 已提交
477 478
  }

479 480 481 482
  public async messageRawPayloadParser(rawPayload: PadchatMessagePayload): Promise<MessagePayload> {
    log.warn('PuppetPadChat', 'messageRawPayloadParser(rawPayload.msg_id=%s)', rawPayload.msg_id)

    let type: MessageType
ruiruibupt's avatar
ruiruibupt 已提交
483 484

    switch (rawPayload.sub_type) {
485
      case PadchatMessageType.Text:
486
        type = MessageType.Text
ruiruibupt's avatar
ruiruibupt 已提交
487
        break
488
      case PadchatMessageType.Image:
489
        type = MessageType.Image
ruiruibupt's avatar
ruiruibupt 已提交
490
        break
491
      case PadchatMessageType.Voice:
492
        type = MessageType.Audio
ruiruibupt's avatar
ruiruibupt 已提交
493
        break
494
      case PadchatMessageType.Emoticon:
495
        type = MessageType.Emoticon
ruiruibupt's avatar
ruiruibupt 已提交
496
        break
497
      case PadchatMessageType.App:
498
        type = MessageType.Attachment
ruiruibupt's avatar
ruiruibupt 已提交
499
        break
500
      case PadchatMessageType.Video:
501
        type = MessageType.Video
ruiruibupt's avatar
ruiruibupt 已提交
502 503
        break
      default:
504 505 506 507 508
        log.warn('PuppetPadChat', 'messageRawPayloadParser() unknown type %s[%s], treat as Text',
                                  PadchatMessageType[rawPayload.sub_type],
                                  rawPayload.sub_type,
                )
        type = MessageType.Text
ruiruibupt's avatar
ruiruibupt 已提交
509 510
    }

511
    const payloadBase = {
512
      id        : rawPayload.msg_id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
513
      timestamp : Date.now(),
ruiruibupt's avatar
ruiruibupt 已提交
514 515
      fromId    : rawPayload.from_user,
      text      : rawPayload.content,
516
      // toId      : rawPayload.to_user,
ruiruibupt's avatar
ruiruibupt 已提交
517
      type      : type,
ruiruibupt's avatar
init  
ruiruibupt 已提交
518
    }
ruiruibupt's avatar
ruiruibupt 已提交
519

520 521 522 523 524 525 526 527 528
    let roomId: undefined | string = undefined
    let toId:   undefined | string = undefined

    // Msg from room
    if (isRoomId(rawPayload.from_user)) {
      // update fromId to actual sender instead of the room
      payloadBase.fromId = rawPayload.content.split(':\n')[0]
      // update the text to actual text of the message
      payloadBase.text = rawPayload.content.split(':\n')[1]
ruiruibupt's avatar
ruiruibupt 已提交
529

530 531 532
      roomId = rawPayload.from_user

      if (!roomId || !payloadBase.fromId) {
ruiruibupt's avatar
ruiruibupt 已提交
533 534
        throw Error('empty roomId or empty contactId!')
      }
535 536 537 538 539 540 541 542
    }

    // Msg to room
    if (isRoomId(rawPayload.to_user)) {
      roomId = rawPayload.to_user

      // TODO: if the message @someone, the toId should set to the mentioned contact id(?)
      toId   = undefined
ruiruibupt's avatar
ruiruibupt 已提交
543
    } else {
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
      toId = rawPayload.to_user
    }

    let payload: MessagePayload

    // Two branch is the same code.
    // Only for making TypeScript happy
    if (toId) {
      payload = {
        ...payloadBase,
        toId,
        roomId,
      }
    } else if (roomId) {
      payload = {
        ...payloadBase,
        toId,
        roomId,
ruiruibupt's avatar
ruiruibupt 已提交
562
      }
563 564
    } else {
      throw new Error('neither toId nor roomId')
ruiruibupt's avatar
ruiruibupt 已提交
565 566 567
    }

    log.verbose('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
568 569 570 571 572 573 574 575
    return payload
  }

  public async messageSendText(
    receiver : Receiver,
    text     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', receiver, text)
ruiruibupt's avatar
ruiruibupt 已提交
576 577 578 579 580
    const id = receiver.contactId || receiver.roomId
    if (!id) {
      throw Error('No id')
    }
    await this.bridge.WXSendMsg(id, text)
ruiruibupt's avatar
init  
ruiruibupt 已提交
581 582 583 584 585 586
  }

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
587
    log.verbose('PuppetPadchat', 'messageSend("%s", %s)', JSON.stringify(receiver), file)
ruiruibupt's avatar
ruiruibupt 已提交
588 589 590 591 592 593 594 595

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

    await this.bridge.WXSendImage(
      id,
596
      await file.toBase64(),
ruiruibupt's avatar
ruiruibupt 已提交
597
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
598 599 600 601 602 603 604
  }

  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
605
                              JSON.stringify(receiver),
ruiruibupt's avatar
init  
ruiruibupt 已提交
606 607
                              messageId,
              )
608
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
609

610 611 612 613
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
614 615
      await this.messageSendText(
        receiver,
616
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
617 618 619 620
      )
    } else {
      await this.messageSendFile(
        receiver,
621
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
622 623
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
624 625 626 627 628 629 630
  }

  /**
   *
   * Room
   *
   */
631
  public async roomRawPayload(id: string): Promise<PadchatRoomRawPayload> {
ruiruibupt's avatar
init  
ruiruibupt 已提交
632
    log.verbose('PuppetPadchat', 'roomRawPayload(%s)', id)
ruiruibupt's avatar
ruiruibupt 已提交
633

634
    const rawPayload = await this.bridge.roomRawPayload(id)
ruiruibupt's avatar
init  
ruiruibupt 已提交
635 636 637
    return rawPayload
  }

638 639
  public async roomRawPayloadParser(rawPayload: PadchatRoomRawPayload): Promise<RoomPayload> {
    log.verbose('PuppetPadchat', 'roomRawPayloadParser(rawPayload.user_name="%s")', rawPayload.user_name)
ruiruibupt's avatar
init  
ruiruibupt 已提交
640

641 642
    // const memberList = (rawPayload.member || [])
    //                     .map(id => this.Contact.load(id))
643

644
    // await Promise.all(memberList.map(c => c.ready()))
645

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
646
    const roomRawMemberList = (await this.bridge.WXGetChatRoomMember(rawPayload.user_name)).member
647

ruiruibupt's avatar
ruiruibupt 已提交
648
    const aliasDict = {} as { [id: string]: string | undefined }
649

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
650 651
    if (Array.isArray(roomRawMemberList)) {
      roomRawMemberList.forEach(
ruiruibupt's avatar
ruiruibupt 已提交
652 653 654
        rawMember => {
          aliasDict[rawMember.user_name] = rawMember.chatroom_nick_name
        },
655 656
      )
    }
657

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
658 659
    const memberIdList = roomRawMemberList.map(m => m.user_name)

ruiruibupt's avatar
init  
ruiruibupt 已提交
660
    const payload: RoomPayload = {
661 662
      id           : rawPayload.user_name,
      topic        : rawPayload.nick_name,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
663
      memberIdList,
664
      aliasDict,
ruiruibupt's avatar
init  
ruiruibupt 已提交
665 666 667 668 669
    }

    return payload
  }

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

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

ruiruibupt's avatar
ruiruibupt 已提交
676
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
677 678 679 680 681 682 683
  }

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

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

689 690 691 692 693 694 695 696 697 698 699 700 701
  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 已提交
702 703 704 705 706
  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
ruiruibupt's avatar
ruiruibupt 已提交
707
    await this.bridge.WXAddChatRoomMember(roomId, contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
708 709 710 711 712 713 714 715 716
  }

  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 已提交
717
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
718
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
719
    }
ruiruibupt's avatar
ruiruibupt 已提交
720 721 722

    await this.bridge.WXSetChatroomName(roomId, topic)

ruiruibupt's avatar
init  
ruiruibupt 已提交
723 724 725 726 727 728 729 730 731
    return
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
732
    // TODO
ruiruibupt's avatar
ruiruibupt 已提交
733
    // await this.bridge.crea
ruiruibupt's avatar
init  
ruiruibupt 已提交
734 735 736 737 738
    return 'mock_room_id'
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
739
    await this.bridge.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
740 741 742 743 744 745 746 747 748 749 750 751
  }

  /**
   *
   * FriendRequest
   *
   */
  public async friendRequestSend(
    contactId : string,
    hello     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'friendRequestSend(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
752 753 754 755 756 757 758 759 760 761 762 763 764

    const rawPayload = await this.contactRawPayload(contactId)

    let strangerV1
    let strangerV2
    if (/^v1_/i.test(rawPayload.stranger)) {
      strangerV1 = rawPayload.stranger
    } else if (/^v2_/i.test(rawPayload.stranger)) {
      strangerV2 = rawPayload.stranger
    } else {
      throw new Error('stranger neither v1 nor v2!')
    }

765 766
    // Issue #1252 : what's wrong here?

ruiruibupt's avatar
ruiruibupt 已提交
767 768 769 770 771 772
    await this.bridge.WXAddUser(
      strangerV1 || '',
      strangerV2 || '',
      '14',
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
773 774 775 776 777 778 779
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
781 782
    // TODO

ruiruibupt's avatar
ruiruibupt 已提交
783 784 785 786 787 788 789 790 791 792
    // const rawPayload = await this.contactRawPayload(contactId)

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

    // await this.bridge.WXAcceptUser(
    //   rawPayload.stranger,
    //   rawPayload.ticket,
    // )
ruiruibupt's avatar
init  
ruiruibupt 已提交
793 794
  }

ruiruibupt's avatar
ruiruibupt 已提交
795
  public async friendRequestRawPayloadParser(rawPayload: any) : Promise<FriendRequestPayload> {
796
    log.verbose('PuppetPadchat', 'friendRequestRawPayloadParser(%s)', rawPayload)
ruiruibupt's avatar
ruiruibupt 已提交
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833

    // TODO

    return rawPayload
    // switch (rawPayload.MsgType) {
    //   case WebMessageType.VERIFYMSG:
    //     if (!rawPayload.RecommendInfo) {
    //       throw new Error('no RecommendInfo')
    //     }
    //     const recommendInfo: WebRecomendInfo = rawPayload.RecommendInfo

    //     if (!recommendInfo) {
    //       throw new Error('no recommendInfo')
    //     }

    //     const payloadReceive: FriendRequestPayloadReceive = {
    //       id        : rawPayload.MsgId,
    //       contactId : recommendInfo.UserName,
    //       hello     : recommendInfo.Content,
    //       ticket    : recommendInfo.Ticket,
    //       type      : FriendRequestType.Receive,
    //     }
    //     return payloadReceive

    //   case WebMessageType.SYS:
    //     const payloadConfirm: FriendRequestPayloadConfirm = {
    //       id        : rawPayload.MsgId,
    //       contactId : rawPayload.FromUserName,
    //       type      : FriendRequestType.Confirm,
    //     }
    //     return payloadConfirm

    //   default:
    //     throw new Error('not supported friend request message raw payload')
    // }
  }

834 835
  public async friendRequestRawPayload(id: string): Promise<any> {
    // log.verbose('PuppetPadchat', 'friendRequestRawPayload(%s)', id)
ruiruibupt's avatar
ruiruibupt 已提交
836 837 838 839 840 841 842 843 844 845 846 847

    // TODO

    console.log(id)
    // const rawPayload = this.cacheMessageRawPayload.get(id)
    // if (!rawPayload) {
    //   throw new Error('no rawPayload')
    // }

    // return rawPayload
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
848 849 850
}

export default PuppetPadchat