Passed
Push — master ( 14af2f...574fad )
by Darko
10:47
created

TraktService::getCalendar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 2
nc 2
nop 2
1
<?php
2
3
namespace App\Services;
4
5
use Illuminate\Support\Facades\Cache;
6
use Illuminate\Support\Facades\Http;
7
use Illuminate\Support\Facades\Log;
8
use Illuminate\Support\Str;
9
10
/**
11
 * Trakt.tv Service
12
 *
13
 * A modern service wrapper for the Trakt.tv API.
14
 * Provides methods to fetch movie and TV show information.
15
 *
16
 * API Documentation: https://trakt.docs.apiary.io/
17
 */
18
class TraktService
19
{
20
    protected const BASE_URL = 'https://api.trakt.tv';
21
22
    protected const CACHE_TTL_HOURS = 24;
23
24
    protected const API_VERSION = 2;
25
26
    protected const ID_TYPES = ['imdb', 'tmdb', 'trakt', 'tvdb'];
27
28
    protected string $clientId;
29
30
    protected int $timeout;
31
32
    protected int $retryTimes;
33
34
    protected int $retryDelay;
35
36
    public function __construct(?string $clientId = null)
37
    {
38
        $this->clientId = $clientId ?? (string) config('nntmux_api.trakttv_api_key', '');
39
        $this->timeout = (int) config('nntmux_api.trakttv_timeout', 30);
40
        $this->retryTimes = (int) config('nntmux_api.trakttv_retry_times', 3);
41
        $this->retryDelay = (int) config('nntmux_api.trakttv_retry_delay', 100);
42
    }
43
44
    /**
45
     * Check if the API key is configured.
46
     */
47
    public function isConfigured(): bool
48
    {
49
        return ! empty($this->clientId);
50
    }
51
52
    /**
53
     * Get the request headers for Trakt API.
54
     */
55
    protected function getHeaders(): array
56
    {
57
        return [
58
            'Content-Type' => 'application/json',
59
            'trakt-api-version' => self::API_VERSION,
60
            'trakt-api-key' => $this->clientId,
61
        ];
62
    }
63
64
    /**
65
     * Make a GET request to the Trakt API.
66
     *
67
     * @param  string  $endpoint  The API endpoint
68
     * @param  array  $params  Query parameters
69
     * @return array|null Response data or null on failure
70
     */
71
    protected function get(string $endpoint, array $params = []): ?array
72
    {
73
        if (! $this->isConfigured()) {
74
            Log::debug('Trakt API key is not configured');
75
76
            return null;
77
        }
78
79
        $url = self::BASE_URL.'/'.ltrim($endpoint, '/');
80
81
        try {
82
            $response = Http::timeout($this->timeout)
83
                ->retry($this->retryTimes, $this->retryDelay, function (\Exception $exception, $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

83
                ->retry($this->retryTimes, $this->retryDelay, function (\Exception $exception, /** @scrutinizer ignore-unused */ $request) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
                    // Don't retry on 404 errors - resource simply doesn't exist
85
                    if ($exception instanceof \Illuminate\Http\Client\RequestException) {
86
                        return $exception->response->status() !== 404;
87
                    }
88
89
                    return true;
90
                }, throw: false)
91
                ->withHeaders($this->getHeaders())
92
                ->get($url, $params);
93
94
            if ($response->successful()) {
0 ignored issues
show
Bug introduced by
The method successful() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

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

94
            if ($response->/** @scrutinizer ignore-call */ successful()) {

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...
95
                $data = $response->json();
0 ignored issues
show
Bug introduced by
The method json() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

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

95
                /** @scrutinizer ignore-call */ 
96
                $data = $response->json();

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...
96
97
                // Check for API error responses
98
                if (isset($data['status']) && $data['status'] === 'failure') {
99
                    Log::debug('Trakt API returned failure status', [
100
                        'endpoint' => $endpoint,
101
                    ]);
102
103
                    return null;
104
                }
105
106
                return $data;
107
            }
108
109
            // Handle specific error codes - 404 is normal (resource not found)
110
            if ($response->status() === 404) {
0 ignored issues
show
Bug introduced by
The method status() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

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

110
            if ($response->/** @scrutinizer ignore-call */ status() === 404) {

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...
111
                Log::debug('Trakt: Resource not found', ['endpoint' => $endpoint]);
112
113
                return null;
114
            }
115
116
            Log::warning('Trakt API request failed', [
117
                'endpoint' => $endpoint,
118
                'status' => $response->status(),
119
                'body' => $response->body(),
0 ignored issues
show
Bug introduced by
The method body() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

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

119
                'body' => $response->/** @scrutinizer ignore-call */ body(),

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...
120
            ]);
121
122
            return null;
123
        } catch (\Throwable $e) {
124
            // Check if this is a 404 wrapped in an exception
125
            if ($e instanceof \Illuminate\Http\Client\RequestException && $e->response->status() === 404) {
126
                Log::debug('Trakt: Resource not found', ['endpoint' => $endpoint]);
127
128
                return null;
129
            }
130
131
            Log::warning('Trakt API request exception', [
132
                'endpoint' => $endpoint,
133
                'message' => $e->getMessage(),
134
            ]);
135
136
            return null;
137
        }
138
    }
139
140
    /**
141
     * Get episode summary using multiple ID types for fallback.
142
     *
143
     * @param  int|string  $showId  The show ID (Trakt, IMDB, TMDB, or TVDB)
144
     * @param  int|string  $season  The season number
145
     * @param  int|string  $episode  The episode number
146
     * @param  string  $extended  Extended info level: 'min', 'full', 'aliases', 'full,aliases'
147
     * @param  string  $idType  The ID type: 'trakt', 'imdb', 'tmdb', 'tvdb' (default: 'trakt')
148
     * @return array|null Episode data or null on failure
149
     */
150
    public function getEpisodeSummary(
151
        int|string $showId,
152
        int|string $season,
153
        int|string $episode,
154
        string $extended = 'min',
155
        string $idType = 'trakt'
156
    ): ?array {
157
        // Validate parameters to avoid unnecessary API calls with invalid IDs
158
        $showIdInt = is_numeric($showId) ? (int) $showId : 0;
159
        $seasonInt = is_numeric($season) ? (int) $season : -1;
160
        $episodeInt = is_numeric($episode) ? (int) $episode : -1;
161
162
        // For numeric show IDs, validate they are positive
163
        // Season and episode must be non-negative (season 0 can be specials)
164
        if (($showIdInt <= 0 && is_numeric($showId)) || $seasonInt < 0 || $episodeInt < 0) {
165
            return null;
166
        }
167
168
        $extended = match ($extended) {
169
            'aliases', 'full', 'full,aliases' => $extended,
170
            default => 'min',
171
        };
172
173
        // Format the show ID based on type for cache key and API call
174
        $formattedId = $this->formatShowIdForApi($showId, $idType);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $formattedId is correct as $this->formatShowIdForApi($showId, $idType) targeting App\Services\TraktService::formatShowIdForApi() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
175
        if ($formattedId === null) {
0 ignored issues
show
introduced by
The condition $formattedId === null is always true.
Loading history...
176
            return null;
177
        }
178
179
        $cacheKey = "trakt_episode_{$idType}_{$showId}_{$season}_{$episode}_{$extended}";
180
181
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($formattedId, $season, $episode, $extended) {
182
            return $this->get("shows/{$formattedId}/seasons/{$season}/episodes/{$episode}", [
183
                'extended' => $extended,
184
            ]);
185
        });
186
    }
187
188
    /**
189
     * Get episode summary with fallback to multiple ID types.
190
     * Tries Trakt ID first, then falls back to TMDB, TVDB, and IMDB.
191
     *
192
     * @param  array  $ids  Array of IDs: ['trakt' => X, 'tmdb' => Y, 'tvdb' => Z, 'imdb' => W]
193
     * @param  int|string  $season  The season number
194
     * @param  int|string  $episode  The episode number
195
     * @param  string  $extended  Extended info level
196
     * @return array|null Episode data or null on failure
197
     */
198
    public function getEpisodeSummaryWithFallback(
199
        array $ids,
200
        int|string $season,
201
        int|string $episode,
202
        string $extended = 'min'
203
    ): ?array {
204
        // Priority order for ID types
205
        $idPriority = ['trakt', 'tmdb', 'tvdb', 'imdb'];
206
207
        foreach ($idPriority as $idType) {
208
            if (! empty($ids[$idType]) && $ids[$idType] > 0) {
209
                $result = $this->getEpisodeSummary($ids[$idType], $season, $episode, $extended, $idType);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->getEpisodeSummary...de, $extended, $idType) targeting App\Services\TraktService::getEpisodeSummary() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
210
                if ($result !== null) {
211
                    return $result;
212
                }
213
            }
214
        }
215
216
        return null;
217
    }
218
219
    /**
220
     * Format show ID for Trakt API based on ID type.
221
     * Trakt API accepts different ID formats for lookups.
222
     *
223
     * @param  int|string  $id  The show ID
224
     * @param  string  $idType  The ID type: 'trakt', 'imdb', 'tmdb', 'tvdb'
225
     * @return string|null Formatted ID for API call or null if invalid
226
     */
227
    protected function formatShowIdForApi(int|string $id, string $idType): ?string
228
    {
229
        if (! in_array($idType, self::ID_TYPES, true)) {
230
            return null;
231
        }
232
233
        return match ($idType) {
234
            'imdb' => is_numeric($id) ? 'tt' . str_pad((string) $id, 7, '0', STR_PAD_LEFT) : (string) $id,
235
            'trakt' => (string) $id,
236
            'tmdb', 'tvdb' => (string) $id,
237
            default => null,
238
        };
239
    }
240
241
    /**
242
     * Get show summary with all external IDs.
243
     * This enriches the response with IDs from all supported providers.
244
     *
245
     * @param  int|string  $showId  Show ID
246
     * @param  string  $idType  The ID type: 'trakt', 'imdb', 'tmdb', 'tvdb'
247
     * @return array|null Show data with all IDs or null on failure
248
     */
249
    public function getShowWithAllIds(int|string $showId, string $idType = 'trakt'): ?array
250
    {
251
        $formattedId = $this->formatShowIdForApi($showId, $idType);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $formattedId is correct as $this->formatShowIdForApi($showId, $idType) targeting App\Services\TraktService::formatShowIdForApi() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
252
        if ($formattedId === null) {
0 ignored issues
show
introduced by
The condition $formattedId === null is always true.
Loading history...
253
            return null;
254
        }
255
256
        $cacheKey = "trakt_show_ids_{$idType}_{$showId}";
257
258
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($formattedId) {
259
            return $this->get("shows/{$formattedId}", ['extended' => 'full']);
260
        });
261
    }
262
263
    /**
264
     * Look up a show by external ID and return all available IDs.
265
     * Useful for cross-referencing between TMDB, TVDB, Trakt, and IMDB.
266
     *
267
     * @param  int|string  $id  The external ID
268
     * @param  string  $idType  The ID type: 'imdb', 'tmdb', 'tvdb'
269
     * @return array|null Array with all IDs: ['trakt' => X, 'imdb' => Y, 'tmdb' => Z, 'tvdb' => W] or null
270
     */
271
    public function lookupShowIds(int|string $id, string $idType = 'tmdb'): ?array
272
    {
273
        if (! in_array($idType, self::ID_TYPES, true)) {
274
            return null;
275
        }
276
277
        // Use the search endpoint to find the show by external ID
278
        $results = $this->searchById($id, $idType, 'show');
279
280
        if (empty($results) || ! is_array($results)) {
281
            return null;
282
        }
283
284
        // Get the first matching show
285
        $show = $results[0]['show'] ?? null;
286
        if ($show === null || ! isset($show['ids'])) {
287
            return null;
288
        }
289
290
        return [
291
            'trakt' => $show['ids']['trakt'] ?? 0,
292
            'imdb' => $show['ids']['imdb'] ?? null,
293
            'tmdb' => $show['ids']['tmdb'] ?? 0,
294
            'tvdb' => $show['ids']['tvdb'] ?? 0,
295
            'tvrage' => $show['ids']['tvrage'] ?? 0,
296
            'slug' => $show['ids']['slug'] ?? '',
297
        ];
298
    }
299
300
    /**
301
     * Get current box office movies.
302
     *
303
     * @return array|null Box office data or null on failure
304
     */
305
    public function getBoxOffice(): ?array
306
    {
307
        $cacheKey = 'trakt_boxoffice_'.date('Y-m-d');
308
309
        return Cache::remember($cacheKey, now()->addHours(6), function () {
310
            return $this->get('movies/boxoffice');
311
        });
312
    }
313
314
    /**
315
     * Get TV show calendar.
316
     *
317
     * @param  string  $startDate  Start date (YYYY-MM-DD format, defaults to today)
318
     * @param  int  $days  Number of days to retrieve (default: 7)
319
     * @return array|null Calendar data or null on failure
320
     */
321
    public function getCalendar(string $startDate = '', int $days = 7): ?array
322
    {
323
        if (empty($startDate)) {
324
            $startDate = date('Y-m-d');
325
        }
326
327
        $cacheKey = "trakt_calendar_{$startDate}_{$days}";
328
329
        return Cache::remember($cacheKey, now()->addHours(6), function () use ($startDate, $days) {
330
            return $this->get("calendars/all/shows/{$startDate}/{$days}");
331
        });
332
    }
333
334
    /**
335
     * Get movie summary.
336
     *
337
     * @param  string  $movie  Movie slug or ID
338
     * @param  string  $extended  Extended info level: 'min' or 'full'
339
     * @return array|null Movie data or null on failure
340
     */
341
    public function getMovieSummary(string $movie, string $extended = 'min'): ?array
342
    {
343
        if (empty($movie)) {
344
            return null;
345
        }
346
347
        $extended = $extended === 'full' ? 'full' : 'min';
348
        $slug = Str::slug($movie);
349
        $cacheKey = "trakt_movie_{$slug}_{$extended}";
350
351
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($slug, $extended) {
352
            return $this->get("movies/{$slug}", ['extended' => $extended]);
353
        });
354
    }
355
356
    /**
357
     * Get IMDB ID for a movie.
358
     *
359
     * @param  string  $movie  Movie slug or ID
360
     * @return string|null IMDB ID or null on failure
361
     */
362
    public function getMovieImdbId(string $movie): ?string
363
    {
364
        $data = $this->getMovieSummary($movie, 'min');
365
366
        return $data['ids']['imdb'] ?? null;
367
    }
368
369
    /**
370
     * Search by external ID.
371
     *
372
     * @param  int|string  $id  The ID to search for
373
     * @param  string  $idType  The ID type: 'imdb', 'tmdb', 'trakt', 'tvdb'
374
     * @param  string  $mediaType  Media type: 'movie', 'show', 'episode', or empty for all
375
     * @return array|null Search results or null on failure
376
     */
377
    public function searchById(int|string $id, string $idType = 'trakt', string $mediaType = ''): ?array
378
    {
379
        if (! in_array($idType, self::ID_TYPES, true)) {
380
            Log::warning('Trakt: Invalid ID type', ['idType' => $idType]);
381
382
            return null;
383
        }
384
385
        // Format IMDB ID with 'tt' prefix if needed
386
        if ($idType === 'imdb' && is_numeric($id)) {
387
            $id = 'tt'.$id;
388
        }
389
390
        $params = [
391
            'id_type' => $idType,
392
            'id' => $id,
393
        ];
394
395
        if (! empty($mediaType)) {
396
            $params['type'] = $mediaType;
397
        }
398
399
        // Don't cache ID searches as they're typically one-off lookups
400
        return $this->get('search/'.$idType.'/'.$id, ['type' => $mediaType ?: null]);
401
    }
402
403
    /**
404
     * Search for a show by name.
405
     *
406
     * @param  string  $query  Search query
407
     * @param  string  $type  Search type: 'show', 'movie', 'episode', 'person', 'list'
408
     * @return array|null Search results or null on failure
409
     */
410
    public function searchShows(string $query, string $type = 'show'): ?array
411
    {
412
        if (empty($query)) {
413
            return null;
414
        }
415
416
        $slug = Str::slug($query);
417
        $cacheKey = "trakt_search_{$type}_{$slug}";
418
419
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($query, $type) {
420
            return $this->get('search/'.$type, ['query' => $query]);
421
        });
422
    }
423
424
    /**
425
     * Get show summary.
426
     *
427
     * @param  string  $show  Show slug or ID
428
     * @param  string  $extended  Extended info level: 'min' or 'full'
429
     * @return array|null Show data or null on failure
430
     */
431
    public function getShowSummary(string $show, string $extended = 'full'): ?array
432
    {
433
        if (empty($show)) {
434
            return null;
435
        }
436
437
        $extended = $extended === 'full' ? 'full' : 'min';
438
        $slug = Str::slug($show);
439
        $cacheKey = "trakt_show_{$slug}_{$extended}";
440
441
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($slug, $extended) {
442
            return $this->get("shows/{$slug}", ['extended' => $extended]);
443
        });
444
    }
445
446
    /**
447
     * Get show seasons.
448
     *
449
     * @param  string  $show  Show slug or ID
450
     * @param  string  $extended  Extended info level
451
     * @return array|null Seasons data or null on failure
452
     */
453
    public function getShowSeasons(string $show, string $extended = 'full'): ?array
454
    {
455
        if (empty($show)) {
456
            return null;
457
        }
458
459
        $slug = Str::slug($show);
460
        $cacheKey = "trakt_seasons_{$slug}_{$extended}";
461
462
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($slug, $extended) {
463
            return $this->get("shows/{$slug}/seasons", ['extended' => $extended]);
464
        });
465
    }
466
467
    /**
468
     * Get season episodes.
469
     *
470
     * @param  string  $show  Show slug or ID
471
     * @param  int  $season  Season number
472
     * @param  string  $extended  Extended info level
473
     * @return array|null Episodes data or null on failure
474
     */
475
    public function getSeasonEpisodes(string $show, int $season, string $extended = 'full'): ?array
476
    {
477
        if (empty($show)) {
478
            return null;
479
        }
480
481
        $slug = Str::slug($show);
482
        $cacheKey = "trakt_season_episodes_{$slug}_{$season}_{$extended}";
483
484
        return Cache::remember($cacheKey, now()->addHours(self::CACHE_TTL_HOURS), function () use ($slug, $season, $extended) {
485
            return $this->get("shows/{$slug}/seasons/{$season}", ['extended' => $extended]);
486
        });
487
    }
488
489
    /**
490
     * Get trending shows.
491
     *
492
     * @param  int  $limit  Number of results to return
493
     * @param  string  $extended  Extended info level
494
     * @return array|null Trending shows or null on failure
495
     */
496
    public function getTrendingShows(int $limit = 10, string $extended = 'full'): ?array
497
    {
498
        $cacheKey = "trakt_trending_shows_{$limit}_{$extended}";
499
500
        return Cache::remember($cacheKey, now()->addHours(1), function () use ($limit, $extended) {
501
            return $this->get('shows/trending', [
502
                'limit' => $limit,
503
                'extended' => $extended,
504
            ]);
505
        });
506
    }
507
508
    /**
509
     * Get trending movies.
510
     *
511
     * @param  int  $limit  Number of results to return
512
     * @param  string  $extended  Extended info level
513
     * @return array|null Trending movies or null on failure
514
     */
515
    public function getTrendingMovies(int $limit = 10, string $extended = 'full'): ?array
516
    {
517
        $cacheKey = "trakt_trending_movies_{$limit}_{$extended}";
518
519
        return Cache::remember($cacheKey, now()->addHours(1), function () use ($limit, $extended) {
520
            return $this->get('movies/trending', [
521
                'limit' => $limit,
522
                'extended' => $extended,
523
            ]);
524
        });
525
    }
526
527
    /**
528
     * Get popular shows.
529
     *
530
     * @param  int  $limit  Number of results to return
531
     * @param  string  $extended  Extended info level
532
     * @return array|null Popular shows or null on failure
533
     */
534
    public function getPopularShows(int $limit = 10, string $extended = 'full'): ?array
535
    {
536
        $cacheKey = "trakt_popular_shows_{$limit}_{$extended}";
537
538
        return Cache::remember($cacheKey, now()->addHours(6), function () use ($limit, $extended) {
539
            return $this->get('shows/popular', [
540
                'limit' => $limit,
541
                'extended' => $extended,
542
            ]);
543
        });
544
    }
545
546
    /**
547
     * Get popular movies.
548
     *
549
     * @param  int  $limit  Number of results to return
550
     * @param  string  $extended  Extended info level
551
     * @return array|null Popular movies or null on failure
552
     */
553
    public function getPopularMovies(int $limit = 10, string $extended = 'full'): ?array
554
    {
555
        $cacheKey = "trakt_popular_movies_{$limit}_{$extended}";
556
557
        return Cache::remember($cacheKey, now()->addHours(6), function () use ($limit, $extended) {
558
            return $this->get('movies/popular', [
559
                'limit' => $limit,
560
                'extended' => $extended,
561
            ]);
562
        });
563
    }
564
565
    /**
566
     * Clear cached data for a specific show.
567
     */
568
    public function clearShowCache(string $show): void
569
    {
570
        $slug = Str::slug($show);
571
        Cache::forget("trakt_show_{$slug}_min");
572
        Cache::forget("trakt_show_{$slug}_full");
573
        Cache::forget("trakt_seasons_{$slug}_full");
574
        Cache::forget("trakt_seasons_{$slug}_min");
575
    }
576
577
    /**
578
     * Clear cached data for a specific movie.
579
     */
580
    public function clearMovieCache(string $movie): void
581
    {
582
        $slug = Str::slug($movie);
583
        Cache::forget("trakt_movie_{$slug}_min");
584
        Cache::forget("trakt_movie_{$slug}_full");
585
    }
586
}
587
588