Passed
Push — master ( bebbb3...38c082 )
by Mattia
03:32
created

MojangClient::getSkin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Minecraft;
6
7
use GuzzleHttp\Client as HttpClient;
8
use GuzzleHttp\Exception\BadResponseException;
9
use Illuminate\Support\Facades\Log;
10
use Psr\Http\Message\ResponseInterface;
11
12
/**
13
 * Class MojangClient.
14
 */
15
class MojangClient
16
{
17
    /**
18
     * User Agent used for requests.
19
     */
20
    private const USER_AGENT = 'Minepic/2.0 (minepic.org)';
21
22
    /**
23
     * HTTP Client for requests.
24
     *
25
     * @var \GuzzleHttp\Client
26
     */
27
    private \GuzzleHttp\Client $httpClient;
28
29
    /**
30
     * MojangClient constructor.
31
     */
32
    public function __construct()
33
    {
34
        $this->httpClient = new HttpClient(
35
            [
36
                'headers' => [
37
                    'User-Agent' => self::USER_AGENT,
38
                ],
39
            ]
40
        );
41
    }
42
43
    private function handleGuzzleBadResponseException(
44
        BadResponseException $badResponseException
45
    ): void {
46
        Log::error(
47
            'Error from Minecraft API',
48
            [
49
                'status_code' => $badResponseException->getResponse()->getStatusCode(),
50
                'content_type' => $badResponseException->getResponse()->getHeader('content-type')[0] ?? '',
51
                'content' => $badResponseException->getResponse()->getBody()->getContents(),
52
            ]
53
        );
54
    }
55
56
    private function handleThrowable(\Throwable $exception): void
57
    {
58
        Log::error($exception->getFile().':'.$exception->getLine().' - '.$exception->getMessage());
59
        Log::error($exception->getTraceAsString());
60
    }
61
62
    /**
63
     * Send new request.
64
     *
65
     * @param string $method HTTP Verb
66
     * @param string $url    API Endpoint
67
     *
68
     * @throws \Throwable
69
     *
70
     * @return array|null
71
     */
72
    private function sendApiRequest(string $method, string $url): ?array
73
    {
74
        try {
75
            $response = $this->httpClient->request($method, $url);
76
            $responseContents = $response->getBody()->getContents();
77
            Log::debug('Minecraft API Response: '.$responseContents, ['method' => $method, 'url' => $url]);
78
79
            return \json_decode($responseContents, true, 512, JSON_THROW_ON_ERROR);
80
        } catch (BadResponseException $exception) {
81
            $this->handleGuzzleBadResponseException($exception);
82
83
            throw $exception;
84
        } catch (\Throwable $exception) {
85
            $this->handleThrowable($exception);
86
87
            throw $exception;
88
        }
89
    }
90
91
    /**
92
     * Generic request.
93
     *
94
     * @param string $method
95
     * @param string $url
96
     *
97
     * @return \Psr\Http\Message\ResponseInterface|null
98
     */
99
    private function sendRequest(string $method, string $url): ?ResponseInterface
100
    {
101
        try {
102
            return $this->httpClient->request($method, $url);
103
        } catch (BadResponseException $exception) {
104
            $this->handleGuzzleBadResponseException($exception);
105
106
            return null;
107
        } catch (\Throwable $exception) {
108
            $this->handleThrowable($exception);
109
110
            return null;
111
        }
112
    }
113
114
    /**
115
     * Account info from username.
116
     *
117
     * @param string $username
118
     *
119
     * @throws \Throwable
120
     *
121
     * @return MojangAccount
122
     */
123
    public function sendUsernameInfoRequest(string $username): MojangAccount
124
    {
125
        $response = $this->sendApiRequest('GET', env('MINECRAFT_PROFILE_URL').$username);
126
127
        return new MojangAccount($response['id'], $response['name']);
128
    }
129
130
    /**
131
     * Account info from UUID.
132
     *
133
     * @param string $uuid User UUID
134
     *
135
     * @throws \Throwable
136
     *
137
     * @return MojangAccount
138
     */
139
    public function getUuidInfo(string $uuid): MojangAccount
140
    {
141
        $response = $this->sendApiRequest('GET', env('MINECRAFT_SESSION_URL').$uuid);
142
        if ($response !== null) {
143
            $account = MojangAccountFactory::makeFromApiResponse($response);
144
            if ($account !== null) {
145
                return $account;
146
            }
147
            throw new \Exception('Cannot create data account');
148
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 142 is false. This is incompatible with the type-hinted return App\Minecraft\MojangAccount. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
149
    }
150
151
    /**
152
     * Get Skin.
153
     *
154
     * @param string $skin Skin uuid
155
     *
156
     * @throws \Exception
157
     *
158
     * @return string
159
     */
160
    public function getSkin(string $skin)
161
    {
162
        $response = $this->sendRequest('GET', env('MINECRAFT_TEXTURE_URL').$skin);
163
164
        if ($response->getHeader('content-type')[0] === 'image/png') {
165
            return $response->getBody()->getContents();
166
        }
167
168
        throw new \Exception('Invalid Response content type: '.$response->getHeader('content-type')[0]);
169
    }
170
}
171