UNPKG

@juzi/wechaty

Version:

Wechaty is a RPA SDK for Chatbot Makers.

446 lines (395 loc) 12.1 kB
/** * 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 { EventEmitter } from 'events' import * as PUPPET from '@juzi/wechaty-puppet' import { log } from '@juzi/wechaty-puppet' import type { Constructor } from 'clone-class' import { retryPolicy, } from '../pure-functions/mod.js' import type { Accepter, } from '../schemas/acceptable.js' import type { ContactInterface, ContactImpl, } from './contact.js' import type { RoomInterface, } from './room.js' import { wechatifyMixin, } from '../user-mixins/wechatify.js' import { validationMixin } from '../user-mixins/validation.js' interface FriendshipAddOptionsObject { room?: RoomInterface, contact?: ContactInterface, hello?: string, } type FriendshipAddOptions = string | FriendshipAddOptionsObject const MixinBase = wechatifyMixin( EventEmitter, ) /** * Send, receive friend request, and friend confirmation events. * * 1. send request * 2. receive request(in friend event) * 3. confirmation friendship(friend event) * * [Examples/Friend-Bot]{@link https://github.com/wechaty/wechaty/blob/1523c5e02be46ebe2cc172a744b2fbe53351540e/examples/friend-bot.ts} */ class FriendshipMixin extends MixinBase implements Accepter { static Type = PUPPET.types.Friendship /** * @ignore */ static load ( id : string, ): FriendshipInterface { const newFriendship = new this(id) return newFriendship } /** * Search a Friend by phone or weixin. * * The best practice is to search friend request once per minute. * Remember not to do this too frequently, or your account may be blocked. * * @param {FriendshipSearchCondition} condition - Search friend by phone or weixin. * @returns {Promise<ContactInterface>} * * @example * const friend_phone = await bot.Friendship.search({phone: '13112341234'}) * const friend_weixin = await bot.Friendship.search({weixin: 'weixin_account'}) * * console.log(`This is the new friend info searched by phone : ${friend_phone}`) * await bot.Friendship.add(friend_phone, 'hello') * */ static async search ( queryFilter : PUPPET.filters.Friendship, type?: PUPPET.types.Contact, ): Promise<undefined | ContactInterface> { log.verbose('Friendship', 'static search("%s")', JSON.stringify(queryFilter), ) if (typeof (type) === 'undefined') { type = PUPPET.types.Contact.Individual } const contactId = await this.wechaty.puppet.friendshipSearch(queryFilter, type) if (!contactId) { return undefined } const contact = await this.wechaty.Contact.find({ id: contactId }) return contact } /** * Send a Friend Request to a `contact` with message `hello`. * * The best practice is to send friend request once per minute. * Remeber not to do this too frequently, or your account may be blocked. * * @param {ContactInterface} contact - Send friend request to contact * @param {FriendshipAddOptions} options - The friend request content * @returns {Promise<void>} * * @example * const contact = await bot.Friendship.search({phone: '13112341234'}) * await bot.Friendship.add(contact, 'Nice to meet you! I am wechaty bot!') * * const memberList = await room.memberList() * for (let i = 0; i < memberList.length; i++) { * await bot.Friendship.add(member, { * room: room, * hello: `Nice to meet you! I am wechaty bot from room: ${await room.topic()}!`, * }) * } * */ static async add ( contact : ContactInterface, options : FriendshipAddOptions, ): Promise<void> { log.verbose('Friendship', 'static add(%s, %s)', contact.id, typeof options === 'string' ? options : options.hello, ) if (typeof options === 'string') { log.warn('Friendship', 'the params hello is deprecated in the next version, please put the attr hello into options object, e.g. { hello: "xxxx" }') await this.wechaty.puppet.friendshipAdd(contact.id, { hello: options }) } else { const friendOption: PUPPET.types.FriendshipAddOptions = { contactId: options.contact?.id, hello: options.hello, roomId: options.room && options.room.id, } await this.wechaty.puppet.friendshipAdd(contact.id, friendOption) } } static async del ( contact: ContactInterface, ): Promise<void> { log.verbose('Friendship', 'static del(%s)', contact.id) throw new Error('to be implemented') } /** * * Instance Properties * */ /** * @ignore */ payload?: PUPPET.payloads.Friendship /* * @hideconstructor */ constructor ( public readonly id: string, ) { super() log.verbose('Friendship', 'constructor(id=%s)', id) } override toString () { if (!this.payload) { return this.constructor.name } return [ 'Friendship#', PUPPET.types.Friendship[this.payload.type], '<', this.payload.contactId, '>', ].join('') } isReady (): boolean { return !!this.payload && (Object.keys(this.payload).length > 0) } /** * no `dirty` support because Friendship has no rawPayload(yet) * @ignore */ async ready (): Promise<void> { if (this.isReady()) { return } this.payload = await this.wechaty.puppet.friendshipPayload(this.id) // if (!this.#payload) { // throw new Error('no payload') // } await (this.contact() as ContactImpl).ready() } /** * Accept Friend Request * * @returns {Promise<void>} * * @example * const bot = new Wechaty() * bot.on('friendship', async friendship => { * try { * console.log(`received friend event.`) * switch (friendship.type()) { * * // 1. New Friend Request * * case Friendship.Type.Receive: * await friendship.accept() * break * * // 2. Friend Ship Confirmed * * case Friendship.Type.Confirm: * console.log(`friend ship confirmed`) * break * } * } catch (e) { * console.error(e) * } * } * .start() */ async accept (): Promise<void> { log.verbose('Friendship', 'accept()') if (!this.payload) { throw new Error('no payload') } if (this.payload.type !== PUPPET.types.Friendship.Receive) { throw new Error('accept() need type to be FriendshipType.Receive, but it got a ' + FriendshipImpl.Type[this.payload.type]) } log.silly('Friendship', 'accept() to %s', this.payload.contactId) await this.wechaty.puppet.friendshipAccept(this.id) const contact = this.contact() try { const doSync = async () => { await (contact as ContactImpl).ready() if (!contact.isReady()) { throw new Error('Friendship.accept() contact.ready() not ready') } log.verbose('Friendship', 'accept() with contact %s ready()', contact.name()) } await retryPolicy.execute(doSync) } catch (e) { this.wechaty.emitError(e) log.warn('Friendship', 'accept() contact %s not ready because of %s', contact, (e && (e as Error).message) || e) // console.error(e) } // try to fix issue #293 - https://github.com/wechaty/wechaty/issues/293 await contact.sync() } /** * Get verify message from * * @returns {string} * @example <caption>If request content is `ding`, then accept the friendship</caption> * const bot = new Wechaty() * bot.on('friendship', async friendship => { * try { * console.log(`received friend event from ${friendship.contact().name()}`) * if (friendship.type() === Friendship.Type.Receive && friendship.hello() === 'ding') { * await friendship.accept() * } * } catch (e) { * console.error(e) * } * } * .start() */ hello (): string { if (!this.payload) { throw new Error('no payload') } return this.payload.hello || '' } /** * Get the contact from friendship * * @returns {ContactInterface} * @example * const bot = new Wechaty() * bot.on('friendship', async friendship => { * const contact = friendship.contact() * const name = contact.name() * console.log(`received friend event from ${name}`) * } * .start() */ contact (): ContactInterface { if (!this.payload) { throw new Error('no payload') } const contact = (this.wechaty.Contact as typeof ContactImpl).load(this.payload.contactId) return contact } /** * Return the Friendship Type * > Tips: FriendshipType is enum here. </br> * - FriendshipType.Unknown </br> * - FriendshipType.Confirm </br> * - FriendshipType.Receive </br> * - FriendshipType.Verify </br> * * @returns {FriendshipType} * * @example <caption>If request content is `ding`, then accept the friendship</caption> * const bot = new Wechaty() * bot.on('friendship', async friendship => { * try { * if (friendship.type() === Friendship.Type.Receive && friendship.hello() === 'ding') { * await friendship.accept() * } * } catch (e) { * console.error(e) * } * } * .start() */ type (): PUPPET.types.Friendship { return this.payload ? this.payload.type : PUPPET.types.Friendship.Unknown } /** * get friendShipPayload Json * @returns {FriendshipPayload} * * @example * const bot = new Wechaty() * bot.on('friendship', async friendship => { * try { * // JSON.stringify(friendship) as well. * const payload = await friendship.toJSON() * } catch (e) { * console.error(e) * } * } * .start() */ toJSON (): string { log.verbose('Friendship', 'toJSON()') if (!this.isReady()) { throw new Error(`Friendship<${this.id}> needs to be ready. Please call ready() before toJSON()`) } return JSON.stringify(this.payload) } /** * create friendShip by friendshipJson * @example * const bot = new Wechaty() * bot.start() * * const payload = '{...}' // your saved JSON payload * const friendship = bot.FriendShip.fromJSON(friendshipFromDisk) * await friendship.accept() */ static async fromJSON ( payload: string | PUPPET.payloads.Friendship, ): Promise<FriendshipInterface> { log.verbose('Friendship', 'static fromJSON(%s)', typeof payload === 'string' ? payload : JSON.stringify(payload), ) if (typeof payload === 'string') { payload = JSON.parse(payload) as PUPPET.payloads.Friendship } /** * Set the payload back to the puppet for future use */ await this.wechaty.puppet.friendshipPayload(payload.id, payload) const instance = this.wechaty.Friendship.load(payload.id) await instance.ready() return instance } } class FriendshipImpl extends validationMixin(FriendshipMixin)<FriendshipInterface>() {} interface FriendshipInterface extends FriendshipImpl {} type FriendshipConstructor = Constructor< FriendshipInterface, typeof FriendshipImpl > export type { FriendshipConstructor, FriendshipInterface, } export { FriendshipImpl, }