Passed
Push — master ( 8b334f...d95312 )
by Darko
11:24
created

TraktPipe::process()   F

Complexity

Conditions 33
Paths 4584

Size

Total Lines 134
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 72
c 2
b 0
f 0
dl 0
loc 134
rs 0
cc 33
nc 4584
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Services\TvProcessing\Pipes;
4
5
use App\Services\TvProcessing\TvProcessingPassable;
6
use App\Services\TvProcessing\TvProcessingResult;
7
use App\Services\TvProcessing\Providers\TraktProvider;
8
9
/**
10
 * Pipe for Trakt.tv API lookups.
11
 */
12
class TraktPipe extends AbstractTvProviderPipe
13
{
14
    // Video type and source constants (matching Videos class protected constants)
15
    private const TYPE_TV = 0;
16
    private const SOURCE_TRAKT = 5;
17
18
    protected int $priority = 50;
19
    private ?TraktProvider $trakt = null;
20
21
    public function getName(): string
22
    {
23
        return 'Trakt';
24
    }
25
26
    public function getStatusCode(): int
27
    {
28
        return -3; // PROCESS_TRAKT
29
    }
30
31
    /**
32
     * Get or create the Trakt instance.
33
     */
34
    private function getTrakt(): TraktProvider
35
    {
36
        if ($this->trakt === null) {
37
            $this->trakt = new TraktProvider();
38
        }
39
        return $this->trakt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->trakt could return the type null which is incompatible with the type-hinted return App\Services\TvProcessing\Providers\TraktProvider. Consider adding an additional type-check to rule them out.
Loading history...
40
    }
41
42
    protected function process(TvProcessingPassable $passable): TvProcessingResult
43
    {
44
        $parsedInfo = $passable->getParsedInfo();
45
        $context = $passable->context;
46
47
        if ($parsedInfo === null || empty($parsedInfo['cleanname'])) {
48
            return TvProcessingResult::notFound($this->getName());
49
        }
50
51
        $cleanName = $parsedInfo['cleanname'];
52
53
        // Check if we've already failed this title
54
        if ($this->isInTitleCache($cleanName)) {
55
            $this->outputSkipped($cleanName);
56
            return TvProcessingResult::skipped('previously failed', $this->getName());
57
        }
58
59
        $trakt = $this->getTrakt();
60
        $siteId = false;
61
62
        // Find the Video ID if it already exists
63
        $videoId = $trakt->getByTitle($cleanName, self::TYPE_TV, self::SOURCE_TRAKT);
64
65
        // If not found and cleanName contains a year in parentheses, try without the year
66
        if ($videoId === 0 && preg_match('/^(.+?)\s*\(\d{4}\)$/', $cleanName, $yearMatch)) {
67
            $nameWithoutYear = trim($yearMatch[1]);
68
            $videoId = $trakt->getByTitle($nameWithoutYear, self::TYPE_TV, self::SOURCE_TRAKT);
69
        }
70
71
        if ($videoId !== 0) {
72
            $siteId = $trakt->getSiteIDFromVideoID('trakt', $videoId);
73
            // If show exists in local DB but doesn't have a Trakt ID, use the existing video
74
            // and process episode matching without trying to search Trakt API
75
            if ($siteId === false || $siteId === 0) {
76
                // Show exists in our DB (likely from another source like TMDB)
77
                // Skip Trakt API search and proceed to episode matching
78
                $this->outputFoundInDb($cleanName);
79
                return $this->processEpisodeForExistingVideo($passable, $trakt, $videoId, $parsedInfo);
80
            }
81
        }
82
83
        if ($videoId === 0) {
84
            // Not in local DB, search Trakt
85
            $this->outputSearching($cleanName);
86
87
            $traktShow = $trakt->getShowInfo((string) $cleanName);
88
89
            // If not found and cleanName contains a year in parentheses, try without the year
90
            if ($traktShow === false && preg_match('/^(.+?)\s*\(\d{4}\)$/', $cleanName, $yearMatch)) {
0 ignored issues
show
introduced by
The condition $traktShow === false is always false.
Loading history...
91
                $nameWithoutYear = trim($yearMatch[1]);
92
                $traktShow = $trakt->getShowInfo($nameWithoutYear);
93
            }
94
95
            if (is_array($traktShow)) {
0 ignored issues
show
introduced by
The condition is_array($traktShow) is always true.
Loading history...
96
                $videoId = $trakt->add($traktShow);
97
                $siteId = (int) $traktShow['trakt'];
98
            }
99
        } else {
100
            $this->outputFoundInDb($cleanName);
101
        }
102
103
        if ((int) $videoId === 0 || (int) $siteId === 0) {
104
            // Show not found
105
            $this->addToTitleCache($cleanName);
106
            $this->outputNotFound($cleanName);
107
            return TvProcessingResult::notFound($this->getName(), ['title' => $cleanName]);
108
        }
109
110
        // Process episode
111
        $seriesNo = ! empty($parsedInfo['season']) ? preg_replace('/^S0*/i', '', (string) $parsedInfo['season']) : '';
112
        $episodeNo = ! empty($parsedInfo['episode']) ? preg_replace('/^E0*/i', '', (string) $parsedInfo['episode']) : '';
113
        $hasAirdate = ! empty($parsedInfo['airdate']);
114
115
        if ($episodeNo === 'all') {
116
            // Full season release
117
            $trakt->setVideoIdFound($videoId, $context->releaseId, 0);
118
            $this->outputFullSeason($cleanName);
119
            return TvProcessingResult::matched($videoId, 0, $this->getName(), ['full_season' => true]);
120
        }
121
122
        // Download all episodes if new show to reduce API/bandwidth usage
123
        if (! $trakt->countEpsByVideoID($videoId)) {
124
            $trakt->getEpisodeInfo($siteId, -1, -1);
0 ignored issues
show
Bug introduced by
It seems like $siteId can also be of type false; however, parameter $siteId of App\Services\TvProcessin...vider::getEpisodeInfo() does only seem to accept integer|string, 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

124
            $trakt->getEpisodeInfo(/** @scrutinizer ignore-type */ $siteId, -1, -1);
Loading history...
125
        }
126
127
        // Check if we have the episode for this video ID
128
        $episode = $trakt->getBySeasonEp($videoId, $seriesNo, $episodeNo, $parsedInfo['airdate'] ?? '');
129
130
        if ($episode === false) {
131
            if ($seriesNo !== '' && $episodeNo !== '') {
132
                // Try to get episode from Trakt
133
                $traktEpisode = $trakt->getEpisodeInfo($siteId, (int) $seriesNo, (int) $episodeNo);
134
135
                if ($traktEpisode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $traktEpisode of type array<string,integer|mixed|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$traktEpisode is a non-empty array, thus is always true.
Loading history...
136
                    $episode = $trakt->addEpisode($videoId, $traktEpisode);
137
                }
138
            }
139
140
            if ($episode === false && $hasAirdate) {
141
                // Refresh episode cache and attempt airdate match
142
                $trakt->getEpisodeInfo($siteId, -1, -1);
143
                $episode = $trakt->getBySeasonEp($videoId, 0, 0, $parsedInfo['airdate']);
144
            }
145
        }
146
147
        if ($episode !== false && is_numeric($episode) && $episode > 0) {
148
            // Success!
149
            $trakt->setVideoIdFound($videoId, $context->releaseId, $episode);
150
            $this->outputMatch(
151
                $cleanName,
152
                $seriesNo !== '' ? (int) $seriesNo : null,
153
                $episodeNo !== '' ? (int) $episodeNo : null,
154
                $hasAirdate ? $parsedInfo['airdate'] : null
155
            );
156
            return TvProcessingResult::matched($videoId, (int) $episode, $this->getName());
157
        }
158
159
        // Episode not found
160
        $trakt->setVideoIdFound($videoId, $context->releaseId, 0);
161
162
        if ($this->echoOutput) {
163
            $this->colorCli->primaryOver('    → ');
164
            $this->colorCli->alternateOver($this->truncateTitle($cleanName));
165
            if ($hasAirdate) {
166
                $this->colorCli->primaryOver(' | ');
167
                $this->colorCli->warningOver($parsedInfo['airdate']);
168
            }
169
            $this->colorCli->primaryOver(' → ');
170
            $this->colorCli->warning('Episode not found');
171
        }
172
173
        return TvProcessingResult::notFound($this->getName(), [
174
            'video_id' => $videoId,
175
            'episode_not_found' => true,
176
        ]);
177
    }
178
179
    /**
180
     * Output full season match message.
181
     */
182
    private function outputFullSeason(string $title): void
183
    {
184
        if (! $this->echoOutput) {
185
            return;
186
        }
187
188
        $this->colorCli->primaryOver('    → ');
189
        $this->colorCli->headerOver($this->truncateTitle($title));
190
        $this->colorCli->primaryOver(' → ');
191
        $this->colorCli->primary('Full Season matched');
192
    }
193
194
    /**
195
     * Process episode matching for a video that already exists in local DB.
196
     * This is used when the show was added from another source (e.g., TMDB) and doesn't have a Trakt ID.
197
     */
198
    private function processEpisodeForExistingVideo(
199
        TvProcessingPassable $passable,
200
        TraktProvider $trakt,
201
        int $videoId,
202
        array $parsedInfo
203
    ): TvProcessingResult {
204
        $context = $passable->context;
205
        $cleanName = $parsedInfo['cleanname'];
206
207
        $seriesNo = ! empty($parsedInfo['season']) ? preg_replace('/^S0*/i', '', (string) $parsedInfo['season']) : '';
208
        $episodeNo = ! empty($parsedInfo['episode']) ? preg_replace('/^E0*/i', '', (string) $parsedInfo['episode']) : '';
209
        $hasAirdate = ! empty($parsedInfo['airdate']);
210
211
        if ($episodeNo === 'all') {
212
            // Full season release
213
            $trakt->setVideoIdFound($videoId, $context->releaseId, 0);
214
            $this->outputFullSeason($cleanName);
215
            return TvProcessingResult::matched($videoId, 0, $this->getName(), ['full_season' => true]);
216
        }
217
218
        // Try to find episode in local DB
219
        $episode = $trakt->getBySeasonEp($videoId, $seriesNo, $episodeNo, $parsedInfo['airdate'] ?? '');
220
221
        if ($episode !== false && is_numeric($episode) && $episode > 0) {
222
            $trakt->setVideoIdFound($videoId, $context->releaseId, $episode);
223
            $this->outputMatch(
224
                $cleanName,
225
                $seriesNo !== '' ? (int) $seriesNo : null,
226
                $episodeNo !== '' ? (int) $episodeNo : null,
227
                $hasAirdate ? $parsedInfo['airdate'] : null
228
            );
229
            return TvProcessingResult::matched($videoId, (int) $episode, $this->getName());
230
        }
231
232
        // Episode not found in local DB - mark video but episode not matched
233
        $trakt->setVideoIdFound($videoId, $context->releaseId, 0);
234
235
        if ($this->echoOutput) {
236
            $this->colorCli->primaryOver('    → ');
237
            $this->colorCli->alternateOver($this->truncateTitle($cleanName));
238
            if ($hasAirdate) {
239
                $this->colorCli->primaryOver(' | ');
240
                $this->colorCli->warningOver($parsedInfo['airdate']);
241
            }
242
            $this->colorCli->primaryOver(' → ');
243
            $this->colorCli->warning('Episode not in local DB');
244
        }
245
246
        return TvProcessingResult::notFound($this->getName(), [
247
            'video_id' => $videoId,
248
            'episode_not_found' => true,
249
        ]);
250
    }
251
}
252