@juzi/wechaty
Version:
Wechaty is a RPA SDK for Chatbot Makers.
446 lines (395 loc) • 12.1 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 { 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,
}