Issues (11)

app/Resolvers/UuidResolver.php (4 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Minepic\Resolvers;
6
7
use Illuminate\Contracts\Events\Dispatcher;
8
use Log;
9
use Minepic\Cache\UserNotFoundCache;
10
use Minepic\Events\Account\AccountCreatedEvent;
11
use Minepic\Events\Account\UsernameChangeEvent;
12
use Minepic\Helpers\Storage\Files\SkinsStorage;
13
use Minepic\Minecraft\MojangAccount;
14
use Minepic\Minecraft\MojangClient;
15
use Minepic\Models\Account;
16
17
/**
18
 * TODO: This class must be refactored. It should return a "resolution" instead of modify its parameters.
19
 */
20
class UuidResolver
21
{
22
    /**
23
     * Requested string.
24
     */
25
    private string $request;
26
27
    private ?string $uuid = null;
28
    /**
29
     * Userdata from/to DB.
30
     */
31
    private ?Account $account;
32
    /**
33
     * User data has been updated?
34
     */
35
    private bool $dataUpdated = false;
36
    /**
37
     * Set force update.
38
     */
39
    private bool $forceUpdate = false;
40
41
    public function __construct(
42
        private readonly MojangClient $mojangClient,
43
        private readonly Dispatcher $eventDispatcher
44
    ) {
45
    }
46
47
    public function getUuid(): ?string
48
    {
49
        return $this->uuid;
50
    }
51
52
    /**
53
     * Return loaded user data.
54
     */
55
    public function getAccount(): Account
56
    {
57
        return $this->account ?? new Account();
58
    }
59
60
    /**
61
     * Insert user data in database.
62
     **
63
     * @throws \Throwable
64
     */
65
    public function insertNewUuid(): bool
66
    {
67
        if ($this->request === '' || UserNotFoundCache::has($this->request)) {
68
            return false;
69
        }
70
71
        $mojangAccount = $this->getFullUserdataApi();
72
        if ($mojangAccount instanceof MojangAccount) {
73
            $this->account = Account::create([
74
                'username' => $mojangAccount->getUsername(),
75
                'uuid' => $mojangAccount->getUuid(),
76
                'skin' => $mojangAccount->getSkin(),
77
                'cape' => $mojangAccount->getCape(),
78
            ]);
79
80
            $this->saveRemoteSkin();
81
82
            $this->uuid = $this->account->uuid;
83
            $this->eventDispatcher->dispatch(new AccountCreatedEvent($this->account));
0 ignored issues
show
It seems like $this->account can also be of type null; however, parameter $account of Minepic\Events\Account\A...tedEvent::__construct() does only seem to accept Minepic\Models\Account, maybe add an additional type check? ( Ignorable by Annotation )

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

83
            $this->eventDispatcher->dispatch(new AccountCreatedEvent(/** @scrutinizer ignore-type */ $this->account));
Loading history...
84
85
            return true;
86
        }
87
88
        UserNotFoundCache::add($this->request);
89
90
        return false;
91
    }
92
93
    /**
94
     * Check requested string and initialize objects.
95
     */
96
    public function resolve(?string $uuid): bool
97
    {
98
        $this->dataUpdated = false;
99
        $this->request = $uuid ?? '';
100
101
        if ($uuid === null) {
102
            \Log::debug('UUID is null');
103
104
            return false;
105
        }
106
107
        if ($this->initializeUuidRequest()) {
108
            return true;
109
        }
110
111
        $this->setFailedRequest('Account not found');
112
113
        return false;
114
    }
115
116
    /**
117
     * Return if data has been updated.
118
     */
119
    public function userDataUpdated(): bool
120
    {
121
        return $this->dataUpdated;
122
    }
123
124
    public function saveRemoteSkin(): bool
125
    {
126
        if ($this->account instanceof Account === false) {
127
            return false;
128
        }
129
130
        if (!empty($this->account->skin) && $this->account->skin !== '') {
131
            try {
132
                $skinData = $this->mojangClient->getSkin($this->account->skin);
133
134
                return SkinsStorage::save($this->account->uuid, $skinData);
135
            } catch (\Exception $e) {
136
                \Log::error($e->getTraceAsString());
137
            }
138
        }
139
140
        return SkinsStorage::copyAsSteve($this->account->uuid);
141
    }
142
143
    /**
144
     * Set force update.
145
     */
146
    public function setForceUpdate(bool $forceUpdate): void
147
    {
148
        $this->forceUpdate = $forceUpdate;
149
    }
150
151
    /**
152
     * Check if cache is still valid.
153
     */
154
    private function checkDbCache(): bool
155
    {
156
        $accountUpdatedAtTimestamp = (int) ($this->account->updated_at->timestamp ?? 0);
0 ignored issues
show
The property timestamp does not exist on string.
Loading history...
157
158
        return (time() - $accountUpdatedAtTimestamp) < (int) env('USERDATA_CACHE_TIME');
159
    }
160
161
    /**
162
     * Check if an UUID is in the database.
163
     */
164
    private function requestedUuidInDb(): bool
165
    {
166
        $this->account = Account::query()
167
            ->whereUuid($this->request)
168
            ->first();
169
170
        if ($this->account === null) {
171
            return false;
172
        }
173
174
        $this->uuid = $this->account->uuid;
175
176
        return true;
177
    }
178
179
    /**
180
     * Update current user fail count.
181
     */
182
    private function updateUserFailUpdate(): void
183
    {
184
        if (isset($this->account->uuid)) {
185
            ++$this->account->fail_count;
186
187
            $this->account->save();
0 ignored issues
show
The method save() does not exist on null. ( Ignorable by Annotation )

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

187
            $this->account->/** @scrutinizer ignore-call */ 
188
                            save();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
188
        }
189
    }
190
191
    /**
192
     * Update db user data.
193
     */
194
    private function updateDbUser(): void
195
    {
196
        if (
197
            $this->account instanceof Account &&
198
            isset($this->account->username) &&
199
            $this->account->uuid !== ''
200
        ) {
201
            // Get data from API
202
            $mojangAccount = $this->getFullUserdataApi();
203
            if ($mojangAccount instanceof MojangAccount) {
204
                $previousUsername = $this->account->username;
205
                // Update database
206
                $this->account->username = $mojangAccount->getUsername();
207
                $this->account->skin = $mojangAccount->getSkin() ?? '';
208
                $this->account->cape = $mojangAccount->getCape() ?? '';
209
                $this->account->fail_count = 0;
210
                $this->account->save();
211
212
                $this->account->refresh();
213
214
                // Update skin
215
                $this->saveRemoteSkin();
216
                $this->logUsernameChange($this->account, $previousUsername);
217
218
                $this->dataUpdated = true;
219
220
                return;
221
            }
222
223
            $this->updateUserFailUpdate();
224
225
            if (!SkinsStorage::exists($this->account->uuid)) {
226
                SkinsStorage::copyAsSteve($this->account->uuid);
227
            }
228
        }
229
        $this->dataUpdated = false;
230
    }
231
232
    /**
233
     * Log the username change.
234
     *
235
     * @param Account $account User Account
236
     * @param string $previousUsername Previous username
237
     */
238
    private function logUsernameChange(Account $account, string $previousUsername): void
239
    {
240
        if ($account->username !== $previousUsername && $previousUsername !== '') {
241
            $this->eventDispatcher->dispatch(
242
                new UsernameChangeEvent($account->uuid, $previousUsername, $account->username)
243
            );
244
        }
245
    }
246
247
    /**
248
     * Get userdata from Mojang/Minecraft API.
249
     */
250
    private function getFullUserdataApi(): ?MojangAccount
251
    {
252
        try {
253
            return $this->mojangClient->getUuidInfo($this->request);
254
        } catch (\Throwable $e) {
255
            \Log::error($e->getTraceAsString(), ['request' => $this->request]);
256
257
            return null;
258
        }
259
    }
260
261
    /**
262
     * Can I exec force update?
263
     */
264
    private function forceUpdatePossible(): bool
265
    {
266
        return $this->forceUpdate &&
267
            ((time() - (int) $this->account->updated_at->timestamp) > (int) env('MIN_USERDATA_UPDATE_INTERVAL'));
0 ignored issues
show
The property timestamp does not exist on string.
Loading history...
268
    }
269
270
    private function initializeUuidRequest(): bool
271
    {
272
        if ($this->requestedUuidInDb()) {
273
            // Check if UUID is in my database
274
            // Data cache still valid?
275
            if (!$this->checkDbCache() || $this->forceUpdatePossible()) {
276
                \Log::debug('Refreshing User DB Data');
277
                // Nope, updating data
278
                $this->updateDbUser();
279
            }
280
281
            if (!SkinsStorage::exists($this->account->uuid)) {
282
                $this->saveRemoteSkin();
283
            }
284
285
            return true;
286
        }
287
288
        if ($this->insertNewUuid()) {
289
            return true;
290
        }
291
292
        return false;
293
    }
294
295
    /**
296
     * Set failed request.
297
     */
298
    private function setFailedRequest(string $errorMessage = ''): void
299
    {
300
        \Log::notice($errorMessage, ['request' => $this->request]);
301
        $this->account = null;
302
        $this->request = '';
303
    }
304
}
305