Completed
Pull Request — develop (#288)
by Armando
30:59 queued 27:12
created

SendtochannelCommand   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 337
Duplicated Lines 10.09 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 51
c 4
b 2
f 0
lcom 1
cbo 9
dl 34
loc 337
ccs 0
cts 215
cp 0
rs 8.3206

4 Methods

Rating   Name   Duplication   Size   Complexity  
F execute() 34 186 36
C sendBack() 0 33 11
A publish() 0 17 2
A executeNoDb() 0 22 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SendtochannelCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SendtochannelCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\TelegramBot\Commands\AdminCommands;
12
13
use Longman\TelegramBot\Request;
14
use Longman\TelegramBot\Conversation;
15
use Longman\TelegramBot\Commands\AdminCommand;
16
use Longman\TelegramBot\Entities\Message;
17
use Longman\TelegramBot\Entities\ReplyKeyboardHide;
18
use Longman\TelegramBot\Entities\ReplyKeyboardMarkup;
19
use Longman\TelegramBot\Exception\TelegramException;
20
21
class SendtochannelCommand extends AdminCommand
22
{
23
    /**
24
     * @var string
25
     */
26
    protected $name = 'sendtochannel';
27
28
    /**
29
     * @var string
30
     */
31
    protected $description = 'Send message to a channel';
32
33
    /**
34
     * @var string
35
     */
36
    protected $usage = '/sendtochannel <message to send>';
37
38
    /**
39
     * @var string
40
     */
41
    protected $version = '0.2.0';
42
43
    /**
44
     * @var bool
45
     */
46
    protected $need_mysql = true;
47
48
    /**
49
     * Conversation Object
50
     *
51
     * @var \Longman\TelegramBot\Conversation
52
     */
53
    protected $conversation;
54
55
    /**
56
     * Command execute method
57
     *
58
     * @return \Longman\TelegramBot\Entities\ServerResponse|mixed
59
     * @throws \Longman\TelegramBot\Exception\TelegramException
60
     */
61
    public function execute()
62
    {
63
        $message = $this->getMessage();
64
        $chat_id = $message->getChat()->getId();
65
        $user_id = $message->getFrom()->getId();
66
67
        $type = $message->getType();
68
        // 'Cast' the command type into message to protect the machine state
69
        // if the commmad is recalled when the conversation is already started
70
        $type = ($type === 'command') ? 'Message' : $type;
71
72
        $text           = trim($message->getText(true));
73
        $text_yes_or_no = ($text === 'Yes' || $text === 'No');
74
75
        $data = [
76
            'chat_id' => $chat_id,
77
        ];
78
79
        // Conversation
80
        $this->conversation = new Conversation($user_id, $chat_id, $this->getName());
81
        $notes              = &$this->conversation->notes;
82
83
        $channels = (array)$this->getConfig('your_channel');
84
        if (isset($notes['state'])) {
85
            $state = $notes['state'];
86
        } else {
87
            $state                    = (count($channels) === 0) ? -1 : 0;
88
            $notes['last_message_id'] = $message->getMessageId();
89
        }
90
91
        switch ($state) {
92
            case -1:
93
                // getConfig has not been configured asking for channel to administer
94 View Code Duplication
                if ($type !== 'Message' || $text === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
95
                    $notes['state'] = -1;
96
                    $this->conversation->update();
97
98
                    $data['text']         = 'Insert the channel name: (@yourchannel)';
99
                    $data['reply_markup'] = new ReplyKeyBoardHide(['selective' => true]);
100
                    $result               = Request::sendMessage($data);
101
102
                    break;
103
                }
104
                $notes['channel']         = $text;
105
                $notes['last_message_id'] = $message->getMessageId();
106
                // Jump to state 1
107
                goto insert;
108
109
            // no break
110
            default:
111
            case 0:
0 ignored issues
show
Unused Code introduced by
case 0: // getConfig...essage->getMessageId(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
112
                // getConfig has been configured choose channel
113
                if ($type !== 'Message' || !in_array($text, $channels, true)) {
114
                    $notes['state'] = 0;
115
                    $this->conversation->update();
116
117
                    $keyboard = [];
118
                    foreach ($channels as $channel) {
119
                        $keyboard[] = [$channel];
120
                    }
121
                    $data['reply_markup'] = new ReplyKeyboardMarkup(
122
                        [
123
                            'keyboard'          => $keyboard,
124
                            'resize_keyboard'   => true,
125
                            'one_time_keyboard' => true,
126
                            'selective'         => true,
127
                        ]
128
                    );
129
130
                    $data['text'] = 'Select a channel from the keyboard:';
131
                    $result       = Request::sendMessage($data);
132
                    break;
133
                }
134
                $notes['channel']         = $text;
135
                $notes['last_message_id'] = $message->getMessageId();
136
137
            // no break
138
            case 1:
139
                insert:
140 View Code Duplication
                if (($type === 'Message' && $text === '') || $notes['last_message_id'] === $message->getMessageId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
                    $notes['state'] = 1;
142
                    $this->conversation->update();
143
144
                    $data['reply_markup'] = new ReplyKeyBoardHide(['selective' => true]);
145
                    $data['text']         = 'Insert the content you want to share: text, photo, audio...';
146
                    $result               = Request::sendMessage($data);
147
                    break;
148
                }
149
                $notes['last_message_id'] = $message->getMessageId();
150
                $notes['message']         = $message->reflect();
151
                $notes['message_type']    = $type;
152
            // no break
153
            case 2:
154
                if (!$text_yes_or_no || $notes['last_message_id'] === $message->getMessageId()) {
155
                    $notes['state'] = 2;
156
                    $this->conversation->update();
157
158
                    // Execute this just with object that allow caption
159
                    if ($notes['message_type'] === 'Video' || $notes['message_type'] === 'Photo') {
160
                        $data['reply_markup'] = new ReplyKeyboardMarkup(
161
                            [
162
                                'keyboard'          => [['Yes', 'No']],
163
                                'resize_keyboard'   => true,
164
                                'one_time_keyboard' => true,
165
                                'selective'         => true,
166
                            ]
167
                        );
168
169
                        $data['text'] = 'Would you like to insert a caption?';
170 View Code Duplication
                        if (!$text_yes_or_no && $notes['last_message_id'] !== $message->getMessageId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
                            $data['text'] .= PHP_EOL . 'Type Yes or No';
172
                        }
173
                        $result = Request::sendMessage($data);
174
                        break;
175
                    }
176
                }
177
                $notes['set_caption']     = ($text === 'Yes');
178
                $notes['last_message_id'] = $message->getMessageId();
179
            // no break
180
            case 3:
181 View Code Duplication
                if ($notes['set_caption'] && ($notes['last_message_id'] === $message->getMessageId() || $type !== 'Message')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
                    $notes['state'] = 3;
183
                    $this->conversation->update();
184
185
                    $data['text']         = 'Insert caption:';
186
                    $data['reply_markup'] = new ReplyKeyBoardHide(['selective' => true]);
187
                    $result               = Request::sendMessage($data);
188
                    break;
189
                }
190
                $notes['last_message_id'] = $message->getMessageId();
191
                $notes['caption']         = $text;
192
            // no break
193
            case 4:
194
                if (!$text_yes_or_no || $notes['last_message_id'] === $message->getMessageId()) {
195
                    $notes['state'] = 4;
196
                    $this->conversation->update();
197
198
                    $data['text'] = 'Message will look like this:';
199
                    $result       = Request::sendMessage($data);
200
201
                    if ($notes['message_type'] !== 'command') {
202
                        if ($notes['set_caption']) {
203
                            $data['caption'] = $notes['caption'];
204
                        }
205
                        $this->sendBack(new Message($notes['message'], $this->telegram->getBotName()), $data);
206
207
                        $data['reply_markup'] = new ReplyKeyboardMarkup(
208
                            [
209
                                'keyboard'          => [['Yes', 'No']],
210
                                'resize_keyboard'   => true,
211
                                'one_time_keyboard' => true,
212
                                'selective'         => true,
213
                            ]
214
                        );
215
216
                        $data['text'] = 'Would you like to post it?';
217 View Code Duplication
                        if (!$text_yes_or_no && $notes['last_message_id'] !== $message->getMessageId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218
                            $data['text'] .= PHP_EOL . 'Type Yes or No';
219
                        }
220
                        $result = Request::sendMessage($data);
221
                    }
222
                    break;
223
                }
224
225
                $notes['post_message']    = ($text === 'Yes');
226
                $notes['last_message_id'] = $message->getMessageId();
227
            // no break
228
            case 5:
229
                $data['reply_markup'] = new ReplyKeyBoardHide(['selective' => true]);
230
231
                if ($notes['post_message']) {
232
                    $data['text'] = $this->publish(
233
                        new Message($notes['message'], $this->telegram->getBotName()),
234
                        $notes['channel'],
235
                        $notes['caption']
236
                    );
237
                } else {
238
                    $data['text'] = 'Abort by user, message not sent..';
239
                }
240
241
                $this->conversation->stop();
242
                $result = Request::sendMessage($data);
243
        }
244
245
        return $result;
246
    }
247
248
    /**
249
     * SendBack
250
     *
251
     * Received a message, the bot can send a copy of it to another chat/channel.
252
     * You don't have to care about the type of the message, the function detect it and use the proper
253
     * REQUEST:: function to send it.
254
     * $data include all the var that you need to send the message to the proper chat
255
     *
256
     * @todo This method will be moved to a higher level maybe in AdminCommand or Command
257
     * @todo Looking for a more significant name
258
     *
259
     * @param \Longman\TelegramBot\Entities\Message $message
260
     * @param array                                 $data
261
     *
262
     * @return \Longman\TelegramBot\Entities\ServerResponse
263
     * @throws \Longman\TelegramBot\Exception\TelegramException
264
     */
265
    protected function sendBack(Message $message, array $data)
266
    {
267
        $type = $message->getType();
268
        $type = ($type === 'command') ? 'Message' : $type;
269
        if ($type === 'Message') {
270
            $data['text'] = $message->getText(true);
271
        } elseif ($type === 'Audio') {
272
            $data['audio']     = $message->getAudio()->getFileId();
273
            $data['duration']  = $message->getAudio()->getDuration();
274
            $data['performer'] = $message->getAudio()->getPerformer();
275
            $data['title']     = $message->getAudio()->getTitle();
276
        } elseif ($type === 'Document') {
277
            $data['document'] = $message->getDocument()->getFileId();
278
        } elseif ($type === 'Photo') {
279
            $data['photo'] = $message->getPhoto()[0]->getFileId();
280
        } elseif ($type === 'Sticker') {
281
            $data['sticker'] = $message->getSticker()->getFileId();
282
        } elseif ($type === 'Video') {
283
            $data['video'] = $message->getVideo()->getFileId();
284
        } elseif ($type === 'Voice') {
285
            $data['voice'] = $message->getVoice()->getFileId();
286
        } elseif ($type === 'Location') {
287
            $data['latitude']  = $message->getLocation()->getLatitude();
288
            $data['longitude'] = $message->getLocation()->getLongitude();
289
        }
290
        $callback_path     = 'Longman\TelegramBot\Request';
291
        $callback_function = 'send' . $type;
292
        if (!method_exists($callback_path, $callback_function)) {
293
            throw new TelegramException('Methods: ' . $callback_function . ' not found in class Request.');
294
        }
295
296
        return call_user_func_array($callback_path . '::' . $callback_function, [$data]);
297
    }
298
299
    /**
300
     * Publish a message to a channel and return success or failure message
301
     *
302
     * @param \Longman\TelegramBot\Entities\Message $message
303
     * @param int                                   $channel
304
     * @param string|null                           $caption
305
     *
306
     * @return string
307
     * @throws \Longman\TelegramBot\Exception\TelegramException
308
     */
309
    protected function publish(Message $message, $channel, $caption = null)
310
    {
311
        $data = [
312
            'chat_id' => $channel,
313
            'caption' => $caption,
314
        ];
315
316
        if ($this->sendBack($message, $data)->isOk()) {
317
            $response = 'Message sent successfully to: ' . $channel;
318
        } else {
319
            $response = 'Message not sent to: ' . $channel . PHP_EOL .
320
                        '- Does the channel exist?' . PHP_EOL .
321
                        '- Is the bot an admin of the channel?';
322
        }
323
324
        return $response;
325
    }
326
327
    /**
328
     * Execute without db
329
     *
330
     * @todo Why send just to the first found channel?
331
     *
332
     * @return mixed
333
     * @throws \Longman\TelegramBot\Exception\TelegramException
334
     */
335
    public function executeNoDb()
336
    {
337
        $message = $this->getMessage();
338
        $chat_id = $message->getChat()->getId();
339
        $text    = trim($message->getText(true));
340
341
        $data = [
342
            'chat_id' => $chat_id,
343
            'text'    => 'Usage: ' . $this->getUsage(),
344
        ];
345
346
        if ($text !== '') {
347
            $channels      = (array)$this->getConfig('your_channel');
348
            $first_channel = $channels[0];
349
            $data['text']  = $this->publish(
350
                new Message($message->reflect(), $this->telegram->getBotName()),
351
                $first_channel
352
            );
353
        }
354
355
        return Request::sendMessage($data);
356
    }
357
}
358