Passed
Push — main ( 1a1ee7...5cb4cc )
by Tan
25:44 queued 13:14
created

Webhook   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 22
eloc 69
dl 0
loc 218
rs 10
c 5
b 1
f 1

11 Methods

Rating   Name   Duplication   Size   Complexity  
A deleteWebHook() 0 8 2
A setWebhook() 0 6 1
A getWebHookInfo() 0 6 1
A buildUrl() 0 9 2
A setUrl() 0 8 2
B handleResponse() 0 35 6
A setToken() 0 8 2
A validateToken() 0 4 2
A sendRequest() 0 16 2
A __construct() 0 5 1
A getUpdates() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace CSlant\TelegramGitNotifier;
6
7
use CSlant\TelegramGitNotifier\Exceptions\WebhookException;
8
use CSlant\TelegramGitNotifier\Interfaces\WebhookInterface;
9
use GuzzleHttp\Client;
10
use GuzzleHttp\Exception\GuzzleException;
11
use GuzzleHttp\Psr7\Response;
12
use JsonException;
13
use Psr\Http\Message\ResponseInterface;
14
use RuntimeException;
15
16
/**
17
 * Class Webhook
18
 * 
19
 * Handles all webhook-related operations with the Telegram Bot API.
20
 * Implements WebhookInterface for managing Telegram webhooks.
21
 */
22
class Webhook implements WebhookInterface
23
{
24
    private const TELEGRAM_API_BASE_URL = 'https://api.telegram.org/bot';
25
    private const DEFAULT_TIMEOUT = 30;
26
27
    private string $token = '';
28
    private string $url = '';
29
    private Client $client;
30
31
    /**
32
     * Initialize the Webhook handler with a Guzzle HTTP client
33
     *
34
     * @param Client|null $client Optional Guzzle client instance
35
     */
36
    public function __construct(?Client $client = null)
37
    {
38
        $this->client = $client ?? new Client([
39
            'timeout' => self::DEFAULT_TIMEOUT,
40
            'http_errors' => false,
41
        ]);
42
    }
43
44
    /**
45
     * Set the Telegram bot token
46
     *
47
     * @param string $token The Telegram bot token
48
     * @return void
49
     * @throws \InvalidArgumentException If the token is empty
50
     */
51
    public function setToken(string $token): void
52
    {
53
        $token = trim($token);
54
        if (empty($token)) {
55
            throw new \InvalidArgumentException('Telegram bot token cannot be empty');
56
        }
57
        
58
        $this->token = $token;
59
    }
60
61
    /**
62
     * Set the webhook URL
63
     *
64
     * @param string $url The webhook URL to set
65
     * @return void
66
     * @throws \InvalidArgumentException If the URL is invalid
67
     */
68
    public function setUrl(string $url): void
69
    {
70
        $url = trim($url);
71
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
72
            throw new \InvalidArgumentException('Invalid webhook URL provided');
73
        }
74
        
75
        $this->url = $url;
76
    }
77
78
    /**
79
     * Set up the webhook with Telegram
80
     *
81
     * @param array<string, mixed> $params Additional parameters for the webhook
82
     * @return array<string, mixed> The Telegram API response
83
     * @throws WebhookException If the operation fails
84
     */
85
    public function setWebhook(array $params = []): array
86
    {
87
        $this->validateToken();
88
        
89
        $url = $this->buildUrl('setWebhook', ['url' => $this->url] + $params);
90
        return $this->sendRequest('POST', $url);
91
    }
92
93
    /**
94
     * Delete the current webhook
95
     *
96
     * @param bool $dropPendingUpdates Whether to drop all pending updates
97
     * @return array<string, mixed> The Telegram API response
98
     * @throws WebhookException If the operation fails
99
     */
100
    public function deleteWebHook(bool $dropPendingUpdates = false): array
101
    {
102
        $this->validateToken();
103
        
104
        $params = $dropPendingUpdates ? ['drop_pending_updates' => 'true'] : [];
105
        $url = $this->buildUrl('deleteWebhook', $params);
106
        
107
        return $this->sendRequest('GET', $url);
108
    }
109
110
    /**
111
     * Get information about the current webhook
112
     *
113
     * @return array<string, mixed> Webhook information
114
     * @throws WebhookException If the operation fails
115
     */
116
    public function getWebHookInfo(): array
117
    {
118
        $this->validateToken();
119
        $url = $this->buildUrl('getWebhookInfo');
120
        
121
        return $this->sendRequest('GET', $url);
122
    }
123
124
    /**
125
     * Get updates from the Telegram API
126
     *
127
     * @param array<string, mixed> $params Additional parameters for the request
128
     * @return array<string, mixed> The updates from Telegram
129
     * @throws WebhookException If the operation fails
130
     */
131
    public function getUpdates(array $params = []): array
132
    {
133
        $this->validateToken();
134
        $url = $this->buildUrl('getUpdates', $params);
135
        
136
        return $this->sendRequest('GET', $url);
137
    }
138
139
    /**
140
     * Build the full Telegram API URL
141
     *
142
     * @param string $method The Telegram Bot API method
143
     * @param array<string, mixed> $params Query parameters
144
     * @return string The complete URL
145
     */
146
    private function buildUrl(string $method, array $params = []): string
147
    {
148
        $url = self::TELEGRAM_API_BASE_URL . $this->token . '/' . $method;
149
        
150
        if (!empty($params)) {
151
            $url .= '?' . http_build_query($params);
152
        }
153
        
154
        return $url;
155
    }
156
157
    /**
158
     * Send a request to the Telegram API
159
     *
160
     * @param string $method The HTTP method
161
     * @param string $url The URL to send the request to
162
     * @return array<string, mixed> The decoded JSON response
163
     * @throws WebhookException If the request fails or the response is invalid
164
     */
165
    private function sendRequest(string $method, string $url): array
166
    {
167
        try {
168
            $response = $this->client->request($method, $url, [
169
                'verify' => config('telegram-git-notifier.app.request_verify', true),
0 ignored issues
show
Unused Code introduced by
The call to config() has too many arguments starting with true. ( Ignorable by Annotation )

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

169
                'verify' => /** @scrutinizer ignore-call */ config('telegram-git-notifier.app.request_verify', true),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
170
                'headers' => [
171
                    'Accept' => 'application/json',
172
                ],
173
            ]);
174
175
            return $this->handleResponse($response);
176
        } catch (GuzzleException $e) {
177
            throw WebhookException::create(
178
                "Failed to send request to Telegram API: {$e->getMessage()}",
179
                $e->getCode(),
180
                $e
181
            );
182
        }
183
    }
184
185
    /**
186
     * Handle the API response
187
     *
188
     * @param ResponseInterface $response The HTTP response
189
     * @return array<string, mixed> The decoded JSON response
190
     * @throws WebhookException If the response cannot be decoded or contains an error
191
     */
192
    private function handleResponse(ResponseInterface $response): array
193
    {
194
        $statusCode = $response->getStatusCode();
195
        $body = (string) $response->getBody();
196
        
197
        if ($statusCode !== 200) {
198
            throw WebhookException::create(
199
                "Telegram API returned status code: {$statusCode}",
200
                $statusCode
201
            );
202
        }
203
        
204
        try {
205
            $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
206
            
207
            if (!is_array($data)) {
208
                throw new JsonException('Invalid JSON response');
209
            }
210
            
211
            if (isset($data['ok']) && $data['ok'] === false) {
212
                $errorCode = $data['error_code'] ?? 0;
213
                $description = $data['description'] ?? 'Unknown error';
214
                
215
                throw WebhookException::create(
216
                    "Telegram API error: {$description}",
217
                    $errorCode
218
                );
219
            }
220
            
221
            return $data;
222
        } catch (JsonException $e) {
223
            throw WebhookException::create(
224
                "Failed to decode Telegram API response: {$e->getMessage()}",
225
                $e->getCode(),
226
                $e
227
            );
228
        }
229
    }
230
231
    /**
232
     * Validate that the bot token is set
233
     *
234
     * @throws WebhookException If the token is not set
235
     */
236
    private function validateToken(): void
237
    {
238
        if (empty($this->token)) {
239
            throw WebhookException::create('Telegram bot token is not set');
240
        }
241
    }
242
}
243