Passed
Push — master ( 662eee...d38dae )
by Mattia
03:49
created

Core::initializeUuidRequest()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 6
nop 0
dl 0
loc 23
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App;
6
7
use App\Cache\UserNotFoundCache;
8
use App\Events\Account\UsernameChangeEvent;
9
use App\Helpers\Storage\Files\SkinsStorage;
10
use App\Minecraft\MojangAccount;
11
use App\Minecraft\MojangClient;
12
use App\Models\Account;
13
use App\Repositories\AccountRepository;
14
use App\Repositories\AccountStatsRepository;
15
use Illuminate\Support\Facades\Event;
16
use Illuminate\Support\Facades\Log;
17
18
/**
19
 * Class Core.
20
 */
21
class Core
22
{
23
    /**
24
     * Requested string.
25
     *
26
     * @var string
27
     */
28
    private string $request = '';
29
    /**
30
     * @var string
31
     */
32
    private string $uuid;
33
    /**
34
     * Userdata from/to DB.
35
     *
36
     * @var Account
37
     */
38
    private ?Account $userdata;
39
40
    /**
41
     * Full userdata.
42
     *
43
     * @var MojangAccount
44
     */
45
    private ?MojangAccount $apiUserdata;
46
    /**
47
     * User data has been updated?
48
     *
49
     * @var bool
50
     */
51
    private bool $dataUpdated = false;
52
    /**
53
     * Set force update.
54
     *
55
     * @var bool
56
     */
57
    private bool $forceUpdate = false;
58
    /**
59
     * @var AccountRepository
60
     */
61
    private AccountRepository $accountRepository;
62
    /**
63
     * @var AccountStatsRepository
64
     */
65
    private AccountStatsRepository $accountStatsRepository;
66
67
    /**
68
     * @var MojangClient
69
     */
70
    private MojangClient $mojangClient;
71
72
    /**
73
     * Core constructor.
74
     *
75
     * @param AccountRepository      $accountRepository      Where user data is stored
76
     * @param AccountStatsRepository $accountStatsRepository
77
     * @param MojangClient           $mojangClient           Client for Mojang API
78
     */
79
    public function __construct(
80
        AccountRepository $accountRepository,
81
        AccountStatsRepository $accountStatsRepository,
82
        MojangClient $mojangClient
83
    ) {
84
        $this->accountRepository = $accountRepository;
85
        $this->accountStatsRepository = $accountStatsRepository;
86
        $this->mojangClient = $mojangClient;
87
    }
88
89
    /**
90
     * @return string
91
     */
92
    public function getUuid(): ?string
93
    {
94
        return $this->uuid;
95
    }
96
97
    /**
98
     * Check if cache is still valid.
99
     *
100
     * @param int
101
     *
102
     * @return bool
103
     */
104
    private function checkDbCache(): bool
105
    {
106
        $accountUpdatedAtTimestamp = $this->userdata->updated_at->timestamp ?? 0;
0 ignored issues
show
Bug introduced by
The property timestamp does not exist on string.
Loading history...
107
108
        return (\time() - $accountUpdatedAtTimestamp) < env('USERDATA_CACHE_TIME');
109
    }
110
111
    /**
112
     * Return loaded user data.
113
     *
114
     * @return Account
115
     */
116
    public function getUserdata(): Account
117
    {
118
        return $this->userdata ?? new Account();
119
    }
120
121
    /**
122
     * Check if an UUID is in the database.
123
     *
124
     * @return bool Returns true/false
125
     */
126
    private function requestedUuidInDb(): bool
127
    {
128
        $account = $this->accountRepository->findByUuid($this->request);
129
130
        if ($account === null) {
131
            return false;
132
        }
133
134
        $this->userdata = $account;
135
        $this->uuid = $account->uuid;
136
137
        return true;
138
    }
139
140
    /**
141
     * Insert user data in database.
142
     *
143
     * @param void
144
     *
145
     * @return bool
146
     */
147
    public function insertNewUuid(): bool
148
    {
149
        if (UserNotFoundCache::has($this->request)) {
150
            Log::debug('Cache Hit Not Found', ['request' => $this->request]);
151
152
            return false;
153
        }
154
155
        if ($this->getFullUserdataApi()) {
156
            $this->userdata = $this->accountRepository->create([
157
                'username' => $this->apiUserdata->getUsername(),
158
                'uuid' => $this->apiUserdata->getUuid(),
159
                'skin' => $this->apiUserdata->getSkin(),
160
                'cape' => $this->apiUserdata->getCape(),
161
            ]);
162
163
            $this->saveRemoteSkin();
164
165
            $this->accountStatsRepository->create([
166
                'uuid' => $this->userdata->uuid,
167
                'count_search' => 0,
168
                'count_request' => 0,
169
                'time_search' => 0,
170
                'time_request' => 0,
171
            ]);
172
173
            return true;
174
        }
175
176
        UserNotFoundCache::add($this->request);
177
178
        return false;
179
    }
180
181
    /**
182
     * Check requested string and initialize objects.
183
     *
184
     * @param string
185
     *
186
     * @throws \Exception
187
     *
188
     * @return bool
189
     */
190
    public function initialize(string $string): bool
191
    {
192
        $this->dataUpdated = false;
193
        $this->request = $string;
194
195
        if ($this->initializeUuidRequest()) {
196
            return true;
197
        }
198
199
        $this->setFailedRequest('Account not found');
200
201
        return false;
202
    }
203
204
    /**
205
     * Update current user fail count.
206
     */
207
    private function updateUserFailUpdate(): bool
208
    {
209
        if (isset($this->userdata->uuid)) {
210
            ++$this->userdata->fail_count;
211
212
            return $this->userdata->save();
0 ignored issues
show
Bug introduced by
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

212
            return $this->userdata->/** @scrutinizer ignore-call */ 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...
213
        }
214
215
        return false;
216
    }
217
218
    /**
219
     * Update db user data.
220
     */
221
    private function updateDbUser(): bool
222
    {
223
        if (isset($this->userdata->username) && $this->userdata->uuid !== '') {
224
            // Get data from API
225
            if ($this->getFullUserdataApi()) {
226
                $originalUsername = $this->userdata->username;
227
                // Update database
228
                $this->accountRepository->update([
229
                    'username' => $this->apiUserdata->getUsername(),
230
                    'skin' => $this->apiUserdata->getSkin(),
231
                    'cape' => $this->apiUserdata->getCape(),
232
                    'fail_count' => 0,
233
                ], $this->userdata->id);
234
235
                $this->userdata->touch();
236
                $this->userdata->refresh();
237
238
                // Update skin
239
                $this->saveRemoteSkin();
240
241
                // Log username change
242
                if ($this->userdata->username !== $originalUsername && $originalUsername !== '') {
243
                    $this->logUsernameChange($this->userdata->uuid, $originalUsername, $this->userdata->username);
244
                }
245
                $this->dataUpdated = true;
246
247
                return true;
248
            }
249
250
            $this->updateUserFailUpdate();
251
252
            if (!SkinsStorage::exists($this->userdata->uuid)) {
253
                SkinsStorage::copyAsSteve($this->userdata->uuid);
254
            }
255
        }
256
        $this->dataUpdated = false;
257
258
        return false;
259
    }
260
261
    /**
262
     * Return if data has been updated.
263
     */
264
    public function userDataUpdated(): bool
265
    {
266
        return $this->dataUpdated;
267
    }
268
269
    /**
270
     * Log the username change.
271
     *
272
     * @param $uuid string User UUID
273
     * @param $prev string Previous username
274
     * @param $new string New username
275
     */
276
    private function logUsernameChange(string $uuid, string $prev, string $new): void
277
    {
278
        Event::dispatch(new UsernameChangeEvent($uuid, $prev, $new));
279
    }
280
281
    /**
282
     * Get userdata from Mojang API.
283
     *
284
     * @param mixed
285
     *
286
     * @throws \Throwable
287
     *
288
     * @return bool
289
     */
290
    private function getFullUserdataApi(): bool
291
    {
292
        try {
293
            $this->apiUserdata = $this->mojangClient->getUuidInfo($this->request);
294
295
            return true;
296
        } catch (\Exception $e) {
297
            Log::error($e->getTraceAsString(), ['request' => $this->request]);
298
            $this->apiUserdata = null;
299
300
            return false;
301
        }
302
    }
303
304
    /**
305
     * Save skin image.
306
     *
307
     * @param mixed
308
     *
309
     * @return bool
310
     */
311
    public function saveRemoteSkin(): bool
312
    {
313
        if (!empty($this->userdata->skin) && $this->userdata->skin !== '') {
314
            try {
315
                $skinData = $this->mojangClient->getSkin($this->userdata->skin);
316
317
                return SkinsStorage::save($this->userdata->uuid, $skinData);
318
            } catch (\Exception $e) {
319
                Log::error($e->getTraceAsString());
320
            }
321
        }
322
323
        return SkinsStorage::copyAsSteve($this->userdata->uuid);
324
    }
325
326
    /**
327
     * Set force update.
328
     *
329
     * @param bool $forceUpdate
330
     */
331
    public function setForceUpdate(bool $forceUpdate): void
332
    {
333
        $this->forceUpdate = $forceUpdate;
334
    }
335
336
    /**
337
     * Can I exec force update?
338
     */
339
    private function forceUpdatePossible(): bool
340
    {
341
        return ($this->forceUpdate) &&
342
            ((\time() - $this->userdata->updated_at->timestamp) > env('MIN_USERDATA_UPDATE_INTERVAL'));
0 ignored issues
show
Bug introduced by
The property timestamp does not exist on string.
Loading history...
343
    }
344
345
    /**
346
     * Use steve skin for given username.
347
     *
348
     * @param string
349
     */
350
    public function updateStats(): void
351
    {
352
        if (!empty($this->userdata->uuid) && env('STATS_ENABLED') && $this->userdata->uuid !== env('DEFAULT_UUID')) {
353
            $this->accountStatsRepository->incrementRequestCounter($this->userdata->uuid);
354
        }
355
    }
356
357
    /**
358
     * @return bool
359
     */
360
    private function initializeUuidRequest(): bool
361
    {
362
        if ($this->requestedUuidInDb()) {
363
            // Check if UUID is in my database
364
            // Data cache still valid?
365
            if (!$this->checkDbCache() || $this->forceUpdatePossible()) {
366
                Log::debug('Refreshing User DB Data');
367
                // Nope, updating data
368
                $this->updateDbUser();
369
            }
370
371
            if (!SkinsStorage::exists($this->request)) {
372
                $this->saveRemoteSkin();
373
            }
374
375
            return true;
376
        }
377
378
        if ($this->insertNewUuid()) {
379
            return true;
380
        }
381
382
        return false;
383
    }
384
385
    /**
386
     * Set failed request.
387
     *
388
     * @param string $errorMessage
389
     */
390
    private function setFailedRequest(string $errorMessage = ''): void
391
    {
392
        Log::notice($errorMessage, ['request' => $this->request]);
393
        $this->uuid = env('DEFAULT_UUID');
394
        $this->userdata = null;
395
        $this->request = '';
396
    }
397
}
398