feat(whatsapp): mismo motor, menú y configuración que Telegram y el chat web
WhatsApp dejaba de usar su bot propio (árbol de menús sin login ni compras) y pasa al motor compartido, así los tres canales quedan iguales. - TelegramBotService recibe el canal por constructor; flujos, menús, Gemini, OCR, validación de pagos y datos bancarios salen de ChatConfig sin duplicar - WhatsApp no tiene teclados inline: las opciones se envían numeradas y la respuesta numérica se traduce al callback equivalente - estado en caché con prefijo por canal (Telegram conserva el suyo para no cerrar las sesiones abiertas) - descarga de comprobantes y audios por la Graph API de Meta - respuesta manual del asesor y aviso de vencimiento salen también por WhatsApp - la pantalla de WhatsApp queda sólo con credenciales; el resto se administra en Chat / Bot Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e3ad0a423a
commit
5bc1ec14ca
@@ -105,6 +105,9 @@ class Kernel extends ConsoleKernel
|
||||
'parse_mode' => 'Markdown',
|
||||
]);
|
||||
}
|
||||
} elseif ($contacto->canal === 'whatsapp' && $contacto->canal_id) {
|
||||
(new \App\Services\TelegramBotService('whatsapp'))
|
||||
->send($contacto->canal_id, $msgCanal);
|
||||
} elseif ($contacto->canal === 'web') {
|
||||
$conv = $contacto->activeConversation();
|
||||
if ($conv) {
|
||||
|
||||
@@ -6,7 +6,7 @@ use App\Models\WhatsappConversation;
|
||||
use App\Models\WhatsappSystemConfig;
|
||||
use App\Models\WhatsappUser;
|
||||
use App\Models\WhatsappWebhookLog;
|
||||
use App\Services\WhatsappBotService;
|
||||
use App\Services\TelegramBotService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -195,13 +195,39 @@ class WhatsappWebhookController extends Controller
|
||||
'is_read' => false,
|
||||
]);
|
||||
|
||||
$botService = new WhatsappBotService();
|
||||
// Interruptor del módulo: con el bot apagado sólo se registra el mensaje.
|
||||
if (WhatsappSystemConfig::get('bot_enabled', '1') !== '1') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mismo motor que Telegram y el chat web: idénticos menús, flujos y configuración.
|
||||
$bot = new TelegramBotService('whatsapp');
|
||||
|
||||
// Confirmar lectura a WhatsApp
|
||||
$botService->markRead($messageId);
|
||||
$bot->waMarkRead($messageId);
|
||||
|
||||
// Procesar con el motor del bot
|
||||
$botService->handle($user, $texto ?? '', $type);
|
||||
switch ($type) {
|
||||
case 'image':
|
||||
case 'document':
|
||||
if ($mediaId) {
|
||||
$bot->handlePhoto($from, $mediaId);
|
||||
return;
|
||||
}
|
||||
$bot->handleText($from, $texto ?? '', $nombre);
|
||||
return;
|
||||
|
||||
case 'audio':
|
||||
case 'voice':
|
||||
if ($mediaId) {
|
||||
$bot->handleVoice($from, $mediaId, $nombre);
|
||||
return;
|
||||
}
|
||||
$bot->handleText($from, $texto ?? '', $nombre);
|
||||
return;
|
||||
|
||||
default:
|
||||
$bot->handleText($from, $texto ?? '', $nombre);
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -83,10 +83,13 @@ class ShowConversaciones extends Component
|
||||
|
||||
$conv->update(['ultimo_mensaje_at' => now()]);
|
||||
|
||||
// Enviar via Telegram si el canal es telegram
|
||||
// Entregar por el canal del cliente
|
||||
if ($conv->canal === 'telegram') {
|
||||
$engine = new ChatBotEngine();
|
||||
$engine->enviarTelegram($conv->contact->canal_id, $this->replyText);
|
||||
} elseif ($conv->canal === 'whatsapp') {
|
||||
(new \App\Services\TelegramBotService('whatsapp'))
|
||||
->send($conv->contact->canal_id, $this->replyText);
|
||||
}
|
||||
|
||||
$this->replyText = '';
|
||||
|
||||
@@ -16,12 +16,6 @@ class ShowConfiguracionBot extends Component
|
||||
public string $webhook_verify_token = '';
|
||||
public string $app_secret = '';
|
||||
public string $bot_enabled = '1';
|
||||
public string $welcome_message = '';
|
||||
public string $default_no_match = '';
|
||||
public string $advisor_message = '';
|
||||
public string $business_hours_enabled = '0';
|
||||
public string $business_hours_start = '';
|
||||
public string $business_hours_end = '';
|
||||
|
||||
protected $rules = [
|
||||
'whatsapp_token' => 'required|string',
|
||||
@@ -29,18 +23,13 @@ class ShowConfiguracionBot extends Component
|
||||
'whatsapp_api_url' => 'required|url',
|
||||
'webhook_verify_token' => 'required|string',
|
||||
'app_secret' => 'required|string',
|
||||
'welcome_message' => 'required|string|max:1000',
|
||||
'default_no_match' => 'required|string|max:500',
|
||||
'advisor_message' => 'required|string|max:500',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$keys = [
|
||||
'whatsapp_token', 'phone_number_id', 'whatsapp_api_url',
|
||||
'webhook_verify_token', 'app_secret', 'bot_enabled', 'welcome_message',
|
||||
'default_no_match', 'advisor_message', 'business_hours_enabled',
|
||||
'business_hours_start', 'business_hours_end',
|
||||
'webhook_verify_token', 'app_secret', 'bot_enabled',
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
@@ -54,9 +43,7 @@ class ShowConfiguracionBot extends Component
|
||||
|
||||
$keys = [
|
||||
'whatsapp_token', 'phone_number_id', 'whatsapp_api_url',
|
||||
'webhook_verify_token', 'app_secret', 'bot_enabled', 'welcome_message',
|
||||
'default_no_match', 'advisor_message', 'business_hours_enabled',
|
||||
'business_hours_start', 'business_hours_end',
|
||||
'webhook_verify_token', 'app_secret', 'bot_enabled',
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
|
||||
@@ -26,15 +26,19 @@ class ValidarPagoTelegramJob implements ShouldQueue
|
||||
public function __construct(
|
||||
private string $chatId,
|
||||
private array $datosPago,
|
||||
private int $intento = 1
|
||||
private int $intento = 1,
|
||||
private string $canal = 'telegram'
|
||||
) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$cacheKey = "tgbot_state_{$this->chatId}";
|
||||
// Telegram conserva su prefijo histórico de caché; los demás canales usan el suyo.
|
||||
$cacheKey = $this->canal === 'telegram'
|
||||
? "tgbot_state_{$this->chatId}"
|
||||
: "{$this->canal}bot_state_{$this->chatId}";
|
||||
$state = Cache::get($cacheKey, []);
|
||||
$usuarioId = $state['user_id'] ?? null;
|
||||
$bot = new TelegramBotService();
|
||||
$bot = new TelegramBotService($this->canal);
|
||||
|
||||
if (! $usuarioId) {
|
||||
return;
|
||||
@@ -49,7 +53,7 @@ class ValidarPagoTelegramJob implements ShouldQueue
|
||||
$nombreUsuario = $state['data']['nombre_remitente'] ?? null;
|
||||
|
||||
$resultado = app(PagoValidadorService::class)->validar(
|
||||
$this->datosPago, $usuarioId, $nombreUsuario, 'telegram'
|
||||
$this->datosPago, $usuarioId, $nombreUsuario, $this->canal
|
||||
);
|
||||
|
||||
if ($resultado['estado'] === 'confirmado') {
|
||||
@@ -68,7 +72,7 @@ class ValidarPagoTelegramJob implements ShouldQueue
|
||||
// No encontrado — reintentar o agotar
|
||||
if ($this->intento < self::MAX_INTENTOS) {
|
||||
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intento {$this->intento}/" . self::MAX_INTENTOS . " — reintentando en 20s");
|
||||
self::dispatch($this->chatId, $this->datosPago, $this->intento + 1)
|
||||
self::dispatch($this->chatId, $this->datosPago, $this->intento + 1, $this->canal)
|
||||
->delay(now()->addSeconds(20));
|
||||
} else {
|
||||
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intentos agotados, mostrando botón manual");
|
||||
|
||||
@@ -31,6 +31,7 @@ class ChatConversation extends Model
|
||||
{
|
||||
return match($this->canal) {
|
||||
'telegram' => '✈️ Telegram',
|
||||
'whatsapp' => '🟢 WhatsApp',
|
||||
default => '💬 Web',
|
||||
};
|
||||
}
|
||||
@@ -39,6 +40,7 @@ class ChatConversation extends Model
|
||||
{
|
||||
return match($this->canal) {
|
||||
'telegram' => 'bg-blue-100 text-blue-700',
|
||||
'whatsapp' => 'bg-emerald-100 text-emerald-700',
|
||||
default => 'bg-green-100 text-green-700',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,6 +40,17 @@ class TelegramBotService
|
||||
|
||||
private ?int $convId = null;
|
||||
|
||||
/**
|
||||
* Canal de transporte: 'telegram' | 'whatsapp'.
|
||||
* Los flujos, menús y configuración son los mismos; sólo cambia el envío.
|
||||
*/
|
||||
private string $canal;
|
||||
|
||||
public function __construct(string $canal = 'telegram')
|
||||
{
|
||||
$this->canal = $canal;
|
||||
}
|
||||
|
||||
// ─── Entry points ─────────────────────────────────────────
|
||||
|
||||
public function handleText(string $chatId, string $text, string $nombre = ''): void
|
||||
@@ -97,8 +108,23 @@ class TelegramBotService
|
||||
|
||||
$step = $state['step'] ?? 'await_email';
|
||||
|
||||
// Pasos que esperan texto libre del usuario: ahí un número es un dato, no una opción.
|
||||
$pasosDeTexto = ['await_email', 'await_otp', 'await_nombre', 'await_celular',
|
||||
'await_amount', 'await_remitente_nombre'];
|
||||
|
||||
// WhatsApp responde con el número de la opción; se traduce al callback equivalente.
|
||||
if ($this->canal === 'whatsapp' && ! in_array($step, $pasosDeTexto, true)
|
||||
&& preg_match('/^\d{1,2}$/', $lower)) {
|
||||
$mapa = Cache::get($this->kbKey($chatId), []);
|
||||
|
||||
if (isset($mapa[$lower])) {
|
||||
$this->handleCallback($chatId, '', $mapa[$lower]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Guardar último texto para que handleFreeText pueda pasarlo a Gemini
|
||||
if (! in_array($step, ['await_email', 'await_otp', 'await_nombre', 'await_celular', 'await_amount', 'await_remitente_nombre'])) {
|
||||
if (! in_array($step, $pasosDeTexto, true)) {
|
||||
$state['data']['last_text'] = $text;
|
||||
$this->setState($chatId, $state);
|
||||
}
|
||||
@@ -193,7 +219,7 @@ class TelegramBotService
|
||||
|
||||
$base64 = base64_encode($imageData['content']);
|
||||
$mime = $imageData['mime'];
|
||||
$datosPago = app(GeminiVisionService::class)->extraerPago($base64, $mime, $usuarioId, 'telegram');
|
||||
$datosPago = app(GeminiVisionService::class)->extraerPago($base64, $mime, $usuarioId, $this->canal);
|
||||
|
||||
if (! $datosPago) {
|
||||
$this->send($chatId, "No pude leer el comprobante. Intenta con una foto más clara.");
|
||||
@@ -219,7 +245,7 @@ class TelegramBotService
|
||||
}
|
||||
|
||||
$resultado = app(PagoValidadorService::class)->validar(
|
||||
$datosPago, $usuarioId, $nombreUsuario, 'telegram'
|
||||
$datosPago, $usuarioId, $nombreUsuario, $this->canal
|
||||
);
|
||||
$motivo = $resultado['motivo'] ?? '';
|
||||
|
||||
@@ -242,7 +268,7 @@ class TelegramBotService
|
||||
);
|
||||
|
||||
// Lanzar job que reintenta cada 20s hasta 9 veces (≈3 min)
|
||||
ValidarPagoTelegramJob::dispatch($chatId, $datosPago, 1)
|
||||
ValidarPagoTelegramJob::dispatch($chatId, $datosPago, 1, $this->canal)
|
||||
->delay(now()->addSeconds(20));
|
||||
} else {
|
||||
$motivoTexto = match ($motivo) {
|
||||
@@ -283,7 +309,7 @@ class TelegramBotService
|
||||
$this->send($chatId, "⏳ Verificando de nuevo...");
|
||||
|
||||
$nombreUsuario = User::find($usuarioId)?->name ?? '';
|
||||
$resultado = app(PagoValidadorService::class)->validar($datosPago, $usuarioId, $nombreUsuario, 'telegram');
|
||||
$resultado = app(PagoValidadorService::class)->validar($datosPago, $usuarioId, $nombreUsuario, $this->canal);
|
||||
$motivo = $resultado['motivo'] ?? '';
|
||||
|
||||
if ($resultado['estado'] === 'confirmado') {
|
||||
@@ -436,7 +462,7 @@ class TelegramBotService
|
||||
|
||||
AiUsageLog::registrar([
|
||||
'servicio' => 'whisper',
|
||||
'canal' => 'telegram',
|
||||
'canal' => $this->canal,
|
||||
'usuario_id' => $usuarioId,
|
||||
'audio_segundos' => $duracionSegundos ?: null,
|
||||
'tiempo_ms' => $tiempoMs,
|
||||
@@ -461,7 +487,7 @@ class TelegramBotService
|
||||
Log::warning('[TelegramBot] Whisper exception: ' . $e->getMessage());
|
||||
AiUsageLog::registrar([
|
||||
'servicio' => 'whisper',
|
||||
'canal' => 'telegram',
|
||||
'canal' => $this->canal,
|
||||
'usuario_id' => $usuarioId,
|
||||
'audio_segundos' => $duracionSegundos ?: null,
|
||||
'tiempo_ms' => $tiempoMs,
|
||||
@@ -510,12 +536,12 @@ class TelegramBotService
|
||||
}
|
||||
|
||||
try {
|
||||
$contact = ChatContact::where('canal', 'telegram')->where('canal_id', (string) $chatId)->first();
|
||||
$contact = ChatContact::where('canal', $this->canal)->where('canal_id', (string) $chatId)->first();
|
||||
if ($contact) {
|
||||
$contact->update(['user_id' => $user->id, 'nombre' => $user->name]);
|
||||
} else {
|
||||
$contact = ChatContact::create([
|
||||
'canal' => 'telegram',
|
||||
'canal' => $this->canal,
|
||||
'canal_id' => (string) $chatId,
|
||||
'nombre' => $user->name,
|
||||
'user_id' => $user->id,
|
||||
@@ -632,12 +658,12 @@ class TelegramBotService
|
||||
'rol_id' => $rolCliente?->id,
|
||||
]);
|
||||
|
||||
$contact = ChatContact::where('canal', 'telegram')->where('canal_id', $chatId)->first();
|
||||
$contact = ChatContact::where('canal', $this->canal)->where('canal_id', $chatId)->first();
|
||||
if ($contact) {
|
||||
$contact->update(['user_id' => $user->id, 'nombre' => $user->name]);
|
||||
} else {
|
||||
$contact = ChatContact::create([
|
||||
'canal' => 'telegram',
|
||||
'canal' => $this->canal,
|
||||
'canal_id' => $chatId,
|
||||
'nombre' => $user->name,
|
||||
'user_id' => $user->id,
|
||||
@@ -655,7 +681,7 @@ class TelegramBotService
|
||||
if (! $conv) {
|
||||
$conv = ChatConversation::create([
|
||||
'contact_id' => $contact->id,
|
||||
'canal' => 'telegram',
|
||||
'canal' => $this->canal,
|
||||
'estado' => 'bot',
|
||||
'ultimo_mensaje_at' => now(),
|
||||
]);
|
||||
@@ -1339,7 +1365,7 @@ class TelegramBotService
|
||||
if ($userId = ($this->getState($chatId)['user_id'] ?? null)) {
|
||||
$state = $this->getState($chatId);
|
||||
$nombre = $state['data']['nombre_remitente'] ?? null;
|
||||
$solicitud = SolicitudRecarga::crear($userId, $monto, 'telegram', $nombre);
|
||||
$solicitud = SolicitudRecarga::crear($userId, $monto, $this->canal, $nombre);
|
||||
$state['data']['solicitud_recarga_id'] = $solicitud->id;
|
||||
$this->setState($chatId, $state);
|
||||
}
|
||||
@@ -1522,7 +1548,7 @@ class TelegramBotService
|
||||
if ($texto) {
|
||||
$contexto = GeminiAgentService::buildContexto($state, $state['user_id'] ?? null);
|
||||
$resultado = app(GeminiAgentService::class)->responder(
|
||||
$texto, $contexto, $state['user_id'] ?? null, 'telegram'
|
||||
$texto, $contexto, $state['user_id'] ?? null, $this->canal
|
||||
);
|
||||
|
||||
if ($resultado['respuesta']) {
|
||||
@@ -1573,20 +1599,30 @@ class TelegramBotService
|
||||
|
||||
// ─── State management ─────────────────────────────────────
|
||||
|
||||
private function stateKey(string $chatId): string
|
||||
{
|
||||
// Telegram conserva su prefijo histórico: cambiarlo cerraría todas las sesiones abiertas.
|
||||
return $this->canal === 'telegram' ? "tgbot_state_{$chatId}" : "{$this->canal}bot_state_{$chatId}";
|
||||
}
|
||||
|
||||
private function getState(string $chatId): array
|
||||
{
|
||||
return Cache::get("tgbot_state_{$chatId}", ['step' => 'await_email', 'data' => []]);
|
||||
return Cache::get($this->stateKey($chatId), ['step' => 'await_email', 'data' => []]);
|
||||
}
|
||||
|
||||
private function setState(string $chatId, array $state): void
|
||||
{
|
||||
Cache::put("tgbot_state_{$chatId}", $state, now()->addSeconds(self::STATE_TTL));
|
||||
Cache::put($this->stateKey($chatId), $state, now()->addSeconds(self::STATE_TTL));
|
||||
}
|
||||
|
||||
// ─── Telegram API ─────────────────────────────────────────
|
||||
|
||||
public function registrarComandos(): bool
|
||||
{
|
||||
if ($this->canal !== 'telegram') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->apiCall('setMyCommands', [
|
||||
'commands' => [
|
||||
['command' => 'start', 'description' => 'Iniciar / Menú principal'],
|
||||
@@ -1608,6 +1644,10 @@ class TelegramBotService
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->canal === 'whatsapp') {
|
||||
return $this->waSend($chatId, $text);
|
||||
}
|
||||
|
||||
return $this->apiCall('sendMessage', [
|
||||
'chat_id' => $chatId,
|
||||
'text' => $text,
|
||||
@@ -1626,6 +1666,10 @@ class TelegramBotService
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->canal === 'whatsapp') {
|
||||
return $this->waSend($chatId, $this->numerarTeclado($chatId, $text, $inlineKeyboard));
|
||||
}
|
||||
|
||||
return $this->apiCall('sendMessage', [
|
||||
'chat_id' => $chatId,
|
||||
'text' => $text ?: '', // zero-width space fallback
|
||||
@@ -1636,6 +1680,10 @@ class TelegramBotService
|
||||
|
||||
private function answerCallback(string $callbackQueryId): void
|
||||
{
|
||||
if ($this->canal !== 'telegram' || $callbackQueryId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->apiCall('answerCallbackQuery', ['callback_query_id' => $callbackQueryId]);
|
||||
}
|
||||
|
||||
@@ -1657,8 +1705,150 @@ class TelegramBotService
|
||||
}
|
||||
}
|
||||
|
||||
// ─── WhatsApp API ─────────────────────────────────────────
|
||||
//
|
||||
// WhatsApp no tiene teclados inline como Telegram: las mismas opciones se
|
||||
// envían numeradas y la respuesta numérica del usuario se traduce de vuelta
|
||||
// al callback_data equivalente, así los flujos son idénticos en ambos canales.
|
||||
// ponytail: texto numerado en vez de mensajes interactivos; si se quieren
|
||||
// botones nativos, el tope de Meta es 3 botones o 10 filas de lista.
|
||||
|
||||
private function kbKey(string $chatId): string
|
||||
{
|
||||
return "wabot_kb_{$chatId}";
|
||||
}
|
||||
|
||||
private function numerarTeclado(string $chatId, string $text, array $inlineKeyboard): string
|
||||
{
|
||||
$mapa = [];
|
||||
$lineas = [];
|
||||
$n = 1;
|
||||
|
||||
foreach ($inlineKeyboard as $fila) {
|
||||
foreach ($fila as $boton) {
|
||||
$etiqueta = $boton['text'] ?? '';
|
||||
$accion = $boton['callback_data'] ?? null;
|
||||
|
||||
if ($accion === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapa[(string) $n] = $accion;
|
||||
$lineas[] = "{$n}. {$etiqueta}";
|
||||
$n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $lineas) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
Cache::put($this->kbKey($chatId), $mapa, now()->addHours(6));
|
||||
|
||||
$cuerpo = $text !== '' ? $text . "\n\n" : '';
|
||||
|
||||
return $cuerpo . implode("\n", $lineas) . "\n\n_Responde con el número de la opción._";
|
||||
}
|
||||
|
||||
public function waMarkRead(string $messageId): bool
|
||||
{
|
||||
if ($this->canal !== 'whatsapp') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->waApiCall('messages', [
|
||||
'messaging_product' => 'whatsapp',
|
||||
'status' => 'read',
|
||||
'message_id' => $messageId,
|
||||
]);
|
||||
}
|
||||
|
||||
private function waSend(string $chatId, string $text): bool
|
||||
{
|
||||
return $this->waApiCall('messages', [
|
||||
'messaging_product' => 'whatsapp',
|
||||
'recipient_type' => 'individual',
|
||||
'to' => $chatId,
|
||||
'type' => 'text',
|
||||
'text' => ['preview_url' => false, 'body' => $text],
|
||||
]);
|
||||
}
|
||||
|
||||
private function waApiCall(string $endpoint, array $payload): bool
|
||||
{
|
||||
[$token, $phoneId, $apiUrl] = $this->waCredenciales();
|
||||
|
||||
if (! $token || ! $phoneId) {
|
||||
Log::warning('[WhatsAppBot] token o phone_number_id sin configurar.');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::withToken($token)
|
||||
->timeout(10)
|
||||
->post("{$apiUrl}{$phoneId}/{$endpoint}", $payload);
|
||||
|
||||
if (! $response->successful()) {
|
||||
Log::error('[WhatsAppBot] API error: ' . $response->body());
|
||||
}
|
||||
|
||||
return $response->successful();
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning("[WhatsAppBot] waApiCall {$endpoint} failed: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array{0:string,1:string,2:string} token, phone_number_id, api_url */
|
||||
private function waCredenciales(): array
|
||||
{
|
||||
return [
|
||||
\App\Models\WhatsappSystemConfig::get('whatsapp_token'),
|
||||
\App\Models\WhatsappSystemConfig::get('phone_number_id'),
|
||||
\App\Models\WhatsappSystemConfig::get('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'),
|
||||
];
|
||||
}
|
||||
|
||||
private function downloadWhatsappMedia(string $mediaId): ?array
|
||||
{
|
||||
[$token, , $apiUrl] = $this->waCredenciales();
|
||||
|
||||
if (! $token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$info = Http::withToken($token)->timeout(10)->get("{$apiUrl}{$mediaId}");
|
||||
$url = $info->json('url');
|
||||
|
||||
if (! $url) {
|
||||
Log::warning('[WhatsAppBot] media sin URL: ' . $info->body());
|
||||
return null;
|
||||
}
|
||||
|
||||
$archivo = Http::withToken($token)->timeout(30)->get($url);
|
||||
$content = $archivo->body();
|
||||
|
||||
if (! $content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $content,
|
||||
'mime' => $info->json('mime_type') ?: 'image/jpeg',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('[WhatsAppBot] downloadWhatsappMedia failed: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function downloadTelegramFile(string $fileId): ?array
|
||||
{
|
||||
if ($this->canal === 'whatsapp') {
|
||||
return $this->downloadWhatsappMedia($fileId);
|
||||
}
|
||||
|
||||
$token = ChatConfig::get('telegram_token');
|
||||
if (! $token) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user