@juzi/wechaty
Version:
Wechaty is a RPA SDK for Chatbot Makers.
939 lines (829 loc) • 29.4 kB
text/typescript
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* 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.
*
*/
import * as PUPPET from '@juzi/wechaty-puppet'
import type {
FileBoxInterface,
} from 'file-box'
import {
concurrencyExecuter,
} from 'rx-queue'
import type {
Constructor,
} from 'clone-class'
import {
log,
} from '../config.js'
import { ContactEventEmitter } from '../schemas/mod.js'
import {
poolifyMixin,
wechatifyMixin,
validationMixin,
} from '../user-mixins/mod.js'
import {
deliverSayableConversationPuppet,
} from '../sayable/mod.js'
import type {
SayableSayer,
Sayable,
SayOptionsObject,
} from '../sayable/mod.js'
import { stringifyFilter } from '../helper-functions/stringify-filter.js'
import type { MessageInterface } from './message.js'
import type { TagInterface } from './tag.js'
import type { ContactSelfImpl } from './contact-self.js'
const MixinBase = wechatifyMixin(
poolifyMixin(
ContactEventEmitter,
)<ContactImplInterface>(),
)
/**
* All wechat contacts(friend) will be encapsulated as a Contact.
* [Examples/Contact-Bot]{@link https://github.com/wechaty/wechaty/blob/1523c5e02be46ebe2cc172a744b2fbe53351540e/examples/contact-bot.ts}
*
* @property {string} id - Get Contact id.
* This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
*/
class ContactMixin extends MixinBase implements SayableSayer {
static Type = PUPPET.types.Contact
static Gender = PUPPET.types.ContactGender
/**
* The way to search Contact
*
* @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/wechaty/wechaty/issues/365}
*/
/**
* Try to find a contact by filter: {name: string | RegExp} / {alias: string | RegExp}
*
* Find contact by name or alias, if the result more than one, return the first one.
*
* @static
* @param {string | ContactQueryFilter} query `string` will search `name` & `alias`
* @returns {(Promise<undefined | ContactInterface>)} If can find the contact, return Contact, or return null
* @example
* const bot = new Wechaty()
* await bot.start()
* const contactFindByName = await bot.Contact.find({ name:"ruirui"} )
* const contactFindByAlias = await bot.Contact.find({ alias:"lijiarui"} )
*/
static async find (
query : string | PUPPET.filters.Contact,
): Promise<undefined | ContactInterface> {
log.silly('Contact', 'find(%s)', JSON.stringify(query, stringifyFilter))
if (typeof query === 'object' && query.id) {
let contact: ContactImpl
if (this.wechaty.puppet.currentUserId === query.id) {
/**
* When the contact id is the currentUserId, return a ContactSelfImpl as the Contact
*/
contact = (this.wechaty.ContactSelf as any as typeof ContactSelfImpl).load(query.id)
} else {
contact = (this.wechaty.Contact as any as typeof ContactImpl).load(query.id)
}
// const contact = (this.wechaty.Contact as any as typeof ContactImpl).load(query.id)
try {
await contact.ready()
} catch (e) {
this.wechaty.emitError(e)
return undefined
}
return contact
}
const contactList = await this.findAll(query)
if (contactList.length <= 0) {
return
}
if (contactList.length > 1) {
log.warn('Contact', 'find() got more than 1 result: %d total', contactList.length)
}
for (const [ idx, contact ] of contactList.entries()) {
// use puppet.contactValidate() to confirm double confirm that this contactId is valid.
// https://github.com/wechaty/wechaty-puppet-padchat/issues/64
// https://github.com/wechaty/wechaty/issues/1345
const valid = await this.wechaty.puppet.contactValidate(contact.id)
if (valid) {
log.silly('Contact', 'find() contact<id=%s> is valid, return it', idx, contact.id)
return contact
} else {
log.silly('Contact', 'find() contact<id=%s> is invalid, skip it', idx, contact.id)
}
}
log.warn('Contact', 'find() all of %d contacts are invalid', contactList.length)
return undefined
}
/**
* 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 {string | ContactQueryFilter} [queryArg] `string` will search `name` & `alias`
* @returns {Promise<ContactInterface[]>}
* @example
* 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 all of the contacts whose name is 'ruirui'
* const contactList = await bot.Contact.findAll({ alias: 'lijiarui' }) // find all of the contacts whose alias is 'lijiarui'
*/
static async findAll (
query? : string | PUPPET.filters.Contact,
): Promise<ContactInterface[]> {
log.verbose('Contact', 'findAll(%s)', JSON.stringify(query, stringifyFilter) || '')
const contactIdList: string[] = await this.wechaty.puppet.contactSearch(query)
let continuousErrorCount = 0
let totalErrorCount = 0
const totalErrorThreshold = Math.round(contactIdList.length / 5)
const idToContact = async (id: string) => {
if (!this.wechaty.isLoggedIn) {
throw new Error('wechaty not logged in')
}
const result = await this.wechaty.Contact.find({ id }).catch(e => {
this.wechaty.emitError(e)
continuousErrorCount++
totalErrorCount++
if (continuousErrorCount > 5) {
throw new Error('5 continuous errors!')
}
if (totalErrorCount > totalErrorThreshold) {
throw new Error(`${totalErrorThreshold} total errors!`)
}
})
continuousErrorCount = 0
return result
}
/**
* we need to use concurrencyExecuter to reduce the parallel number of the requests
*/
const CONCURRENCY = 17
const contactIterator = concurrencyExecuter(CONCURRENCY)(idToContact)(contactIdList)
const contactList: ContactInterface[] = []
for await (const contact of contactIterator) {
if (contact) {
contactList.push(contact)
}
}
return contactList
}
static async batchLoadContacts (contactIdList: string[]) {
if (typeof this.wechaty.puppet.batchContactPayload === 'function') {
const contactList: ContactInterface[] = contactIdList.map(id => {
if (this.wechaty.puppet.currentUserId === id) {
return (this.wechaty.ContactSelf as any as typeof ContactSelfImpl).load(id)
} else {
return (this.wechaty.Contact as any as typeof ContactImpl).load(id)
}
})
const needPayloadSet: Set<string> = new Set()
for (const contact of contactList) {
if (!contact.isReady()) {
needPayloadSet.add(contact.id)
}
}
if (needPayloadSet.size > 0) {
const payloadMap = await this.wechaty.puppet.batchContactPayload(Array.from(needPayloadSet))
for (const contact of contactList) {
contact.payload = payloadMap.get(contact.id)
}
}
return contactList
} else {
let continuousErrorCount = 0
let totalErrorCount = 0
const totalErrorThreshold = Math.round(contactIdList.length / 5)
const idToContact = async (id: string) => {
if (!this.wechaty.isLoggedIn) {
throw new Error('wechaty not logged in')
}
const result = await this.wechaty.Contact.find({ id }).catch(e => {
this.wechaty.emitError(e)
continuousErrorCount++
totalErrorCount++
if (continuousErrorCount > 5) {
throw new Error('5 continuous errors!')
}
if (totalErrorCount > totalErrorThreshold) {
throw new Error(`${totalErrorThreshold} total errors!`)
}
})
continuousErrorCount = 0
return result
}
/**
* we need to use concurrencyExecuter to reduce the parallel number of the requests
*/
const CONCURRENCY = 17
const contactIterator = concurrencyExecuter(CONCURRENCY)(idToContact)(contactIdList)
const contactList: ContactInterface[] = []
for await (const contact of contactIterator) {
if (contact) {
contactList.push(contact)
}
}
return contactList
}
}
// TODO
// eslint-disable-next-line no-use-before-define
static async delete (contact: ContactInterface): Promise<void> {
log.verbose('Contact', 'static delete(%s)', contact.id)
await this.wechaty.puppet.contactDelete(contact.id)
}
/**
*
* Instance properties
* @ignore
*
*/
payload?: PUPPET.payloads.Contact
/**
* @hideconstructor
*/
constructor (
public readonly id: string,
) {
super()
log.silly('Contact', `constructor(${id})`)
}
/**
* @ignore
*/
override toString (): string {
if (!this.payload) {
return this.constructor.name
}
const identity = this.payload.alias
|| this.payload.name
|| this.id
|| 'loading...'
return `Contact<${identity}>`
}
/**
* > Tips:
* This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
*
* @param {(string | ContactInterface | FileBox | UrlLink | MiniProgram | Location)} sayable
* 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 | MessageInterface>}
* @example
* 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
*
* // 1. send text to contact
*
* await contact.say('welcome to wechaty!')
* const msg = await contact.say('welcome to wechaty!') // only supported by puppet-padplus
*
* // 2. send media file to contact
*
* import { FileBox } from 'wechaty'
* const fileBox1 = FileBox.fromUrl('https://wechaty.github.io/wechaty/images/bot-qr-code.png')
* const fileBox2 = FileBox.fromFile('/tmp/text.txt')
* await contact.say(fileBox1)
* const msg1 = await contact.say(fileBox1) // only supported by puppet-padplus
* await contact.say(fileBox2)
* const msg2 = await contact.say(fileBox2) // only supported by puppet-padplus
*
* // 3. send contact card to contact
*
* const contactCard = bot.Contact.load('contactId')
* const msg = await contact.say(contactCard) // only supported by puppet-padplus
*
* // 4. send url link to contact
*
* const urlLink = new UrlLink ({
* description : 'WeChat Bot SDK for Individual Account, Powered by TypeScript, Docker, and Love',
* thumbnailUrl: 'https://avatars0.githubusercontent.com/u/25162437?s=200&v=4',
* title : 'Welcome to Wechaty',
* url : 'https://github.com/wechaty/wechaty',
* })
* await contact.say(urlLink)
* const msg = await contact.say(urlLink) // only supported by puppet-padplus
*
* // 5. send mini program to contact
*
* const miniProgram = new MiniProgram ({
* username : 'gh_xxxxxxx', //get from mp.weixin.qq.com
* appid : '', //optional, get from mp.weixin.qq.com
* title : '', //optional
* pagepath : '', //optional
* description : '', //optional
* thumbnailurl : '', //optional
* })
* await contact.say(miniProgram)
* const msg = await contact.say(miniProgram) // only supported by puppet-padplus
*
* // 6. send location to contact
* const location = new Location ({
* accuracy : 15,
* address : '北京市北京市海淀区45 Chengfu Rd',
* latitude : 39.995120999999997,
* longitude : 116.334154,
* name : '东升乡人民政府(海淀区成府路45号)',
* })
* await contact.say(location)
* const msg = await contact.say(location)
*/
async say (
sayable: Sayable,
options?: SayOptionsObject,
): Promise<void | MessageInterface> {
log.verbose('Contact', 'say(%s)', sayable)
if (options?.mentionList) {
log.warn('Contact', 'you cannot mention someone in private conversation!')
delete options.mentionList
}
const msgId = await deliverSayableConversationPuppet(this.wechaty.puppet)(this.id)(sayable, options)
if (msgId) {
const msg = await this.wechaty.Message.find({ id: msgId })
if (msg) {
return msg
}
}
}
/**
* Get the name from a contact
*
* @returns {string}
* @example
* const name = contact.name()
*/
name (): string {
return (this.payload && this.payload.name) || ''
}
aka (): string {
return (this.payload && this.payload.aka) || ''
}
realName (): string {
return (this.payload && this.payload.realName) || ''
}
async alias () : Promise<undefined | string>
async alias (newAlias: string) : Promise<void>
async alias (empty: null) : Promise<void>
/**
* 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 {(Promise<undefined | string | void>)}
* @example <caption> GET the alias for a contact, return {(Promise<string | null>)}</caption>
* const alias = await 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>
* try {
* await contact.alias('lijiarui')
* console.log(`change ${contact.name()}'s alias successfully!`)
* } catch (e) {
* console.log(`failed to change ${contact.name()} alias!`)
* }
*
* @example <caption>DELETE the alias for a contact</caption>
* try {
* const oldAlias = await contact.alias(null)
* console.log(`delete ${contact.name()}'s alias successfully!`)
* console.log('old alias is ${oldAlias}`)
* } catch (e) {
* console.log(`failed to delete ${contact.name()}'s alias!`)
* }
*/
async alias (newAlias?: null | string): Promise<void | undefined | string> {
log.silly('Contact', 'alias(%s)',
newAlias === undefined
? ''
: newAlias,
)
if (!this.payload) {
throw new Error('no payload')
}
if (typeof newAlias === 'undefined') {
return this.payload.alias
}
try {
await this.wechaty.puppet.contactAlias(this.id, newAlias)
await this.wechaty.puppet.contactPayloadDirty(this.id)
/**
* In normal puppet, the dirty event handler, onDirty, is a sync function, so the contactPayload will get the new payload
* However for wechaty-puppet-service, it uses flashstore to cache payloads, and deleting a cache is an async function
* So there is a chance contactPayload will still get old contact
*/
let maxCheck = 10
let changed = false
while (maxCheck-- > 0 && !changed) {
await new Promise(resolve => {
setTimeout(resolve, 300)
})
this.payload = await this.wechaty.puppet.contactPayload(this.id)
const payloadAlias = this.payload.alias || ''
changed = newAlias === payloadAlias
}
if (!changed) {
throw new Error('failed to modify clias, still got old alias after 10 tries')
}
} catch (e) {
this.wechaty.emitError(e)
log.error('Contact', 'alias(%s) rejected: %s', newAlias, (e as Error).message)
}
}
/**
* GET / SET / DELETE the phone list for a contact
*
* @param {(none | string[])} phoneList
* @returns {(Promise<string[] | void>)}
* @example <caption> GET the phone list for a contact, return {(Promise<string[]>)}</caption>
* const phoneList = await contact.phone()
* if (phone.length === 0) {
* console.log('You have not yet set any phone number for contact ' + contact.name())
* } else {
* console.log('You have already set phone numbers for contact ' + contact.name() + ':' + phoneList.join(','))
* }
*
* @example <caption>SET the phoneList for a contact</caption>
* try {
* const phoneList = ['13999999999', '13888888888']
* await contact.alias(phoneList)
* console.log(`change ${contact.name()}'s phone successfully!`)
* } catch (e) {
* console.log(`failed to change ${contact.name()} phone!`)
* }
*/
async phone (): Promise<string[]>
async phone (phoneList: string[]): Promise<void>
async phone (phoneList?: string[]): Promise<string[] | void> {
log.silly('Contact', 'phone(%s)', phoneList === undefined ? '' : JSON.stringify(phoneList))
if (!this.payload) {
throw new Error('no payload')
}
if (typeof phoneList === 'undefined') {
return this.payload.phone
}
try {
await this.wechaty.puppet.contactPhone(this.id, phoneList)
await this.wechaty.puppet.contactPayloadDirty(this.id)
this.payload = await this.wechaty.puppet.contactPayload(this.id)
} catch (e) {
this.wechaty.emitError(e)
log.error('Contact', 'phone(%s) rejected: %s', JSON.stringify(phoneList), (e as Error).message)
}
}
async corporation (): Promise<undefined | string>
async corporation (remark: string | null): Promise<void>
async corporation (remark?: string | null): Promise<void | undefined | string> {
log.silly('Contact', 'corporation(%s)', remark)
if (!this.payload) {
throw new Error('no payload')
}
if (typeof remark === 'undefined') {
return this.payload.corporation
}
if (this.payload.type !== PUPPET.types.Contact.Individual) {
throw new Error('Can not set corporation remark on non individual contact.')
}
try {
await this.wechaty.puppet.contactCorporationRemark(this.id, remark)
await this.wechaty.puppet.contactPayloadDirty(this.id)
this.payload = await this.wechaty.puppet.contactPayload(this.id)
} catch (e) {
this.wechaty.emitError(e)
log.error('Contact', 'corporation(%s) rejected: %s', remark, (e as Error).message)
}
}
async description (): Promise<undefined | string>
async description (newDescription: string | null): Promise<void>
async description (newDescription?: string | null): Promise<void | undefined | string> {
log.silly('Contact', 'description(%s)', newDescription)
if (!this.payload) {
throw new Error('no payload')
}
if (typeof newDescription === 'undefined') {
return this.payload.description
}
try {
await this.wechaty.puppet.contactDescription(this.id, newDescription)
await this.wechaty.puppet.contactPayloadDirty(this.id)
this.payload = await this.wechaty.puppet.contactPayload(this.id)
} catch (e) {
this.wechaty.emitError(e)
log.error('Contact', 'description(%s) rejected: %s', newDescription, (e as Error).message)
}
}
title (): string | null {
if (!this.payload) {
throw new Error('no payload')
}
return this.payload.title || null
}
coworker (): boolean {
if (!this.payload) {
throw new Error('no payload')
}
return !!this.payload.coworker
}
/**
* Check if contact is friend
*
* > Tips:
* This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
*
* @returns {boolean | null}
*
* <br>True for friend of the bot <br>
* False for not friend of the bot, null for unknown.
* @example
* const isFriend = contact.friend()
*/
friend (): undefined | boolean {
log.verbose('Contact', 'friend()')
return this.payload?.friend
}
/**
* 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
*/
/**
* Return the type of the Contact
* > Tips: ContactType is enum here.</br>
* @returns {ContactType.Unknown | ContactType.Personal | ContactType.Official}
*
* @example
* const bot = new Wechaty()
* await bot.start()
* const isOfficial = contact.type() === bot.Contact.Type.Official
*/
type (): PUPPET.types.Contact {
if (!this.payload) {
throw new Error('no payload')
}
return this.payload.type
}
/**
* @ignore
* TODO: Check if the contact is star contact.
*
* @returns {boolean | null} - True for star friend, False for no star friend.
* @example
* const isStar = contact.star()
*/
star (): undefined | boolean {
return this.payload?.star
}
/**
* Contact gender
* > Tips: ContactGender is enum here. </br>
*
* @returns {ContactGender.Unknown | ContactGender.Male | ContactGender.Female}
* @example
* const gender = contact.gender() === bot.Contact.Gender.Male
*/
gender (): PUPPET.types.ContactGender {
return this.payload
? this.payload.gender
: PUPPET.types.ContactGender.Unknown
}
/**
* Get the region 'province' from a contact
*
* @returns {string | null}
* @example
* const province = contact.province()
*/
province (): undefined | string {
return this.payload?.province
}
/**
* Get the region 'city' from a contact
*
* @returns {string | null}
* @example
* const city = contact.city()
*/
city (): undefined | string {
return this.payload?.city
}
/**
* Get avatar picture file stream
*
* @returns {Promise<FileBox>}
* @example
* // Save avatar to local file like `1-name.jpg`
*
* const file = await contact.avatar()
* const name = file.name
* await file.toFile(name, true)
* console.log(`Contact: ${contact.name()} with avatar file: ${name}`)
*/
async avatar (): Promise<FileBoxInterface> {
log.verbose('Contact', 'avatar()')
const fileBox = await this.wechaty.puppet.contactAvatar(this.id)
return fileBox
}
/**
* Get all tags of contact
*
* @returns {Promise<TagInterface[]>}
* @example
* const tags = await contact.tags()
*/
async tags (): Promise<TagInterface[]> {
log.verbose('Contact', 'tags() for %s', this)
try {
const tagPayloadList = this.payload?.tags || []
const tagListPromises = tagPayloadList.map(tagId => this.wechaty.Tag.find({ id: tagId }))
const tagList = await Promise.all(tagListPromises)
return tagList.filter(tag => !!tag) as TagInterface[]
} catch (e) {
this.wechaty.emitError(e)
log.error('Contact', 'tags() exception: %s', (e as Error).message)
return []
}
}
/**
* Add a Tag
*/
async tag (tags: TagInterface | TagInterface[]): Promise<void> {
log.verbose('Contact', 'tag(%s) for %s', JSON.stringify(tags), this)
if (!Array.isArray(tags)) {
tags = [ tags ]
}
const tagIds = tags.map(tag => tag.id)
await this.wechaty.puppet.tagContactTagAdd(tagIds, [ this.id ])
}
/**
* Remove a Tag
*/
async tagRemove (tags: TagInterface | TagInterface[]): Promise<void> {
log.verbose('Contact', 'tagRemove(%s) for %s', JSON.stringify(tags), this)
if (!Array.isArray(tags)) {
tags = [ tags ]
}
const tagIds = tags.map(tag => tag.id)
await this.wechaty.puppet.tagContactTagRemove(tagIds, [ this.id ])
}
/**
* Force reload data for Contact, Sync data from low-level API again.
*
* @returns {Promise<this>}
* @example
* await contact.sync()
*/
async sync (): Promise<void> {
await this.wechaty.puppet.contactPayloadDirty(this.id)
await this.ready(true)
}
/**
* `ready()` is For FrameWork ONLY!
*
* Please not to use `ready()` at the user land.
* If you want to sync data, use `sync()` instead.
*
* @ignore
*/
async ready (
forceSync = false,
): Promise<void> {
log.silly('Contact', 'ready() @ %s with id="%s"', this.wechaty.puppet, this.id)
if (!forceSync && this.isReady()) { // already ready
log.silly('Contact', 'ready() isReady() true')
return
}
try {
this.payload = await this.wechaty.puppet.contactPayload(this.id)
// log.silly('Contact', `ready() this.wechaty.puppet.contactPayload(%s) resolved`, this)
} catch (e) {
this.wechaty.emitError(e)
log.verbose('Contact', 'ready() this.wechaty.puppet.contactPayload(%s) exception: %s',
this.id,
(e as Error).message,
)
throw e
}
}
async readMark (hasRead: boolean): Promise<void>
async readMark (): Promise<boolean>
/**
* Mark the conversation as read
* @param { undefined | boolean } hasRead
*
* @example
* const bot = new Wechaty()
* const contact = await bot.Contact.find({name: 'xxx'})
* await contact.readMark()
*/
async readMark (hasRead?: boolean): Promise<void | boolean> {
try {
if (typeof hasRead === 'undefined') {
return this.wechaty.puppet.conversationReadMark(this.id)
} else {
await this.wechaty.puppet.conversationReadMark(this.id, hasRead)
}
} catch (e) {
this.wechaty.emitError(e)
log.error('Contact', 'readMark() exception: %s', (e as Error).message)
}
}
/**
* @ignore
*/
isReady (): boolean {
return !!(this.payload && this.payload.name)
}
/**
* Check if contact is self
*
* @returns {boolean} True for contact is self, False for contact is others
* @example
* const isSelf = contact.self()
*/
self (): boolean {
return this.id === this.wechaty.puppet.currentUserId
}
/**
* Get the handle from a contact.
*
* > A Twitter handle is the username that appears at the end of your unique Twitter URL.
*
* Sometimes cannot get handle due to the puppet implementation.
*
* @ignore
* @returns {string | null}
* @example
* const handle = contact.handle()
*/
handle (): undefined | string {
return this.payload?.handle
}
/**
* Huan(202203): `weixin()` will be removed in v2.0
* @link https://github.com/wechaty/puppet/issues/181
* @deprecated use `handle()` instead
*/
weixin (): undefined | string {
// log.warn('Contact', 'weixin() is deprecated, use `handle()` instead.')
// console.error(new Error().stack)
return this.payload?.weixin
}
additionalInfo (): undefined | any {
let additionalInfoObj = {}
if (this.payload?.additionalInfo) {
try {
additionalInfoObj = JSON.parse(this.payload.additionalInfo)
} catch (e) {
log.warn('Contact', 'additionalInfo() parse failed, additionalInfo: %s', this.payload.additionalInfo)
}
}
return additionalInfoObj
}
async payloadModify (payload: Partial<PUPPET.payloads.Contact>): Promise<void> {
return this.wechaty.puppet.contactPayloadModify(this.id, payload)
}
}
class ContactImplBase extends validationMixin(ContactMixin)<ContactImplInterface>() {}
interface ContactImplInterface extends ContactImplBase {}
type ContactProtectedProperty =
| 'ready'
type ContactInterface = Omit<ContactImplInterface, ContactProtectedProperty>
class ContactImpl extends validationMixin(ContactImplBase)<ContactInterface>() {}
type ContactConstructor = Constructor<
ContactImplInterface,
Omit<typeof ContactImpl, 'load' | 'batchLoadContacts'>
>
export type {
ContactConstructor,
ContactProtectedProperty,
ContactInterface,
}
export {
ContactImpl,
}