puppet-padchat.ts 27.5 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/'

ruiruibupt's avatar
ruiruibupt 已提交
49
import Misc           from '../misc'
50

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

import {
  Bridge,
  resolverDict,
ruiruibupt's avatar
ruiruibupt 已提交
58
  // AutoDataType,
ruiruibupt's avatar
init  
ruiruibupt 已提交
59 60 61 62 63 64 65
}                       from './bridge'

import {
  PadchatContactRawPayload,
  PadchatMessageRawPayload,
  PadchatMessageType,
  PadchatRoomRawPayload,
66
  // PadchatRoomRawMember,
ruiruibupt's avatar
init  
ruiruibupt 已提交
67 68 69 70 71 72
}                       from './padchat-schemas'

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

export interface RawWebSocketDataType {
ruiruibupt's avatar
ruiruibupt 已提交
73 74
  type?:   number, // -1 when logout
  msg?:    string, // '掉线了' when logout
ruiruibupt's avatar
init  
ruiruibupt 已提交
75 76 77 78 79 80 81 82 83 84 85 86
  apiName: string, // raw function name
  data:    string,
  msgId:   string,
  userId:  string, // token
}

import * as WebSocket from 'ws'

// Mock userid
const TOKEN = 'padchattest'

export class PuppetPadchat extends Puppet {
87 88 89 90 91 92

  public readonly cachePadchatContactRawPayload       : LRU.Cache<string, PadchatContactRawPayload>
  // public readonly cachePadchatFriendRequestRawPayload : LRU.Cache<string, FriendRequestRawPayload>
  public readonly cachePadchatMessageRawPayload       : LRU.Cache<string, PadchatMessageRawPayload>
  public readonly cachePadchatRoomRawPayload          : LRU.Cache<string, PadchatRoomRawPayload>

ruiruibupt's avatar
ruiruibupt 已提交
93
  public bridge:  Bridge
ruiruibupt's avatar
ruiruibupt 已提交
94
  // public botWs:   WebSocket
ruiruibupt's avatar
init  
ruiruibupt 已提交
95 96 97 98 99 100

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

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

    this.cachePadchatContactRawPayload       = new LRU<string, PadchatContactRawPayload>(lruOptions)
    // this.cacheFriendRequestPayload = new LRU<string, FriendRequestPayload>(lruOptions)
    this.cachePadchatMessageRawPayload       = new LRU<string, PadchatMessageRawPayload>(lruOptions)
    this.cachePadchatRoomRawPayload          = new LRU<string, PadchatRoomRawPayload>(lruOptions)

ruiruibupt's avatar
ruiruibupt 已提交
115
    this.bridge = new Bridge({
ruiruibupt's avatar
ruiruibupt 已提交
116
      memory   : this.options.memory,
ruiruibupt's avatar
ruiruibupt 已提交
117 118 119 120
      userId   : TOKEN,
      autoData : {},
      // profile:  profile, // should be profile in the future
    })
ruiruibupt's avatar
init  
ruiruibupt 已提交
121 122 123
  }

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

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

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

ruiruibupt's avatar
ruiruibupt 已提交
134
    const puppet = this
ruiruibupt's avatar
init  
ruiruibupt 已提交
135

ruiruibupt's avatar
ruiruibupt 已提交
136 137 138 139 140 141 142
    // 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.
143
      // puppet.emit('heartbeat', food.data)
ruiruibupt's avatar
ruiruibupt 已提交
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 || '')
        })
      }
ruiruibupt's avatar
init  
ruiruibupt 已提交
156

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

ruiruibupt's avatar
ruiruibupt 已提交
159 160 161 162 163 164 165 166 167 168 169 170
    })

    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 已提交
171 172 173 174 175

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

ruiruibupt's avatar
ruiruibupt 已提交
176
  }
ruiruibupt's avatar
init  
ruiruibupt 已提交
177 178

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

ruiruibupt's avatar
ruiruibupt 已提交
181 182 183 184 185 186 187
    /**
     * state has two main state: ON / OFF
     * ON (pending)
     * OFF (pending)
     */
    this.state.on('pending')

ruiruibupt's avatar
ruiruibupt 已提交
188 189
    await this.startBridge()
    await this.startWatchdog()
ruiruibupt's avatar
ruiruibupt 已提交
190

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

  }

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

    if (this.state.off()) {
      const e = new Error('initBridge() found targetState != live, no init anymore')
      log.warn('PuppetPadchat', e.message)
      throw e
    }

ruiruibupt's avatar
ruiruibupt 已提交
205
    this.bridge.removeAllListeners()
ruiruibupt's avatar
init  
ruiruibupt 已提交
206 207 208
    // 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))
ruiruibupt's avatar
ruiruibupt 已提交
209 210 211 212 213 214
    this.bridge.on('login'    , (userId: string) => {
      this.login(userId)
      this.bridge.syncContactsAndRooms()
    })
    this.bridge.on('ws', data => this.wsOnMessage(data))

ruiruibupt's avatar
init  
ruiruibupt 已提交
215 216
    // this.bridge.on('logout'   , Event.onLogout.bind(this))
    // this.bridge.on('message'  , Event.onMessage.bind(this))
ruiruibupt's avatar
ruiruibupt 已提交
217 218 219
    this.bridge.on('scan', (qrCode, statusCode, data) => {
      this.emit('scan', qrCode, statusCode, data)
    })
ruiruibupt's avatar
init  
ruiruibupt 已提交
220 221
    // this.bridge.on('unload'   , Event.onUnload.bind(this))

ruiruibupt's avatar
ruiruibupt 已提交
222
    await this.bridge.start()
ruiruibupt's avatar
init  
ruiruibupt 已提交
223 224 225 226 227 228 229 230 231 232
  }

  private async wsOnMessage(data: WebSocket.Data) {
    if (typeof data !== 'string') {
      const e = new Error('Ipad Websocket return wrong data!')
      log.warn('PuppetPadchat', e.message)
      throw e
    }

    const rawWebSocketData = JSON.parse(data) as RawWebSocketDataType
ruiruibupt's avatar
ruiruibupt 已提交
233
    log.silly('PuppetPadchat', 'WebSocket Server result: %s', JSON.stringify(rawWebSocketData))
ruiruibupt's avatar
init  
ruiruibupt 已提交
234 235

    // Data From Tencent
ruiruibupt's avatar
ruiruibupt 已提交
236
    if (!rawWebSocketData.msgId) {
ruiruibupt's avatar
init  
ruiruibupt 已提交
237 238 239 240 241 242 243
      // rawWebSocketData:
      // {
      //   "apiName": "",
      //   "data": "XXXX",
      //   "msgId": "",
      //   "userId": "test"
      // }
ruiruibupt's avatar
ruiruibupt 已提交
244
      if (!rawWebSocketData.data && !rawWebSocketData.apiName) {
ruiruibupt's avatar
ruiruibupt 已提交
245
        log.silly('PuppetPadchat', 'WebSocket Server get empty message data form Tencent server')
ruiruibupt's avatar
init  
ruiruibupt 已提交
246 247 248
        return
      }

ruiruibupt's avatar
ruiruibupt 已提交
249 250 251 252
      const msgRawPayloadList: PadchatMessageRawPayload[] = JSON.parse(decodeURIComponent(rawWebSocketData.data))

      msgRawPayloadList.forEach(async (msgRawPayload) => {
        log.silly('PuppetPadchat', 'WebSocket Server rawData result: %s', JSON.stringify(msgRawPayload))
253
        if (!msgRawPayload.msg_id) {
ruiruibupt's avatar
ruiruibupt 已提交
254 255 256
          log.silly('PuppetPadchat', 'WebSocket Server: get empty message msg_id form Tencent server for payoad: %s',
                                      JSON.stringify(msgRawPayload),
                    )
ruiruibupt's avatar
ruiruibupt 已提交
257 258
          return
        }
ruiruibupt's avatar
init  
ruiruibupt 已提交
259

260 261 262 263 264
        // const msg  = this.Message.create(
        //   msgRawPayload['msg_id'],
        //   await this.messagePayload(msgRawPayload.msg_id),
        // )
        // await msg.ready()
ruiruibupt's avatar
init  
ruiruibupt 已提交
265

ruiruibupt's avatar
ruiruibupt 已提交
266 267
        this.cachePadchatMessageRawPayload.set(msgRawPayload.msg_id, msgRawPayload)

268
        this.emit('message', msgRawPayload.msg_id)
ruiruibupt's avatar
ruiruibupt 已提交
269
      })
ruiruibupt's avatar
init  
ruiruibupt 已提交
270 271 272

    // Data Return From WebSocket Client
    } else {
ruiruibupt's avatar
ruiruibupt 已提交
273 274
      // check logout:
      if (rawWebSocketData.type === -1) {
ruiruibupt's avatar
ruiruibupt 已提交
275 276
        // this.emit('logout', this.selfId())
        this.logout() // no need to await
ruiruibupt's avatar
ruiruibupt 已提交
277 278
      }

ruiruibupt's avatar
init  
ruiruibupt 已提交
279 280 281
      log.silly('PuppetPadchat', 'return apiName: %s, msgId: %s', rawWebSocketData.apiName, rawWebSocketData.msgId)
      const msgId = rawWebSocketData.msgId

282 283 284 285 286 287 288 289
      let rawData: Object
      if (!rawWebSocketData.data) {
        log.silly('PuppetPadchat', 'WebSocket Server get empty message data form API call: %s', rawWebSocketData.apiName)
        rawData = {}
      } else {
        rawData = JSON.parse(decodeURIComponent(rawWebSocketData.data))
      }

ruiruibupt's avatar
init  
ruiruibupt 已提交
290 291 292 293 294 295 296 297 298 299 300 301 302
      // rawWebSocketData:
      // {
      //     "apiName": "WXHeartBeat",
      //     "data": "%7B%22status%22%3A0%2C%22message%22%3A%22ok%22%7D",
      //     "msgId": "abc231923912983",
      //     "userId": "test"
      // }

      if (resolverDict[msgId]) {
        const resolve = resolverDict[msgId]
        delete resolverDict[msgId]
        // resolve({rawData: rawData, msgId: rawWebSocketData.msgId})
        resolve(rawData)
ruiruibupt's avatar
ruiruibupt 已提交
303 304
      } else {
        log.warn('PuppetPadchat', 'wsOnMessage() msgId %s not in resolverDict', msgId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318
      }
    }
  }

  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 已提交
319 320 321

    this.watchdog.sleep()
    await this.logout()
ruiruibupt's avatar
ruiruibupt 已提交
322 323 324

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
326 327
    // await some tasks...
    this.state.off(true)
ruiruibupt's avatar
ruiruibupt 已提交
328 329

    // this.emit('stop')
ruiruibupt's avatar
init  
ruiruibupt 已提交
330 331 332 333 334
  }

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

335
    if (!this.id) {
ruiruibupt's avatar
init  
ruiruibupt 已提交
336 337 338
      throw new Error('logout before login?')
    }

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

ruiruibupt's avatar
ruiruibupt 已提交
342
    // TODO: this.bridge.logout
ruiruibupt's avatar
init  
ruiruibupt 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356
  }

  /**
   *
   * 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 已提交
357
      const payload = await this.contactPayload(contactId)
ruiruibupt's avatar
ruiruibupt 已提交
358
      return payload.alias || ''
ruiruibupt's avatar
init  
ruiruibupt 已提交
359
    }
ruiruibupt's avatar
ruiruibupt 已提交
360 361 362

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
363 364 365
    return
  }

366 367 368
  // public async contactFindAll(query: ContactQueryFilter): Promise<string[]> {
  public async contactList(): Promise<string[]> {
    log.verbose('PuppetPadchat', 'contactList()')
ruiruibupt's avatar
init  
ruiruibupt 已提交
369

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

ruiruibupt's avatar
ruiruibupt 已提交
372
    const contactIdList = this.bridge.getContactIdList()
ruiruibupt's avatar
ruiruibupt 已提交
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
    // 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 已提交
398 399 400 401 402 403 404 405
  // 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 已提交
406

ruiruibupt's avatar
ruiruibupt 已提交
407 408 409
  //   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 已提交
410

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

ruiruibupt's avatar
ruiruibupt 已提交
413 414 415 416
  //   let filterValue: string | RegExp | undefined  = query[filterKey]
  //   if (!filterValue) {
  //     throw new Error('filterValue not found')
  //   }
ruiruibupt's avatar
ruiruibupt 已提交
417

ruiruibupt's avatar
ruiruibupt 已提交
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
  //   /**
  //    * 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 已提交
433

ruiruibupt's avatar
ruiruibupt 已提交
434 435
  //   return filterFunction
  // }
ruiruibupt's avatar
init  
ruiruibupt 已提交
436 437 438 439

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

ruiruibupt's avatar
ruiruibupt 已提交
440 441 442 443 444 445
    const payload = await this.contactPayload(contactId)

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

446
    const file = FileBox.fromRemote(payload.avatar)
ruiruibupt's avatar
ruiruibupt 已提交
447
    return file
ruiruibupt's avatar
init  
ruiruibupt 已提交
448 449 450 451
  }

  public async contactRawPayload(id: string): Promise<PadchatContactRawPayload> {
    log.verbose('PuppetPadchat', 'contactRawPayload(%s)', id)
ruiruibupt's avatar
ruiruibupt 已提交
452 453 454 455 456 457 458 459 460 461 462 463 464 465

    const rawPayload = await Misc.retry(async (retry, attempt) => {
      log.verbose('PuppetPadchat', 'contactRawPayload(%s) retry() attempt=%d', id, attempt)
      const tryRawPayload = this.bridge.contactRawPayloadDict[id] // await this.bridge.WXGetContactPayload(id)
      if (tryRawPayload) {
        return tryRawPayload
      }
      return retry(new Error('tryRawPayload empty'))
    })

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

ruiruibupt's avatar
init  
ruiruibupt 已提交
466 467 468 469 470 471
    return rawPayload
  }

  public async contactRawPayloadParser(rawPayload: PadchatContactRawPayload): Promise<ContactPayload> {
    log.verbose('PuppetPadchat', 'contactRawPayloadParser(%s)', rawPayload)

472 473 474 475
    if (!rawPayload.user_name) {
      throw Error('cannot get user_name(wxid)!')
    }

ruiruibupt's avatar
ruiruibupt 已提交
476
    const gender = {
477 478 479
      0: ContactGender.Unknown,
      1: ContactGender.Male,
      2: ContactGender.Female,
ruiruibupt's avatar
ruiruibupt 已提交
480 481 482 483 484 485
    }

    if (/@chatroom$/.test(rawPayload.user_name)) {
      throw Error('Room Object instead of Contact!')
    }

486
    let contactType = ContactType.Unknown
ruiruibupt's avatar
ruiruibupt 已提交
487
    if (/^gh_/.test(rawPayload.user_name)) {
488
      contactType = ContactType.Official
ruiruibupt's avatar
ruiruibupt 已提交
489
    } else {
490
      contactType = ContactType.Personal
ruiruibupt's avatar
ruiruibupt 已提交
491 492
    }

ruiruibupt's avatar
init  
ruiruibupt 已提交
493
    const payload: ContactPayload = {
494
      id        : rawPayload.user_name,
ruiruibupt's avatar
ruiruibupt 已提交
495 496 497 498 499 500 501
      gender    : gender[rawPayload.sex],
      type      : contactType,
      alias     : rawPayload.remark,
      avatar    : rawPayload.big_head,
      city      : rawPayload.city,
      name      : rawPayload.nick_name,
      province  : rawPayload.provincia,
ruiruibupt's avatar
ruiruibupt 已提交
502
      signature : (rawPayload.signature).replace('+', ' '),   // Stay+Foolish
ruiruibupt's avatar
init  
ruiruibupt 已提交
503 504 505 506 507 508 509 510 511
    }
    return payload
  }

  /**
   *
   * Message
   *
   */
ruiruibupt's avatar
ruiruibupt 已提交
512 513 514 515

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

ruiruibupt's avatar
ruiruibupt 已提交
516 517
    // TODO

ruiruibupt's avatar
ruiruibupt 已提交
518
    const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
519
    const filename = 'test-' + id + '.txt'
ruiruibupt's avatar
ruiruibupt 已提交
520

521
    const file = FileBox.fromBase64(
ruiruibupt's avatar
ruiruibupt 已提交
522 523 524 525 526 527 528
      base64,
      filename,
    )

    return file
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
529
  public async messageRawPayload(id: string): Promise<PadchatMessageRawPayload> {
530 531 532 533 534
    // throw Error('should not call messageRawPayload: ' + id)

    /**
     * Issue #1249
     */
ruiruibupt's avatar
ruiruibupt 已提交
535 536 537 538 539

    // this.cachePadchatMessageRawPayload.set(id, {
    //   id: 'xxx',
    //   data: 'xxx',
    // } as any)
540 541 542 543 544 545 546 547 548

    const rawPayload = this.cachePadchatMessageRawPayload.get(id)

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

    return rawPayload

ruiruibupt's avatar
ruiruibupt 已提交
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
    // 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 已提交
570 571 572
  }

  public async messageRawPayloadParser(rawPayload: PadchatMessageRawPayload): Promise<MessagePayload> {
573
    let type = MessageType.Unknown
ruiruibupt's avatar
ruiruibupt 已提交
574 575 576

    switch (rawPayload.sub_type) {
      case PadchatMessageType.TEXT:
577
        type = MessageType.Text
ruiruibupt's avatar
ruiruibupt 已提交
578 579
        break
      case PadchatMessageType.IMAGE:
580
        type = MessageType.Image
ruiruibupt's avatar
ruiruibupt 已提交
581 582
        break
      case PadchatMessageType.VOICE:
583
        type = MessageType.Audio
ruiruibupt's avatar
ruiruibupt 已提交
584 585
        break
      case PadchatMessageType.EMOTICON:
586
        type = MessageType.Emoticon
ruiruibupt's avatar
ruiruibupt 已提交
587 588
        break
      case PadchatMessageType.APP:
589
        type = MessageType.Attachment
ruiruibupt's avatar
ruiruibupt 已提交
590 591
        break
      case PadchatMessageType.VIDEO:
592
        type = MessageType.Video
ruiruibupt's avatar
ruiruibupt 已提交
593 594
        break
      default:
595
        type = MessageType.Unknown
ruiruibupt's avatar
ruiruibupt 已提交
596 597
    }

ruiruibupt's avatar
init  
ruiruibupt 已提交
598
    const payload: MessagePayload = {
599
      id        : rawPayload.msg_id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
600
      timestamp : Date.now(),
ruiruibupt's avatar
ruiruibupt 已提交
601 602 603 604
      fromId    : rawPayload.from_user,
      text      : rawPayload.content,
      toId      : rawPayload.to_user,
      type      : type,
ruiruibupt's avatar
init  
ruiruibupt 已提交
605
    }
ruiruibupt's avatar
ruiruibupt 已提交
606 607 608 609 610

    if (/@chatroom$/.test(rawPayload.from_user)) {
      payload.roomId = rawPayload.from_user
      payload.fromId = rawPayload.content.split(':\n')[0]
      payload.text = rawPayload.content.split(':\n')[1]
ruiruibupt's avatar
ruiruibupt 已提交
611 612 613 614

      if (!payload.roomId || !payload.fromId) {
        throw Error('empty roomId or empty contactId!')
      }
615 616 617 618
      // const room = this.Room.load(payload.roomId)
      // const contact = this.Contact.load(payload.fromId)
      // await room.ready()
      // await contact.ready()
ruiruibupt's avatar
ruiruibupt 已提交
619 620 621 622
    } else {
      if (!payload.fromId) {
        throw Error('empty contactId!')
      }
623 624
      // const contact = this.Contact.load(payload.fromId)
      // await contact.ready()
ruiruibupt's avatar
ruiruibupt 已提交
625 626 627
    }

    log.verbose('PuppetPadchat', 'messagePayload(%s)', JSON.stringify(payload))
ruiruibupt's avatar
init  
ruiruibupt 已提交
628 629 630 631 632 633 634 635
    return payload
  }

  public async messageSendText(
    receiver : Receiver,
    text     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', receiver, text)
ruiruibupt's avatar
ruiruibupt 已提交
636 637 638 639 640
    const id = receiver.contactId || receiver.roomId
    if (!id) {
      throw Error('No id')
    }
    await this.bridge.WXSendMsg(id, text)
ruiruibupt's avatar
init  
ruiruibupt 已提交
641 642 643 644 645 646 647
  }

  public async messageSendFile(
    receiver : Receiver,
    file     : FileBox,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageSend(%s, %s)', receiver, file)
ruiruibupt's avatar
ruiruibupt 已提交
648 649 650 651 652 653 654 655

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

    await this.bridge.WXSendImage(
      id,
656
      await file.toBase64(),
ruiruibupt's avatar
ruiruibupt 已提交
657
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
658 659 660 661 662 663 664 665 666 667
  }

  public async messageForward(
    receiver  : Receiver,
    messageId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'messageForward(%s, %s)',
                              receiver,
                              messageId,
              )
668
    const payload = await this.messagePayload(messageId)
ruiruibupt's avatar
ruiruibupt 已提交
669

670 671 672 673
    if (payload.type === MessageType.Text) {
      if (!payload.text) {
        throw new Error('no text')
      }
ruiruibupt's avatar
ruiruibupt 已提交
674 675
      await this.messageSendText(
        receiver,
676
        payload.text,
ruiruibupt's avatar
ruiruibupt 已提交
677 678 679 680
      )
    } else {
      await this.messageSendFile(
        receiver,
681
        await this.messageFile(messageId),
ruiruibupt's avatar
ruiruibupt 已提交
682 683
      )
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
684 685 686 687 688 689 690 691 692
  }

  /**
   *
   * Room
   *
   */
  public async roomRawPayload(id: string): Promise<PadchatRoomRawPayload> {
    log.verbose('PuppetPadchat', 'roomRawPayload(%s)', id)
ruiruibupt's avatar
ruiruibupt 已提交
693 694 695 696 697 698 699 700 701 702 703 704 705

    const rawPayload = await Misc.retry(async (retry, attempt) => {
      log.silly('PuppetPadchat', 'roomRawPayload(%s) retry() attempt=%d', id, attempt)
      const tryRawPayload = this.bridge.roomRawPayloadDict[id] // await this.bridge.WXGetRoomPayload(id)
      if (tryRawPayload) {
        return tryRawPayload
      }
      return retry(new Error('tryRawPayload empty'))
    })

    if (!rawPayload) {
      throw new Error('no rawPayload')
    }
ruiruibupt's avatar
init  
ruiruibupt 已提交
706 707 708 709
    return rawPayload
  }

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

712 713
    // const memberList = (rawPayload.member || [])
    //                     .map(id => this.Contact.load(id))
714

715
    // await Promise.all(memberList.map(c => c.ready()))
716

ruiruibupt's avatar
ruiruibupt 已提交
717
    const padchatRoomRawMemberList = (await this.bridge.WXGetChatRoomMember(rawPayload.user_name)).member
718

719 720 721 722
    // const nameMap         = await this.roomParseMap('name'        , padchatRoomRawMemberList.member)
    // const roomAliasMap    = await this.roomParseMap('roomAlias'   , padchatRoomRawMemberList.member)
    // const contactAliasMap = await this.roomParseMap('contactAlias', padchatRoomRawMemberList.member)

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

ruiruibupt's avatar
ruiruibupt 已提交
725 726 727 728 729 730 731 732 733 734 735
    if (Array.isArray(padchatRoomRawMemberList)) {
      // const memberListPayload = await Promise.all(
      //   padchatRoomRawMemberList.member
      //     .map(rawMember => rawMember.user_name)
      //     .map(contactId => this.contactPayload(contactId)),
      // )
      // memberListPayload.forEach(
      padchatRoomRawMemberList.forEach(
        rawMember => {
          aliasDict[rawMember.user_name] = rawMember.chatroom_nick_name
        },
736 737
      )
    }
738

ruiruibupt's avatar
init  
ruiruibupt 已提交
739
    const payload: RoomPayload = {
740 741 742 743 744 745 746
      id           : rawPayload.user_name,
      topic        : rawPayload.nick_name,
      memberIdList : rawPayload.member,
      aliasDict,
      // nameMap        : nameMap,
      // roomAliasMap   : roomAliasMap,
      // contactAliasMap: contactAliasMap,
ruiruibupt's avatar
init  
ruiruibupt 已提交
747 748 749 750 751
    }

    return payload
  }

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  // private async roomParseMap(
  //   parseSection: keyof RoomMemberQueryFilter,
  //   memberList?:  PadchatRoomRawMember[],
  // ): Promise<Map<string, string>> {
  //   log.verbose('PuppetPadchat', 'roomParseMap(%s, memberList.length=%d)',
  //                                   parseSection,
  //                                   memberList && memberList.length,
  //               )

  //   const dict: Map<string, string> = new Map<string, string>()
  //   if (memberList && Array.isArray(memberList)) {
  //     for (const member of memberList) {
  //       let tmpName: string

  //       switch (parseSection) {
  //         case 'name':
  //           tmpName = member.nick_name
  //           break
  //         case 'roomAlias':
  //           tmpName = member.chatroom_nick_name
  //           break
  //         case 'contactAlias':
  //           const payload = await this.contactPayload(member.user_name)
  //           tmpName = payload.alias || ''
  //           // const contact = this.Contact.load(member.user_name)
  //           // tmpName = contact.alias() || ''
  //           break
  //         default:
  //           throw new Error('PuppetPadchat parseMap failed, member not found')
  //       }

  //       dict.set(member.user_name, Misc.stripEmoji(tmpName))
  //     }
  //   }
  //   return dict
  // }

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

ruiruibupt's avatar
ruiruibupt 已提交
792
    const roomIdList = this.bridge.getRoomIdList()
ruiruibupt's avatar
init  
ruiruibupt 已提交
793

794 795
    // TODO: Issue #1255

ruiruibupt's avatar
ruiruibupt 已提交
796 797 798 799 800 801 802 803
    // const rooomMap = (await this.bridge.checkSyncContactOrRoom()).roomMap
    // const roomIdList: string[] = []
    // rooomMap.forEach(async (value , id) => {
    //   roomIdList.push(id)
    //   this.Room.load(id, await this.roomRawPayloadParser(value))
    // })

    // return roomIdList
ruiruibupt's avatar
ruiruibupt 已提交
804
    return roomIdList
ruiruibupt's avatar
init  
ruiruibupt 已提交
805 806 807 808 809 810 811
  }

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

813
    // Should check whether user is in the room. WXDeleteChatRoomMember won't check if user in the room automatically
ruiruibupt's avatar
ruiruibupt 已提交
814
    await this.bridge.WXDeleteChatRoomMember(roomId, contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
815 816 817 818 819 820 821
  }

  public async roomAdd(
    roomId    : string,
    contactId : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId)
ruiruibupt's avatar
ruiruibupt 已提交
822
    await this.bridge.WXAddChatRoomMember(roomId, contactId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
823 824 825 826 827 828 829 830 831
  }

  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 已提交
832
      const payload = await this.roomPayload(roomId)
ruiruibupt's avatar
ruiruibupt 已提交
833
      return payload.topic
ruiruibupt's avatar
init  
ruiruibupt 已提交
834
    }
ruiruibupt's avatar
ruiruibupt 已提交
835 836 837

    await this.bridge.WXSetChatroomName(roomId, topic)

ruiruibupt's avatar
init  
ruiruibupt 已提交
838 839 840 841 842 843 844 845 846
    return
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
847
    // TODO
ruiruibupt's avatar
ruiruibupt 已提交
848
    // await this.bridge.crea
ruiruibupt's avatar
init  
ruiruibupt 已提交
849 850 851 852 853
    return 'mock_room_id'
  }

  public async roomQuit(roomId: string): Promise<void> {
    log.verbose('PuppetPadchat', 'roomQuit(%s)', roomId)
ruiruibupt's avatar
ruiruibupt 已提交
854
    await this.bridge.WXQuitChatRoom(roomId)
ruiruibupt's avatar
init  
ruiruibupt 已提交
855 856 857 858 859 860 861 862 863 864 865 866
  }

  /**
   *
   * FriendRequest
   *
   */
  public async friendRequestSend(
    contactId : string,
    hello     : string,
  ): Promise<void> {
    log.verbose('PuppetPadchat', 'friendRequestSend(%s, %s)', contactId, hello)
ruiruibupt's avatar
ruiruibupt 已提交
867 868 869 870 871 872 873 874 875 876 877 878 879

    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!')
    }

880 881
    // Issue #1252 : what's wrong here?

ruiruibupt's avatar
ruiruibupt 已提交
882 883 884 885 886 887
    await this.bridge.WXAddUser(
      strangerV1 || '',
      strangerV2 || '',
      '14',
      hello,
    )
ruiruibupt's avatar
init  
ruiruibupt 已提交
888 889 890 891 892 893 894
  }

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

ruiruibupt's avatar
ruiruibupt 已提交
896 897
    // TODO

ruiruibupt's avatar
ruiruibupt 已提交
898 899 900 901 902 903 904 905 906 907
    // 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 已提交
908 909
  }

ruiruibupt's avatar
ruiruibupt 已提交
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962
  public async friendRequestRawPayloadParser(rawPayload: any) : Promise<FriendRequestPayload> {
    log.verbose('PuppetWechat4u', 'friendRequestRawPayloadParser(%s)', rawPayload)

    // 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')
    // }
  }

  public async friendRequestRawPayload(id: string)            : Promise<any> {
    // log.verbose('PuppetWechat4u', 'friendRequestRawPayload(%s)', id)

    // TODO

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

    // return rawPayload
  }

ruiruibupt's avatar
init  
ruiruibupt 已提交
963 964 965
}

export default PuppetPadchat