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
Bug
introduced
by
![]() |
|||||||
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
|
|||||||
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
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. ![]() |
|||||||
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
|
|||||||
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 |