Completed
Pull Request — master (#644)
by Iman
04:56
created

Botan   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 8
dl 0
loc 240
ccs 0
cts 131
cp 0
rs 9.8
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A initializeBotan() 0 21 3
A lock() 0 6 2
D track() 0 84 16
C shortenUrl() 0 39 7
A getEventName() 0 14 3
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;
12
13
use GuzzleHttp\Client;
14
use GuzzleHttp\Exception\RequestException;
15
use Longman\TelegramBot\Entities\Update;
16
use Longman\TelegramBot\Exception\TelegramException;
17
18
/**
19
 * Class Botan
20
 *
21
 * Integration with http://botan.io statistics service for Telegram bots
22
 */
23
class Botan
24
{
25
    /**
26
     * Botan.io API URL
27
     *
28
     * @var string
29
     */
30
    protected static $api_base_uri = 'https://api.botan.io';
31
32
    /**
33
     * Yandex AppMetrica application key
34
     *
35
     * @var string
36
     */
37
    protected static $token = '';
38
39
    /**
40
     * Guzzle Client object
41
     *
42
     * @var \GuzzleHttp\Client
43
     */
44
    private static $client;
45
46
    /**
47
     * The actual command that is going to be reported
48
     *
49
     *  Set as public to let the developers either:
50
     *  - block tracking from inside commands by setting the value to non-existent command
51
     *  - override which command is tracked when commands call other commands with executeCommand()
52
     *
53
     * @var string
54
     */
55
    public static $command = '';
56
57
    /**
58
     * Initialize Botan
59
     *
60
     * @param  string $token
61
     * @param  array  $options
62
     *
63
     * @throws \Longman\TelegramBot\Exception\TelegramException
64
     */
65
    public static function initializeBotan($token, array $options = [])
66
    {
67
        if (empty($token)) {
68
            throw new TelegramException('Botan token is empty!');
69
        }
70
71
        $options_default = [
72
            'timeout' => 3,
73
        ];
74
75
        $options = array_merge($options_default, $options);
76
77
        if (!is_numeric($options['timeout'])) {
78
            throw new TelegramException('Timeout must be a number!');
79
        }
80
81
        self::$token = $token;
82
        self::$client = new Client(['base_uri' => self::$api_base_uri, 'timeout' => $options['timeout']]);
83
84
        BotanDB::initializeBotanDb();
85
    }
86
87
    /**
88
     * Lock function to make sure only the first command is reported (the one user requested)
89
     *
90
     *  This is in case commands are calling other commands with executeCommand()
91
     *
92
     * @param string $command
93
     */
94
    public static function lock($command = '')
95
    {
96
        if (empty(self::$command)) {
97
            self::$command = strtolower($command);
98
        }
99
    }
100
101
    /**
102
     * Track function
103
     *
104
     * @param \Longman\TelegramBot\Entities\Update $update
105
     * @param string                               $command
106
     *
107
     * @return bool|string
108
     * @throws \Longman\TelegramBot\Exception\TelegramException
109
     */
110
    public static function track(Update $update, $command = '')
111
    {
112
        $command = strtolower($command);
113
114
        if (empty(self::$token) || $command !== self::$command) {
115
            return false;
116
        }
117
118
        if ($update === null) {
119
            throw new TelegramException('Update object is empty!');
120
        }
121
122
        // Release the lock in case this is getUpdates instance in foreach loop
123
        self::$command = '';
124
125
        $data = [];
126
        $update_data = (array) $update; // For now, this is the only way
127
        $update_type = $update->getUpdateType();
128
129
        $update_object_names = [
130
            'message'              => 'Message',
131
            'edited_message'       => 'Edited Message',
132
            'channel_post'         => 'Channel Post',
133
            'edited_channel_post'  => 'Edited Channel Post',
134
            'inline_query'         => 'Inline Query',
135
            'chosen_inline_result' => 'Chosen Inline Result',
136
            'callback_query'       => 'Callback Query',
137
        ];
138
139
        if (array_key_exists($update_type, $update_object_names)) {
140
            $data = $update_data[$update_type];
141
            $event_name = $update_object_names[$update_type];
142
143
            if ($update_type === 'message' && $entities = $update->getMessage()->getEntities()) {
144
                foreach ($entities as $entity) {
145
                    if ($entity->getType() !== 'bot_command' || $entity->getOffset() !== 0) {
146
                        continue;
147
                    }
148
                    $event_name = self::getEventName($command);
149
                    break;
150
                }
151
            }
152
        }
153
154
        if (empty($event_name)) {
155
            TelegramLog::error('Botan.io stats report failed, no suitable update object found!');
156
157
            return false;
158
        }
159
160
        // In case there is no from field assign id = 0
161
        $uid = isset($data['from']['id']) ? $data['from']['id'] : 0;
162
163
        try {
164
            $response = self::$client->post(
165
                sprintf(
166
                    '/track?token=%1$s&uid=%2$s&name=%3$s',
167
                    self::$token,
168
                    $uid,
169
                    urlencode($event_name)
170
                ),
171
                [
172
                    'headers' => [
173
                        'Content-Type' => 'application/json',
174
                    ],
175
                    'json'    => $data,
176
                ]
177
            );
178
179
            $result = (string) $response->getBody();
180
        } catch (RequestException $e) {
181
            $result = $e->getMessage();
182
        }
183
184
        $responseData = json_decode($result, true);
185
186
        if (!$responseData || $responseData['status'] !== 'accepted') {
187
            TelegramLog::debug('Botan.io stats report failed: %s', $result ?: 'empty response');
188
189
            return false;
190
        }
191
192
        return $responseData;
193
    }
194
195
    /**
196
     * Url Shortener function
197
     *
198
     * @param string  $url
199
     * @param integer $user_id
200
     *
201
     * @return string
202
     * @throws \Longman\TelegramBot\Exception\TelegramException
203
     */
204
    public static function shortenUrl($url, $user_id)
205
    {
206
        if (empty(self::$token)) {
207
            return $url;
208
        }
209
210
        if (empty($user_id)) {
211
            throw new TelegramException('User id is empty!');
212
        }
213
214
        if ($cached = BotanDB::selectShortUrl($url, $user_id)) {
215
            return $cached;
216
        }
217
218
        try {
219
            $response = self::$client->post(
220
                sprintf(
221
                    '/s?token=%1$s&user_ids=%2$s&url=%3$s',
222
                    self::$token,
223
                    $user_id,
224
                    urlencode($url)
225
                )
226
            );
227
228
            $result = (string) $response->getBody();
229
        } catch (RequestException $e) {
230
            $result = $e->getMessage();
231
        }
232
233
        if (filter_var($result, FILTER_VALIDATE_URL) === false) {
234
            TelegramLog::debug('Botan.io URL shortening failed for "%s": %s', $url, $result ?: 'empty response');
235
236
            return $url;
237
        }
238
239
        BotanDB::insertShortUrl($url, $user_id, $result);
240
241
        return $result;
242
    }
243
244
    /**
245
     * @param $command
246
     * @return string
247
     */
248
    private static function getEventName($command)
249
    {
250
        if ($command === 'generic') {
251
            $command = 'Generic';
252
        } elseif ($command === 'genericmessage') {  // This should not happen as it equals normal message but leaving it as a fail-safe
253
            $command = 'Generic Message';
254
        } else {
255
            $command = '/'.$command;
256
        }
257
258
        $event_name = 'Command ('.$command.')';
259
260
        return $event_name;
261
    }
262
}
263