234 lines
7.9 KiB
PHP
234 lines
7.9 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Notification;
|
||
|
||
use App\Services\Database\Connection;
|
||
use PDO;
|
||
use Throwable;
|
||
|
||
class TelegramNotifier
|
||
{
|
||
private PDO $pdo;
|
||
private ?string $botToken = null;
|
||
private ?string $chatId = null;
|
||
private ?string $lastError = null;
|
||
|
||
public function __construct(Connection $connection)
|
||
{
|
||
$this->pdo = $connection->getPdo();
|
||
$this->loadSettings();
|
||
}
|
||
|
||
/**
|
||
* Load settings from database settings table.
|
||
*/
|
||
public function loadSettings(): void
|
||
{
|
||
$this->botToken = $this->getSetting('telegram_bot_token');
|
||
$this->chatId = $this->getSetting('telegram_chat_id');
|
||
}
|
||
|
||
/**
|
||
* Helper to retrieve setting by key.
|
||
*/
|
||
private function getSetting(string $key): ?string
|
||
{
|
||
try {
|
||
$stmt = $this->pdo->prepare("SELECT `value` FROM settings WHERE `key` = ?");
|
||
$stmt->execute([$key]);
|
||
$val = $stmt->fetchColumn();
|
||
return $val !== false ? $val : null;
|
||
} catch (Throwable $e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Configure manually (for testing unsaved parameters).
|
||
*/
|
||
public function configure(string $botToken, string $chatId): void
|
||
{
|
||
$this->botToken = $botToken;
|
||
$this->chatId = $chatId;
|
||
}
|
||
|
||
/**
|
||
* Retrieve the last HTTP or API error message.
|
||
*/
|
||
public function getLastError(): ?string
|
||
{
|
||
return $this->lastError;
|
||
}
|
||
|
||
/**
|
||
* Send a notification to Telegram.
|
||
*/
|
||
public function send(string $message, string $type = 'info'): bool
|
||
{
|
||
$this->lastError = null;
|
||
|
||
if (!$this->botToken || !$this->chatId) {
|
||
$this->lastError = "Telegram token or chat ID is missing.";
|
||
return false;
|
||
}
|
||
|
||
$icons = [
|
||
'info' => "\xF0\x9F\x93\xA1", // 📡
|
||
'success' => "\xE2\x9C\x85", // ✅
|
||
'warning' => "\xE2\x9A\xA0\xEF\xB8\x8F", // ⚠️
|
||
'error' => "\xE2\x9D\x8C", // ❌
|
||
'opportunity' => "\xF0\x9F\x92\xA1", // 💡
|
||
'funding' => "\xF0\x9F\x92\xB0", // 💰
|
||
];
|
||
|
||
$icon = $icons[$type] ?? $icons['info'];
|
||
$fullMessage = "{$icon} *ScoutIQ*\n\n{$message}";
|
||
|
||
try {
|
||
$url = "https://api.telegram.org/bot{$this->botToken}/sendMessage";
|
||
$payload = json_encode([
|
||
'chat_id' => $this->chatId,
|
||
'text' => $fullMessage,
|
||
'parse_mode' => 'Markdown',
|
||
'disable_web_page_preview' => true,
|
||
]);
|
||
|
||
if (function_exists('curl_init')) {
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_URL, $url);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||
|
||
$response = curl_exec($ch);
|
||
if ($response === false) {
|
||
$this->lastError = "Connection error: " . curl_error($ch);
|
||
curl_close($ch);
|
||
return false;
|
||
}
|
||
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($httpCode !== 200) {
|
||
$resDecoded = json_decode($response, true);
|
||
$this->lastError = $resDecoded['description'] ?? "API error HTTP {$httpCode}";
|
||
error_log("Telegram API returned code {$httpCode}: {$response}");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
} else {
|
||
$context = stream_context_create([
|
||
'http' => [
|
||
'method' => 'POST',
|
||
'header' => "Content-Type: application/json\r\n",
|
||
'content' => $payload,
|
||
'timeout' => 10,
|
||
],
|
||
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
|
||
]);
|
||
|
||
$response = @file_get_contents($url, false, $context);
|
||
if ($response === false) {
|
||
$error = error_get_last();
|
||
$this->lastError = $error['message'] ?? "Connection timed out.";
|
||
return false;
|
||
}
|
||
|
||
$resDecoded = json_decode($response, true);
|
||
if (($resDecoded['ok'] ?? false) === false) {
|
||
$this->lastError = $resDecoded['description'] ?? "API error";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
} catch (Throwable $e) {
|
||
$this->lastError = $e->getMessage();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Notify about a new opportunity.
|
||
*/
|
||
public function notifyNewOpportunity(array $opportunity): void
|
||
{
|
||
$title = $opportunity['title'] ?? 'Untitled';
|
||
$fitScore = $opportunity['fit_score'] ?? 0;
|
||
$url = $opportunity['url'] ?? '';
|
||
$orgName = $opportunity['org_name'] ?? '';
|
||
$whyItMatters = $opportunity['why_it_matters'] ?? [];
|
||
$requirements = $opportunity['requirements'] ?? [];
|
||
$deadline = $opportunity['deadline'] ?? null;
|
||
$effort = $opportunity['effort'] ?? 'متوسط';
|
||
$potentialReturn = $opportunity['potential_return'] ?? 'متوسط';
|
||
|
||
$effortEmoji = (str_contains($effort, 'عالي') || str_contains(strtolower($effort), 'high')) ? '🔴' : ((str_contains($effort, 'متوسط') || str_contains(strtolower($effort), 'medium')) ? '🟡' : '🟢');
|
||
$returnEmoji = (str_contains($potentialReturn, 'عالي') || str_contains(strtolower($potentialReturn), 'high')) ? '🟢' : ((str_contains($potentialReturn, 'متوسط') || str_contains(strtolower($potentialReturn), 'medium')) ? '🟡' : '🔴');
|
||
|
||
$message = "💡 *ScoutIQ*\n\n";
|
||
$message .= "*{$title}*";
|
||
if ($orgName) {
|
||
$message .= " – {$orgName}";
|
||
}
|
||
|
||
$message .= "\n\n🔥 *Match Score:* {$fitScore}/100\n";
|
||
|
||
if (!empty($whyItMatters)) {
|
||
$message .= "\n*لماذا تهمك؟*\n";
|
||
foreach ((array)$whyItMatters as $why) {
|
||
$message .= "• {$why}\n";
|
||
}
|
||
}
|
||
|
||
if (!empty($requirements)) {
|
||
$message .= "\n*المتطلبات:*\n";
|
||
foreach ((array)$requirements as $req) {
|
||
$message .= "• {$req}\n";
|
||
}
|
||
}
|
||
|
||
if ($deadline) {
|
||
$message .= "\n*الموعد النهائي:*\n{$deadline}\n";
|
||
}
|
||
|
||
$message .= "\n*الجهد المطلوب:*\n{$effortEmoji} {$effort}\n";
|
||
$message .= "\n*العائد المحتمل:*\n{$returnEmoji} {$potentialReturn}\n";
|
||
|
||
$message .= "\n*الإجراء:*\n";
|
||
if ($url) {
|
||
$message .= "[قدم الآن]({$url})";
|
||
} else {
|
||
$message .= "لا يوجد رابط متاح";
|
||
}
|
||
|
||
$this->send($message, 'opportunity');
|
||
}
|
||
|
||
/**
|
||
* Notify about collector results.
|
||
*/
|
||
public function notifyCollectorResults(array $results): void
|
||
{
|
||
$message = "*Data Collection Complete*\n\n";
|
||
$message .= "Sources: {$results['processed']}/{$results['total_sources']}\n";
|
||
$message .= "New Opportunities: {$results['new_opportunities']}\n";
|
||
$message .= "New Organizations: {$results['new_organizations']}\n";
|
||
$message .= "Errors: {$results['errors']}";
|
||
|
||
$this->send($message, 'success');
|
||
}
|
||
|
||
/**
|
||
* Send test message.
|
||
*/
|
||
public function sendTest(): bool
|
||
{
|
||
return $this->send("Test notification from ScoutIQ. Your Telegram integration is working correctly!", 'success');
|
||
}
|
||
} |