*/ public function publishOffer(ProductOffer $offer, ?string $chatId = null, bool $dryRun = false): array { $token = trim((string) config('services.telegram.offers_bot_token', '')); $chatId = trim((string) ($chatId ?: config('services.telegram.offers_chat_id', ''))); $channelName = trim((string) config('services.telegram.offers_channel_name', '')); $message = $this->buildMessage($offer); $imageUrl = $this->resolveImageUrl($offer); if ($dryRun) { return [ 'ok' => true, 'dry_run' => true, 'chat_id' => $chatId, 'channel_name' => $channelName, 'image_url' => $imageUrl, 'message' => $message, 'offer_id' => (int) $offer->id, ]; } if ($token === '') { throw new RuntimeException('TELEGRAM_OFFERS_BOT_TOKEN non configurato.'); } if ($chatId === '') { throw new RuntimeException('TELEGRAM_OFFERS_CHAT_ID non configurato.'); } $baseUrl = 'https://api.telegram.org/bot' . $token; $response = null; $usedMethod = 'sendMessage'; if ($imageUrl !== null) { $response = Http::asForm()->post($baseUrl . '/sendPhoto', [ 'chat_id' => $chatId, 'photo' => $imageUrl, 'caption' => Str::limit($message, 1024, ''), ]); $usedMethod = 'sendPhoto'; } if (! $response || ! $response->ok()) { $response = Http::asForm()->post($baseUrl . '/sendMessage', [ 'chat_id' => $chatId, 'text' => $message, 'disable_web_page_preview' => false, ]); $usedMethod = 'sendMessage'; } return [ 'ok' => $response->ok(), 'dry_run' => false, 'chat_id' => $chatId, 'channel_name' => $channelName, 'method' => $usedMethod, 'image_url' => $imageUrl, 'offer_id' => (int) $offer->id, 'status' => $response->status(), 'response' => $response->json(), 'message' => $message, ]; } private function buildMessage(ProductOffer $offer): string { $payload = is_array($offer->meta['payload'] ?? null) ? $offer->meta['payload'] : []; $product = $offer->product; $lines = array_values(array_filter([ 'Offerta Amazon NetGescon', trim((string) ($offer->title ?: $product?->name ?: 'Prodotto Amazon')), trim((string) ($product?->internal_code ?? '')) !== '' ? 'Catalogo: ' . trim((string) $product?->internal_code) : null, $this->formatMoney($offer->price_amount, (string) ($offer->currency ?: data_get($payload, 'currency', 'EUR')), 'Prezzo'), $this->formatMoney($offer->price_compare_at, (string) ($offer->currency ?: data_get($payload, 'currency', 'EUR')), 'Listino'), trim((string) ($offer->availability ?: data_get($payload, 'availability', ''))) !== '' ? 'Disponibilita: ' . trim((string) ($offer->availability ?: data_get($payload, 'availability', ''))): null, trim((string) data_get($payload, 'asin', $offer->external_product_id)) !== '' ? 'ASIN: ' . trim((string) data_get($payload, 'asin', $offer->external_product_id)) : null, trim((string) ($offer->referral_url ?: data_get($payload, 'referral_url', ''))) !== '' ? 'Apri offerta: ' . trim((string) ($offer->referral_url ?: data_get($payload, 'referral_url', ''))): null, ], static fn(?string $line): bool => is_string($line) && trim($line) !== '')); return implode(PHP_EOL, $lines); } private function resolveImageUrl(ProductOffer $offer): ?string { $payload = is_array($offer->meta['payload'] ?? null) ? $offer->meta['payload'] : []; $imageUrl = trim((string) data_get($payload, 'image_url', '')); return $imageUrl !== '' && str_starts_with($imageUrl, 'http') ? $imageUrl : null; } private function formatMoney(mixed $amount, string $currency, string $label): ?string { if (! is_numeric($amount)) { return null; } return $label . ': ' . number_format((float) $amount, 2, ',', '.') . ' ' . strtoupper(trim($currency) !== '' ? $currency : 'EUR'); } }