Passed
Push — master ( 1145e4...3a5c01 )
by Alexey
03:43
created

PrivateMessageProcessor::sendHelp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 2
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\Telegram\Methods\SendMessage;
15
use unreal4u\TelegramAPI\Telegram\Types\Message;
16
use unreal4u\TelegramAPI\TgLog;
17
18
/**
19
 * Processes all private messages
20
 */
21
class PrivateMessageProcessor
22
{
23
    const TEMPLATE_ERROR = '@SkobkinPointTools/Telegram/error.md.twig';
24
    const TEMPLATE_STATS = '@SkobkinPointTools/Telegram/stats.md.twig';
25
    const TEMPLATE_HELP = '@SkobkinPointTools/Telegram/help.md.twig';
26
    const TEMPLATE_LAST_EVENTS = '@SkobkinPointTools/Telegram/last_global_subscriptions.md.twig';
27
    const TEMPLATE_LAST_USER_SUB_EVENTS = '@SkobkinPointTools/Telegram/last_user_subscriptions.md.twig';
28
    const TEMPLATE_USER_SUBSCRIBERS = '@SkobkinPointTools/Telegram/user_subscribers.md.twig';
29
30
    const PARSE_MODE_MARKDOWN = 'Markdown';
31
    const PARSE_MODE_HTML5 = 'HTML';
32
33
    /**
34
     * @var TgLog
35
     */
36
    private $client;
37
38
    /**
39
     * @var UserApi
40
     */
41
    private $userApi;
42
43
    /**
44
     * @var AccountFactory
45
     */
46
    private $accountFactory;
47
48
    /**
49
     * @var EntityManagerInterface
50
     */
51
    private $em;
52
53
    /**
54
     * @var \Twig_Environment
55
     */
56
    private $twig;
57
58
    /**
59
     * @var UserRepository
60
     */
61
    private $userRepo;
62
63
    /**
64
     * @var SubscriptionRepository
65
     */
66
    private $subscriptionRepo;
67
68
    /**
69
     * @var SubscriptionEventRepository
70
     */
71
    private $subscriptionEventRepo;
72
73
    /**
74
     * @var int
75
     */
76
    private $pointUserId;
77
78
    /**
79
     * @var string
80
     */
81
    private $pointUserLogin;
82
83
84
    public function __construct(
85
        TgLog $client,
86
        UserApi $userApi,
87
        AccountFactory $accountFactory,
88
        EntityManagerInterface $em,
89
        \Twig_Environment $twig,
90
        int $pointUserId,
91
        string $pointUserLogin
92
    )
93
    {
94
        $this->client = $client;
95
        $this->userApi = $userApi;
96
        $this->accountFactory = $accountFactory;
97
        $this->em = $em;
98
        $this->twig = $twig;
99
        $this->pointUserId = $pointUserId;
100
        $this->pointUserLogin = $pointUserLogin;
101
102
        $this->userRepo = $em->getRepository('SkobkinPointToolsBundle:User');
103
        $this->subscriptionRepo = $em->getRepository('SkobkinPointToolsBundle:Subscription');
104
        $this->subscriptionEventRepo = $em->getRepository('SkobkinPointToolsBundle:SubscriptionEvent');
105
    }
106
107
    public function process(Message $message)
108
    {
109
        if (!IncomingUpdateDispatcher::CHAT_TYPE_PRIVATE === $message->chat->type) {
110
            throw new \InvalidArgumentException('This service can process only private chat messages');
111
        }
112
113
        // Registering Telegram user
114
        /** @var Account $account */
115
        $account = $this->accountFactory->findOrCreateFromMessage($message);
116
        $this->em->flush();
117
118
        // Creating blank response for later use
119
        $sendMessage = $this->createResponseMessage($message, self::PARSE_MODE_MARKDOWN, true);
120
121
        $words = explode(' ', $message->text, 4);
122
123
        if (0 === count($words)) {
124
            return;
125
        }
126
127
        try {
128
            switch ($words[0]) {
129
                case '/link':
130
                case 'link':
131
                    if (array_key_exists(2, $words)) {
132
                        if ($this->linkAccount($account, $words[1], $words[2])) {
133
                            // Saving linking status
134
                            $this->em->flush();
135
                            $this->sendAccountLinked($sendMessage);
136
                        } else {
137
                            $this->sendError($sendMessage, 'Account linking error', 'Check login and password or try again later.');
138
                        }
139
                    } else {
140
                        $this->sendError($sendMessage, 'Login/Password error', 'You need to specify login and password separated by space after /link (example: `/link mylogin MypASSw0rd`)');
141
                    }
142
                    break;
143
144
                case '/me':
145
                case 'me':
146
                    if ($user = $account->getUser()) {
147
                        $this->sendUserEvents($sendMessage, $user);
148
                    } else {
149
                        $this->sendError($sendMessage, 'Account not linked', 'You must /link your account first to be able to use this command.');
150
                    }
151
                    break;
152
153
                case '/last':
154
                case 'l':
155
                case 'last':
156
                    if (array_key_exists(1, $words)) {
157 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...
158
                            $this->sendUserEvents($sendMessage, $user);
159
                        } else {
160
                            $this->sendError($sendMessage, 'User not found');
161
                        }
162
                    } else {
163
                        $this->sendGlobalEvents($sendMessage);
164
                    }
165
166
                    break;
167
168
                case '/sub':
169
                case 'sub':
170
                case 'subscribers':
171
                    if (array_key_exists(1, $words)) {
172 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...
173
                            $this->sendUserSubscribers($sendMessage, $user);
174
                        } else {
175
                            $this->sendError($sendMessage, 'User not found');
176
                        }
177
                    } else {
178
                        $user = $this->userRepo->findUserByLogin($this->pointUserLogin);
179
                        $this->sendUserSubscribers($sendMessage, $user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userRepo->findUse...($this->pointUserLogin) on line 178 can be null; however, Skobkin\Bundle\PointTool...::sendUserSubscribers() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
180
                    }
181
182
                    break;
183
184
                case '/stats':
185
                case 'stats':
186
                    $this->sendStats($sendMessage);
187
188
                    break;
189
190
                case '/help':
191
                default:
192
                    $this->sendHelp($sendMessage);
193
                    break;
194
            }
195
        } catch (CommandProcessingException $e) {
196
            $this->sendError($sendMessage, 'Processing error', $e->getMessage());
197
198
            if ($e->getPrevious()) {
199
                throw $e->getPrevious();
200
            }
201
        } catch (\Exception $e) {
202
            $this->sendError($sendMessage, 'Unknown error');
203
204
            throw $e;
205
        }
206
    }
207
208
    private function linkAccount(Account $account, string $login, string $password): bool
209
    {
210
        if ($this->userApi->isAuthDataValid($login, $password)) {
211
            /** @var User $user */
212
            if (null === $user = $this->userRepo->findOneBy(['login' => $login])) {
213
                throw new CommandProcessingException('User not found in Point Tools database. Please try again later.');
214
            }
215
216
            $account->setUser($user);
217
218
            return true;
219
        }
220
221
        return false;
222
    }
223
224
    private function sendAccountLinked(SendMessage $sendMessage)
225
    {
226
        $sendMessage->text = 'Account linked. Try using /me now.';
227
228
        $this->client->performApiRequest($sendMessage);
229
    }
230
231
    private function sendUserSubscribers(SendMessage $sendMessage, User $user)
232
    {
233
        $subscribers = [];
234
235
        foreach ($user->getSubscribers() as $subscription) {
236
            $subscribers[] = '@'.$subscription->getSubscriber()->getLogin();
237
        }
238
239
        $sendMessage->text = $this->twig->render(self::TEMPLATE_USER_SUBSCRIBERS, [
240
            'user' => $user,
241
            'subscribers' => $subscribers,
242
        ]);
243
244
        $this->client->performApiRequest($sendMessage);
245
    }
246
247
    /**
248
     * @param SendMessage $sendMessage
249
     * @param User $user
250
     */
251 View Code Duplication
    private function sendUserEvents(SendMessage $sendMessage, User $user)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
252
    {
253
        $events = $this->subscriptionEventRepo->getUserLastSubscribersEvents($user, 10);
254
        $sendMessage->text = $this->twig->render(self::TEMPLATE_LAST_USER_SUB_EVENTS, [
255
            'user' => $user,
256
            'events' => $events,
257
        ]);
258
259
        $this->client->performApiRequest($sendMessage);
260
    }
261
262 View Code Duplication
    private function sendGlobalEvents(SendMessage $sendMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
263
    {
264
        $events = $this->subscriptionEventRepo->getLastSubscriptionEvents(10);
265
        $sendMessage->text = $this->twig->render(self::TEMPLATE_LAST_EVENTS, ['events' => $events]);
266
267
        $this->client->performApiRequest($sendMessage);
268
    }
269
270
    private function sendStats(SendMessage $sendMessage)
271
    {
272
        $sendMessage->text = $this->twig->render(self::TEMPLATE_STATS, [
273
            'total_users' => $this->userRepo->getUsersCount(),
274
            'active_users' => $this->subscriptionRepo->getUserSubscribersCountById($this->pointUserId),
275
            'today_events' => $this->subscriptionEventRepo->getLastDayEventsCount(),
276
        ]);
277
278
        $this->client->performApiRequest($sendMessage);
279
    }
280
281
    private function sendHelp(SendMessage $sendMessage)
282
    {
283
        $sendMessage->text = $this->twig->render(self::TEMPLATE_HELP);
284
285
        $this->client->performApiRequest($sendMessage);
286
    }
287
288 View Code Duplication
    private function sendError(SendMessage $sendMessage, string $title, string $text = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
289
    {
290
        $sendMessage->text = $this->twig->render(self::TEMPLATE_ERROR, [
291
            'title' => $title,
292
            'text' => $text,
293
        ]);
294
295
        $this->client->performApiRequest($sendMessage);
296
    }
297
298
    private function createResponseMessage(Message $message, string $parseMode = self::PARSE_MODE_MARKDOWN, bool $disableWebPreview = false): SendMessage
299
    {
300
        $sendMessage = new SendMessage();
301
        $sendMessage->chat_id = $message->chat->id;
0 ignored issues
show
Documentation Bug introduced by
The property $chat_id was declared of type string, but $message->chat->id is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
302
        $sendMessage->parse_mode = $parseMode;
303
        $sendMessage->disable_web_page_preview = $disableWebPreview;
304
305
        return $sendMessage;
306
    }
307
}