<?php
namespace App\Services;
// Load Telegram config if not already loaded by an autoloader or entry script
if (!defined('TELEGRAM_BOT_TOKEN')) {
// Adjust path as necessary depending on how this service is included/loaded
$telegramConfigPath = __DIR__.'/../../config/telegram_config.php';
if (file_exists($telegramConfigPath)) {
require_once $telegramConfigPath;
} else {
// Fallback or error if config is essential and not found
// For now, we'll define a dummy one if not found to prevent immediate script halt
// In a real app, this should be a fatal error or proper handling.
if (!defined('TELEGRAM_BOT_TOKEN')) {
define('TELEGRAM_BOT_TOKEN', 'YOUR_TELEGRAM_BOT_TOKEN_PLACEHOLDER');
}
error_log("Warning: telegram_config.php not found at ".$telegramConfigPath.". Using placeholder token.");
}
}
class TelegramService
{
private string $apiToken;
private string $apiUrlBase;
/**
* @param string $apiToken
*/
public function __construct(?string $apiToken = null)
{
$this->apiToken = $apiToken ?: (defined('TELEGRAM_BOT_TOKEN') ? TELEGRAM_BOT_TOKEN : '');
if (empty($this->apiToken) || $this->apiToken === 'YOUR_TELEGRAM_BOT_TOKEN' || $this->apiToken === 'YOUR_TELEGRAM_BOT_TOKEN_PLACEHOLDER') {
// Log or throw an exception if the token is missing or still a placeholder
error_log("Telegram API Token is missing or is a placeholder. Please configure it in config/telegram_config.php");
// Depending on strictness, you might throw an exception here:
// throw new \Exception("Telegram API Token is not configured.");
}
$this->apiUrlBase = "https://api.telegram.org/bot{$this->apiToken}/";
}
/**
* Sends a GET request to the Telegram API.
* @param string $method Telegram API method name (e.g., "getMe").
* @param array $params Optional parameters for the method.
* @return array|false Decoded JSON response as an associative array, or false on failure.
*/
private function apiRequest(string $method, array $params = [], string $requestType = 'POST')
{
if (empty($this->apiToken) || $this->apiToken === 'YOUR_TELEGRAM_BOT_TOKEN' || $this->apiToken === 'YOUR_TELEGRAM_BOT_TOKEN_PLACEHOLDER') {
error_log("Cannot make API request: Telegram API Token is not properly configured.");
return false;
}
$url = $this->apiUrlBase.$method;
$ch = curl_init();
if ($requestType === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
// Send data as JSON payload
$jsonData = json_encode($params);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: '.strlen($jsonData)
]);
} else {
// GET request
if (!empty($params)) {
$url .= "?".http_build_query($params);
}
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 seconds timeout
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // Should be true in production
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError) {
error_log("Telegram API cURL Error for method {$method}: {$curlError}");
return false;
}
if ($httpCode !== 200) {
error_log("Telegram API HTTP Error for method {$method}: Code {$httpCode}. Response: {$response}");
// Attempt to decode response anyway, it might contain error details from Telegram
$decodedResponse = json_decode($response, true);
return $decodedResponse ?: false; // Return decoded error or false
}
$decodedResponse = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("Telegram API JSON Decode Error for method {$method}: ".json_last_error_msg().". Response: {$response}");
return false;
}
return $decodedResponse;
}
/**
* A simple method to test connection, like "getMe".
* @return array|false Bot information or false on failure.
*/
public function getMe()
{
return $this->apiRequest('getMe', [], 'GET');
}
/**
* Sends a text message to a specific chat.
* @param int $chatId Target chat_id.
* @param string $text Text of the message to be sent.
* @param ?array $replyMarkup Optional. Inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.
* @param ?string $parseMode Optional. Mode for parsing entities in the message text. See formatting options for more details. (e.g., MarkdownV2, HTML)
* @return array|false API response or false on failure.
*/
public function sendMessage(int $chatId, string $text, ?array $replyMarkup = null, ?string $parseMode = null)
{
$params = [
'chat_id' => $chatId,
'text' => $text
];
if ($replyMarkup !== null) {
$params['reply_markup'] = $replyMarkup;
}
if ($parseMode !== null) {
$params['parse_mode'] = $parseMode;
}
return $this->apiRequest('sendMessage', $params);
}
/**
* Edits text of a message sent by the bot.
* @param int $chatId Target chat_id where the message to edit is located.
* @param int $messageId Identifier of the message to edit.
* @param string $text New text of the message.
* @param ?array $replyMarkup Optional. Inline keyboard.
* @param ?string $parseMode Optional. Mode for parsing entities in the message text (e.g., MarkdownV2, HTML).
* @return array|false API response or false on failure.
*/
public function editMessageText(int $chatId, int $messageId, string $text, ?array $replyMarkup = null, ?string $parseMode = null)
{
$params = [
'chat_id' => $chatId,
'message_id' => $messageId,
'text' => $text
];
if ($replyMarkup !== null) {
$params['reply_markup'] = $replyMarkup;
}
if ($parseMode !== null) {
$params['parse_mode'] = $parseMode;
}
return $this->apiRequest('editMessageText', $params);
}
/**
* Sets the webhook for the bot.
* @param string $webhookUrl HTTPS URL to send updates to.
* @param ?array $options Optional parameters like certificate, allowed_updates etc.
* @return array|false API response or false on failure.
*/
public function setWebhook(string $webhookUrl, ?array $options = [])
{
$params = array_merge(['url' => $webhookUrl], $options);
return $this->apiRequest('setWebhook', $params);
}
/**
* Deletes the currently set webhook.
* @return array|false API response or false on failure.
*/
public function deleteWebhook()
{
return $this->apiRequest('deleteWebhook');
}
/**
* Gets the current webhook information.
* @return array|false API response or false on failure.
*/
public function getWebhookInfo()
{
return $this->apiRequest('getWebhookInfo', [], 'GET');
}
/**
* Answers callback query to remove the loading state from inline keyboard buttons.
* @param string $callbackQueryId Unique identifier for the callback query to be answered.
* @param ?string $text Optional. Text of the notification (max 200 characters).
* @param ?bool $showAlert Optional. If true, an alert will be shown instead of a notification.
* @return array|false API response or false on failure.
*/
public function answerCallbackQuery(string $callbackQueryId, ?string $text = null, ?bool $showAlert = null)
{
$params = [
'callback_query_id' => $callbackQueryId
];
if ($text !== null) {
$params['text'] = $text;
}
if ($showAlert !== null) {
$params['show_alert'] = $showAlert;
}
return $this->apiRequest('answerCallbackQuery', $params);
}
}