|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * Telegram |
| 5 | + * @author Amaury Bouchard <amaury@amaury.net> |
| 6 | + * @copyright © 2025, Amaury Bouchard |
| 7 | + * @link https://www.temma.net/documentation/datasource-telegram |
| 8 | + */ |
| 9 | + |
| 10 | +namespace Temma\Datasources; |
| 11 | + |
| 12 | +use \Temma\Base\Log as TµLog; |
| 13 | + |
| 14 | +/** |
| 15 | + * Telegram Bot management object. |
| 16 | + * |
| 17 | + * This object is used to send notifications to a Telegram bot. |
| 18 | + * To send a notifications to a Telegram bot, you need to create a bot and retrieve the associated |
| 19 | + * token, and then the related chat_id. |
| 20 | + * - On Telegram, search for BotFather. |
| 21 | + * - Send `/newbot`. |
| 22 | + * - You will get an API token. |
| 23 | + * |
| 24 | + * For a private discussion: |
| 25 | + * - Chat with your bot. |
| 26 | + * - Do a request: `curl -s "https://api.telegram.org/bot<token>/getUpdates"` |
| 27 | + * - In the JSON response, search for `"chat":{"id":123456789}`. This is the `chat_id`. |
| 28 | + * |
| 29 | + * For a group conversation: |
| 30 | + * - Add the bot to a group. |
| 31 | + * - Get the `chat_id` (negative number). |
| 32 | + * |
| 33 | + * For a channel: |
| 34 | + * - Create a channel. |
| 35 | + * - Add the bot to the channel, as administrator. |
| 36 | + * - `chat_id` is the channel name (like `@channel_name`). |
| 37 | + * |
| 38 | + * See https://core.telegram.org/bots/features#botfather |
| 39 | + * |
| 40 | + * <b>Usage</b> |
| 41 | + * <code> |
| 42 | + * // initialization |
| 43 | + * $tg = \Temma\Datasources\Telegram::factory('telegram://API_TOKEN'); |
| 44 | + * $tg = \Temma\Base\Datasource::factory('telegram://API_TOKEN'); |
| 45 | + * |
| 46 | + * // send a simple text message |
| 47 | + * $tg['123456789'] = 'Text message'; |
| 48 | + * $tg->write('@my_channel', 'Text message'); |
| 49 | + * $gt->set('@channel_name', 'Text message'); |
| 50 | + * |
| 51 | + * // send a notification formatted with simplified HTML |
| 52 | + * // See https://core.telegram.org/bots/api#html-style |
| 53 | + * $tg['@my_channel'] = 'Text with <i>simple</i> <b>formatting</b>.'; |
| 54 | + * |
| 55 | + */ |
| 56 | +class Telegram extends \Temma\Base\Datasource { |
| 57 | + /** Constant: URL of the Telegram API. */ |
| 58 | + const string API_URL = 'https://api.telegram.org/bot%s/sendMessage'; |
| 59 | + /** API token. */ |
| 60 | + private ?string $_apiToken = null; |
| 61 | + |
| 62 | + /* ********** CONSTRUCTION ********** */ |
| 63 | + /** |
| 64 | + * Create a new instance of this class. |
| 65 | + * @param string $dsn Connection string. |
| 66 | + * @return \Temma\Datasources\Telegram The created instance. |
| 67 | + * @throws \Temma\Exceptions\Database If the DSN is invalid. |
| 68 | + */ |
| 69 | + static public function factory(string $dsn) : \Temma\Datasources\Telegram { |
| 70 | + TµLog::log('Temma/Base', 'DEBUG', "\\Temma\\Datasources\\Telegram object creation with DSN: '$dsn'."); |
| 71 | + $webhook = null; |
| 72 | + if (!str_starts_with($dsn, 'telegram://')) { |
| 73 | + TµLog::log('Temma/Base', 'WARN', "Invalid Telegram DSN '$dsn'."); |
| 74 | + throw new \Temma\Exceptions\Database("Invalid Telegram DSN '$dsn'.", \Temma\Exceptions\Database::FUNDAMENTAL); |
| 75 | + } |
| 76 | + $dsn = mb_substr($dsn, mb_strlen('telegram://')); |
| 77 | + return (new self($dsn)); |
| 78 | + } |
| 79 | + /** |
| 80 | + * Constructor. |
| 81 | + * @param string $token API token. |
| 82 | + */ |
| 83 | + public function __construct(string $token) { |
| 84 | + $this->_apiToken = $token; |
| 85 | + } |
| 86 | + |
| 87 | + /* ********** STANDARD REQUESTS ********** */ |
| 88 | + /** |
| 89 | + * Disabled clear. |
| 90 | + * @param string $pattern Not used. |
| 91 | + * @throws \Temma\Exceptions\Database Always throws an exception. |
| 92 | + */ |
| 93 | + public function clear(string $pattern) : void { |
| 94 | + throw new \Temma\Exceptions\Database("No clear() method on this object.", \Temma\Exceptions\Database::FUNDAMENTAL); |
| 95 | + } |
| 96 | + |
| 97 | + /* ********** RAW REQUESTS ********** */ |
| 98 | + /** |
| 99 | + * Send a Telegram notification. |
| 100 | + * @param string $chatId Chat ID or channel name. |
| 101 | + * @param string $text Text message. |
| 102 | + * @param mixed $options Not used. |
| 103 | + * @return bool Always true. |
| 104 | + * @throws \Exception If an error occured. |
| 105 | + */ |
| 106 | + public function write(string $chatId, string $text=null, mixed $options=null) : bool { |
| 107 | + $this->set($chatId, $text, $options); |
| 108 | + return (true); |
| 109 | + } |
| 110 | + |
| 111 | + /* ********** KEY-VALUE REQUESTS ********** */ |
| 112 | + /** |
| 113 | + * Send a notification to Telegram. |
| 114 | + * @param string $chatId Chat ID or channel name. |
| 115 | + * @param string $message Text message. |
| 116 | + * @param mixed $options Not used. |
| 117 | + * @return bool Always true. |
| 118 | + * @throws \Exception If an error occured. |
| 119 | + */ |
| 120 | + public function set(string $chatId, mixed $message=null, mixed $options=null) : bool { |
| 121 | + if (!$this->_enabled) |
| 122 | + return (false); |
| 123 | + $data = []; |
| 124 | + if (!is_scalar($message)) |
| 125 | + throw new \Temma\Exceptions\Database("Bad Telegram message parameter.", \Temma\Exceptions\Database::FUNDAMENTAL); |
| 126 | + $message = (string)$message; |
| 127 | + // send the message |
| 128 | + $url = sprintf(self::API_URL, $this->_apiToken); |
| 129 | + $postfields = [ |
| 130 | + 'chat_id' => $chatId, |
| 131 | + 'text' => $message, |
| 132 | + 'parse_mode' => 'HTML', |
| 133 | + 'disable_web_page_preview' => true, |
| 134 | + ]; |
| 135 | + $ch = curl_init(); |
| 136 | + curl_setopt_array($ch, [ |
| 137 | + CURLOPT_URL => $url, |
| 138 | + CURLOPT_POST => true, |
| 139 | + CURLOPT_POSTFIELDS => $postfields, |
| 140 | + CURLOPT_RETURNTRANSFER => true, |
| 141 | + CURLOPT_CONNECTTIMEOUT => 5, |
| 142 | + CURLOPT_TIMEOUT => 10, |
| 143 | + ]); |
| 144 | + $response = curl_exec($ch); |
| 145 | + if ($response === false) { |
| 146 | + $error = curl_error($ch); |
| 147 | + curl_close($ch); |
| 148 | + throw new \Temma\Exceptions\Database("Erreur cURL lors de l'envoi Telegram: $error", \Temma\Exceptions\Database::FUNDAMENTAL); |
| 149 | + } |
| 150 | + curl_close($ch); |
| 151 | + $data = json_decode($response, true); |
| 152 | + if (!$data['ok']) { |
| 153 | + throw new \Temma\Exceptions\Database("Telegram API error: " . ($data['description'] ?? 'Unknown error.'), \Temma\Exceptions\Database::FUNDAMENTAL); |
| 154 | + } |
| 155 | + return (true); |
| 156 | + } |
| 157 | + /** |
| 158 | + * Disable mSet(). |
| 159 | + * @param array $data Associative array with keys (recipients' user key) and their associated values (text messages). |
| 160 | + * @param mixed $options (optional) Options. |
| 161 | + * @return int The number of sent messages. |
| 162 | + */ |
| 163 | + public function mSet(array $data, mixed $options=null) : int { |
| 164 | + throw new \Temma\Exceptions\Database("No mSet() method on this object.", \Temma\Exceptions\Database::FUNDAMENTAL); |
| 165 | + } |
| 166 | +} |
| 167 | + |
0 commit comments