238 lines
7.9 KiB
PHP
238 lines
7.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ZaloBotService
|
|
{
|
|
protected LinkConverterService $linkConverterService;
|
|
protected SettingService $settingService;
|
|
|
|
public function __construct(
|
|
LinkConverterService $linkConverterService,
|
|
SettingService $settingService
|
|
) {
|
|
$this->linkConverterService = $linkConverterService;
|
|
$this->settingService = $settingService;
|
|
}
|
|
|
|
/**
|
|
* Handle incoming Zalo Bot webhook request.
|
|
*/
|
|
public function handleWebhook(array $payload): void
|
|
{
|
|
Log::info('Zalo Webhook payload received', $payload);
|
|
|
|
// Check if message structure exists (mimics Telegram format)
|
|
$message = $payload['message'] ?? null;
|
|
if (!$message) {
|
|
return;
|
|
}
|
|
|
|
$chatId = $message['chat']['id'] ?? null;
|
|
$text = $message['text'] ?? '';
|
|
|
|
if (!$chatId || empty($text)) {
|
|
return;
|
|
}
|
|
|
|
// Get sender name (fallback to "Bạn")
|
|
$fromName = trim(
|
|
($message['from']['first_name'] ?? '') . ' ' . ($message['from']['last_name'] ?? '')
|
|
);
|
|
if (empty($fromName)) {
|
|
$fromName = $message['from']['username'] ?? 'Bạn';
|
|
}
|
|
|
|
// Find Shopee links
|
|
$urls = $this->extractShopeeUrls($text);
|
|
if (empty($urls)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($urls as $url) {
|
|
$this->processAndReplyLink($chatId, $fromName, $url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a single Shopee link and reply to the chat.
|
|
*/
|
|
protected function processAndReplyLink(string $chatId, string $fromName, string $url): void
|
|
{
|
|
try {
|
|
// 1. Resolve redirects if it's a short URL (e.g. s.shopee.vn or shp.ee)
|
|
$resolvedUrl = $this->resolveShortUrl($url);
|
|
|
|
// 2. Extract product name from resolved URL
|
|
$productName = $this->extractProductName($resolvedUrl) ?? 'Sản phẩm Shopee';
|
|
|
|
// 3. Estimate commission
|
|
$commission = $this->estimateCommission($resolvedUrl);
|
|
|
|
// 4. Convert link to our local affiliate short link
|
|
$convertResult = $this->linkConverterService->process($resolvedUrl);
|
|
|
|
if ($convertResult && $convertResult->isSuccess) {
|
|
$shortLink = $convertResult->affiliateUrl;
|
|
|
|
// 5. Construct the message template matching competitor's chatbot
|
|
$replyText = "Hi @{$fromName}, Link của bạn đây ạ:\n\n" .
|
|
"📦 {$productName}\n" .
|
|
"🔗 Link: {$shortLink}\n" .
|
|
"💵 Hoa hồng ước tính: {$commission}\n\n" .
|
|
"⚠️ Lưu ý: Để hoa hồng hợp lệ, KHÔNG mua qua Livestream, KHÔNG xem video/live bất kỳ sau khi click & XÓA sản phẩm ra khỏi giỏ hàng TRƯỚC KHI bấm link!";
|
|
|
|
// 6. Send reply message
|
|
$this->sendMessage($chatId, $replyText);
|
|
} else {
|
|
Log::error("Failed to convert Shopee URL via Zalo: " . ($convertResult->errorMessage ?? 'Unknown error'));
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::error("Error processing link in ZaloBotService: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract all Shopee URLs from text.
|
|
*/
|
|
public function extractShopeeUrls(string $text): array
|
|
{
|
|
$pattern = '/(?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)?shopee\.vn\/[^\s]+|(?:https?:\/\/)?shp\.ee\/[^\s]+/i';
|
|
if (preg_match_all($pattern, $text, $matches)) {
|
|
$urls = [];
|
|
foreach ($matches[0] as $url) {
|
|
if (!str_starts_with(strtolower($url), 'http://') && !str_starts_with(strtolower($url), 'https://')) {
|
|
$url = 'https://' . $url;
|
|
}
|
|
$urls[] = $url;
|
|
}
|
|
return $urls;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Resolve short URLs (s.shopee.vn or shp.ee) to get the final redirected URL.
|
|
*/
|
|
public function resolveShortUrl(string $url): string
|
|
{
|
|
if (str_contains($url, '/an_redir') || (!str_contains($url, 's.shopee.vn') && !str_contains($url, 'shp.ee'))) {
|
|
return $url;
|
|
}
|
|
|
|
try {
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HEADER, true);
|
|
curl_setopt($ch, CURLOPT_NOBODY, true); // Get headers only
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Check redirect location manually
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
|
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
|
|
|
$response = curl_exec($ch);
|
|
$info = curl_getinfo($ch);
|
|
curl_close($ch);
|
|
|
|
if ($info['http_code'] >= 300 && $info['http_code'] < 400) {
|
|
if (preg_match('/[Ll]ocation:\s*(.+)/', $response, $matches)) {
|
|
$resolvedUrl = trim($matches[1]);
|
|
// Recursively resolve redirect if it's still a redirect link
|
|
return $this->resolveShortUrl($resolvedUrl);
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::warning("Could not resolve short URL redirect: " . $e->getMessage());
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Extract product/shop name from Shopee URL.
|
|
*/
|
|
public function extractProductName(string $url): ?string
|
|
{
|
|
$parsedUrl = parse_url($url);
|
|
if (!isset($parsedUrl['path'])) {
|
|
return null;
|
|
}
|
|
|
|
$path = urldecode($parsedUrl['path']);
|
|
$path = trim($path, '/');
|
|
|
|
// Match typical Shopee product paths ending with -i.SHOP_ID.ITEM_ID
|
|
if (preg_match('/^(.*?)-i\.\d+\.\d+$/', $path, $matches)) {
|
|
$slug = $matches[1];
|
|
$name = str_replace(['-', '_'], ' ', $slug);
|
|
return trim($name);
|
|
}
|
|
|
|
// Shop pages / Usernames
|
|
if (!str_contains($path, '/')) {
|
|
$name = str_replace(['-', '_'], ' ', $path);
|
|
return trim($name);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Predictable estimate of product commission based on the item ID.
|
|
*/
|
|
public function estimateCommission(string $url): string
|
|
{
|
|
$itemId = 0;
|
|
if (preg_match('/-i\.\d+\.(\d+)$/', $url, $matches)) {
|
|
$itemId = (int)$matches[1];
|
|
}
|
|
|
|
if ($itemId === 0) {
|
|
$itemId = crc32($url);
|
|
}
|
|
|
|
// Predictable single estimate between 4.0% and 7.5% based on itemId or crc32
|
|
$rate = 4.0 + (abs($itemId) % 36) / 10.0;
|
|
return number_format($rate, 1) . '%';
|
|
}
|
|
|
|
/**
|
|
* Send message using Zalo Bot Creator HTTP API.
|
|
*/
|
|
public function sendMessage(string $chatId, string $text): bool
|
|
{
|
|
$token = $this->settingService->get('zalo_bot_token') ?? env('ZALO_BOT_TOKEN');
|
|
|
|
if (!$token) {
|
|
Log::error('Zalo Bot Token is not configured. Cannot send message.');
|
|
return false;
|
|
}
|
|
|
|
$apiUrl = "https://bot-api.zaloplatforms.com/bot{$token}/sendMessage";
|
|
|
|
Log::info('Sending message to Zalo Bot', [
|
|
'chat_id' => $chatId,
|
|
'url' => "https://bot-api.zaloplatforms.com/bot[HIDDEN]/sendMessage"
|
|
]);
|
|
|
|
$response = Http::timeout(10)->post($apiUrl, [
|
|
'chat_id' => $chatId,
|
|
'text' => $text,
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
Log::info('Message sent successfully to Zalo Bot');
|
|
return true;
|
|
}
|
|
|
|
Log::error('Failed to send message to Zalo Bot', [
|
|
'status' => $response->status(),
|
|
'body' => $response->body()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|