Skip to content

Commit f99072b

Browse files
committed
Add Telegram data source.
1 parent 44aafe2 commit f99072b

3 files changed

Lines changed: 176 additions & 3 deletions

File tree

lib/Temma/Base/Datasource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ static public function factory(string $dsn) : \Temma\Base\Datasource {
102102
return (\Temma\Datasources\Discord::factory($dsn));
103103
if (str_starts_with($dsn, 'googlechat://'))
104104
return (\Temma\Datasources\GoogleChat::factory($dsn));
105+
if (str_starts_with($dsn, 'telegram://'))
106+
return (\Temma\Datasources\Telegram::factory($dsn));
105107
if (str_starts_with($dsn, 'dummy://'))
106108
return (\Temma\Datasources\Dummy::factory(''));
107109
if (str_starts_with($dsn, 'env://')) {

lib/Temma/Datasources/GoogleChat.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* $gchat = \Temma\Datasources\GoogleChat::factory('gchat://WEBHOOK_URL');
2525
* $gchat = \Temma\Base\Datasource::factory('gchat://WEBHOOK_URL');
2626
* // where WEBHOOK_URL is the fetched webhook URL without the 'https://' part at its beginning.
27-
* // example: 'slack:/at.googleapis.com/v1/spaces/XXXXXXX/messages?key=YYYYYYY&token=ZZZZZZZ'
27+
* // example: 'googlechat://chat.googleapis.com/v1/spaces/XXXXXXX/messages?key=YYYYYYY&token=ZZZZZZZ'
2828
*
2929
* // send a simple text message to the given channel
3030
* $gchat[''] = 'Text message';
@@ -36,7 +36,11 @@
3636
* $gchat[''] = 'Text with *simple* _formatting_.';
3737
*
3838
* // Send a rich notification, with a title, a subtitle a pictogram and/or sections and buttons.
39-
* $gchat['] = [
39+
* // Possible icons (in sections) are: AIRPLANE, BOOKMARK, BUS, CAR, HORLOGE, CONFIRMATION_NUMBER_ICON,
40+
* // DESCRIPTION, DOLLAR, E-MAIL, EVENT_SEAT, FLIGHT_ARRIVAL, FLIGHT_DEPARTURE, HOTEL, HOTEL_ROOM_TYPE,
41+
* // INVITER, MAP_PIN, ABONNEMENT, MULTIPLE_PEOPLE, PERSONNE, TÉLÉPHONE, RESTAURANT_ICON, SHOPPING_CART,
42+
* // STAR, STORE, TICKET, TRAIN, VIDEO_CAMERA, VIDEO_PLAY
43+
* $gchat[''] = [
4044
* 'title' => 'A title',
4145
* 'subtitle' => 'A subtitle',
4246
* 'picto' => 'https://url of the picto',
@@ -132,7 +136,7 @@ public function write(string $channel, string $text=null, mixed $options=null) :
132136
* @param string $channel Not used.
133137
* @param string|array $message Text message, or an associative array with formatted message data.
134138
* @param mixed $options Not used.
135-
* @return bool always true.
139+
* @return bool Always true.
136140
* @throws \Exception If an error occured.
137141
*/
138142
public function set(string $channel, mixed $message=null, mixed $options=null) : bool {

lib/Temma/Datasources/Telegram.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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

Comments
 (0)