Completed
Push — master ( 259b4f...1970e3 )
by Marcin
03:08
created

Imdb::searchByTitleApi()   B

Complexity

Conditions 4
Paths 21

Size

Total Lines 36
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 0
cts 33
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 28
nc 21
nop 1
crap 20
1
<?php
2
/**
3
 * xMDB-API
4
 *
5
 * Copyright © 2017 pudelek.org.pl
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For the full copyright and license information, please view source file
10
 * that is bundled with this package in the file LICENSE
11
 *
12
 * @author  Marcin Pudełek <[email protected]>
13
 */
14
15
/**
16
 * Created by Marcin.
17
 * Date: 01.12.2017
18
 * Time: 22:52
19
 */
20
21
namespace mrcnpdlk\Xmdb;
22
23
24
use Campo\UserAgent;
25
use Curl\Curl;
26
use HttpLib\Http;
27
use Imdb\Cache;
28
use Imdb\Config;
29
use Imdb\TitleSearch;
30
use KHerGe\JSON\JSON;
31
use mrcnpdlk\Xmdb\Model\Imdb\Character;
32
use mrcnpdlk\Xmdb\Model\Imdb\Image;
33
use mrcnpdlk\Xmdb\Model\Imdb\Info;
34
use mrcnpdlk\Xmdb\Model\Imdb\Person;
35
use mrcnpdlk\Xmdb\Model\Imdb\Rating;
36
use mrcnpdlk\Xmdb\Model\Imdb\Title;
37
use Sunra\PhpSimple\HtmlDomParser;
38
39
class Imdb
40
{
41
42
    const ANONYMOUS_URL = 'http://anonymouse.org/cgi-bin/anon-www.cgi/';
43
44
    /**
45
     * @var \mrcnpdlk\Xmdb\Client
46
     */
47
    private $oClient;
48
    /**
49
     * @var \Imdb\Config
50
     */
51
    private $oConfig;
52
    /** @noinspection PhpUndefinedClassInspection */
53
54
    /**
55
     * @var \Psr\Log\LoggerInterface
56
     */
57
    private $oLog;
58
    /**
59
     * @var \Psr\SimpleCache\CacheInterface
60
     */
61
    private $oCache;
62
63
    /**
64
     * Imdb constructor.
65
     *
66
     * @param \mrcnpdlk\Xmdb\Client $oClient
67
     *
68
     * @throws \mrcnpdlk\Xmdb\Exception
69
     */
70
    public function __construct(Client $oClient)
71
    {
72
        try {
73
            $this->oClient                = $oClient;
74
            $this->oLog                   = $oClient->getLogger();
75
            $this->oCache                 = $oClient->getAdapter()->getCache();
76
            $this->oConfig                = new Config();
77
            $this->oConfig->usecache      = null !== $this->oCache;
78
            $this->oConfig->language      = $oClient->getLang();
79
            $this->oConfig->default_agent = UserAgent::random();
80
81
82
        } catch (\Exception $e) {
83
            throw new Exception(sprintf('Cannot create Tmdb Client'), 1, $e);
84
        }
85
    }
86
87
    /**
88
     * @param string $imdbId
89
     *
90
     * @return \mrcnpdlk\Xmdb\Model\Imdb\Info
91
     * @throws \mrcnpdlk\Xmdb\Exception
92
     */
93
    public function getInfo(string $imdbId): Info
94
    {
95
        try {
96
            $searchUrl = 'http://app.imdb.com/title/maindetails?tconst=' . $imdbId;
97
98
            $oResp = $this->oClient->getAdapter()->useCache(
99
                function () use ($searchUrl) {
100
                    $oCurl = new Curl();
101
                    $oCurl->setUserAgent(UserAgent::random());
102
                    $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
103
                    $oCurl->get($searchUrl);
104
105
                    if ($oCurl->error) {
106
                        throw new \RuntimeException('Curl Error! ' . Http::message($oCurl->httpStatusCode), $oCurl->error_code);
107
                    }
108
109
                    return $oCurl->response->data ?? null;
110
                },
111
                [$searchUrl, $this->oClient->getLang()],
112
                3600 * 2)
113
            ;
114
            $oData = $oResp->data ?? $oResp;
115
116
            $oInfo              = new Info();
117
            $oInfo->id          = $oData->tconst;
118
            $oInfo->title       = $oData->title;
119
            $oInfo->year        = $oData->year;
120
            $oInfo->image       = isset($oData->image) ? new Image($oData->image->url, $oData->image->width, $oData->image->height) : null;
121
            $oInfo->releaseDate = $oData->release_date->normal ?? null;
122
            $oInfo->genres      = $oData->genres;
123
            $oInfo->rating      = $oData->rating;
124
            $oInfo->votes       = $oData->num_votes;
125
            $oInfo->runtime     = $oData->runtime->time ?? null;
126
127 View Code Duplication
            foreach ($oData->directors_summary ?? [] as $oDir) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
                $oPerson            = $oDir->name;
129
                $oImage             = isset($oPerson->image) ? new Image($oPerson->image->url, $oPerson->image->width,
130
                    $oPerson->image->height) : null;
131
                $oInfo->directors[] = new Person($oPerson->nconst, $oPerson->name, $oImage);
132
            }
133
134 View Code Duplication
            foreach ($oData->writers_summary ?? [] as $oDir) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
135
                $oPerson          = $oDir->name;
136
                $oImage           = isset($oPerson->image) ? new Image($oPerson->image->url, $oPerson->image->width,
137
                    $oPerson->image->height) : null;
138
                $oInfo->writers[] = new Person($oPerson->nconst, $oPerson->name, $oImage);
139
            }
140
141
            foreach ($oData->photos ?? [] as $photo) {
142
                $oInfo->photos[] = new Image($photo->image->url, $photo->image->width, $photo->image->height);
143
            }
144
145
            foreach ($oData->cast_summary ?? [] as $ch) {
146
                $oImage        = $ch->name->image ? new Image($ch->name->image->url, $ch->name->image->width,
147
                    $ch->name->image->height) : null;
148
                $oPerson       = $ch->name ? new Person($ch->name->nconst, $ch->name->name, $oImage) : null;
149
                $oInfo->cast[] = new Character($ch->char, $oPerson);
150
            }
151
152
            return $oInfo;
153
154
        } catch (\Exception $e) {
155
            throw new Exception(sprintf('Item [%s] not found, reason: %s', $imdbId, $e->getMessage()));
156
        }
157
    }
158
159
    /**
160
     * @param string $imdbId
161
     *
162
     * @return \mrcnpdlk\Xmdb\Model\Imdb\Rating
163
     * @throws \mrcnpdlk\Xmdb\Exception
164
     */
165
    public function getRating(string $imdbId): Rating
166
    {
167
        try {
168
            $searchUrl = 'http://p.media-imdb.com/static-content/documents/v1/title/'
169
                . $imdbId
170
                . '/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json?u='
171
                . $this->oClient->getImdbUser();
172
173
            $oResp = $this->oClient->getAdapter()->useCache(
174
                function () use ($searchUrl) {
175
                    $oCurl = new Curl();
176
                    $oCurl->setOpt(\CURLOPT_ENCODING, 'gzip');
177
                    $oCurl->setUserAgent(UserAgent::random());
178
                    $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
179
                    $oCurl->get($searchUrl);
180
181
                    if ($oCurl->error) {
182
                        throw new \RuntimeException('Curl Error! ' . Http::message($oCurl->httpStatusCode), $oCurl->error_code);
183
                    }
184
185
                    preg_match("/^[\w\.]*\((.*)\)$/", $oCurl->response, $output_array);
186
                    $json = new JSON();
187
188
                    return $json->decode($output_array[1]);
189
                },
190
                [$searchUrl, $this->oClient->getLang()],
191
                180)
192
            ;
193
194
            if (!isset($oResp->resource)) {
195
                throw new \RuntimeException('Resource is empty');
196
            }
197
198
            $oData = $oResp->resource;
199
200
            $oRating         = new Rating();
201
            $oRating->id     = $imdbId;
202
            $oRating->title  = $oData->title;
203
            $oRating->year   = $oData->year;
204
            $oRating->rating = $oData->rating;
205
            $oRating->votes  = $oData->ratingCount;
206
            $oRating->type   = $oData->titleType;
207
208
            return $oRating;
209
210
        } catch (\Exception $e) {
211
            throw new Exception(sprintf('Item [%s] not found, reason: %s', $imdbId, $e->getMessage()));
212
        }
213
    }
214
215
    /**
216
     * Combined search by title
217
     *
218
     * @param string $title
219
     *
220
     * @return Title[]
221
     */
222
    public function searchByTitle(string $title): array
223
    {
224
        /**
225
         * @var Title[] $answer
226
         * @var Title[] $tmpList
227
         */
228
        $answer      = [];
229
        $tmpList     = [];
230
        $tNativeList = $this->searchByTitleNative($title);
231
        foreach ($tNativeList as $item) {
232
            $tmpList[$item->imdbId] = $item;
233
        }
234
235
        $tApiList = $this->searchByTitleApi($title);
236
        foreach ($tApiList as $item) {
237
            if (!\array_key_exists($item->imdbId, $tmpList)) {
238
                $tmpList[$item->imdbId] = $item;
239
            } else {
240
                $tmpList[$item->imdbId]->isMovie = $tmpList[$item->imdbId]->isMovie ?? $item->isMovie;
241
            }
242
        }
243
244
        foreach ($tmpList as $item) {
245
            $answer[] = $item;
246
        }
247
248
        return $answer;
249
    }
250
251
    /**
252
     * @param string $title
253
     *
254
     * @return Title[]
255
     */
256
    public function searchByTitleApi(string $title): array
257
    {
258
        try {
259
            $answer       = [];
260
            $oTitleSearch = new TitleSearch($this->oConfig, $this->oLog, $this->oCache);
261
            $tList        = $oTitleSearch->search($title, [
262
                TitleSearch::MOVIE,
263
                TitleSearch::TV_SERIES,
264
                TitleSearch::VIDEO,
265
                TitleSearch::TV_MOVIE,
266
            ]);
267
268
            foreach ($tList as $element) {
269
                $oTitle                  = new Title();
270
                $oTitle->imdbId          = 'tt' . $element->imdbid();
271
                $oTitle->title           = $element->title();
272
                $oTitle->rating          = null; //set null for speedy
273
                $oTitle->episode         = null;
274
                $oTitle->year            = empty($element->year()) ? null : $element->year();
275
                $oTitle->type            = $element->movietype();
276
                $oTitle->isMovie         = \in_array($element->movietype(), [TitleSearch::MOVIE, TitleSearch::TV_MOVIE, TitleSearch::VIDEO],
277
                    true);
278
                $oTitle->director        = [];
279
                $oTitle->directorDisplay = implode(', ', $oTitle->director);
280
                $oTitle->star            = [];
281
                $oTitle->starDisplay     = implode(', ', $oTitle->star);
282
                $answer[]                = $oTitle;
283
            }
284
285
            return $answer;
286
        } catch (\Exception $e) {
287
            $this->oLog->warning(sprintf('Item [%s] not found, reason: %s', $title, $e->getMessage()));
288
289
            return [];
290
        }
291
    }
292
293
    /**
294
     * @param string $title
295
     *
296
     * @return Title[]
297
     */
298
    public function searchByTitleNative(string $title): array
299
    {
300
        try {
301
            /**
302
             * @var Title[] $answer
303
             */
304
            $answer = [];
305
            $searchUrl
306
                    = 'http://www.imdb.com/search/title' .
307
                '?at=0&sort=num_votes&title_type=feature,tv_movie,tv_series,tv_episode,tv_special,mini_series,documentary,short,video&title='
308
                . $title;
309
310
            $htmlContent = $this->oClient->getAdapter()->useCache(
311
                function () use ($searchUrl) {
312
                    $oCurl = new Curl();
313
                    $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
314
315
                    return $oCurl->get($searchUrl);
316
                },
317
                [$searchUrl, $this->oClient->getLang()],
318
                3600 * 2)
319
            ;
320
321
            $html = HtmlDomParser::str_get_html($htmlContent);
322
            if (!$html) {
323
                throw new \RuntimeException('Response from IMDB malformed!');
324
            }
325
            $listerItemNode = $html->find('div.lister-item');
326
327
            foreach ($listerItemNode as $element) {
328
                $content = $element->find('div.lister-item-content', 0);
329
                if (!$content) {
330
                    throw new \RuntimeException('Empty search result!');
331
                }
332
                $titleNode = $content->find('h3.lister-item-header a', 0);
333
                if ($titleNode) {
334
                    $foundTitle = trim($titleNode->text());
335
                } else {
336
                    throw new \RuntimeException('Title not found!');
337
                }
338
                $yearNode = $content->find('h3.lister-item-header .lister-item-year', 0);
339
                if ($yearNode) {
340
                    $foundYear = trim($yearNode->text());
341
                    preg_match("/^\(?([0-9\-\–\s]+)([\w\s]*)?\)?$/u", $foundYear, $yearOut);
342
                    $foundYear = isset($yearOut[1]) ? trim($yearOut[1]) : $foundYear;
343
                    $foundType = isset($yearOut[2]) && trim($yearOut[2]) ? trim($yearOut[2]) : null;
344
                } else {
345
                    $foundYear = null;
346
                    $foundType = null;
347
                }
348
                $episodeNode  = $content->find('h3.lister-item-header a', 1);
349
                $foundEpisode = null;
350
                if ($episodeNode) {
351
                    $foundEpisode = trim($episodeNode->text());
352
                }
353
                $ratingNode  = $content->find('div.ratings-bar div.ratings-imdb-rating', 0);
354
                $foundRating = null;
355
                if ($ratingNode) {
356
                    $foundRating = trim($ratingNode->text());
357
                }
358
359
                $metascoreNode  = $content->find('div.ratings-bar div.ratings-metascore', 0);
360
                $foundMetascore = null;
361
                if ($metascoreNode) {
362
                    $oSpanNode      = $metascoreNode->find('span', 0);
363
                    $foundMetascore = $oSpanNode ? (int)$oSpanNode->text() : null;
364
                }
365
366
                $idNode = $element->find('div.lister-top-right div.ribbonize', 0);
367
                $imdbId = null;
368
                if ($idNode) {
369
                    $imdbId = $idNode->getAttribute('data-tconst');
370
                }
371
372
                $directors   = [];
373
                $stars       = [];
374
                $personsNode = $content->find('p', 2);
375
                if ($personsNode) {
376
                    $persons = $personsNode->find('a');
377
                    if (\is_array($persons)) {
378
                        foreach ($persons as $person) {
379
                            if (strpos($person->getAttribute('href'), 'adv_li_dr_') !== false) {
380
                                $directors[] = $person->text();
381
                            }
382
                            if (strpos($person->getAttribute('href'), 'adv_li_st_') !== false) {
383
                                $stars[] = $person->text();
384
                            }
385
                        }
386
                    }
387
                }
388
389
                if ($imdbId) {
390
                    $oTitle                  = new Title();
391
                    $oTitle->title           = $foundTitle;
392
                    $oTitle->imdbId          = $imdbId;
393
                    $oTitle->rating          = $foundRating;
0 ignored issues
show
Documentation Bug introduced by
It seems like $foundRating can also be of type string. However, the property $rating is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
394
                    $oTitle->metascore       = $foundMetascore;
395
                    $oTitle->episode         = $foundEpisode;
396
                    $oTitle->year            = $foundYear;
397
                    $oTitle->type            = $foundType;
398
                    $oTitle->director        = $directors;
399
                    $oTitle->directorDisplay = implode(', ', $oTitle->director);
400
                    $oTitle->star            = $stars;
401
                    $oTitle->starDisplay     = implode(', ', $oTitle->star);
402
403
                    $answer[] = $oTitle;
404
                }
405
406
            }
407
408
            return $answer;
409
        } catch (\Exception $e) {
410
            $this->oLog->warning(sprintf('Item [%s] not found, reason: %s', $title, $e->getMessage()));
411
412
            return [];
413
        }
414
    }
415
}
416