import { Injectable, Logger } from '@nestjs/common'; const BASE_URL = 'https://coreapi.daftareshoma.com/api/Customize'; const MAX_AUDIO_BYTES = 8 * 1024 * 1024; export type DaftaresomaOutgoingCallInput = { fromNumber: string; toNumber: string; callerExtension: string; }; export type DaftaresomaCallSearchRow = { id?: string; fromNumber?: string; toNumber?: string; recordingId?: string | null; direction?: string; reportDispositionType?: string; startTime?: string; endTime?: string; duration?: string; extension?: string; }; @Injectable() export class DaftaresomaClient { private readonly logger = new Logger(DaftaresomaClient.name); async outgoingCall( apiToken: string, input: DaftaresomaOutgoingCallInput, ): Promise { const res = await fetch(`${BASE_URL}/OutgoingCall`, { method: 'POST', headers: { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify({ from_number: input.fromNumber, to_number: input.toNumber, caller_extension: input.callerExtension, }), signal: AbortSignal.timeout(15000), }); if (!res.ok) { const text = await res.text().catch(() => ''); this.logger.warn( `OutgoingCall failed (${res.status}): ${text.slice(0, 300)}`, ); throw new Error( text.trim() ? `تماس خروجی دفتر شما ناموفق بود (${res.status}): ${text.slice(0, 200)}` : `تماس خروجی دفتر شما ناموفق بود (${res.status})`, ); } } async findCallById( apiToken: string, callId: string, ): Promise { const res = await fetch(`${BASE_URL}/CustomerCallSearch`, { method: 'POST', headers: { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify({ id: callId, limit: 1, pagination: 1 }), signal: AbortSignal.timeout(20000), }); if (!res.ok) { const text = await res.text().catch(() => ''); this.logger.warn( `CustomerCallSearch failed (${res.status}): ${text.slice(0, 200)}`, ); return null; } const json = (await res.json()) as { data?: DaftaresomaCallSearchRow[]; }; const row = Array.isArray(json.data) ? json.data[0] : null; return row ?? null; } async downloadRecording( apiToken: string, recordingId: string, ): Promise<{ buffer: ArrayBuffer; contentType: string; filename: string }> { const url = new URL(`${BASE_URL}/GetCallRecording`); url.searchParams.set('recordingId', recordingId); const res = await fetch(url.toString(), { method: 'GET', headers: { Authorization: `Bearer ${apiToken}`, Accept: '*/*', 'User-Agent': 'MontactCRM-VoIP/1.0', }, signal: AbortSignal.timeout(60000), redirect: 'follow', }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error( `دانلود ضبط دفتر شما ناموفق بود (${res.status})${text ? `: ${text.slice(0, 160)}` : ''}`, ); } const contentLength = Number(res.headers.get('content-length') || 0); if (contentLength > MAX_AUDIO_BYTES) { throw new Error('حجم فایل ضبط بیش از حد مجاز است'); } const buffer = await res.arrayBuffer(); if (buffer.byteLength > MAX_AUDIO_BYTES) { throw new Error('حجم فایل ضبط بیش از حد مجاز است'); } if (!buffer.byteLength) { throw new Error('فایل ضبط خالی است'); } const contentType = res.headers.get('content-type')?.split(';')[0]?.trim() || 'audio/wav'; const filename = `daftareshoma-${recordingId}.wav`; return { buffer, contentType, filename }; } }