Completed
Branch dev (4bcb34)
by Darko
13:52
created

TVDB::processSite()   F

Complexity

Conditions 35
Paths 11098

Size

Total Lines 127
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 35
eloc 72
nc 11098
nop 4
dl 0
loc 127
rs 2
c 0
b 0
f 0

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 Blacklight\processing\tv;
4
5
use Blacklight\ColorCLI;
6
use Adrenth\Thetvdb\Client;
7
use Blacklight\ReleaseImage;
8
use Adrenth\Thetvdb\Exception\UnauthorizedException;
9
use Adrenth\Thetvdb\Exception\CouldNotLoginException;
10
use Adrenth\Thetvdb\Exception\RequestFailedException;
11
use Adrenth\Thetvdb\Exception\InvalidArgumentException;
12
use Adrenth\Thetvdb\Exception\InvalidJsonInResponseException;
13
14
/**
15
 * Class TVDB -- functions used to post process releases against TVDB.
16
 */
17
class TVDB extends TV
18
{
19
    private const TVDB_URL = 'https://api.thetvdb.com';
20
    private const TVDB_API_KEY = '31740C28BAC74DEF';
21
    private const MATCH_PROBABILITY = 75;
22
23
    /**
24
     * @var \Adrenth\Thetvdb\Client
25
     */
26
    public $client;
27
28
    /**
29
     * @var string Authorization token for TVDB v2 API
30
     */
31
    public $token;
32
33
    /**
34
     * @string URL for show poster art
35
     */
36
    public $posterUrl;
37
38
    /**
39
     * @var string URL for show fanart
40
     */
41
    public $fanartUrl;
42
43
    /**
44
     * @bool Do a local lookup only if server is down
45
     */
46
    private $local;
47
48
    /**
49
     * TVDB constructor.
50
     *
51
     * @param array $options
52
     * @throws \Exception
53
     */
54
    public function __construct(array $options = [])
55
    {
56
        parent::__construct($options);
57
        $this->client = new Client();
58
        $this->client->setLanguage('en');
59
        $this->posterUrl = self::TVDB_URL.DS.'graphical/%s-g.jpg';
0 ignored issues
show
Bug introduced by
The constant Blacklight\processing\tv\DS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
60
        $this->fanartUrl = self::TVDB_URL.DS.'_cache/fanart/original/%s-3.jpg';
61
        $this->local = false;
62
63
        // Check if we can get the time for API status
64
        // If we can't then we set local to true
65
        try {
66
            $this->token = $this->client->authentication()->login(self::TVDB_API_KEY);
67
        } catch (CouldNotLoginException $error) {
68
            echo ColorCLI::warning('Could not reach TVDB API. Running in local mode only!');
69
            $this->local = true;
70
        } catch (UnauthorizedException $error) {
71
            echo ColorCLI::warning('Bad response from TVDB API. Running in local mode only!');
72
            $this->local = true;
73
        }
74
75
        if (\strlen($this->token) > 0) {
76
            $this->client->setToken($this->token);
77
        }
78
    }
79
80
    /**
81
     * Main processing director function for scrapers
82
     * Calls work query function and initiates processing.
83
     *
84
     * @param      $groupID
85
     * @param      $guidChar
86
     * @param      $process
87
     * @param bool $local
88
     */
89
    public function processSite($groupID, $guidChar, $process, $local = false): void
90
    {
91
        $res = $this->getTvReleases($groupID, $guidChar, $process, parent::PROCESS_TVDB);
92
93
        $tvCount = \count($res);
0 ignored issues
show
Bug introduced by
$res of type integer is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

93
        $tvCount = \count(/** @scrutinizer ignore-type */ $res);
Loading history...
94
95
        if ($this->echooutput && $tvCount > 0) {
96
            echo ColorCLI::header('Processing TVDB lookup for '.number_format($tvCount).' release(s).');
97
        }
98
99
        if ($res instanceof \Traversable) {
0 ignored issues
show
introduced by
$res is never a sub-type of Traversable.
Loading history...
100
            $this->titleCache = [];
101
102
            foreach ($res as $row) {
103
                $tvDbId = false;
104
105
                // Clean the show name for better match probability
106
                $release = $this->parseInfo($row['searchname']);
107
                if (\is_array($release) && $release['name'] !== '') {
108
                    if (\in_array($release['cleanname'], $this->titleCache, false)) {
109
                        if ($this->echooutput) {
110
                            echo ColorCLI::headerOver('Title: ').
111
                                    ColorCLI::warningOver($release['cleanname']).
112
                                    ColorCLI::header(' already failed lookup for this site.  Skipping.');
113
                        }
114
                        $this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']);
115
                        continue;
116
                    }
117
118
                    // Find the Video ID if it already exists by checking the title.
119
                    $videoId = $this->getByTitle($release['cleanname'], parent::TYPE_TV);
120
121
                    if ($videoId !== false) {
122
                        $tvDbId = $this->getSiteByID('tvdb', $videoId);
123
                    }
124
125
                    // Force local lookup only
126
                    $lookupSetting = true;
127
                    if ($local === true || $this->local === true) {
128
                        $lookupSetting = false;
129
                    }
130
131
                    if ($tvDbId === false && $lookupSetting) {
132
133
                        // If it doesnt exist locally and lookups are allowed lets try to get it.
134
                        if ($this->echooutput) {
135
                            echo ColorCLI::primaryOver('Video ID for ').
136
                                ColorCLI::headerOver($release['cleanname']).
137
                                ColorCLI::primary(' not found in local db, checking web.');
138
                        }
139
140
                        // Check if we have a valid country and set it in the array
141
                        $country = (
142
                            isset($release['country']) && \strlen($release['country']) === 2
143
                            ? (string) $release['country']
144
                            : ''
145
                        );
146
147
                        // Get the show from TVDB
148
                        $tvdbShow = $this->getShowInfo((string) $release['cleanname'], $country);
149
150
                        if (\is_array($tvdbShow)) {
151
                            $tvdbShow['country'] = $country;
152
                            $videoId = $this->add($tvdbShow);
153
                            $tvDbId = (int) $tvdbShow['tvdb'];
154
                        }
155
                    } elseif ($this->echooutput && $tvDbId !== false) {
156
                        echo ColorCLI::primaryOver('Video ID for ').
157
                            ColorCLI::headerOver($release['cleanname']).
158
                            ColorCLI::primary(' found in local db, attempting episode match.');
159
                    }
160
161
                    if (is_numeric($videoId) && $videoId > 0 && is_numeric($tvDbId) && $tvDbId > 0) {
162
                        // Now that we have valid video and tvdb ids, try to get the poster
163
                        $this->getPoster($videoId, $tvDbId);
164
165
                        $seasonNo = (! empty($release['season']) ? preg_replace('/^S0*/i', '', $release['season']) : '');
166
                        $episodeNo = (! empty($release['episode']) ? preg_replace('/^E0*/i', '', $release['episode']) : '');
167
168
                        if ($episodeNo === 'all') {
169
                            // Set the video ID and leave episode 0
170
                            $this->setVideoIdFound($videoId, $row['id'], 0);
171
                            echo ColorCLI::primary('Found TVDB Match for Full Season!');
172
                            continue;
173
                        }
174
175
                        // Download all episodes if new show to reduce API/bandwidth usage
176
                        if ($this->countEpsByVideoID($videoId) === false) {
177
                            $this->getEpisodeInfo($tvDbId, -1, -1, '', $videoId);
178
                        }
179
180
                        // Check if we have the episode for this video ID
181
                        $episode = $this->getBySeasonEp($videoId, $seasonNo, $episodeNo, $release['airdate']);
182
183
                        if ($episode === false && $lookupSetting) {
184
                            // Send the request for the episode to TVDB
185
                            $tvdbEpisode = $this->getEpisodeInfo(
186
                                $tvDbId,
187
                                $seasonNo,
188
                                $episodeNo,
189
                                $release['airdate']
190
                            );
191
192
                            if ($tvdbEpisode) {
193
                                $episode = $this->addEpisode($videoId, $tvdbEpisode);
194
                            }
195
                        }
196
197
                        if ($episode !== false && is_numeric($episode) && $episode > 0) {
198
                            // Mark the releases video and episode IDs
199
                            $this->setVideoIdFound($videoId, $row['id'], $episode);
200
                            if ($this->echooutput) {
201
                                echo ColorCLI::primary('Found TVDB Match!');
202
                            }
203
                        } else {
204
                            //Processing failed, set the episode ID to the next processing group
205
                            $this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']);
206
                        }
207
                    } else {
208
                        //Processing failed, set the episode ID to the next processing group
209
                        $this->setVideoNotFound(parent::PROCESS_TVMAZE, $row['id']);
210
                        $this->titleCache[] = $release['cleanname'];
211
                    }
212
                } else {
213
                    //Parsing failed, take it out of the queue for examination
214
                    $this->setVideoNotFound(parent::FAILED_PARSE, $row['id']);
215
                    $this->titleCache[] = $release['cleanname'];
216
                }
217
            }
218
        }
219
    }
220
221
    /**
222
     * Placeholder for Videos getBanner.
223
     *
224
     * @param $videoID
225
     * @param $siteId
226
     *
227
     * @return bool
228
     */
229
    protected function getBanner($videoID, $siteId): bool
230
    {
231
        return false;
232
    }
233
234
    /**
235
     * Calls the API to perform initial show name match to TVDB title
236
     * Returns a formatted array of show data or false if no match.
237
     *
238
     * @param string $cleanName
239
     *
240
     * @param string $country
241
     *
242
     * @return array|false
243
     */
244
    protected function getShowInfo($cleanName, $country = '')
245
    {
246
        $return = $response = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
247
        $highestMatch = 0;
248
        try {
249
            $response = $this->client->search()->seriesByName($cleanName);
250
        } catch (InvalidArgumentException $error) {
251
            return false;
252
        } catch (InvalidJsonInResponseException $error) {
253
            if (strpos($error->getMessage(), 'Could not decode JSON data') === 0 || strpos($error->getMessage(), 'Incorrect data structure') === 0) {
254
                return false;
255
            }
256
        } catch (RequestFailedException $error) {
257
            return false;
258
        } catch (UnauthorizedException $error) {
259
            if (strpos($error->getMessage(), 'Unauthorized') === 0) {
260
                return false;
261
            }
262
        }
263
264
        if ($response === false && $country !== '') {
0 ignored issues
show
introduced by
The condition $response === false is always false.
Loading history...
265
            try {
266
                $response = $this->client->search()->seriesByName(rtrim(str_replace($country, '', $cleanName)));
267
            } catch (InvalidArgumentException $error) {
268
                return false;
269
            } catch (InvalidJsonInResponseException $error) {
270
                if (strpos($error->getMessage(), 'Could not decode JSON data') === 0 || strpos($error->getMessage(), 'Incorrect data structure') === 0) {
271
                    return false;
272
                }
273
            } catch (RequestFailedException $error) {
274
                return false;
275
            } catch (UnauthorizedException $error) {
276
                if (strpos($error->getMessage(), 'Unauthorized') === 0) {
277
                    return false;
278
                }
279
            }
280
        }
281
282
        sleep(1);
283
284
        if (\is_array($response)) {
0 ignored issues
show
introduced by
The condition is_array($response) is always false.
Loading history...
285
            foreach ($response->getData() as $show) {
286
                if ($this->checkRequiredAttr($show, 'tvdbS')) {
287
                    // Check for exact title match first and then terminate if found
288
                    if (strtolower($show->getSeriesName()) === strtolower($cleanName)) {
289
                        $highest = $show;
290
                        break;
291
                    }
292
293
                    // Check each show title for similarity and then find the highest similar value
294
                    $matchPercent = $this->checkMatch(strtolower($show->getSeriesName()), strtolower($cleanName), self::MATCH_PROBABILITY);
295
296
                    // If new match has a higher percentage, set as new matched title
297
                    if ($matchPercent > $highestMatch) {
298
                        $highestMatch = $matchPercent;
299
                        $highest = $show;
300
                    }
301
302
                    // Check for show aliases and try match those too
303
                    if (! empty($show->getAliases())) {
304
                        foreach ($show->getAliases() as $key => $name) {
305
                            $matchPercent = $this->checkMatch(strtolower($name), strtolower($cleanName), $matchPercent);
306
                            if ($matchPercent > $highestMatch) {
307
                                $highestMatch = $matchPercent;
308
                                $highest = $show;
309
                            }
310
                        }
311
                    }
312
                }
313
            }
314
            if (! empty($highest)) {
315
                $return = $this->formatShowInfo($highest);
316
            }
317
        }
318
319
        return $return;
320
    }
321
322
    /**
323
     * Retrieves the poster art for the processed show.
324
     *
325
     * @param int $videoId -- the local Video ID
326
     * @param int $showId  -- the TVDB ID
327
     *
328
     * @return int
329
     */
330
    public function getPoster($videoId, $showId): int
331
    {
332
        $ri = new ReleaseImage();
333
334
        // Try to get the Poster
335
        $hasCover = $ri->saveImage($videoId, sprintf($this->posterUrl, $showId), $this->imgSavePath);
336
337
        // Couldn't get poster, try fan art instead
338
        if ($hasCover !== 1) {
339
            $hasCover = $ri->saveImage($videoId, sprintf($this->fanartUrl, $showId), $this->imgSavePath);
340
        }
341
        // Mark it retrieved if we saved an image
342
        if ($hasCover === 1) {
343
            $this->setCoverFound($videoId);
344
        }
345
346
        return $hasCover;
347
    }
348
349
    /**
350
     * Gets the specific episode info for the parsed release after match
351
     * Returns a formatted array of episode data or false if no match.
352
     *
353
     * @param int $tvDbId
354
     * @param int $season
355
     * @param int $episode
356
     * @param string  $airDate
357
     * @param int $videoId
358
     *
359
     * @return array|false
360
     */
361
    protected function getEpisodeInfo($tvDbId, $season, $episode, $airDate = '', $videoId = 0)
362
    {
363
        $return = $response = false;
364
365
        if ($airDate !== '') {
366
            try {
367
                $response = $this->client->series()->getEpisodesWithQuery($tvDbId, ['firstAired' => $airDate]);
368
            } catch (InvalidArgumentException $error) {
369
                return false;
370
            } catch (InvalidJsonInResponseException $error) {
371
                if (strpos($error->getMessage(), 'Could not decode JSON data') === 0 || strpos($error->getMessage(), 'Incorrect data structure') === 0) {
372
                    return false;
373
                }
374
            } catch (RequestFailedException $error) {
375
                return false;
376
            } catch (UnauthorizedException $error) {
377
                if (strpos($error->getMessage(), 'Unauthorized') === 0) {
378
                    return false;
379
                }
380
            }
381
        } elseif ($videoId > 0) {
382
            try {
383
                $response = $this->client->series()->getEpisodes($tvDbId);
384
            } catch (InvalidArgumentException $error) {
385
                return false;
386
            } catch (InvalidJsonInResponseException $error) {
387
                if (strpos($error->getMessage(), 'Could not decode JSON data') === 0 || strpos($error->getMessage(), 'Incorrect data structure') === 0) {
388
                    return false;
389
                }
390
            } catch (RequestFailedException $error) {
391
                return false;
392
            } catch (UnauthorizedException $error) {
393
                if (strpos($error->getMessage(), 'Unauthorized') === 0) {
394
                    return false;
395
                }
396
            }
397
        } else {
398
            try {
399
                $response = $this->client->series()->getEpisodesWithQuery($tvDbId, ['airedSeason' => $season, 'airedEpisode' => $episode]);
400
            } catch (InvalidArgumentException $error) {
401
                return false;
402
            } catch (InvalidJsonInResponseException $error) {
403
                if (strpos($error->getMessage(), 'Could not decode JSON data') === 0 || strpos($error->getMessage(), 'Incorrect data structure') === 0) {
404
                    return false;
405
                }
406
            } catch (RequestFailedException $error) {
407
                return false;
408
            } catch (UnauthorizedException $error) {
409
                if (strpos($error->getMessage(), 'Unauthorized') === 0) {
410
                    return false;
411
                }
412
            }
413
        }
414
415
        sleep(1);
416
417
        if (\is_object($response->getData())) {
418
            if ($this->checkRequiredAttr($response->getData(), 'tvdbE')) {
0 ignored issues
show
Bug introduced by
'tvdbE' of type string is incompatible with the type integer expected by parameter $type of Blacklight\processing\tv\TV::checkRequiredAttr(). ( Ignorable by Annotation )

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

418
            if ($this->checkRequiredAttr($response->getData(), /** @scrutinizer ignore-type */ 'tvdbE')) {
Loading history...
419
                $return = $this->formatEpisodeInfo($response);
420
            }
421
        } elseif ($videoId > 0 && \is_array($response->getData())) {
422
            foreach ($response->getData() as $singleEpisode) {
423
                if ($this->checkRequiredAttr($singleEpisode, 'tvdbE')) {
424
                    $this->addEpisode($videoId, $this->formatEpisodeInfo($singleEpisode));
425
                }
426
            }
427
        }
428
429
        return $return;
430
    }
431
432
    /**
433
     * Assigns API show response values to a formatted array for insertion
434
     * Returns the formatted array.
435
     *
436
     * @param $show
437
     *
438
     * @return array
439
     */
440
    protected function formatShowInfo($show): array
441
    {
442
        preg_match('/tt(?P<imdbid>\d{6,7})$/i', $show->imdbId, $imdb);
443
444
        return [
445
            'type'      => parent::TYPE_TV,
446
            'title'     => (string) $show->getSeriesName(),
447
            'summary'   => (string) $show->getOverview(),
448
            'started'   => $show->firstAired->format('Y-m-d'),
449
            'publisher' => (string) $show->getNetwork(),
450
            'source'    => parent::SOURCE_TVDB,
451
            'imdb'      => (int) ($imdb['imdbid'] ?? 0),
452
            'tvdb'      => (int) $show->getid(),
453
            'trakt'     => 0,
454
            'tvrage'    => 0,
455
            'tvmaze'    => 0,
456
            'tmdb'      => 0,
457
            'aliases'   => ! empty($show->getAliases()) ? $show->getAliases() : '',
458
            'localzone' => "''",
459
        ];
460
    }
461
462
    /**
463
     * Assigns API episode response values to a formatted array for insertion
464
     * Returns the formatted array.
465
     *
466
     * @param $episode
467
     *
468
     * @return array
469
     */
470
    protected function formatEpisodeInfo($episode): array
471
    {
472
        return [
473
            'title'       => (string) $episode->name,
474
            'series'      => (int) $episode->season,
475
            'episode'     => (int) $episode->number,
476
            'se_complete' => 'S'.sprintf('%02d', $episode->season).'E'.sprintf('%02d', $episode->number),
477
            'firstaired'  => $episode->firstAired->format('Y-m-d'),
478
            'summary'     => (string) $episode->overview,
479
        ];
480
    }
481
}
482