Passed
Push — master ( 9aa048...898b5e )
by Mattia
04:05
created

MojangClient   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
eloc 46
c 5
b 1
f 1
dl 0
loc 164
rs 10
wmc 16

8 Methods

Rating   Name   Duplication   Size   Complexity  
A sendRequest() 0 12 3
A sendApiRequest() 0 21 4
A sendUsernameInfoRequest() 0 9 2
A handleThrowable() 0 4 1
A getUuidInfo() 0 9 2
A getSkin() 0 9 2
A __construct() 0 6 1
A handleGuzzleBadResponseException() 0 9 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Minepic\Minecraft;
6
7
use Minepic\Minecraft\Exceptions\UserNotFoundException;
8
use GuzzleHttp\Client as HttpClient;
9
use GuzzleHttp\Exception\BadResponseException;
10
use Illuminate\Support\Facades\Log;
11
use Psr\Http\Message\ResponseInterface;
12
13
/**
14
 * Class MojangClient.
15
 */
16
class MojangClient
17
{
18
    /**
19
     * User Agent used for requests.
20
     */
21
    private const USER_AGENT = 'Minepic/2.0 (minepic.org)';
22
23
    /**
24
     * HTTP Client for requests.
25
     *
26
     * @var \GuzzleHttp\Client
27
     */
28
    private \GuzzleHttp\Client $httpClient;
29
30
    /**
31
     * MojangClient constructor.
32
     */
33
    public function __construct()
34
    {
35
        $this->httpClient = new HttpClient(
36
            [
37
                'headers' => [
38
                    'User-Agent' => self::USER_AGENT,
39
                ],
40
            ]
41
        );
42
    }
43
44
    private function handleGuzzleBadResponseException(
45
        BadResponseException $badResponseException
46
    ): void {
47
        Log::error(
48
            'Error from Minecraft API',
49
            [
50
                'status_code' => $badResponseException->getResponse()->getStatusCode(),
51
                'content_type' => $badResponseException->getResponse()->getHeader('content-type')[0] ?? '',
52
                'content' => $badResponseException->getResponse()->getBody()->getContents(),
53
            ]
54
        );
55
    }
56
57
    private function handleThrowable(\Throwable $exception): void
58
    {
59
        Log::error($exception->getFile().':'.$exception->getLine().' - '.$exception->getMessage());
60
        Log::error($exception->getTraceAsString());
61
    }
62
63
    /**
64
     * Send new request.
65
     *
66
     * @param string $method HTTP Verb
67
     * @param string $url    API Endpoint
68
     *
69
     * @throws \Throwable
70
     *
71
     * @return array
72
     */
73
    private function sendApiRequest(string $method, string $url): ?array
74
    {
75
        try {
76
            $response = $this->httpClient->request($method, $url);
77
            // No Content
78
            if ($response->getStatusCode() === 204) {
79
                return null;
80
            }
81
82
            $responseContents = $response->getBody()->getContents();
83
            Log::debug('Minecraft API Response: '.$responseContents, ['method' => $method, 'url' => $url]);
84
85
            return \json_decode($responseContents, true, 512, JSON_THROW_ON_ERROR);
86
        } catch (BadResponseException $exception) {
87
            $this->handleGuzzleBadResponseException($exception);
88
89
            throw $exception;
90
        } catch (\Throwable $exception) {
91
            $this->handleThrowable($exception);
92
93
            throw $exception;
94
        }
95
    }
96
97
    /**
98
     * Generic request.
99
     *
100
     * @param string $method
101
     * @param string $url
102
     *
103
     * @throws \Throwable
104
     *
105
     * @return \Psr\Http\Message\ResponseInterface
106
     */
107
    private function sendRequest(string $method, string $url): ResponseInterface
108
    {
109
        try {
110
            return $this->httpClient->request($method, $url);
111
        } catch (BadResponseException $exception) {
112
            $this->handleGuzzleBadResponseException($exception);
113
114
            throw $exception;
115
        } catch (\Throwable $exception) {
116
            $this->handleThrowable($exception);
117
118
            throw $exception;
119
        }
120
    }
121
122
    /**
123
     * Account info from username.
124
     *
125
     * @param string $username
126
     *
127
     * @throws \Throwable
128
     *
129
     * @return MojangAccount
130
     */
131
    public function sendUsernameInfoRequest(string $username): MojangAccount
132
    {
133
        $response = $this->sendApiRequest('GET', env('MINECRAFT_PROFILE_URL').$username);
134
135
        if ($response !== null) {
136
            return new MojangAccount($response['id'], $response['name']);
137
        }
138
139
        throw new UserNotFoundException("Unknown user {$username}");
140
    }
141
142
    /**
143
     * Account info from UUID.
144
     *
145
     * @param string $uuid User UUID
146
     *
147
     * @throws \Throwable
148
     *
149
     * @return MojangAccount
150
     */
151
    public function getUuidInfo(string $uuid): MojangAccount
152
    {
153
        $response = $this->sendApiRequest('GET', env('MINECRAFT_SESSION_URL').$uuid);
154
155
        if ($response === null) {
156
            throw new \Exception('Cannot create data account');
157
        }
158
159
        return MojangAccountFactory::makeFromApiResponse($response);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Minepic\Minecraft...mApiResponse($response) could return the type null which is incompatible with the type-hinted return Minepic\Minecraft\MojangAccount. Consider adding an additional type-check to rule them out.
Loading history...
160
    }
161
162
    /**
163
     * Get Skin.
164
     *
165
     * @param string $skin Skin uuid
166
     *
167
     * @throws \Exception|\Throwable
168
     *
169
     * @return string
170
     */
171
    public function getSkin(string $skin): string
172
    {
173
        $response = $this->sendRequest('GET', env('MINECRAFT_TEXTURE_URL').$skin);
174
175
        if ($response->getHeader('content-type')[0] === 'image/png') {
176
            return $response->getBody()->getContents();
177
        }
178
179
        throw new \Exception('Invalid Response content type: '.$response->getHeader('content-type')[0]);
180
    }
181
}
182