contact.ts 18.5 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
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
20
import { FileBox } from 'file-box'
21
import { instanceToClass } from 'clone-class'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22

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

32
// import Message          from './message'
33

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
43
/**
L
lijiarui 已提交
44
 * All wechat contacts(friend) will be encapsulated as a Contact.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
45
 *
L
lijiarui 已提交
46
 * `Contact` is `Sayable`,
47
 * [Examples/Contact-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/contact-bot.ts}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
48
 */
49
export class Contact extends Accessory implements Sayable {
50

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

56 57 58 59 60 61 62 63 64 65 66 67 68
  protected static [POOL]: Map<string, Contact>
  protected static get pool() {
    return this[POOL]
  }
  protected static set pool(newPool: Map<string, Contact>) {
    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
  }
69

H
hcz 已提交
70 71
  /**
   * @private
72
   * About the Generic: https://stackoverflow.com/q/43003970/1123955
H
hcz 已提交
73
   */
74
  public static load<T extends typeof Contact>(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
75 76
    this : T,
    id   : string,
77 78
  ): T['prototype'] {
    if (!this.pool) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
79
      log.verbose('Contact', 'load(%s) init pool', id)
80 81
      this.pool = new Map<string, Contact>()
    }
82 83 84 85 86 87
    if (this === Contact) {
      throw new Error(
        'The lgobal Contact class can not be used directly!'
        + 'See: https://github.com/Chatie/wechaty/issues/1217',
      )
    }
88 89
    if (this.pool === Contact.pool) {
      throw new Error('the current pool is equal to the global pool error!')
90
    }
91 92 93
    const existingContact = this.pool.get(id)
    if (existingContact) {
      return existingContact
94
    }
95 96 97

    // 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.
98 99
    const newContact = new (this as any)(id) as Contact
    // newContact.payload = this.puppet.cacheContactPayload.get(id)
100

101
    this.pool.set(id, newContact)
102

103
    return newContact
104 105
  }

L
lijiarui 已提交
106
  /**
L
lijiarui 已提交
107
   * The way to search Contact
L
lijiarui 已提交
108
   *
L
lijiarui 已提交
109 110 111 112 113 114 115 116
   * @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 已提交
117
   *
L
lijiarui 已提交
118 119 120 121 122
   * 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 已提交
123
   * @example
L
lijiarui 已提交
124 125
   * const contactFindByName = await Contact.find({ name:"ruirui"} )
   * const contactFindByAlias = await Contact.find({ alias:"lijiarui"} )
L
lijiarui 已提交
126
   */
127 128 129 130
  public static async find<T extends typeof Contact>(
    this  : T,
    query : ContactQueryFilter,
  ): Promise<T['prototype'] | null> {
L
lijiarui 已提交
131 132
    log.verbose('Contact', 'find(%s)', JSON.stringify(query))

133
    const contactList = await this.findAll(query)
134 135 136 137 138

    if (!contactList) {
      return null
    }
    if (contactList.length < 1) {
L
lijiarui 已提交
139
      return null
140
    }
L
lijiarui 已提交
141 142

    if (contactList.length > 1) {
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
      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 已提交
165
    }
166 167
    log.warn('Contact', 'find() got %d contacts but no one is valid.', contactList.length)
    return null
L
lijiarui 已提交
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
  }

  /**
   * 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
   * const contactList = await Contact.findAll()                    // get the contact list of the bot
   * const contactList = await Contact.findAll({name: 'ruirui'})    // find allof the contacts whose name is 'ruirui'
   * const contactList = await Contact.findAll({alias: 'lijiarui'}) // find all of the contacts whose alias is 'lijiarui'
   */
187 188 189 190
  public static async findAll<T extends typeof Contact>(
    this  : T,
    query : ContactQueryFilter = { name: /.*/ },
  ): Promise<T['prototype'][]> {
L
lijiarui 已提交
191 192 193
    // log.verbose('Cotnact', 'findAll({ name: %s })', query.name)
    log.verbose('Cotnact', 'findAll({ %s })',
                            Object.keys(query)
194
                                  .map(k => `${k}: ${query[k as keyof ContactQueryFilter]}`)
L
lijiarui 已提交
195 196 197 198 199 200 201 202
                                  .join(', '),
              )

    if (Object.keys(query).length !== 1) {
      throw new Error('query only support one key. multi key support is not availble now.')
    }

    try {
203
      const contactIdList: string[] = await this.puppet.contactSearch(query)
204
      const contactList = contactIdList.map(id => this.load(id))
L
lijiarui 已提交
205 206 207 208 209

      await Promise.all(contactList.map(c => c.ready()))
      return contactList

    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
210
      log.error('Contact', 'this.puppet.contactFindAll() rejected: %s', e.message)
L
lijiarui 已提交
211 212 213 214
      return [] // fail safe
    }
  }

215 216 217 218 219
  // TODO
  public static async delete(contact: Contact): Promise<void> {
    log.verbose('Cotnact', 'static delete(%s)', contact.id)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220
  /**
221
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
222
   * Instance properties
223
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224
   */
225
  protected get payload(): undefined | ContactPayload {
226 227 228
    if (!this.id) {
      return undefined
    }
229 230
    return this.puppet.contactPayloadCache(this.id)
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231

232 233 234 235 236 237 238 239
  /**
   * @private
   */
  constructor(
    public readonly id: string,
  ) {
    super()
    log.silly('Contact', `constructor(${id})`)
240 241 242 243 244

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

    if (MyClass === Contact) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245 246 247 248
      throw new Error(
        'Contact class can not be instanciated directly!'
        + 'See: https://github.com/Chatie/wechaty/issues/1217',
      )
249 250 251 252 253
    }

    if (!this.puppet) {
      throw new Error('Contact class can not be instanciated without a puppet!')
    }
254 255 256 257 258 259
  }

  /**
   * @private
   */
  public toString(): string {
260 261 262
    if (!this.payload) {
      return this.constructor.name
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
263
    const identity = this.payload.alias || this.payload.name || this.id
264
    return `Contact<${identity || 'Unknown'}>`
265 266
  }

L
lijiarui 已提交
267 268 269 270
  /**
   * Sent Text to contact
   *
   * @param {string} text
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
271 272 273 274 275 276 277
   * @example
   * const contact = await Contact.find({name: 'lijiarui'})         // change 'lijiarui' to any of your contact name in wechat
   * try {
   *   await contact.say('welcome to wechaty!')
   * } catch (e) {
   *   console.error(e)
   * }
L
lijiarui 已提交
278
   */
279
  public async say(text: string): Promise<void>
L
lijiarui 已提交
280 281 282 283

  /**
   * Send Media File to Contact
   *
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
284
   * @param {Message} Message
L
lijiarui 已提交
285 286
   * @example
   * const contact = await Contact.find({name: 'lijiarui'})         // change 'lijiarui' to any of your contact name in wechat
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
287
   * try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
288
   *   await contact.say(bot.Message.create(__dirname + '/wechaty.png') // put the filePath you want to send here
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
289 290 291
   * } catch (e) {
   *   console.error(e)
   * }
L
lijiarui 已提交
292
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
293 294
  public async say(file: FileBox)    : Promise<void>
  public async say(contact: Contact) : Promise<void>
295

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
296 297
  public async say(textOrContactOrFile: string | Contact | FileBox): Promise<void> {
    log.verbose('Contact', 'say(%s)', textOrContactOrFile)
298

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
299 300
    if (typeof textOrContactOrFile === 'string') {
      /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
301
       * 1. Text
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
302
       */
303 304
      await this.puppet.messageSendText({
        contactId: this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
305 306 307
      }, textOrContactOrFile)
    } else if (textOrContactOrFile instanceof Contact) {
      /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
308
       * 2. Contact
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
309 310 311 312 313 314
       */
      await this.puppet.messageSendContact({
        contactId: this.id,
      }, textOrContactOrFile.id)
    } else if (textOrContactOrFile instanceof FileBox) {
      /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
315
       * 3. File
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
316
       */
317 318
      await this.puppet.messageSendFile({
        contactId: this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
319
      }, textOrContactOrFile)
320
    } else {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
321
      throw new Error('unsupported')
322 323
    }
  }
L
lijiarui 已提交
324 325 326 327 328 329 330 331

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

336 337 338
  public alias()                  : null | string
  public alias(newAlias:  string) : Promise<void>
  public alias(empty:     null)   : Promise<void>
L
lijiarui 已提交
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

  /**
   * 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 (李卓桓) 已提交
355 356
   * try {
   *   await contact.alias('lijiarui')
L
lijiarui 已提交
357
   *   console.log(`change ${contact.name()}'s alias successfully!`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
358
   * } catch (e) {
L
lijiarui 已提交
359 360 361 362
   *   console.log(`failed to change ${contact.name()} alias!`)
   * }
   *
   * @example <caption>DELETE the alias for a contact</caption>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
363 364
   * try {
   *   const oldAlias = await contact.alias(null)
L
lijiarui 已提交
365
   *   console.log(`delete ${contact.name()}'s alias successfully!`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
366 367
   *   console.log('old alias is ${oldAlias}`)
   * } catch (e) {
L
lijiarui 已提交
368 369 370
   *   console.log(`failed to delete ${contact.name()}'s alias!`)
   * }
   */
371
  public alias(newAlias?: null | string): null | string | Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
372
    log.silly('Contact', 'alias(%s)',
373 374 375 376 377
                            newAlias === undefined
                              ? ''
                              : newAlias,
                )

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
378 379 380 381
    if (!this.payload) {
      throw new Error('no payload')
    }

382 383 384 385
    if (typeof newAlias === 'undefined') {
      return this.payload && this.payload.alias || null
    }

386
    const future = this.puppet.contactAlias(this.id, newAlias)
387 388

    future
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
389
      .then(() => this.payload!.alias = (newAlias || undefined))
390 391 392 393 394 395 396
      .catch(e => {
        log.error('Contact', 'alias(%s) rejected: %s', newAlias, e.message)
        Raven.captureException(e)
      })

    return future
  }
L
lijiarui 已提交
397

L
lijiarui 已提交
398 399 400
  /**
   * Check if contact is stranger
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
401 402
   * @deprecated use friend() instead
   *
L
lijiarui 已提交
403
   * @returns {boolean | null} - True for not friend of the bot, False for friend of the bot, null for unknown.
L
lijiarui 已提交
404 405 406
   * @example
   * const isStranger = contact.stranger()
   */
407 408 409 410 411
  public stranger(): null | boolean {
    log.warn('Contact', 'stranger() DEPRECATED. use friend() instead.')
    if (!this.payload) return null
    return !this.friend()
  }
L
lijiarui 已提交
412

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
413 414 415 416 417 418 419
  /**
   * Check if contact is friend
   *
   * @returns {boolean | null} - True for friend of the bot, False for not friend of the bot, null for unknown.
   * @example
   * const isFriend = contact.friend()
   */
420 421 422 423 424 425 426
  public friend(): null | boolean {
    log.verbose('Contact', 'friend()')
    if (!this.payload) {
      return null
    }
    return this.payload.friend || null
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
427

J
Jas 已提交
428 429 430
  /**
   * Check if it's a offical account
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
431 432
   * @deprecated use type() instead
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
433
   * @returns {boolean | null} - True for official account, Flase for contact is not a official account, null for unknown
L
lijiarui 已提交
434 435
   * @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}
J
Jas 已提交
436 437 438
   * @example
   * const isOfficial = contact.official()
   */
439 440
  public official(): boolean {
    log.warn('Contact', 'official() DEPRECATED. use type() instead')
441
    return !!this.payload && this.payload.type === ContactType.Official
442
  }
J
Jas 已提交
443 444 445 446

  /**
   * Check if it's a personal account
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
447 448 449
   * @deprecated use type() instead
   *
   * @returns {boolean} - True for personal account, Flase for contact is not a personal account
J
Jas 已提交
450 451 452
   * @example
   * const isPersonal = contact.personal()
   */
453 454 455 456
  public personal(): boolean {
    log.warn('Contact', 'personal() DEPRECATED. use type() instead')
    return !this.official()
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
457 458 459 460 461 462 463 464

  /**
   * Return the type of the Contact
   *
   * @returns ContactType - Contact.Type.PERSONAL for personal account, Contact.Type.OFFICIAL for official account
   * @example
   * const isOfficial = contact.type() === Contact.Type.OFFICIAL
   */
465 466 467
  public type(): ContactType {
    return this.payload!.type
  }
J
Jas 已提交
468

L
lijiarui 已提交
469 470 471
  /**
   * Check if the contact is star contact.
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
472
   * @returns {boolean | null} - True for star friend, False for no star friend.
L
lijiarui 已提交
473 474 475
   * @example
   * const isStar = contact.star()
   */
476 477 478 479 480 481 482 483
  public star(): null | boolean {
    if (!this.payload) {
      return null
    }
    return this.payload.star === undefined
      ? null
      : this.payload.star
  }
L
lijiarui 已提交
484

485 486
  /**
   * Contact gender
L
lijiarui 已提交
487
   *
488
   * @returns {ContactGender.Male(2)|Gender.Female(1)|Gender.Unknown(0)}
L
lijiarui 已提交
489 490 491
   * @example
   * const gender = contact.gender()
   */
492
  public gender(): ContactGender {
493 494
    return this.payload
      ? this.payload.gender
495
      : ContactGender.Unknown
496
  }
L
lijiarui 已提交
497 498 499 500

  /**
   * Get the region 'province' from a contact
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
501
   * @returns {string | null}
L
lijiarui 已提交
502 503
   * @example
   * const province = contact.province()
504
   */
505 506 507
  public province(): null | string {
    return this.payload && this.payload.province || null
  }
L
lijiarui 已提交
508 509 510 511

  /**
   * Get the region 'city' from a contact
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
512
   * @returns {string | null}
L
lijiarui 已提交
513 514 515
   * @example
   * const city = contact.city()
   */
516 517 518
  public city(): null | string {
    return this.payload && this.payload.city || null
  }
519 520 521

  /**
   * Get avatar picture file stream
L
lijiarui 已提交
522
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
523
   * @returns {Promise<FileBox>}
L
lijiarui 已提交
524 525
   * @example
   * const avatarFileName = contact.name() + `.jpg`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
526
   * const fileBox = await contact.avatar()
L
lijiarui 已提交
527
   * const avatarWriteStream = createWriteStream(avatarFileName)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
528
   * fileBox.pipe(avatarWriteStream)
L
lijiarui 已提交
529
   * log.info('Bot', 'Contact: %s: %s with avatar file: %s', contact.weixin(), contact.name(), avatarFileName)
530
   */
531
  // TODO: use File to replace ReadableStream
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
532
  public async avatar(): Promise<FileBox> {
533 534
    log.verbose('Contact', 'avatar()')

535
    return this.puppet.contactAvatar(this.id)
536
  }
537

L
lijiarui 已提交
538
  /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
539
   * Force reload(re-ready()) data for Contact
L
lijiarui 已提交
540
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
541 542
   * @deprecated use sync() instead
   *
L
lijiarui 已提交
543 544 545 546
   * @returns {Promise<this>}
   * @example
   * await contact.refresh()
   */
547 548 549 550
  public refresh(): Promise<void> {
    log.warn('Contact', 'refresh() DEPRECATED. use sync() instead.')
    return this.sync()
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
551 552 553 554 555 556 557 558

  /**
   * sycc data for Contact
   *
   * @returns {Promise<this>}
   * @example
   * await contact.sync()
   */
559
  public async sync(): Promise<void> {
560
    await this.ready(true)
561
  }
L
lijiarui 已提交
562

L
lijiarui 已提交
563 564 565
  /**
   * @private
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
566
  public async ready(dirty = false): Promise<void> {
567
    log.silly('Contact', 'ready() @ %s', this.puppet)
568 569 570 571 572 573 574

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

    try {
575 576 577 578
      if (dirty) {
        await this.puppet.contactPayloadDirty(this.id)
      }
      await this.puppet.contactPayload(this.id)
579
      // log.silly('Contact', `ready() this.puppet.contactPayload(%s) resolved`, this)
580 581 582 583 584 585 586 587 588 589

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

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
591 592 593
  /**
   * @private
   */
594 595 596
  public isReady(): boolean {
    return !!(this.payload && this.payload.name)
  }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
597

L
lijiarui 已提交
598 599 600 601 602 603 604
  /**
   * Check if contact is self
   *
   * @returns {boolean} True for contact is self, False for contact is others
   * @example
   * const isSelf = contact.self()
   */
605
  public self(): boolean {
606
    const userId = this.puppet.selfId()
607

608
    if (!userId) {
609 610 611
      return false
    }

612
    return this.id === userId
613
  }
614

L
lijiarui 已提交
615
  /**
L
lijiarui 已提交
616
   * Get the weixin number from a contact.
L
lijiarui 已提交
617
   *
L
lijiarui 已提交
618
   * Sometimes cannot get weixin number due to weixin security mechanism, not recommend.
L
lijiarui 已提交
619
   *
L
lijiarui 已提交
620 621
   * @private
   * @returns {string | null}
L
lijiarui 已提交
622
   * @example
L
lijiarui 已提交
623
   * const weixin = contact.weixin()
L
lijiarui 已提交
624
   */
625 626 627
  public weixin(): null | string {
    return this.payload && this.payload.weixin || null
  }
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
}

export class ContactSelf extends Contact {
  constructor(
    id: string,
  ) {
    super(id)
  }

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

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

    if (!file) {
      return await super.avatar()
    }

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

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

654 655 656
  public async qrcode(): Promise<string> {
    log.verbose('Contact', 'qrcode()')

657
    if (this.id !== this.puppet.selfId()) {
658 659 660
      throw new Error('only can get qrcode for the login userself')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
661
    return await this.puppet.contactQrcode(this.id)
662 663
  }

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

Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
666
export default Contact