Passed
Push — master ( 3e42de...6c1980 )
by Alexey
03:33
created

PrivateMessageProcessor::processLast()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 5
Ratio 41.67 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 5
loc 12
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 2
crap 12
1
<?php
2
3
namespace Skobkin\Bundle\PointToolsBundle\Service\Telegram;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Skobkin\Bundle\PointToolsBundle\Entity\Telegram\Account;
7
use Skobkin\Bundle\PointToolsBundle\Entity\User;
8
use Skobkin\Bundle\PointToolsBundle\Exception\Telegram\CommandProcessingException;
9
use Skobkin\Bundle\PointToolsBundle\Repository\SubscriptionEventRepository;
10
use Skobkin\Bundle\PointToolsBundle\Repository\SubscriptionRepository;
11
use Skobkin\Bundle\PointToolsBundle\Repository\UserRepository;
12
use Skobkin\Bundle\PointToolsBundle\Service\Factory\Telegram\AccountFactory;
13
use Skobkin\Bundle\PointToolsBundle\Service\UserApi;
14
use unreal4u\TelegramAPI\Abstracts\KeyboardMethods;
15
use unreal4u\TelegramAPI\Telegram\Types\Message;
16
use unreal4u\TelegramAPI\Telegram\Types\ReplyKeyboardMarkup;
17
use unreal4u\TelegramAPI\Telegram\Types\ReplyKeyboardRemove;
18
19
/**
20
 * Processes all private messages
21
 */
22
class PrivateMessageProcessor
23
{
24
    /**
25
     * @var MessageSender
26
     */
27
    private $messenger;
28
29
    /**
30
     * @var UserApi
31
     */
32
    private $userApi;
33
34
    /**
35
     * @var AccountFactory
36
     */
37
    private $accountFactory;
38
39
    /**
40
     * @var EntityManagerInterface
41
     */
42
    private $em;
43
44
    /**
45
     * @var \Twig_Environment
46
     */
47
    private $twig;
48
49
    /**
50
     * @var UserRepository
51
     */
52
    private $userRepo;
53
54
    /**
55
     * @var SubscriptionRepository
56
     */
57
    private $subscriptionRepo;
58
59
    /**
60
     * @var SubscriptionEventRepository
61
     */
62
    private $subscriptionEventRepo;
63
64
    /**
65
     * @var int
66
     */
67
    private $pointUserId;
68
69
70
    public function __construct(
71
        MessageSender $messageSender,
72
        UserApi $userApi,
73
        AccountFactory $accountFactory,
74
        EntityManagerInterface $em,
75
        \Twig_Environment $twig,
76
        int $pointUserId
77
    )
78
    {
79
        $this->messenger = $messageSender;
80
        $this->userApi = $userApi;
81
        $this->accountFactory = $accountFactory;
82
        $this->em = $em;
83
        $this->twig = $twig;
84
        $this->pointUserId = $pointUserId;
85
86
        $this->userRepo = $em->getRepository('SkobkinPointToolsBundle:User');
87
        $this->subscriptionRepo = $em->getRepository('SkobkinPointToolsBundle:Subscription');
88
        $this->subscriptionEventRepo = $em->getRepository('SkobkinPointToolsBundle:SubscriptionEvent');
89
    }
90
91
    public function process(Message $message)
92
    {
93
        if (!IncomingUpdateDispatcher::CHAT_TYPE_PRIVATE === $message->chat->type) {
94
            throw new \LogicException('This service can process only private chat messages');
95
        }
96
97
        try {
98
            // Registering Telegram user
99
            /** @var Account $account */
100
            $account = $this->accountFactory->findOrCreateFromMessage($message);
101
            $this->em->flush();
102
        } catch (\Exception $e) {
103
            // Low-level message in case of incorrect $account
104
            $this->messenger->sendMessageToChat($message->chat->id, 'There was an error during your Telegram account registration. Try again or report the bug.');
105
        }
106
107
        try {
108
            $words = explode(' ', $message->text, 10);
109
110
            if (0 === count($words)) {
111
                return;
112
            }
113
114
            switch ($words[0]) {
115
                case '/link':
116
                case 'link':
117
                    $this->processLink($account, $words);
118
                    break;
119
120
                case '/me':
121
                case 'me':
122
                    $this->processMe($account);
123
                    break;
124
125
                case '/last':
126
                case 'last':
127
                    $this->processLast($account, $words);
128
                    break;
129
130
                case '/sub':
131
                case 'sub':
132
                    $this->processSub($account, $words);
133
                    break;
134
135
                case '/stats':
136
                case 'stats':
137
                    $this->processStats($account);
138
                    break;
139
140
                // Settings menu
141
                case '/set':
142
                    $this->processSet($account, $words);
143
                    break;
144
145
                // Exit from any menu and remove keyboard
146
                case '/exit':
147
                case 'exit':
148
                    $this->processExit($account);
149
                    break;
150
151
                case '/help':
152
                default:
153
                    $this->processHelp($account);
154
                    break;
155
            }
156
        } catch (CommandProcessingException $e) {
157
            $this->sendError($account, 'Processing error', $e->getMessage());
158
159
            if ($e->getPrevious()) {
160
                throw $e->getPrevious();
161
            }
162
        } catch (\Exception $e) {
163
            $this->sendError($account, 'Unknown error');
164
165
            throw $e;
166
        }
167
    }
168
169
    private function linkAccount(Account $account, string $login, string $password): bool
170
    {
171
        /** @var User $user */
172
        if (null === $user = $this->userRepo->findUserByLogin($login)) {
173
            throw new CommandProcessingException('User not found in Point Tools database. Please try again later.');
174
        }
175
176
        if ($this->userApi->isAuthDataValid($login, $password)) {
177
            $account->setUser($user);
178
179
            return true;
180
        }
181
182
        return false;
183
    }
184
185
    private function processLink(Account $account, array $words)
186
    {
187
        if (array_key_exists(2, $words)) {
188
            if ($this->linkAccount($account, $words[1], $words[2])) {
189
                // Saving linking status
190
                $this->em->flush();
191
                $this->sendAccountLinked($account);
192
            } else {
193
                $this->sendError($account, 'Account linking error', 'Check login and password or try again later.');
194
            }
195
        } else {
196
            $this->sendError($account, 'Login/Password error', 'You need to specify login and password separated by space after /link (example: `/link mylogin MypASSw0rd`)');
197
        }
198
    }
199
200
    private function processMe(Account $account)
201
    {
202
        if ($user = $account->getUser()) {
203
            $this->sendUserEvents($account, $user);
204
        } else {
205
            $this->sendError($account, 'Account not linked', 'You must /link your account first to be able to use this command.');
206
        }
207
    }
208
209
    private function processLast(Account $account, array $words)
210
    {
211
        if (array_key_exists(1, $words)) {
212 View Code Duplication
            if (null !== $user = $this->userRepo->findUserByLogin($words[1])) {
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...
213
                $this->sendUserEvents($account, $user);
214
            } else {
215
                $this->sendError($account, 'User not found');
216
            }
217
        } else {
218
            $this->sendGlobalEvents($account);
219
        }
220
    }
221
222
    private function processSub(Account $account, array $words)
223
    {
224
        if (array_key_exists(1, $words)) {
225 View Code Duplication
            if (null !== $user = $this->userRepo->findUserByLogin($words[1])) {
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...
226
                $this->sendUserSubscribers($account, $user);
227
            } else {
228
                $this->sendError($account, 'User not found');
229
            }
230
        } else {
231
            if ($user = $account->getUser()) {
232
                $this->sendUserSubscribers($account, $user);
233
            } else {
234
                $this->sendError($account, 'Account not linked', 'You must /link your account first to be able to use this command.');
235
            }
236
        }
237
    }
238
239
    private function processStats(Account $account)
240
    {
241
        $this->sendStats($account);
242
    }
243
244
    private function processSet(Account $account, array $words)
245
    {
246
        $keyboard = new ReplyKeyboardMarkup();
247
248
        if (array_key_exists(1, $words)) {
249
            if (array_key_exists(2, $words)) {
250
                if ('renames' === $words[2]) {
251
                    $account->toggleRenameNotification();
252
                    $this->em->flush();
253
254
                    $this->sendPlainTextMessage($account, 'Renaming notifications are turned '.($account->isRenameNotification() ? 'on' : 'off'));
255
                } elseif ('subscribers' === $words[2]) {
256
                    $account->toggleSubscriberNotification();
257
                    $this->em->flush();
258
259
                    $this->sendPlainTextMessage($account, 'Subscribers notifications are turned '.($account->isSubscriberNotification() ? 'on' : 'off'));
260
                } else {
261
                    $this->sendError($account, 'Notification type does not exist.');
262
                }
263 View Code Duplication
            } else {
1 ignored issue
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...
264
                $keyboard->keyboard = [
265
                    ['/set notifications renames'],
266
                    ['/set notifications subscribers'],
267
                    ['exit'],
268
                ];
269
270
                $this->sendPlainTextMessage($account, 'Choose which notification type to toggle', $keyboard);
271
            }
272
273 View Code Duplication
        } else {
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...
274
            $keyboard->keyboard = [
275
                ['/set notifications'],
276
                ['exit'],
277
            ];
278
279
            $this->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/settings.md.twig', ['account' => $account], $keyboard);
280
        }
281
    }
282
283
    /**
284
     * Processes exit from keyboard menus and removes the keyboard
285
     */
286
    private function processExit(Account $account)
287
    {
288
        $keyboardRemove = new ReplyKeyboardRemove();
289
290
        $this->sendPlainTextMessage($account, 'Done', $keyboardRemove);
291
    }
292
293
    private function processHelp(Account $account)
294
    {
295
        $this->sendHelp($account);
296
    }
297
298
    private function sendAccountLinked(Account $account)
299
    {
300
        $this->messenger->sendMessageToUser($account, 'Account linked. Try using /me now.');
301
    }
302
303
    private function sendUserSubscribers(Account $account, User $user)
304
    {
305
        $subscribers = [];
306
        foreach ($user->getSubscribers() as $subscription) {
307
            $subscribers[] = '@'.$subscription->getSubscriber()->getLogin();
308
        }
309
310
        $this->sendTemplatedMessage(
311
            $account,
312
            '@SkobkinPointTools/Telegram/user_subscribers.md.twig',
313
            [
314
                'user' => $user,
315
                'subscribers' => $subscribers,
316
            ]
317
        );
318
    }
319
320
    private function sendUserEvents(Account $account, User $user)
321
    {
322
        $events = $this->subscriptionEventRepo->getUserLastSubscribersEvents($user, 10);
323
324
        $this->sendTemplatedMessage(
325
            $account,
326
            '@SkobkinPointTools/Telegram/last_user_subscriptions.md.twig',
327
            [
328
                'user' => $user,
329
                'events' => $events,
330
            ]
331
        );
332
    }
333
334
    private function sendGlobalEvents(Account $account)
335
    {
336
        $events = $this->subscriptionEventRepo->getLastSubscriptionEvents(10);
337
338
        $this->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/last_global_subscriptions.md.twig', ['events' => $events]);
339
    }
340
341
    private function sendStats(Account $account)
342
    {
343
        $this->sendTemplatedMessage(
344
            $account,
345
            '@SkobkinPointTools/Telegram/stats.md.twig',
346
            [
347
                'total_users' => $this->userRepo->getUsersCount(),
348
                'active_users' => $this->subscriptionRepo->getUserSubscribersCountById($this->pointUserId),
349
                'today_events' => $this->subscriptionEventRepo->getLastDayEventsCount(),
350
            ]
351
        );
352
    }
353
354
    private function sendHelp(Account $account)
355
    {
356
        $this->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/help.md.twig');
357
    }
358
359
    private function sendError(Account $account, string $title, string $text = '')
360
    {
361
        $this->sendTemplatedMessage(
362
            $account,
363
            '@SkobkinPointTools/Telegram/error.md.twig',
364
            [
365
                'title' => $title,
366
                'text' => $text,
367
            ]
368
        );
369
    }
370
371
    private function sendTemplatedMessage(
372
        Account $account,
373
        string $template,
374
        array $templateData = [],
375
        KeyboardMethods $keyboardMarkup = null,
376
        bool $disableWebPreview = true,
377
        string $format = MessageSender::PARSE_MODE_MARKDOWN
378
    ): bool
379
    {
380
        $text = $this->twig->render($template, $templateData);
381
382
        return $this->messenger->sendMessageToUser($account, $text, $format, $keyboardMarkup, $disableWebPreview, false);
383
    }
384
385
    private function sendPlainTextMessage(Account $account, string $text, KeyboardMethods $keyboardMarkup = null, bool $disableWebPreview = true): bool
386
    {
387
        return $this->messenger->sendMessageToUser($account, $text, MessageSender::PARSE_MODE_NOPARSE, $keyboardMarkup, $disableWebPreview);
388
    }
389
}