Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
 
3
/*
4
 * This file is part of the Monolog package.
5
 *
6
 * (c) Jordi Boggiano <j.boggiano@seld.be>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Monolog\Handler;
13
 
14
use RuntimeException;
15
use Monolog\Logger;
16
use Monolog\Utils;
17
 
18
/**
19
 * Handler send logs to Telegram using Telegram Bot API.
20
 *
21
 * How to use:
22
 *  1) Create telegram bot with https://telegram.me/BotFather
23
 *  2) Create a telegram channel where logs will be recorded.
24
 *  3) Add created bot from step 1 to the created channel from step 2.
25
 *
26
 * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler
27
 *
28
 * @link https://core.telegram.org/bots/api
29
 *
30
 * @author Mazur Alexandr <alexandrmazur96@gmail.com>
31
 *
32
 * @phpstan-import-type Record from \Monolog\Logger
33
 */
34
class TelegramBotHandler extends AbstractProcessingHandler
35
{
36
    private const BOT_API = 'https://api.telegram.org/bot';
37
 
38
    /**
39
     * The available values of parseMode according to the Telegram api documentation
40
     */
41
    private const AVAILABLE_PARSE_MODES = [
42
        'HTML',
43
        'MarkdownV2',
44
        'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead
45
    ];
46
 
47
    /**
48
     * The maximum number of characters allowed in a message according to the Telegram api documentation
49
     */
50
    private const MAX_MESSAGE_LENGTH = 4096;
51
 
52
    /**
53
     * Telegram bot access token provided by BotFather.
54
     * Create telegram bot with https://telegram.me/BotFather and use access token from it.
55
     * @var string
56
     */
57
    private $apiKey;
58
 
59
    /**
60
     * Telegram channel name.
61
     * Since to start with '@' symbol as prefix.
62
     * @var string
63
     */
64
    private $channel;
65
 
66
    /**
67
     * The kind of formatting that is used for the message.
68
     * See available options at https://core.telegram.org/bots/api#formatting-options
69
     * or in AVAILABLE_PARSE_MODES
70
     * @var ?string
71
     */
72
    private $parseMode;
73
 
74
    /**
75
     * Disables link previews for links in the message.
76
     * @var ?bool
77
     */
78
    private $disableWebPagePreview;
79
 
80
    /**
81
     * Sends the message silently. Users will receive a notification with no sound.
82
     * @var ?bool
83
     */
84
    private $disableNotification;
85
 
86
    /**
87
     * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages.
88
     * False - truncates a message that is too long.
89
     * @var bool
90
     */
91
    private $splitLongMessages;
92
 
93
    /**
94
     * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests).
95
     * @var bool
96
     */
97
    private $delayBetweenMessages;
98
 
99
    /**
100
     * @param string $apiKey Telegram bot access token provided by BotFather
101
     * @param string $channel Telegram channel name
102
     * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages
103
     * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API
104
     * @throws MissingExtensionException
105
     */
106
    public function __construct(
107
        string $apiKey,
108
        string $channel,
109
               $level = Logger::DEBUG,
110
        bool   $bubble = true,
111
        string $parseMode = null,
112
        bool   $disableWebPagePreview = null,
113
        bool   $disableNotification = null,
114
        bool   $splitLongMessages = false,
115
        bool   $delayBetweenMessages = false
116
    )
117
    {
118
        if (!extension_loaded('curl')) {
119
            throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler');
120
        }
121
 
122
        parent::__construct($level, $bubble);
123
 
124
        $this->apiKey = $apiKey;
125
        $this->channel = $channel;
126
        $this->setParseMode($parseMode);
127
        $this->disableWebPagePreview($disableWebPagePreview);
128
        $this->disableNotification($disableNotification);
129
        $this->splitLongMessages($splitLongMessages);
130
        $this->delayBetweenMessages($delayBetweenMessages);
131
    }
132
 
133
    public function setParseMode(string $parseMode = null): self
134
    {
135
        if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES)) {
136
            throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.');
137
        }
138
 
139
        $this->parseMode = $parseMode;
140
 
141
        return $this;
142
    }
143
 
144
    public function disableWebPagePreview(bool $disableWebPagePreview = null): self
145
    {
146
        $this->disableWebPagePreview = $disableWebPagePreview;
147
 
148
        return $this;
149
    }
150
 
151
    public function disableNotification(bool $disableNotification = null): self
152
    {
153
        $this->disableNotification = $disableNotification;
154
 
155
        return $this;
156
    }
157
 
158
    /**
159
     * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages.
160
     * False - truncates a message that is too long.
161
     * @param bool $splitLongMessages
162
     * @return $this
163
     */
164
    public function splitLongMessages(bool $splitLongMessages = false): self
165
    {
166
        $this->splitLongMessages = $splitLongMessages;
167
 
168
        return $this;
169
    }
170
 
171
    /**
172
     * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests).
173
     * @param bool $delayBetweenMessages
174
     * @return $this
175
     */
176
    public function delayBetweenMessages(bool $delayBetweenMessages = false): self
177
    {
178
        $this->delayBetweenMessages = $delayBetweenMessages;
179
 
180
        return $this;
181
    }
182
 
183
    /**
184
     * {@inheritDoc}
185
     */
186
    public function handleBatch(array $records): void
187
    {
188
        /** @var Record[] $messages */
189
        $messages = [];
190
 
191
        foreach ($records as $record) {
192
            if (!$this->isHandling($record)) {
193
                continue;
194
            }
195
 
196
            if ($this->processors) {
197
                /** @var Record $record */
198
                $record = $this->processRecord($record);
199
            }
200
 
201
            $messages[] = $record;
202
        }
203
 
204
        if (!empty($messages)) {
205
            $this->send((string)$this->getFormatter()->formatBatch($messages));
206
        }
207
    }
208
 
209
    /**
210
     * @inheritDoc
211
     */
212
    protected function write(array $record): void
213
    {
214
        $this->send($record['formatted']);
215
    }
216
 
217
    /**
218
     * Send request to @link https://api.telegram.org/bot on SendMessage action.
219
     * @param string $message
220
     */
221
    protected function send(string $message): void
222
    {
223
        $messages = $this->handleMessageLength($message);
224
 
225
        foreach ($messages as $key => $msg) {
226
            if ($this->delayBetweenMessages && $key > 0) {
227
                sleep(1);
228
            }
229
 
230
            $this->sendCurl($msg);
231
        }
232
    }
233
 
234
    protected function sendCurl(string $message): void
235
    {
236
        $ch = curl_init();
237
        $url = self::BOT_API . $this->apiKey . '/SendMessage';
238
        curl_setopt($ch, CURLOPT_URL, $url);
239
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
240
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
241
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
242
            'text' => $message,
243
            'chat_id' => $this->channel,
244
            'parse_mode' => $this->parseMode,
245
            'disable_web_page_preview' => $this->disableWebPagePreview,
246
            'disable_notification' => $this->disableNotification,
247
        ]));
248
 
249
        $result = Curl\Util::execute($ch);
250
        if (!is_string($result)) {
251
            throw new RuntimeException('Telegram API error. Description: No response');
252
        }
253
        $result = json_decode($result, true);
254
 
255
        if ($result['ok'] === false) {
256
            throw new RuntimeException('Telegram API error. Description: ' . $result['description']);
257
        }
258
    }
259
 
260
    /**
261
     * Handle a message that is too long: truncates or splits into several
262
     * @param string $message
263
     * @return string[]
264
     */
265
    private function handleMessageLength(string $message): array
266
    {
267
        $truncatedMarker = ' (...truncated)';
268
        if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) {
269
            return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker];
270
        }
271
 
272
        return str_split($message, self::MAX_MESSAGE_LENGTH);
273
    }
274
}