Issues (36)

Commands/AdminCommands/SendtochannelCommand.php (2 issues)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is part of the TelegramBot package.
5
 *
6
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
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 Longman\TelegramBot\Commands\AdminCommands;
13
14
use Longman\TelegramBot\Commands\AdminCommand;
15
use Longman\TelegramBot\Conversation;
16
use Longman\TelegramBot\Entities\Chat;
17
use Longman\TelegramBot\Entities\Entity;
18
use Longman\TelegramBot\Entities\Keyboard;
19
use Longman\TelegramBot\Entities\Message;
20
use Longman\TelegramBot\Entities\ServerResponse;
21
use Longman\TelegramBot\Exception\TelegramException;
22
use Longman\TelegramBot\Request;
23
24
class SendtochannelCommand extends AdminCommand
25
{
26
    /**
27
     * @var string
28
     */
29
    protected $name = 'sendtochannel';
30
31
    /**
32
     * @var string
33
     */
34
    protected $description = 'Send message to a channel';
35
36
    /**
37
     * @var string
38
     */
39
    protected $usage = '/sendtochannel <message to send>';
40
41
    /**
42
     * @var string
43
     */
44
    protected $version = '0.3.0';
45
46
    /**
47
     * @var bool
48
     */
49
    protected $need_mysql = true;
50
51
    /**
52
     * Conversation Object
53
     *
54
     * @var Conversation
55
     */
56
    protected $conversation;
57
58
    /**
59
     * Command execute method
60
     *
61
     * @return ServerResponse|mixed
62
     * @throws TelegramException
63
     */
64
    public function execute(): ServerResponse
65
    {
66
        $message = $this->getMessage();
67
        $chat_id = $message->getChat()->getId();
68
        $user_id = $message->getFrom()->getId();
69
70
        $type = $message->getType();
71
        // 'Cast' the command type to message to protect the machine state
72
        // if the command is recalled when the conversation is already started
73
        in_array($type, ['command', 'text'], true) && $type = 'message';
74
75
        $text           = trim($message->getText(true));
0 ignored issues
show
It seems like $message->getText(true) can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

75
        $text           = trim(/** @scrutinizer ignore-type */ $message->getText(true));
Loading history...
76
        $text_yes_or_no = ($text === 'Yes' || $text === 'No');
77
78
        $data = [
79
            'chat_id' => $chat_id,
80
        ];
81
82
        // Conversation
83
        $this->conversation = new Conversation($user_id, $chat_id, $this->getName());
84
85
        $notes = &$this->conversation->notes;
86
        !is_array($notes) && $notes = [];
87
88
        $channels = (array) $this->getConfig('your_channel');
89
        if (isset($notes['state'])) {
90
            $state = $notes['state'];
91
        } else {
92
            $state                    = (count($channels) === 0) ? -1 : 0;
93
            $notes['last_message_id'] = $message->getMessageId();
94
        }
95
96
        $yes_no_keyboard = new Keyboard(
97
            [
98
                'keyboard'          => [['Yes', 'No']],
99
                'resize_keyboard'   => true,
100
                'one_time_keyboard' => true,
101
                'selective'         => true,
102
            ]
103
        );
104
105
        switch ($state) {
106
            case -1:
107
                // getConfig has not been configured asking for channel to administer
108
                if ($type !== 'message' || $text === '') {
109
                    $notes['state'] = -1;
110
                    $this->conversation->update();
111
112
                    $result = $this->replyToChat(
113
                        'Insert the channel name or ID (_@yourchannel_ or _-12345_)',
114
                        [
115
                            'parse_mode'   => 'markdown',
116
                            'reply_markup' => Keyboard::remove(['selective' => true]),
117
                        ]
118
                    );
119
120
                    break;
121
                }
122
                $notes['channel']         = $text;
123
                $notes['last_message_id'] = $message->getMessageId();
124
                // Jump to state 1
125
                goto insert;
126
127
            // no break
128
            default:
129
            case 0:
130
                // getConfig has been configured choose channel
131
                if ($type !== 'message' || $text === '') {
132
                    $notes['state'] = 0;
133
                    $this->conversation->update();
134
135
                    $keyboard = array_map(function ($channel) {
136
                        return [$channel];
137
                    }, $channels);
138
139
                    $result = $this->replyToChat(
140
                        'Choose a channel from the keyboard' . PHP_EOL .
141
                        '_or_ insert the channel name or ID (_@yourchannel_ or _-12345_)',
142
                        [
143
                            'parse_mode'   => 'markdown',
144
                            'reply_markup' => new Keyboard(
145
                                [
146
                                    'keyboard'          => $keyboard,
147
                                    'resize_keyboard'   => true,
148
                                    'one_time_keyboard' => true,
149
                                    'selective'         => true,
150
                                ]
151
                            ),
152
                        ]
153
                    );
154
                    break;
155
                }
156
                $notes['channel']         = $text;
157
                $notes['last_message_id'] = $message->getMessageId();
158
159
            // no break
160
            case 1:
161
                insert:
162
                if (($type === 'message' && $text === '') || $notes['last_message_id'] === $message->getMessageId()) {
163
                    $notes['state'] = 1;
164
                    $this->conversation->update();
165
166
                    $result = $this->replyToChat(
167
                        'Insert the content you want to share: text, photo, audio...',
168
                        ['reply_markup' => Keyboard::remove(['selective' => true])]
169
                    );
170
                    break;
171
                }
172
                $notes['last_message_id'] = $message->getMessageId();
173
                $notes['message']         = $message->getRawData();
174
                $notes['message_type']    = $type;
175
            // no break
176
            case 2:
177
                if (!$text_yes_or_no || $notes['last_message_id'] === $message->getMessageId()) {
178
                    $notes['state'] = 2;
179
                    $this->conversation->update();
180
181
                    // Grab any existing caption.
182
                    if ($caption = $message->getCaption()) {
183
                        $notes['caption'] = $caption;
184
                        $text             = 'No';
185
                    } elseif (in_array($notes['message_type'], ['video', 'photo'], true)) {
186
                        $text = 'Would you like to insert a caption?';
187
                        if (!$text_yes_or_no && $notes['last_message_id'] !== $message->getMessageId()) {
188
                            $text .= PHP_EOL . 'Type Yes or No';
189
                        }
190
191
                        $result = $this->replyToChat(
192
                            $text,
193
                            ['reply_markup' => $yes_no_keyboard]
194
                        );
195
                        break;
196
                    }
197
                }
198
                $notes['set_caption']     = ($text === 'Yes');
199
                $notes['last_message_id'] = $message->getMessageId();
200
            // no break
201
            case 3:
202
                if ($notes['set_caption'] && ($notes['last_message_id'] === $message->getMessageId() || $type !== 'message')) {
203
                    $notes['state'] = 3;
204
                    $this->conversation->update();
205
206
                    $result = $this->replyToChat(
207
                        'Insert caption:',
208
                        ['reply_markup' => Keyboard::remove(['selective' => true])]
209
                    );
210
                    break;
211
                }
212
                $notes['last_message_id'] = $message->getMessageId();
213
                if (isset($notes['caption'])) {
214
                    // If caption has already been send with the file, no need to ask for it.
215
                    $notes['set_caption'] = true;
216
                } else {
217
                    $notes['caption'] = $text;
218
                }
219
            // no break
220
            case 4:
221
                if (!$text_yes_or_no || $notes['last_message_id'] === $message->getMessageId()) {
222
                    $notes['state'] = 4;
223
                    $this->conversation->update();
224
225
                    $result = $this->replyToChat('Message will look like this:');
226
227
                    if ($notes['message_type'] !== 'command') {
228
                        if ($notes['set_caption']) {
229
                            $data['caption'] = $notes['caption'];
230
                        }
231
                        $this->sendBack(new Message($notes['message'], $this->telegram->getBotUsername()), $data);
232
233
                        $data['reply_markup'] = $yes_no_keyboard;
234
235
                        $data['text'] = 'Would you like to post it?';
236
                        if (!$text_yes_or_no && $notes['last_message_id'] !== $message->getMessageId()) {
237
                            $data['text'] .= PHP_EOL . 'Type Yes or No';
238
                        }
239
                        $result = Request::sendMessage($data);
240
                    }
241
                    break;
242
                }
243
244
                $notes['post_message']    = ($text === 'Yes');
245
                $notes['last_message_id'] = $message->getMessageId();
246
            // no break
247
            case 5:
248
                $data['reply_markup'] = Keyboard::remove(['selective' => true]);
249
250
                if ($notes['post_message']) {
251
                    $data['parse_mode'] = 'markdown';
252
                    $data['text']       = $this->publish(
253
                        new Message($notes['message'], $this->telegram->getBotUsername()),
254
                        $notes['channel'],
255
                        $notes['caption']
256
                    );
257
                } else {
258
                    $data['text'] = 'Aborted by user, message not sent..';
259
                }
260
261
                $this->conversation->stop();
262
                $result = Request::sendMessage($data);
263
        }
264
265
        return $result;
266
    }
267
268
    /**
269
     * SendBack
270
     *
271
     * Received a message, the bot can send a copy of it to another chat/channel.
272
     * You don't have to care about the type of the message, the function detect it and use the proper
273
     * REQUEST:: function to send it.
274
     * $data include all the var that you need to send the message to the proper chat
275
     *
276
     * @todo This method will be moved to a higher level maybe in AdminCommand or Command
277
     * @todo Looking for a more significant name
278
     *
279
     * @param Message $message
280
     * @param array   $data
281
     *
282
     * @return ServerResponse
283
     * @throws TelegramException
284
     */
285
    protected function sendBack(Message $message, array $data): ServerResponse
286
    {
287
        $type = $message->getType();
288
        in_array($type, ['command', 'text'], true) && $type = 'message';
289
290
        if ($type === 'message') {
291
            $data['text'] = $message->getText(true);
292
        } elseif ($type === 'audio') {
293
            $data['audio']     = $message->getAudio()->getFileId();
294
            $data['duration']  = $message->getAudio()->getDuration();
295
            $data['performer'] = $message->getAudio()->getPerformer();
296
            $data['title']     = $message->getAudio()->getTitle();
297
        } elseif ($type === 'document') {
298
            $data['document'] = $message->getDocument()->getFileId();
299
        } elseif ($type === 'photo') {
300
            $data['photo'] = $message->getPhoto()[0]->getFileId();
301
        } elseif ($type === 'sticker') {
302
            $data['sticker'] = $message->getSticker()->getFileId();
303
        } elseif ($type === 'video') {
304
            $data['video'] = $message->getVideo()->getFileId();
305
        } elseif ($type === 'voice') {
306
            $data['voice'] = $message->getVoice()->getFileId();
307
        } elseif ($type === 'location') {
308
            $data['latitude']  = $message->getLocation()->getLatitude();
309
            $data['longitude'] = $message->getLocation()->getLongitude();
310
        }
311
312
        return Request::send('send' . ucfirst($type), $data);
313
    }
314
315
    /**
316
     * Publish a message to a channel and return success or failure message in markdown format
317
     *
318
     * @param Message     $message
319
     * @param string|int  $channel_id
320
     * @param string|null $caption
321
     *
322
     * @return string
323
     * @throws TelegramException
324
     */
325
    protected function publish(Message $message, $channel_id, $caption = null): string
326
    {
327
        $res = $this->sendBack($message, [
328
            'chat_id' => $channel_id,
329
            'caption' => $caption,
330
        ]);
331
332
        if ($res->isOk()) {
333
            /** @var Chat $channel */
334
            $channel          = $res->getResult()->getChat();
335
            $escaped_username = $channel->getUsername() ? Entity::escapeMarkdown($channel->getUsername()) : '';
336
337
            $response = sprintf(
338
                'Message successfully sent to *%s*%s',
339
                filter_var($channel->getTitle(), FILTER_SANITIZE_SPECIAL_CHARS),
340
                $escaped_username ? " (@{$escaped_username})" : ''
341
            );
342
        } else {
343
            $escaped_username = Entity::escapeMarkdown($channel_id);
344
            $response         = "Message not sent to *{$escaped_username}*" . PHP_EOL .
345
                                '- Does the channel exist?' . PHP_EOL .
346
                                '- Is the bot an admin of the channel?';
347
        }
348
349
        return $response;
350
    }
351
352
    /**
353
     * Execute without db
354
     *
355
     * @todo Why send just to the first found channel?
356
     *
357
     * @return mixed
358
     * @throws TelegramException
359
     */
360
    public function executeNoDb(): ServerResponse
361
    {
362
        $message = $this->getMessage();
363
        $text    = trim($message->getText(true));
0 ignored issues
show
It seems like $message->getText(true) can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

363
        $text    = trim(/** @scrutinizer ignore-type */ $message->getText(true));
Loading history...
364
365
        if ($text === '') {
366
            return $this->replyToChat('Usage: ' . $this->getUsage());
367
        }
368
369
        $channels = array_filter((array) $this->getConfig('your_channel'));
370
        if (empty($channels)) {
371
            return $this->replyToChat('No channels defined in the command config!');
372
        }
373
374
        return $this->replyToChat($this->publish(
375
            new Message($message->getRawData(), $this->telegram->getBotUsername()),
376
            reset($channels)
377
        ), ['parse_mode' => 'markdown']);
378
    }
379
}
380