import { APIClient } from '@wireapp/api-client' ; import { ClientType } from '@wireapp/api-client/src/client/' ; import type { ConversationEvent , TeamEvent , UserEvent } from '@wireapp/api-client/src/event' ; import { Account } from '@wireapp/core' ; import { PayloadBundle , PayloadBundleType } from '@wireapp/core/src/main/conversation/' ; import type { CRUDEngine } from '@wireapp/store-engine' ; import logdown from 'logdown' ; import UUID from 'uuidjs' ; import type { BotConfig , BotCredentials } from './Interfaces' ; import { MessageHandler } from './MessageHandler' ; import { AccessTokenData , AUTH_ACCESS_TOKEN_KEY , AUTH_COOKIE_KEY , AUTH_TABLE_NAME , Cookie , } from '@wireapp/api-client/src/auth' ; const defaultConfig = { backend : 'production' , clientType : ClientType . TEMPORARY , conversations : [ ] , owners : [ ] , } ; export class Bot extends MessageHandler { public account : Account | undefined = undefined ; private readonly config : Required < BotConfig > ; private readonly handlers : Map < string , MessageHandler > ; private readonly logger : logdown . Logger ; constructor ( private readonly credentials , config ? ) { super ( ) ; this . config = { ... defaultConfig , ... config } ; this . credentials = credentials ; this . handlers = new Map ( ) ; this . logger = logdown ( '@wireapp/bot-api/Bot' , { logger : console , markdown : false , } ) ; } public addHandler ( handler ) { this . handlers . set ( UUID . genV4 ( ) . toString ( ) , handler ) ; } public removeHandler ( key ) { this . handlers . delete ( key ) ; } private isAllowedConversation ( conversationId ) { return this . config . conversations . length === 0 ? true : this . config . conversations . includes ( conversationId ) ; } private isOwner ( userId ) { return this . config . owners . length === 0 ? true : this . config . owners . includes ( userId ) ; } public async start ( storeEngine ? ) { const login = { clientType : this . config . clientType , email : this . credentials . email , password : this . credentials . password , } ; const apiClient = new APIClient ( { urls : this . config . backend === 'staging' ? APIClient . BACKEND . STAGING : APIClient . BACKEND . PRODUCTION , } ) ; apiClient . on ( APIClient . TOPIC . ACCESS_TOKEN_REFRESH , async ( accessToken ) => { await storeEngine ?. updateOrCreate ( AUTH_TABLE_NAME , AUTH_ACCESS_TOKEN_KEY , accessToken ) ; } ) ; this . account = storeEngine ? new Account ( apiClient , ( ) => Promise . resolve ( storeEngine ) ) : new Account ( apiClient ) ; for ( const payloadType of Object . values ( PayloadBundleType ) ) { this . account . removeAllListeners ( payloadType ) ; this . account . on ( payloadType as any , this . handleEvent . bind ( this ) ) ; } try { if ( ! storeEngine ) { throw new Error ( 'Store engine not provided' ) ; } const cookie = await this . getCookie ( storeEngine ) ; await this . account . init ( this . config . clientType , cookie , storeEngine ) ; } catch ( error ) { this . logger . warn ( 'Failed to init account from cookie' , error ) ; await this . account . login ( login , true , undefined , storeEngine ) ; } await this . account . listen ( ) ; this . handlers . forEach ( handler => ( handler . account = this . account ) ) ; return apiClient ; } async getCookie ( storeEngine ) { const { expiration , zuid } = await storeEngine . read < Cookie > ( AUTH_TABLE_NAME , AUTH_COOKIE_KEY ) ; const cookie = new Cookie ( zuid , expiration ) ; return cookie ; } handleEvent ( payload ) { if ( 'conversation' in payload && this . validateMessage ( payload . conversation , payload . from ) ) { this . handlers . forEach ( handler => handler . handleEvent ( payload ) ) ; } } private validateMessage ( conversationID , userID ) { if ( ! this . isAllowedConversation ( conversationID ) ) { this . logger . info ( `Skipping message because conversation "${ conversationID }" is not in the list of allowed conversations.` ) ; } if ( ! this . isOwner ( userID ) ) { this . logger . info ( `Skipping message because sender "${ userID }" is not in the list of owners.` ) ; } return this . isAllowedConversation ( conversationID ) && this . isOwner ( userID ) ; } }