contact.ts 20.6 KB
Newer Older
1
/**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
2
 *   Wechaty - https://github.com/chatie/wechaty
3
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4
 *   @copyright 2016-2018 Huan LI <zixia@zixia.net>
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 *   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.
 *
L
lijiarui 已提交
18
 *   @ignore
19
 */
20
import { instanceToClass } from 'clone-class'
21
import { FileBox } from 'file-box'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22

23 24 25
import {
  Accessory,
}             from '../accessory'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
26
import {
27
  log,
28
  qrCodeForChatie,
29
  Raven,
30
}                   from '../config'
31 32 33
import {
  Sayable,
}             from '../types'
34

35
import {
36
  ContactGender,
37 38 39
  ContactPayload,
  ContactQueryFilter,
  ContactType,
40
}                         from 'wechaty-puppet'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
41

42 43
export const POOL = Symbol('pool')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
44
/**
L
lijiarui 已提交
45
 * All wechat contacts(friend) will be encapsulated as a Contact.
L
lijiarui 已提交
46
 * [Examples/Contact-Bot]{@link https://github.com/Chatie/wechaty/blob/1523c5e02be46ebe2cc172a744b2fbe53351540e/examples/contact-bot.ts}
47 48 49
 *
 * @property {string}  id               - Get Contact id.
 * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/Chatie/wechaty/wiki/Puppet#3-puppet-compatible-table)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
50
 */
51
export class Contact extends Accessory implements Sayable {
52

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
53
  // tslint:disable-next-line:variable-name
54
  public static Type   = ContactType
55
  // tslint:disable-next-line:variable-name
56
  public static Gender = ContactGender
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
57

58
  protected static [POOL]: Map<string, Contact>
59
  protected static get pool () {
60 61
    return this[POOL]
  }
62
  protected static set pool (newPool: Map<string, Contact>) {
63 64 65 66 67 68 69 70
    if (this === Contact) {
      throw new Error(
        'The global Contact class can not be used directly!'
        + 'See: https://github.com/Chatie/wechaty/issues/1217',
      )
    }
    this[POOL] = newPool
  }
71

H
hcz 已提交
72 73
  /**
   * @private
74
   * About the Generic: https://stackoverflow.com/q/43003970/1123955
H
hcz 已提交
75
   */
L
lijiarui 已提交
76 77
  /**
   * Get Contact by id
78 79
   * > Tips:
   * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/Chatie/wechaty/wiki/Puppet#3-puppet-compatible-table)
L
lijiarui 已提交
80 81 82 83 84 85 86 87 88
   *
   * @static
   * @param {string} id
   * @returns {Contact}
   * @example
   * const bot = new Wechaty()
   * await bot.start()
   * const contact = bot.Contact.load('contactId')
   */
89
  public static load<T extends typeof Contact> (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90 91
    this : T,
    id   : string,
92 93
  ): T['prototype'] {
    if (!this.pool) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
94
      log.verbose('Contact', 'load(%s) init pool', id)
95 96
      this.pool = new Map<string, Contact>()
    }
97 98 99 100 101 102
    if (this === Contact) {
      throw new Error(
        'The lgobal Contact class can not be used directly!'
        + 'See: https://github.com/Chatie/wechaty/issues/1217',
      )
    }
103 104
    if (this.pool === Contact.pool) {
      throw new Error('the current pool is equal to the global pool error!')
105
    }
106 107 108
    const existingContact = this.pool.get(id)
    if (existingContact) {
      return existingContact
109
    }
110 111 112

    // when we call `load()`, `this` should already be extend-ed a child class.
    // so we force `this as any` at here to make the call.
113 114
    const newContact = new (this as any)(id) as Contact
    // newContact.payload = this.puppet.cacheContactPayload.get(id)
115

116
    this.pool.set(id, newContact)
117

118
    return newContact
119 120
  }

L
lijiarui 已提交
121
  /**
L
lijiarui 已提交
122
   * The way to search Contact
L
lijiarui 已提交
123
   *
L
lijiarui 已提交
124 125 126 127 128 129 130 131
   * @typedef    ContactQueryFilter
   * @property   {string} name    - The name-string set by user-self, should be called name
   * @property   {string} alias   - The name-string set by bot for others, should be called alias
   * [More Detail]{@link https://github.com/Chatie/wechaty/issues/365}
   */

  /**
   * Try to find a contact by filter: {name: string | RegExp} / {alias: string | RegExp}
L
lijiarui 已提交
132
   *
L
lijiarui 已提交
133 134 135 136 137
   * Find contact by name or alias, if the result more than one, return the first one.
   *
   * @static
   * @param {ContactQueryFilter} query
   * @returns {(Promise<Contact | null>)} If can find the contact, return Contact, or return null
L
lijiarui 已提交
138
   * @example
L
lijiarui 已提交
139 140 141 142
   * const bot = new Wechaty()
   * await bot.start()
   * const contactFindByName = await bot.Contact.find({ name:"ruirui"} )
   * const contactFindByAlias = await bot.Contact.find({ alias:"lijiarui"} )
L
lijiarui 已提交
143
   */
144
  public static async find<T extends typeof Contact> (
145
    this  : T,
146
    query : string | ContactQueryFilter,
147
  ): Promise<T['prototype'] | null> {
L
lijiarui 已提交
148 149
    log.verbose('Contact', 'find(%s)', JSON.stringify(query))

150
    const contactList = await this.findAll(query)
151 152 153 154 155

    if (!contactList) {
      return null
    }
    if (contactList.length < 1) {
L
lijiarui 已提交
156
      return null
157
    }
L
lijiarui 已提交
158 159

    if (contactList.length > 1) {
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
      log.warn('Contact', 'find() got more than one(%d) result', contactList.length)
    }

    let n = 0
    for (n = 0; n < contactList.length; n++) {
      const contact = contactList[n]
      // use puppet.contactValidate() to confirm double confirm that this contactId is valid.
      // https://github.com/lijiarui/wechaty-puppet-padchat/issues/64
      // https://github.com/Chatie/wechaty/issues/1345
      const valid = await this.puppet.contactValidate(contact.id)
      if (valid) {
        log.verbose('Contact', 'find() confirm contact[#%d] with id=%d is vlaid result, return it.',
                            n,
                            contact.id,
                  )
        return contact
      } else {
        log.verbose('Contact', 'find() confirm contact[#%d] with id=%d is INVALID result, try next',
                            n,
                            contact.id,
                    )
      }
L
lijiarui 已提交
182
    }
183 184
    log.warn('Contact', 'find() got %d contacts but no one is valid.', contactList.length)
    return null
L
lijiarui 已提交
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
  }

  /**
   * Find contact by `name` or `alias`
   *
   * If use Contact.findAll() get the contact list of the bot.
   *
   * #### definition
   * - `name`   the name-string set by user-self, should be called name
   * - `alias`  the name-string set by bot for others, should be called alias
   *
   * @static
   * @param {ContactQueryFilter} [queryArg]
   * @returns {Promise<Contact[]>}
   * @example
L
lijiarui 已提交
200 201 202 203 204
   * const bot = new Wechaty()
   * await bot.start()
   * const contactList = await bot.Contact.findAll()                    // get the contact list of the bot
   * const contactList = await bot.Contact.findAll({name: 'ruirui'})    // find allof the contacts whose name is 'ruirui'
   * const contactList = await bot.Contact.findAll({alias: 'lijiarui'}) // find all of the contacts whose alias is 'lijiarui'
L
lijiarui 已提交
205
   */
206
  public static async findAll<T extends typeof Contact> (
207
    this  : T,
208
    query? : string | ContactQueryFilter,
209
  ): Promise<Array<T['prototype']>> {
ruiruibupt's avatar
ruiruibupt 已提交
210
    log.verbose('Contact', 'findAll(%s)', JSON.stringify(query))
L
lijiarui 已提交
211

212
    if (query && Object.keys(query).length !== 1) {
L
lijiarui 已提交
213 214 215 216
      throw new Error('query only support one key. multi key support is not availble now.')
    }

    try {
217
      const contactIdList: string[] = await this.puppet.contactSearch(query)
218
      const contactList = contactIdList.map(id => this.load(id))
L
lijiarui 已提交
219

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220
      const BATCH_SIZE = 16
221 222
      let   batchIndex = 0

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223 224
      const invalidContactId: string[] = []

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
225
      while (batchIndex * BATCH_SIZE < contactList.length) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
226
        const batchContactList = contactList.slice(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227 228
          BATCH_SIZE * batchIndex,
          BATCH_SIZE * (batchIndex + 1),
229 230
        )
        await Promise.all(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231
          batchContactList.map(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
232 233 234 235 236 237 238
            c => {
              c.ready()
              .catch(e => {
                log.error('Contact', 'findAll() ready() exception: %s', e.message)
                invalidContactId.push(c.id)
              })
            },
239 240 241 242 243 244
          ),
        )

        batchIndex++
      }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245
      return contactList.filter(contact => !(contact.id in invalidContactId))
L
lijiarui 已提交
246 247

    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
248
      log.error('Contact', 'this.puppet.contactFindAll() rejected: %s', e.message)
L
lijiarui 已提交
249 250 251 252
      return [] // fail safe
    }
  }

253
  // TODO
254
  public static async delete (contact: Contact): Promise<void> {
ruiruibupt's avatar
ruiruibupt 已提交
255
    log.verbose('Contact', 'static delete(%s)', contact.id)
256 257
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
258
  /**
259
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
260
   * Instance properties
L
lijiarui 已提交
261
   * @private
262
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
263
   */
264
  protected get payload (): undefined | ContactPayload {
265 266 267
    if (!this.id) {
      return undefined
    }
268 269
    return this.puppet.contactPayloadCache(this.id)
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
270

271 272
  public readonly id: string // Contact Id

273 274 275
  /**
   * @private
   */
276
  constructor (
277
    id: string,
278 279 280
  ) {
    super()
    log.silly('Contact', `constructor(${id})`)
281

282 283
    this.id = id

284 285 286 287
    // tslint:disable-next-line:variable-name
    const MyClass = instanceToClass(this, Contact)

    if (MyClass === Contact) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
288 289 290 291
      throw new Error(
        'Contact class can not be instanciated directly!'
        + 'See: https://github.com/Chatie/wechaty/issues/1217',
      )
292 293 294 295 296
    }

    if (!this.puppet) {
      throw new Error('Contact class can not be instanciated without a puppet!')
    }
297 298 299 300 301
  }

  /**
   * @private
   */
302
  public toString (): string {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
303 304 305 306
    const identity = this.payload
      ? this.payload.alias || this.payload.name || this.id
      : this.id
    return `Contact<${identity}>`
307 308
  }

309 310 311
  public async say (text: string): Promise<void>
  public async say (file: FileBox)    : Promise<void>
  public async say (contact: Contact) : Promise<void>
L
lijiarui 已提交
312 313

  /**
314 315 316
   * > Tips:
   * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/Chatie/wechaty/wiki/Puppet#3-puppet-compatible-table)
   *
L
lijiarui 已提交
317 318 319 320
   * @param {(string | Contact | FileBox)} textOrContactOrFile
   * send text, Contact, or file to contact. </br>
   * You can use {@link https://www.npmjs.com/package/file-box|FileBox} to send file
   * @returns {Promise<void>}
L
lijiarui 已提交
321
   * @example
L
lijiarui 已提交
322 323 324 325
   * const bot = new Wechaty()
   * await bot.start()
   * const contact = await bot.Contact.find({name: 'lijiarui'})  // change 'lijiarui' to any of your contact name in wechat
   *
L
lijiarui 已提交
326
   * // 1. send text to contact
L
lijiarui 已提交
327 328 329
   *
   * await contact.say('welcome to wechaty!')
   *
L
lijiarui 已提交
330
   * // 2. send media file to contact
L
lijiarui 已提交
331 332 333 334 335 336 337
   *
   * import { FileBox }  from 'file-box'
   * const fileBox1 = FileBox.fromUrl('https://chatie.io/wechaty/images/bot-qr-code.png')
   * const fileBox2 = FileBox.fromLocal('/tmp/text.txt')
   * await contact.say(fileBox1)
   * await contact.say(fileBox2)
   *
L
lijiarui 已提交
338
   * // 3. send contact card to contact
L
lijiarui 已提交
339 340 341
   *
   * const contactCard = bot.Contact.load('contactId')
   * await contact.say(contactCard)
L
lijiarui 已提交
342
   */
343
  public async say (textOrContactOrFile: string | Contact | FileBox): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
344
    log.verbose('Contact', 'say(%s)', textOrContactOrFile)
345

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
346 347
    if (typeof textOrContactOrFile === 'string') {
      /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
348
       * 1. Text
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
349
       */
350 351
      await this.puppet.messageSendText({
        contactId: this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
352 353 354
      }, textOrContactOrFile)
    } else if (textOrContactOrFile instanceof Contact) {
      /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
355
       * 2. Contact
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
356 357 358 359 360 361
       */
      await this.puppet.messageSendContact({
        contactId: this.id,
      }, textOrContactOrFile.id)
    } else if (textOrContactOrFile instanceof FileBox) {
      /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
362
       * 3. File
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
363
       */
364 365
      await this.puppet.messageSendFile({
        contactId: this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
366
      }, textOrContactOrFile)
367
    } else {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
368
      throw new Error('unsupported')
369 370
    }
  }
L
lijiarui 已提交
371 372 373 374 375 376 377 378

  /**
   * Get the name from a contact
   *
   * @returns {string}
   * @example
   * const name = contact.name()
   */
379
  public name (): string {
380 381
    return this.payload && this.payload.name || ''
  }
L
lijiarui 已提交
382

383 384 385
  public alias ()                  : null | string
  public alias (newAlias:  string) : Promise<void>
  public alias (empty:     null)   : Promise<void>
L
lijiarui 已提交
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401

  /**
   * GET / SET / DELETE the alias for a contact
   *
   * Tests show it will failed if set alias too frequently(60 times in one minute).
   * @param {(none | string | null)} newAlias
   * @returns {(string | null | Promise<boolean>)}
   * @example <caption> GET the alias for a contact, return {(string | null)}</caption>
   * const alias = contact.alias()
   * if (alias === null) {
   *   console.log('You have not yet set any alias for contact ' + contact.name())
   * } else {
   *   console.log('You have already set an alias for contact ' + contact.name() + ':' + alias)
   * }
   *
   * @example <caption>SET the alias for a contact</caption>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
402 403
   * try {
   *   await contact.alias('lijiarui')
L
lijiarui 已提交
404
   *   console.log(`change ${contact.name()}'s alias successfully!`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
405
   * } catch (e) {
L
lijiarui 已提交
406 407 408 409
   *   console.log(`failed to change ${contact.name()} alias!`)
   * }
   *
   * @example <caption>DELETE the alias for a contact</caption>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
410 411
   * try {
   *   const oldAlias = await contact.alias(null)
L
lijiarui 已提交
412
   *   console.log(`delete ${contact.name()}'s alias successfully!`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
413 414
   *   console.log('old alias is ${oldAlias}`)
   * } catch (e) {
L
lijiarui 已提交
415 416 417
   *   console.log(`failed to delete ${contact.name()}'s alias!`)
   * }
   */
418
  public alias (newAlias?: null | string): null | string | Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
419
    log.silly('Contact', 'alias(%s)',
420 421 422 423 424
                            newAlias === undefined
                              ? ''
                              : newAlias,
                )

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
425 426 427 428
    if (!this.payload) {
      throw new Error('no payload')
    }

429 430 431 432
    if (typeof newAlias === 'undefined') {
      return this.payload && this.payload.alias || null
    }

433
    const future = this.puppet.contactAlias(this.id, newAlias)
434

435 436
    const payload = this.payload

437
    future
438
      .then(() => payload.alias = (newAlias || undefined))
439 440 441 442 443 444 445
      .catch(e => {
        log.error('Contact', 'alias(%s) rejected: %s', newAlias, e.message)
        Raven.captureException(e)
      })

    return future
  }
L
lijiarui 已提交
446

L
lijiarui 已提交
447 448
  /**
   *
L
lijiarui 已提交
449 450
   * @description
   * Should use {@link Contact#friend} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
451
   *
L
lijiarui 已提交
452
   * @deprecated
L
lijiarui 已提交
453
   */
454
  public stranger (): null | boolean {
455 456 457 458
    log.warn('Contact', 'stranger() DEPRECATED. use friend() instead.')
    if (!this.payload) return null
    return !this.friend()
  }
L
lijiarui 已提交
459

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
460 461 462
  /**
   * Check if contact is friend
   *
463 464 465
   * > Tips:
   * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/Chatie/wechaty/wiki/Puppet#3-puppet-compatible-table)
   *
L
lijiarui 已提交
466 467 468 469
   * @returns {boolean | null}
   *
   * <br>True for friend of the bot <br>
   * False for not friend of the bot, null for unknown.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
470 471 472
   * @example
   * const isFriend = contact.friend()
   */
473
  public friend (): null | boolean {
474 475 476 477 478 479
    log.verbose('Contact', 'friend()')
    if (!this.payload) {
      return null
    }
    return this.payload.friend || null
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
480

J
Jas 已提交
481
  /**
L
lijiarui 已提交
482
   * @ignore
L
lijiarui 已提交
483 484
   * @see {@link https://github.com/Chatie/webwx-app-tracker/blob/7c59d35c6ea0cff38426a4c5c912a086c4c512b2/formatted/webwxApp.js#L3243|webwxApp.js#L324}
   * @see {@link https://github.com/Urinx/WeixinBot/blob/master/README.md|Urinx/WeixinBot/README}
L
lijiarui 已提交
485 486 487 488 489 490
   */
  /**
   * @description
   * Check if it's a offical account, should use {@link Contact#type} instead
   * @deprecated
   *
J
Jas 已提交
491
   */
492
  public official (): boolean {
493
    log.warn('Contact', 'official() DEPRECATED. use type() instead')
494
    return !!this.payload && this.payload.type === ContactType.Official
495
  }
J
Jas 已提交
496 497

  /**
L
lijiarui 已提交
498 499 500
   * @description
   * Check if it's a personal account, should use {@link Contact#type} instead
   * @deprecated
J
Jas 已提交
501
   */
502
  public personal (): boolean {
503
    log.warn('Contact', 'personal() DEPRECATED. use type() instead')
504
    return !!this.payload && this.payload.type === ContactType.Personal
505
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
506

L
lijiarui 已提交
507 508 509 510 511 512 513 514
  /**
   * Enum for ContactType
   * @enum {number}
   * @property {number} Unknown    - ContactType.Unknown    (0) for Unknown
   * @property {number} Personal   - ContactType.Personal   (1) for Personal
   * @property {number} Official   - ContactType.Official   (2) for Official
   */

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
515 516
  /**
   * Return the type of the Contact
L
lijiarui 已提交
517 518
   * > Tips: ContactType is enum here.</br>
   * @returns {ContactType.Unknown | ContactType.Personal | ContactType.Official}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
519 520
   *
   * @example
L
lijiarui 已提交
521 522 523
   * const bot = new Wechaty()
   * await bot.start()
   * const isOfficial = contact.type() === bot.Contact.Type.Official
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
524
   */
525 526 527 528 529
  public type (): ContactType {
    if (!this.payload) {
      throw new Error('no payload')
    }
    return this.payload.type
530
  }
J
Jas 已提交
531

L
lijiarui 已提交
532
  /**
L
lijiarui 已提交
533 534
   * @private
   * TODO
L
lijiarui 已提交
535 536
   * Check if the contact is star contact.
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
537
   * @returns {boolean | null} - True for star friend, False for no star friend.
L
lijiarui 已提交
538 539 540
   * @example
   * const isStar = contact.star()
   */
541
  public star (): null | boolean {
542 543 544 545 546 547 548
    if (!this.payload) {
      return null
    }
    return this.payload.star === undefined
      ? null
      : this.payload.star
  }
L
lijiarui 已提交
549

550 551
  /**
   * Contact gender
L
lijiarui 已提交
552
   * > Tips: ContactGender is enum here. </br>
L
lijiarui 已提交
553
   *
L
lijiarui 已提交
554
   * @returns {ContactGender.Unknown | ContactGender.Male | ContactGender.Female}
L
lijiarui 已提交
555
   * @example
L
lijiarui 已提交
556
   * const gender = contact.gender() === bot.Contact.Gender.Male
L
lijiarui 已提交
557
   */
558
  public gender (): ContactGender {
559 560
    return this.payload
      ? this.payload.gender
561
      : ContactGender.Unknown
562
  }
L
lijiarui 已提交
563 564 565 566

  /**
   * Get the region 'province' from a contact
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
567
   * @returns {string | null}
L
lijiarui 已提交
568 569
   * @example
   * const province = contact.province()
570
   */
571
  public province (): null | string {
572 573
    return this.payload && this.payload.province || null
  }
L
lijiarui 已提交
574 575 576 577

  /**
   * Get the region 'city' from a contact
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
578
   * @returns {string | null}
L
lijiarui 已提交
579 580 581
   * @example
   * const city = contact.city()
   */
582
  public city (): null | string {
583 584
    return this.payload && this.payload.city || null
  }
585 586 587

  /**
   * Get avatar picture file stream
L
lijiarui 已提交
588
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
589
   * @returns {Promise<FileBox>}
L
lijiarui 已提交
590
   * @example
L
lijiarui 已提交
591
   * // Save avatar to local file like `1-name.jpg`
L
lijiarui 已提交
592 593 594 595 596
   *
   * const file = await contact.avatar()
   * const name = file.name
   * await file.toFile(name, true)
   * console.log(`Contact: ${contact.name()} with avatar file: ${name}`)
597
   */
598
  public async avatar (): Promise<FileBox> {
599 600
    log.verbose('Contact', 'avatar()')

601 602 603 604 605 606 607
    try {
      const fileBox = await this.puppet.contactAvatar(this.id)
      return fileBox
    } catch (e) {
      log.error('Contact', 'avatar() exception: %s', e.message)
      return qrCodeForChatie()
    }
608
  }
609

L
lijiarui 已提交
610
  /**
L
lijiarui 已提交
611 612
   * @description
   * Force reload(re-ready()) data for Contact, use {@link Contact#sync} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
613
   *
L
lijiarui 已提交
614
   * @deprecated
L
lijiarui 已提交
615
   */
616
  public refresh (): Promise<void> {
617 618 619
    log.warn('Contact', 'refresh() DEPRECATED. use sync() instead.')
    return this.sync()
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
620 621

  /**
L
lijiarui 已提交
622
   * Force reload(re-ready()) data for Contact,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
623 624 625 626 627
   *
   * @returns {Promise<this>}
   * @example
   * await contact.sync()
   */
628
  public async sync (): Promise<void> {
629
    await this.ready(true)
630
  }
L
lijiarui 已提交
631

L
lijiarui 已提交
632 633 634
  /**
   * @private
   */
635
  public async ready (dirty = false): Promise<void> {
636
    log.silly('Contact', 'ready() @ %s', this.puppet)
637 638 639 640 641 642 643

    if (this.isReady()) { // already ready
      log.silly('Contact', 'ready() isReady() true')
      return
    }

    try {
644 645 646 647
      if (dirty) {
        await this.puppet.contactPayloadDirty(this.id)
      }
      await this.puppet.contactPayload(this.id)
648
      // log.silly('Contact', `ready() this.puppet.contactPayload(%s) resolved`, this)
649 650 651 652 653 654 655 656 657 658

    } catch (e) {
      log.error('Contact', `ready() this.puppet.contactPayload(%s) exception: %s`,
                            this,
                            e.message,
                )
      Raven.captureException(e)
      throw e
    }
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
659

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
660 661 662
  /**
   * @private
   */
663
  public isReady (): boolean {
664 665
    return !!(this.payload && this.payload.name)
  }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
666

L
lijiarui 已提交
667 668 669 670 671 672 673
  /**
   * Check if contact is self
   *
   * @returns {boolean} True for contact is self, False for contact is others
   * @example
   * const isSelf = contact.self()
   */
674
  public self (): boolean {
675
    const userId = this.puppet.selfId()
676

677
    if (!userId) {
678 679 680
      return false
    }

681
    return this.id === userId
682
  }
683

L
lijiarui 已提交
684
  /**
L
lijiarui 已提交
685
   * Get the weixin number from a contact.
L
lijiarui 已提交
686
   *
L
lijiarui 已提交
687
   * Sometimes cannot get weixin number due to weixin security mechanism, not recommend.
L
lijiarui 已提交
688
   *
L
lijiarui 已提交
689 690
   * @private
   * @returns {string | null}
L
lijiarui 已提交
691
   * @example
L
lijiarui 已提交
692
   * const weixin = contact.weixin()
L
lijiarui 已提交
693
   */
694
  public weixin (): null | string {
695 696
    return this.payload && this.payload.weixin || null
  }
697 698
}

699 700
// tslint:disable:max-classes-per-file

701
export class ContactSelf extends Contact {
702
  constructor (
703 704 705 706 707
    id: string,
  ) {
    super(id)
  }

708 709
  public async avatar ()              : Promise<FileBox>
  public async avatar (file: FileBox) : Promise<void>
710

711
  public async avatar (file?: FileBox): Promise<void | FileBox> {
712 713 714
    log.verbose('Contact', 'avatar(%s)', file ? file.name : '')

    if (!file) {
715 716
      const filebox = await super.avatar()
      return filebox
717 718 719 720 721 722 723 724
    }

    if (this.id !== this.puppet.selfId()) {
      throw new Error('set avatar only available for user self')
    }

    await this.puppet.contactAvatar(this.id, file)
  }
725

726
  public async qrcode (): Promise<string> {
727 728
    log.verbose('Contact', 'qrcode()')

729
    if (this.id !== this.puppet.selfId()) {
730 731 732
      throw new Error('only can get qrcode for the login userself')
    }

733 734
    const qrcodeData = await this.puppet.contactQrcode(this.id)
    return qrcodeData
735 736
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
737
}