| Total Complexity | 90 |
| Total Lines | 717 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like PopulateAniListService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use PopulateAniListService, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 13 | class PopulateAniListService |
||
| 14 | { |
||
| 15 | /** |
||
| 16 | * AniList GraphQL API endpoint |
||
| 17 | */ |
||
| 18 | private const API_URL = 'https://graphql.anilist.co'; |
||
| 19 | |||
| 20 | /** |
||
| 21 | * Rate limit: 20 requests per minute (conservative limit to avoid hitting AniList's 90/min limit) |
||
| 22 | */ |
||
| 23 | private const RATE_LIMIT_PER_MINUTE = 20; |
||
| 24 | |||
| 25 | /** |
||
| 26 | * Whether to echo message output. |
||
| 27 | */ |
||
| 28 | public bool $echooutput; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * The directory to store anime covers. |
||
| 32 | */ |
||
| 33 | public string $imgSavePath; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * HTTP client for API requests |
||
| 37 | */ |
||
| 38 | protected Client $client; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * Rate limiting: track requests and timestamps |
||
| 42 | */ |
||
| 43 | private array $rateLimitQueue = []; |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Track when we received a 429 error and need to pause. |
||
| 47 | * Format: ['timestamp' => int, 'paused_until' => int] |
||
| 48 | */ |
||
| 49 | private static ?array $rateLimitPause = null; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @throws \Exception |
||
| 53 | */ |
||
| 54 | public function __construct() |
||
| 55 | { |
||
| 56 | $this->echooutput = config('nntmux.echocli'); |
||
| 57 | |||
| 58 | // Use storage_path directly to match CoverController expectations |
||
| 59 | $this->imgSavePath = storage_path('covers/anime/'); |
||
| 60 | $this->client = new Client([ |
||
| 61 | 'base_uri' => self::API_URL, |
||
| 62 | 'timeout' => 30, |
||
| 63 | 'headers' => [ |
||
| 64 | 'Content-Type' => 'application/json', |
||
| 65 | 'Accept' => 'application/json', |
||
| 66 | ], |
||
| 67 | ]); |
||
| 68 | } |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Main switch that initiates AniList table population. |
||
| 72 | * |
||
| 73 | * @throws \Exception |
||
| 74 | */ |
||
| 75 | public function populateTable(string $type = '', int|string $anilistId = ''): void |
||
| 76 | { |
||
| 77 | switch ($type) { |
||
| 78 | case 'info': |
||
| 79 | $this->populateInfoTable((string) $anilistId); |
||
| 80 | break; |
||
| 81 | } |
||
| 82 | } |
||
| 83 | |||
| 84 | /** |
||
| 85 | * Search for anime by title using AniList GraphQL API. |
||
| 86 | * |
||
| 87 | * @return array|false |
||
| 88 | * |
||
| 89 | * @throws \Exception |
||
| 90 | */ |
||
| 91 | public function searchAnime(string $title, int $limit = 10) |
||
| 92 | { |
||
| 93 | $query = ' |
||
| 94 | query ($search: String, $perPage: Int) { |
||
| 95 | Page (perPage: $perPage) { |
||
| 96 | media (search: $search, type: ANIME) { |
||
| 97 | id |
||
| 98 | idMal |
||
| 99 | title { |
||
| 100 | romaji |
||
| 101 | english |
||
| 102 | native |
||
| 103 | } |
||
| 104 | type |
||
| 105 | format |
||
| 106 | status |
||
| 107 | countryOfOrigin |
||
| 108 | episodes |
||
| 109 | duration |
||
| 110 | source |
||
| 111 | popularity |
||
| 112 | favourites |
||
| 113 | startDate { |
||
| 114 | year |
||
| 115 | month |
||
| 116 | day |
||
| 117 | } |
||
| 118 | endDate { |
||
| 119 | year |
||
| 120 | month |
||
| 121 | day |
||
| 122 | } |
||
| 123 | description |
||
| 124 | averageScore |
||
| 125 | hashtag |
||
| 126 | coverImage { |
||
| 127 | large |
||
| 128 | } |
||
| 129 | genres |
||
| 130 | studios { |
||
| 131 | nodes { |
||
| 132 | name |
||
| 133 | } |
||
| 134 | } |
||
| 135 | characters { |
||
| 136 | nodes { |
||
| 137 | name { |
||
| 138 | full |
||
| 139 | } |
||
| 140 | } |
||
| 141 | } |
||
| 142 | relations { |
||
| 143 | edges { |
||
| 144 | relationType |
||
| 145 | node { |
||
| 146 | id |
||
| 147 | title { |
||
| 148 | romaji |
||
| 149 | english |
||
| 150 | } |
||
| 151 | } |
||
| 152 | } |
||
| 153 | } |
||
| 154 | } |
||
| 155 | } |
||
| 156 | } |
||
| 157 | '; |
||
| 158 | |||
| 159 | $variables = [ |
||
| 160 | 'search' => $title, |
||
| 161 | 'perPage' => $limit, |
||
| 162 | ]; |
||
| 163 | |||
| 164 | $response = $this->makeGraphQLRequest($query, $variables); |
||
| 165 | |||
| 166 | if ($response === false || ! isset($response['data']['Page']['media'])) { |
||
| 167 | return false; |
||
| 168 | } |
||
| 169 | |||
| 170 | return $response['data']['Page']['media']; |
||
| 171 | } |
||
| 172 | |||
| 173 | /** |
||
| 174 | * Refresh AniList data for a specific anidbid. |
||
| 175 | * If anilist_id exists in database, uses it. Otherwise returns false. |
||
| 176 | * |
||
| 177 | * @return bool True on success, false on failure |
||
| 178 | * |
||
| 179 | * @throws \Exception |
||
| 180 | */ |
||
| 181 | public function refreshAnimeByAnidbid(int $anidbid): bool |
||
| 182 | { |
||
| 183 | // Get anilist_id from database |
||
| 184 | $anidbInfo = AnidbInfo::query()->where('anidbid', $anidbid)->first(); |
||
| 185 | |||
| 186 | if (! $anidbInfo || ! $anidbInfo->anilist_id) { |
||
| 187 | return false; |
||
| 188 | } |
||
| 189 | |||
| 190 | // Fetch fresh data from AniList |
||
| 191 | $anilistData = $this->getAnimeById($anidbInfo->anilist_id); |
||
| 192 | |||
| 193 | if ($anilistData === false) { |
||
| 194 | return false; |
||
| 195 | } |
||
| 196 | |||
| 197 | // Update database with fresh data |
||
| 198 | $this->insertAniListInfo($anidbid, $anilistData); |
||
| 199 | |||
| 200 | return true; |
||
| 201 | } |
||
| 202 | |||
| 203 | /** |
||
| 204 | * Get anime by AniList ID. |
||
| 205 | * |
||
| 206 | * @return array|false |
||
| 207 | * |
||
| 208 | * @throws \Exception |
||
| 209 | */ |
||
| 210 | public function getAnimeById(int $anilistId) |
||
| 211 | { |
||
| 212 | $query = ' |
||
| 213 | query ($id: Int) { |
||
| 214 | Media (id: $id, type: ANIME) { |
||
| 215 | id |
||
| 216 | idMal |
||
| 217 | title { |
||
| 218 | romaji |
||
| 219 | english |
||
| 220 | native |
||
| 221 | } |
||
| 222 | type |
||
| 223 | format |
||
| 224 | status |
||
| 225 | countryOfOrigin |
||
| 226 | episodes |
||
| 227 | duration |
||
| 228 | source |
||
| 229 | popularity |
||
| 230 | favourites |
||
| 231 | startDate { |
||
| 232 | year |
||
| 233 | month |
||
| 234 | day |
||
| 235 | } |
||
| 236 | endDate { |
||
| 237 | year |
||
| 238 | month |
||
| 239 | day |
||
| 240 | } |
||
| 241 | description |
||
| 242 | averageScore |
||
| 243 | hashtag |
||
| 244 | coverImage { |
||
| 245 | large |
||
| 246 | } |
||
| 247 | genres |
||
| 248 | studios { |
||
| 249 | nodes { |
||
| 250 | name |
||
| 251 | } |
||
| 252 | } |
||
| 253 | characters { |
||
| 254 | nodes { |
||
| 255 | name { |
||
| 256 | full |
||
| 257 | } |
||
| 258 | } |
||
| 259 | } |
||
| 260 | relations { |
||
| 261 | edges { |
||
| 262 | relationType |
||
| 263 | node { |
||
| 264 | id |
||
| 265 | title { |
||
| 266 | romaji |
||
| 267 | english |
||
| 268 | } |
||
| 269 | } |
||
| 270 | } |
||
| 271 | } |
||
| 272 | } |
||
| 273 | } |
||
| 274 | '; |
||
| 275 | |||
| 276 | $variables = ['id' => $anilistId]; |
||
| 277 | |||
| 278 | $response = $this->makeGraphQLRequest($query, $variables); |
||
| 279 | |||
| 280 | if ($response === false || ! isset($response['data']['Media'])) { |
||
| 281 | return false; |
||
| 282 | } |
||
| 283 | |||
| 284 | return $response['data']['Media']; |
||
| 285 | } |
||
| 286 | |||
| 287 | /** |
||
| 288 | * Make a GraphQL request to AniList API with rate limiting. |
||
| 289 | * |
||
| 290 | * @return array|false |
||
| 291 | * |
||
| 292 | * @throws \Exception |
||
| 293 | */ |
||
| 294 | private function makeGraphQLRequest(string $query, array $variables = []) |
||
| 295 | { |
||
| 296 | // Check if we're paused due to 429 error |
||
| 297 | if (self::$rateLimitPause !== null) { |
||
| 298 | $now = time(); |
||
| 299 | $pausedUntil = self::$rateLimitPause['paused_until']; |
||
| 300 | |||
| 301 | if ($now < $pausedUntil) { |
||
| 302 | $remainingSeconds = $pausedUntil - $now; |
||
| 303 | if ($this->echooutput) { |
||
| 304 | cli()->warning("API calls paused due to 429 error. Resuming in {$remainingSeconds} seconds..."); |
||
| 305 | } |
||
| 306 | |||
| 307 | // Throw exception to stop processing |
||
| 308 | throw new \Exception("AniList API rate limit exceeded (429). Paused until " . date('Y-m-d H:i:s', $pausedUntil)); |
||
| 309 | } else { |
||
| 310 | // Pause period expired, clear it |
||
| 311 | self::$rateLimitPause = null; |
||
| 312 | } |
||
| 313 | } |
||
| 314 | |||
| 315 | // Enforce rate limiting |
||
| 316 | $this->enforceRateLimit(); |
||
| 317 | |||
| 318 | try { |
||
| 319 | $response = $this->client->post('', [ |
||
| 320 | 'json' => [ |
||
| 321 | 'query' => $query, |
||
| 322 | 'variables' => $variables, |
||
| 323 | ], |
||
| 324 | ]); |
||
| 325 | |||
| 326 | $statusCode = $response->getStatusCode(); |
||
| 327 | $body = json_decode($response->getBody()->getContents(), true); |
||
| 328 | |||
| 329 | // Handle 429 Too Many Requests - pause for 15 minutes |
||
| 330 | if ($statusCode === 429) { |
||
| 331 | $pauseUntil = time() + (15 * 60); // 15 minutes from now |
||
| 332 | self::$rateLimitPause = [ |
||
| 333 | 'timestamp' => time(), |
||
| 334 | 'paused_until' => $pauseUntil, |
||
| 335 | ]; |
||
| 336 | |||
| 337 | if ($this->echooutput) { |
||
| 338 | cli()->error('AniList API returned 429 (Too Many Requests). Pausing all API calls for 15 minutes.'); |
||
| 339 | } |
||
| 340 | |||
| 341 | return false; |
||
| 342 | } |
||
| 343 | |||
| 344 | // Track rate limit from headers |
||
| 345 | $remaining = (int) ($response->getHeader('X-RateLimit-Remaining')[0] ?? self::RATE_LIMIT_PER_MINUTE); |
||
| 346 | $resetAt = (int) ($response->getHeader('X-RateLimit-Reset')[0] ?? time() + 60); |
||
| 347 | |||
| 348 | // Record this request |
||
| 349 | $this->rateLimitQueue[] = [ |
||
| 350 | 'timestamp' => time(), |
||
| 351 | 'remaining' => $remaining, |
||
| 352 | 'resetAt' => $resetAt, |
||
| 353 | ]; |
||
| 354 | |||
| 355 | // Clean old entries (older than 1 minute) |
||
| 356 | $this->rateLimitQueue = array_filter($this->rateLimitQueue, function ($entry) { |
||
| 357 | return $entry['timestamp'] > (time() - 60); |
||
| 358 | }); |
||
| 359 | |||
| 360 | if ($statusCode === 200 && isset($body['data'])) { |
||
| 361 | return $body; |
||
| 362 | } |
||
| 363 | |||
| 364 | if (isset($body['errors'])) { |
||
| 365 | if ($this->echooutput) { |
||
| 366 | cli()->error('AniList API Error: '.json_encode($body['errors'])); |
||
| 367 | } |
||
| 368 | |||
| 369 | return false; |
||
| 370 | } |
||
| 371 | |||
| 372 | return false; |
||
| 373 | } catch (ClientException $e) { |
||
| 374 | // Check if this is a 429 error from Guzzle |
||
| 375 | $statusCode = $e->getResponse()->getStatusCode(); |
||
| 376 | if ($statusCode === 429) { |
||
| 377 | $pauseUntil = time() + (15 * 60); // 15 minutes from now |
||
| 378 | self::$rateLimitPause = [ |
||
| 379 | 'timestamp' => time(), |
||
| 380 | 'paused_until' => $pauseUntil, |
||
| 381 | ]; |
||
| 382 | |||
| 383 | if ($this->echooutput) { |
||
| 384 | cli()->error('AniList API returned 429 (Too Many Requests). Pausing all API calls for 15 minutes.'); |
||
| 385 | } |
||
| 386 | |||
| 387 | throw new \Exception("AniList API rate limit exceeded (429). Paused until " . date('Y-m-d H:i:s', $pauseUntil)); |
||
| 388 | } |
||
| 389 | |||
| 390 | if ($this->echooutput) { |
||
| 391 | cli()->error('AniList API Request Failed: '.$e->getMessage()); |
||
| 392 | } |
||
| 393 | |||
| 394 | return false; |
||
| 395 | } catch (GuzzleException $e) { |
||
| 396 | if ($this->echooutput) { |
||
| 397 | cli()->error('AniList API Request Failed: '.$e->getMessage()); |
||
| 398 | } |
||
| 399 | |||
| 400 | return false; |
||
| 401 | } |
||
| 402 | } |
||
| 403 | |||
| 404 | /** |
||
| 405 | * Enforce rate limiting: 90 requests per minute. |
||
| 406 | * |
||
| 407 | * @throws \Exception |
||
| 408 | */ |
||
| 409 | private function enforceRateLimit(): void |
||
| 410 | { |
||
| 411 | // Count requests in the last minute |
||
| 412 | $recentRequests = array_filter($this->rateLimitQueue, function ($entry) { |
||
| 413 | return $entry['timestamp'] > (time() - 60); |
||
| 414 | }); |
||
| 415 | |||
| 416 | $requestCount = count($recentRequests); |
||
| 417 | |||
| 418 | // If we're approaching the limit, wait |
||
| 419 | if ($requestCount >= (self::RATE_LIMIT_PER_MINUTE - 5)) { |
||
| 420 | // Wait until the oldest request is more than 1 minute old |
||
| 421 | if (! empty($this->rateLimitQueue)) { |
||
| 422 | $oldestRequest = min(array_column($this->rateLimitQueue, 'timestamp')); |
||
| 423 | $waitTime = 60 - (time() - $oldestRequest) + 1; |
||
| 424 | |||
| 425 | if ($waitTime > 0 && $waitTime <= 60) { |
||
| 426 | if ($this->echooutput) { |
||
| 427 | cli()->warning("Rate limit approaching. Waiting {$waitTime} seconds..."); |
||
| 428 | } |
||
| 429 | sleep($waitTime); |
||
| 430 | } |
||
| 431 | } |
||
| 432 | } |
||
| 433 | } |
||
| 434 | |||
| 435 | /** |
||
| 436 | * Insert or update AniList title data. |
||
| 437 | */ |
||
| 438 | private function insertAniListTitle(int $anidbid, string $type, string $lang, string $title): void |
||
| 439 | { |
||
| 440 | $check = AnidbTitle::query()->where([ |
||
| 441 | 'anidbid' => $anidbid, |
||
| 442 | 'type' => $type, |
||
| 443 | 'lang' => $lang, |
||
| 444 | 'title' => $title, |
||
| 445 | ])->first(); |
||
| 446 | |||
| 447 | if ($check === null) { |
||
| 448 | AnidbTitle::insertOrIgnore([ |
||
| 449 | 'anidbid' => $anidbid, |
||
| 450 | 'type' => $type, |
||
| 451 | 'lang' => $lang, |
||
| 452 | 'title' => $title, |
||
| 453 | ]); |
||
| 454 | } |
||
| 455 | } |
||
| 456 | |||
| 457 | /** |
||
| 458 | * Insert or update AniList info data. |
||
| 459 | */ |
||
| 460 | protected function insertAniListInfo(int $anidbid, array $anilistData): void |
||
| 461 | { |
||
| 462 | // Extract data from AniList response |
||
| 463 | $startDate = null; |
||
| 464 | if (isset($anilistData['startDate']['year'], $anilistData['startDate']['month'], $anilistData['startDate']['day'])) { |
||
| 465 | $startDate = sprintf( |
||
| 466 | '%04d-%02d-%02d', |
||
| 467 | $anilistData['startDate']['year'], |
||
| 468 | $anilistData['startDate']['month'] ?? 1, |
||
| 469 | $anilistData['startDate']['day'] ?? 1 |
||
| 470 | ); |
||
| 471 | } |
||
| 472 | |||
| 473 | $endDate = null; |
||
| 474 | if (isset($anilistData['endDate']['year'], $anilistData['endDate']['month'], $anilistData['endDate']['day'])) { |
||
| 475 | $endDate = sprintf( |
||
| 476 | '%04d-%02d-%02d', |
||
| 477 | $anilistData['endDate']['year'], |
||
| 478 | $anilistData['endDate']['month'] ?? 1, |
||
| 479 | $anilistData['endDate']['day'] ?? 1 |
||
| 480 | ); |
||
| 481 | } |
||
| 482 | |||
| 483 | $description = $anilistData['description'] ?? ''; |
||
| 484 | // Remove HTML tags from description |
||
| 485 | $description = strip_tags($description); |
||
| 486 | $description = html_entity_decode($description, ENT_QUOTES | ENT_HTML5, 'UTF-8'); |
||
| 487 | |||
| 488 | $rating = isset($anilistData['averageScore']) ? (string) $anilistData['averageScore'] : null; |
||
| 489 | |||
| 490 | $picture = null; |
||
| 491 | if (isset($anilistData['coverImage']['large'])) { |
||
| 492 | $picture = basename(parse_url($anilistData['coverImage']['large'], PHP_URL_PATH)); |
||
| 493 | } |
||
| 494 | |||
| 495 | $genres = ''; |
||
| 496 | if (isset($anilistData['genres']) && is_array($anilistData['genres'])) { |
||
| 497 | $genres = implode(', ', $anilistData['genres']); |
||
| 498 | } |
||
| 499 | |||
| 500 | $creators = ''; |
||
| 501 | if (isset($anilistData['studios']['nodes']) && is_array($anilistData['studios']['nodes'])) { |
||
| 502 | $studioNames = array_map(function ($studio) { |
||
| 503 | return $studio['name'] ?? ''; |
||
| 504 | }, $anilistData['studios']['nodes']); |
||
| 505 | $creators = implode(', ', array_filter($studioNames)); |
||
| 506 | } |
||
| 507 | |||
| 508 | $characters = ''; |
||
| 509 | if (isset($anilistData['characters']['nodes']) && is_array($anilistData['characters']['nodes'])) { |
||
| 510 | $characterNames = array_map(function ($character) { |
||
| 511 | return $character['name']['full'] ?? ''; |
||
| 512 | }, array_slice($anilistData['characters']['nodes'], 0, 10)); // Limit to 10 characters |
||
| 513 | $characters = implode(', ', array_filter($characterNames)); |
||
| 514 | } |
||
| 515 | |||
| 516 | $related = ''; |
||
| 517 | $similar = ''; |
||
| 518 | if (isset($anilistData['relations']['edges']) && is_array($anilistData['relations']['edges'])) { |
||
| 519 | $relatedItems = []; |
||
| 520 | $similarItems = []; |
||
| 521 | foreach ($anilistData['relations']['edges'] as $edge) { |
||
| 522 | $relationType = $edge['relationType'] ?? ''; |
||
| 523 | $title = $edge['node']['title']['english'] ?? $edge['node']['title']['romaji'] ?? ''; |
||
| 524 | $id = $edge['node']['id'] ?? ''; |
||
| 525 | |||
| 526 | if (in_array($relationType, ['SEQUEL', 'PREQUEL', 'SIDE_STORY', 'PARENT', 'SPIN_OFF'])) { |
||
| 527 | $relatedItems[] = $title.' ('.$id.')'; |
||
| 528 | } elseif (in_array($relationType, ['ALTERNATIVE', 'CHARACTER'])) { |
||
| 529 | $similarItems[] = $title.' ('.$id.')'; |
||
| 530 | } |
||
| 531 | } |
||
| 532 | $related = implode(', ', array_slice($relatedItems, 0, 20)); |
||
| 533 | $similar = implode(', ', array_slice($similarItems, 0, 20)); |
||
| 534 | } |
||
| 535 | |||
| 536 | $anilistId = $anilistData['id'] ?? null; |
||
| 537 | $malId = $anilistData['idMal'] ?? null; |
||
| 538 | $country = $anilistData['countryOfOrigin'] ?? null; |
||
| 539 | $mediaType = $anilistData['type'] ?? null; // ANIME or MANGA |
||
| 540 | $episodes = isset($anilistData['episodes']) ? (int) $anilistData['episodes'] : null; |
||
| 541 | $duration = isset($anilistData['duration']) ? (int) $anilistData['duration'] : null; |
||
| 542 | $status = $anilistData['status'] ?? null; |
||
| 543 | $source = $anilistData['source'] ?? null; |
||
| 544 | $hashtag = $anilistData['hashtag'] ?? null; |
||
| 545 | |||
| 546 | // Use updateOrInsert to handle race conditions atomically |
||
| 547 | // This prevents duplicate key errors when multiple processes try to insert the same record |
||
| 548 | $check = AnidbInfo::query()->where('anidbid', $anidbid)->first(); |
||
| 549 | |||
| 550 | $data = [ |
||
| 551 | 'anidbid' => $anidbid, |
||
| 552 | 'anilist_id' => $anilistId, |
||
| 553 | 'mal_id' => $malId, |
||
| 554 | 'country' => $country, |
||
| 555 | 'media_type' => $mediaType, |
||
| 556 | 'type' => $anilistData['format'] ?? null, |
||
| 557 | 'episodes' => $episodes, |
||
| 558 | 'duration' => $duration, |
||
| 559 | 'status' => $status, |
||
| 560 | 'source' => $source, |
||
| 561 | 'hashtag' => $hashtag, |
||
| 562 | 'startdate' => $startDate, |
||
| 563 | 'enddate' => $endDate, |
||
| 564 | 'description' => $description, |
||
| 565 | 'rating' => $rating, |
||
| 566 | 'picture' => $picture, |
||
| 567 | 'categories' => $genres, |
||
| 568 | 'characters' => $characters, |
||
| 569 | 'creators' => $creators, |
||
| 570 | 'related' => $related, |
||
| 571 | 'similar' => $similar, |
||
| 572 | 'updated' => now(), |
||
| 573 | ]; |
||
| 574 | |||
| 575 | // If record exists, preserve existing values for null fields |
||
| 576 | if ($check !== null) { |
||
| 577 | $data['anilist_id'] = $anilistId ?? $check->anilist_id; |
||
| 578 | $data['mal_id'] = $malId ?? $check->mal_id; |
||
| 579 | $data['country'] = $country ?? $check->country; |
||
| 580 | $data['media_type'] = $mediaType ?? $check->media_type; |
||
| 581 | $data['type'] = $anilistData['format'] ?? $anilistData['type'] ?? $check->type; |
||
| 582 | $data['episodes'] = $episodes ?? $check->episodes; |
||
| 583 | $data['duration'] = $duration ?? $check->duration; |
||
| 584 | $data['status'] = $status ?? $check->status; |
||
| 585 | $data['source'] = $source ?? $check->source; |
||
| 586 | $data['hashtag'] = $hashtag ?? $check->hashtag; |
||
| 587 | $data['startdate'] = $startDate ?? $check->startdate; |
||
| 588 | $data['enddate'] = $endDate ?? $check->enddate; |
||
| 589 | $data['description'] = $description ?: $check->description; |
||
| 590 | $data['rating'] = $rating ?? $check->rating; |
||
| 591 | $data['picture'] = $picture ?? $check->picture; |
||
| 592 | $data['categories'] = $genres ?: $check->categories; |
||
| 593 | $data['characters'] = $characters ?: $check->characters; |
||
| 594 | $data['creators'] = $creators ?: $check->creators; |
||
| 595 | $data['related'] = $related ?: $check->related; |
||
| 596 | $data['similar'] = $similar ?: $check->similar; |
||
| 597 | } |
||
| 598 | |||
| 599 | // Use updateOrInsert to atomically handle insert/update (prevents race conditions) |
||
| 600 | AnidbInfo::query()->updateOrInsert( |
||
| 601 | ['anidbid' => $anidbid], |
||
| 602 | $data |
||
| 603 | ); |
||
| 604 | |||
| 605 | // Insert titles |
||
| 606 | if (isset($anilistData['title']['romaji']) && ! empty($anilistData['title']['romaji'])) { |
||
| 607 | $this->insertAniListTitle($anidbid, 'main', 'x-jat', $anilistData['title']['romaji']); |
||
| 608 | } |
||
| 609 | if (isset($anilistData['title']['english']) && ! empty($anilistData['title']['english'])) { |
||
| 610 | $this->insertAniListTitle($anidbid, 'official', 'en', $anilistData['title']['english']); |
||
| 611 | } |
||
| 612 | if (isset($anilistData['title']['native']) && ! empty($anilistData['title']['native'])) { |
||
| 613 | $this->insertAniListTitle($anidbid, 'main', 'ja', $anilistData['title']['native']); |
||
| 614 | } |
||
| 615 | |||
| 616 | // Download cover image if available |
||
| 617 | if (! empty($picture) && isset($anilistData['coverImage']['large'])) { |
||
| 618 | $this->downloadCoverImage($anidbid, $anilistData['coverImage']['large']); |
||
| 619 | } |
||
| 620 | } |
||
| 621 | |||
| 622 | /** |
||
| 623 | * Download cover image from AniList. |
||
| 624 | */ |
||
| 625 | private function downloadCoverImage(int $anidbid, string $imageUrl): void |
||
| 667 | } |
||
| 668 | } |
||
| 669 | } |
||
| 670 | |||
| 671 | /** |
||
| 672 | * Directs flow for populating the AniList Info table. |
||
| 673 | * |
||
| 674 | * @throws \Exception |
||
| 675 | */ |
||
| 676 | private function populateInfoTable(string $anilistId = ''): void |
||
| 730 | } |
||
| 731 | } |
||
| 734 |