import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import axios from 'axios'; @Injectable() export class OzonetelAgentService { private readonly logger = new Logger(OzonetelAgentService.name); private readonly apiDomain: string; private readonly apiKey: string; private readonly accountId: string; private cachedToken: string | null = null; private tokenExpiry: number = 0; constructor(private config: ConfigService) { this.apiDomain = config.get('exotel.subdomain') ?? 'in1-ccaas-api.ozonetel.com'; this.apiKey = config.get('exotel.apiKey') ?? ''; this.accountId = config.get('exotel.accountSid') ?? ''; } private async getToken(): Promise { if (this.cachedToken && Date.now() < this.tokenExpiry) { return this.cachedToken; } const url = `https://${this.apiDomain}/ca_apis/CAToken/generateToken`; this.logger.log('Generating CloudAgent API token'); const response = await axios.post(url, { userName: this.accountId }, { headers: { apiKey: this.apiKey, 'Content-Type': 'application/json' }, }); const data = response.data; if (data.token) { this.cachedToken = data.token; this.tokenExpiry = Date.now() + 55 * 60 * 1000; this.logger.log('CloudAgent token generated successfully'); return data.token; } throw new Error(data.message ?? 'Token generation failed'); } async loginAgent(params: { agentId: string; password: string; phoneNumber: string; mode?: string; }): Promise<{ status: string; message: string }> { const url = `https://${this.apiDomain}/CAServices/AgentAuthenticationV2/index.php`; this.logger.log(`Logging in agent ${params.agentId} with phone ${params.phoneNumber}`); try { const response = await axios.post( url, new URLSearchParams({ userName: this.accountId, apiKey: this.apiKey, phoneNumber: params.phoneNumber, action: 'login', mode: params.mode ?? 'blended', state: 'Ready', }).toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, auth: { username: params.agentId, password: params.password, }, }, ); const data = response.data; // "already logged in" is not a real error — treat as success if (data.status === 'error' && data.message?.includes('already logged in')) { this.logger.log(`Agent ${params.agentId} already logged in — treating as success`); return { status: 'success', message: data.message }; } this.logger.log(`Agent login response: ${JSON.stringify(data)}`); return data; } catch (error: any) { this.logger.error(`Agent login failed: ${error.message}`); throw error; } } async manualDial(params: { agentId: string; campaignName: string; customerNumber: string; }): Promise<{ status: string; ucid?: string; message?: string }> { const url = `https://${this.apiDomain}/ca_apis/AgentManualDial`; this.logger.log(`Manual dial: agent=${params.agentId} campaign=${params.campaignName} number=${params.customerNumber}`); try { const token = await this.getToken(); const response = await axios.post(url, { userName: this.accountId, agentID: params.agentId, campaignName: params.campaignName, customerNumber: params.customerNumber, UCID: 'true', }, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }); this.logger.log(`Manual dial response: ${JSON.stringify(response.data)}`); return response.data; } catch (error: any) { const responseData = error?.response?.data ? JSON.stringify(error.response.data) : ''; this.logger.error(`Manual dial failed: ${error.message} ${responseData}`); throw error; } } async changeAgentState(params: { agentId: string; state: 'Ready' | 'Pause'; pauseReason?: string; }): Promise<{ status: string; message: string }> { const url = `https://${this.apiDomain}/ca_apis/changeAgentState`; this.logger.log(`Changing agent ${params.agentId} state to ${params.state}`); try { const body: Record = { userName: this.accountId, agentId: params.agentId, state: params.state, }; if (params.pauseReason) { body.pauseReason = params.pauseReason; } const token = await this.getToken(); const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }); this.logger.log(`Change agent state response: ${JSON.stringify(response.data)}`); return response.data; } catch (error: any) { const responseData = error?.response?.data ? JSON.stringify(error.response.data) : ''; this.logger.error(`Change agent state failed: ${error.message} ${responseData}`); throw error; } } async setDisposition(params: { agentId: string; ucid: string; disposition: string; }): Promise<{ status: string; message?: string; details?: string }> { const url = `https://${this.apiDomain}/ca_apis/DispositionAPIV2`; const did = process.env.OZONETEL_DID ?? '918041763265'; this.logger.log(`Set disposition: agent=${params.agentId} ucid=${params.ucid} disposition=${params.disposition}`); try { const token = await this.getToken(); const response = await axios.post(url, { userName: this.accountId, agentID: params.agentId, did, ucid: params.ucid, action: 'Set', disposition: params.disposition, autoRelease: 'true', }, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }); this.logger.log(`Set disposition response: ${JSON.stringify(response.data)}`); return response.data; } catch (error: any) { const responseData = error?.response?.data ? JSON.stringify(error.response.data) : ''; this.logger.error(`Set disposition failed: ${error.message} ${responseData}`); throw error; } } async logoutAgent(params: { agentId: string; password: string; }): Promise<{ status: string; message: string }> { const url = `https://${this.apiDomain}/CAServices/AgentAuthenticationV2/index.php`; this.logger.log(`Logging out agent ${params.agentId}`); try { const response = await axios.post( url, new URLSearchParams({ userName: this.accountId, apiKey: this.apiKey, action: 'logout', mode: 'blended', state: 'Ready', }).toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, auth: { username: params.agentId, password: params.password, }, }, ); this.logger.log(`Agent logout response: ${JSON.stringify(response.data)}`); return response.data; } catch (error: any) { this.logger.error(`Agent logout failed: ${error.message}`); throw error; } } }