@juzi/wechaty-puppet-whatsapp
Version:
Wechaty Puppet for WhatsApp
211 lines (186 loc) • 6.91 kB
text/typescript
/* eslint-disable no-case-declarations */
import * as PUPPET from '@juzi/wechaty-puppet'
import {
MIN_BATTERY_VALUE_FOR_LOGOUT,
DEFAULT_TIMEOUT,
log,
STRINGS,
LANGUAGE,
} from '../../config.js'
import { WA_ERROR_TYPE } from '../../exception/error-type.js'
import WAError from '../../exception/whatsapp-error.js'
import {
WAState,
} from '../../schema/whatsapp-interface.js'
import WhatsAppBase from '../whatsapp-base.js'
import type {
WhatsAppContact,
BatteryInfo,
WAStateType,
} from '../../schema/whatsapp-type.js'
import {
batchProcess,
isContactId,
isRoomId,
sleep,
} from '../../helper/miscellaneous.js'
const PRE = 'LoginEventHandler'
export default class LoginEventHandler extends WhatsAppBase { // FIXME: I have no good idea for this class name.
protected loadingData: boolean = false
private qrcodeOrLoginCheckTimer?: NodeJS.Timer
private hasLogin: boolean = false
private lastQRCodeTime = Date.now()
public onQRCode (qrcode: string) {
log.info(PRE, `onQRCode(${qrcode})`)
// NOTE: This event will not be fired if a session is specified.
this.lastQRCodeTime = Date.now()
this.hasLogin = false
this.emit('scan', { qrcode, status: PUPPET.types.ScanStatus.Waiting, timestamp: Date.now() })
this.checkQRCodeOrLoginEvent()
}
private checkQRCodeOrLoginEvent () {
if (this.qrcodeOrLoginCheckTimer) {
return
}
this.qrcodeOrLoginCheckTimer = setInterval(() => {
if (!this.hasLogin && Date.now() > this.lastQRCodeTime + 2 * 60 * 1000) {
this.emit('error', 'can not get scan or login event more than 2 mins')
}
}, 25 * 1000)
}
public clearQrcodeOrLoginCheckTimer () {
if (!this.qrcodeOrLoginCheckTimer) {
return
}
clearInterval(this.qrcodeOrLoginCheckTimer as any)
}
public async onAuthenticated () {
log.info(PRE, 'onAuthenticated()')
}
public async onAuthFailure (message: string) {
log.warn(PRE, 'auth_failure: %s', message)
// avoid reuse invalid session data
await this.clearSession()
}
public async onWhatsAppReady () {
log.verbose(PRE, 'onWhatsAppReady()')
if (this.hasLogin) {
log.info(PRE, 'onWhatsAppReady() already login, skip')
return
}
this.hasLogin = true
this.clearQrcodeOrLoginCheckTimer()
const whatsapp = this.getWhatsAppClient()
try {
this.botId = whatsapp.info.wid._serialized
await this.manager.initCache(this.botId)
} catch (error) {
throw WAError(WA_ERROR_TYPE.ERR_INIT, `Can not get bot id from WhatsApp client, current state: ${await whatsapp.getState()}`, JSON.stringify(error))
}
await this.onLogin()
const contactOrRoomList = await this.manager.syncContactOrRoomList()
await this.onReady(contactOrRoomList)
this.manager.startSchedule()
}
public async onLogin () {
log.verbose(PRE, 'onLogin()')
const whatsapp = this.getWhatsAppClient()
log.info(PRE, `WhatsApp Client Info: ${JSON.stringify(whatsapp.info)}`)
const cacheManager = await this.manager.getCacheManager()
const botSelf = await this.manager.requestManager.getContactById(this.botId!)
await cacheManager.setContactOrRoomRawPayload(this.botId!, {
...botSelf,
avatar: await this.manager.requestManager.getAvatarUrl(this.botId!),
})
this.emit('login', this.botId!)
log.info(PRE, `onLogin(${this.botId}})`)
}
public async onReady (contactOrRoomList: WhatsAppContact[]) {
log.verbose(PRE, 'onReady()')
if (this.loadingData) {
log.verbose(PRE, 'onReady() loading data are under process.')
return
}
this.loadingData = true
let friendCount = 0
let contactCount = 0
let roomCount = 0
const cacheManager = await this.manager.getCacheManager()
const batchSize = 100
await batchProcess(batchSize, contactOrRoomList, async (contactOrRoom: WhatsAppContact) => {
const contactOrRoomId = contactOrRoom.id._serialized
const avatar = await contactOrRoom.getProfilePicUrl()
const contactWithAvatar = Object.assign(contactOrRoom, { avatar })
if (isContactId(contactOrRoomId)) {
contactCount++
if (contactOrRoom.isMyContact) {
friendCount++
}
await cacheManager.setContactOrRoomRawPayload(contactOrRoomId, contactWithAvatar)
} else if (isRoomId(contactOrRoomId)) {
const memberList = await this.manager.syncRoomMemberList(contactOrRoomId)
if (memberList.length > 0) {
roomCount++
await cacheManager.setContactOrRoomRawPayload(contactOrRoomId, contactWithAvatar)
} else {
await cacheManager.deleteContactOrRoom(contactOrRoomId)
await cacheManager.deleteRoomMemberIdList(contactOrRoomId)
}
} else {
log.warn(PRE, `Unknown contact type: ${JSON.stringify(contactOrRoom)}`)
}
await this.manager.processHistoryMessages(contactOrRoom)
})
log.info(PRE, `onReady() all contacts and rooms are ready, friendCount: ${friendCount} contactCount: ${contactCount} roomCount: ${roomCount}`)
await sleep(15 * 1000)
this.emit('ready')
this.loadingData = false
}
public async onLogout (reason: string = STRINGS[LANGUAGE].LOGOUT_REASON.DEFAULT) {
log.verbose(PRE, `onLogout(${reason})`)
await this.clearSession()
this.hasLogin = false
this.manager.stopSchedule()
this.emit('logout', this.getBotId(), reason as string)
this.baseStop()
if (!this.getWhatsAppClient().pupPage) {
await this.getWhatsAppClient().initialize()
}
}
public async onChangeState (state: WAStateType) {
log.info(PRE, `onChangeState(${JSON.stringify(state)})`)
if (!this.botId) {
throw WAError(WA_ERROR_TYPE.ERR_INIT, 'No login bot id.')
}
switch (state) {
case WAState.TIMEOUT:
this.pendingLogoutEmitTimer = setTimeout(() => {
this.emit('logout', this.getBotId(), STRINGS[LANGUAGE].LOGOUT_REASON.NETWORK_TIMEOUT_IN_PHONE)
this.pendingLogoutEmitTimer = undefined
}, DEFAULT_TIMEOUT.TIMEOUT_WAIT_CONNECTED)
break
case WAState.CONNECTED:
this.clearPendingLogoutEmitTimer()
this.emit('login', this.botId)
this.loadingData = false
const contactOrRoomList = await this.manager.syncContactOrRoomList()
await this.onReady(contactOrRoomList)
break
default:
break
}
}
/**
* unsupported events
* leave logs to for further dev
*/
public async onChangeBattery (batteryInfo: BatteryInfo) {
log.verbose(PRE, `onChangeBattery(${JSON.stringify(batteryInfo)})`)
if (!this.botId) {
throw WAError(WA_ERROR_TYPE.ERR_INIT, 'No login bot id.')
}
if (batteryInfo.battery <= MIN_BATTERY_VALUE_FOR_LOGOUT && !batteryInfo.plugged) {
this.emit('logout', this.botId, STRINGS[LANGUAGE].LOGOUT_REASON.BATTERY_LOWER_IN_PHONE)
}
}
}