Completed
Push — master ( 45165d...5e44c4 )
by Alexey
04:08 queued 11s
created

AppInfoScraper::getScriptData()   C

Complexity

Conditions 15
Paths 14

Size

Total Lines 40
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 15.0739

Importance

Changes 0
Metric Value
eloc 29
c 0
b 0
f 0
dl 0
loc 40
ccs 27
cts 29
cp 0.931
rs 5.9166
cc 15
nc 14
nop 2
crap 15.0739

How to fix   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
/** @noinspection MultiAssignmentUsageInspection */
4
declare(strict_types=1);
5
6
/**
7
 * @author   Ne-Lexa
8
 * @license  MIT
9
 *
10
 * @see      https://github.com/Ne-Lexa/google-play-scraper
11
 */
12
13
namespace Nelexa\GPlay\Scraper;
14
15
use Nelexa\GPlay\Exception\GooglePlayException;
16
use Nelexa\GPlay\GPlayApps;
17
use Nelexa\GPlay\Model\AppId;
18
use Nelexa\GPlay\Model\AppInfo;
19
use Nelexa\GPlay\Model\Category;
20
use Nelexa\GPlay\Model\Developer;
21
use Nelexa\GPlay\Model\GoogleImage;
22
use Nelexa\GPlay\Model\HistogramRating;
23
use Nelexa\GPlay\Model\Review;
24
use Nelexa\GPlay\Model\Video;
25
use Nelexa\GPlay\Scraper\Extractor\ReviewsExtractor;
26
use Nelexa\GPlay\Util\DateStringFormatter;
27
use Nelexa\GPlay\Util\LocaleHelper;
28
use Nelexa\GPlay\Util\ScraperUtil;
29
use Nelexa\HttpClient\ResponseHandlerInterface;
30
use Psr\Http\Message\RequestInterface;
31
use Psr\Http\Message\ResponseInterface;
32
use function GuzzleHttp\Psr7\parse_query;
33
34
/**
35
 * @internal
36
 */
37
class AppInfoScraper implements ResponseHandlerInterface
38
{
39
    /**
40
     * @param RequestInterface  $request
41
     * @param ResponseInterface $response
42
     *
43
     * @throws GooglePlayException
44
     *
45
     * @return AppInfo
46
     */
47 19
    public function __invoke(RequestInterface $request, ResponseInterface $response): AppInfo
48
    {
49 19
        $query = parse_query($request->getUri()->getQuery());
50
51 19
        $id = $query[GPlayApps::REQ_PARAM_ID];
52 19
        $locale = $query[GPlayApps::REQ_PARAM_LOCALE] ?? GPlayApps::DEFAULT_LOCALE;
53 19
        $country = $query[GPlayApps::REQ_PARAM_COUNTRY] ?? GPlayApps::DEFAULT_COUNTRY;
54
55
        [
56
            $scriptDataInfo,
57
            $scriptDataRating,
58
            $scriptDataPrice,
59
            $scriptDataVersion,
60
            $scriptDataReviews,
61 19
        ] = $this->getScriptData($request, $response);
62
63 19
        $name = $scriptDataInfo[0][0][0];
64 19
        $description = $this->extractDescription($scriptDataInfo);
65 19
        $translatedFromLocale = $this->extractTranslatedFromLocale($scriptDataInfo, $locale);
66 19
        $developer = $this->extractDeveloper($scriptDataInfo);
67 19
        $category = $this->extractCategory($scriptDataInfo[0][12][13][0]);
68 19
        $summary = $this->extractSummary($scriptDataInfo);
69 19
        $installs = $scriptDataInfo[0][12][9][2] ?? 0;
70 19
        $score = (float) ($scriptDataRating[0][6][0][1] ?? 0);
71 19
        $numberVoters = (int) ($scriptDataRating[0][6][2][1] ?? 0);
72 19
        $numberReviews = (int) ($scriptDataRating[0][6][3][1] ?? 0);
73 19
        $histogramRating = $this->extractHistogramRating($scriptDataRating);
74 19
        $price = $this->extractPrice($scriptDataPrice);
75 19
        $currency = $scriptDataPrice[0][2][0][0][0][1][0][1];
76 19
        $priceText = $scriptDataPrice[0][2][0][0][0][1][0][2] ?: null;
77 19
        $offersIAPCost = $scriptDataInfo[0][12][12][0] ?? null;
78 19
        $containsAds = (bool) ($scriptDataInfo[0][12][14][0] ?? false);
79
80 19
        [$size, $appVersion, $androidVersion] = $scriptDataVersion;
81
82 19
        if (LocaleHelper::isDependOnDevice($locale, $size)) {
83 13
            $size = null;
84
        }
85
86 19
        if (LocaleHelper::isDependOnDevice($locale, $appVersion)) {
87 9
            $appVersion = null;
88
        }
89
90 19
        if (LocaleHelper::isDependOnDevice($locale, $androidVersion)) {
91 9
            $androidVersion = null;
92 9
            $minAndroidVersion = null;
93
        } else {
94 11
            $minAndroidVersion = preg_replace('~.*?(\d+(\.\d+)*).*~', '$1', $androidVersion);
95
        }
96
97 19
        $editorsChoice = !empty($scriptDataInfo[0][12][15][1][1]);
98 19
        $privacyPoliceUrl = $scriptDataInfo[0][12][7][2] ?? '';
99 19
        $categoryFamily = $this->extractCategory($scriptDataInfo[0][12][13][1] ?? []);
100 19
        $icon = $this->extractIcon($scriptDataInfo);
101 19
        $cover = $this->extractCover($scriptDataInfo);
102 19
        $screenshots = $this->extractScreenshots($scriptDataInfo);
103 19
        $video = $this->extractVideo($scriptDataInfo);
104 19
        $contentRating = $scriptDataInfo[0][12][4][0] ?? '';
105 19
        $released = $this->extractReleaseDate($scriptDataInfo, $locale);
106 19
        $updated = $this->extractUpdatedDate($scriptDataInfo);
107 19
        $recentChanges = $this->extractRecentChanges($scriptDataInfo);
108 19
        $reviews = $this->extractReviews(new AppId($id, $locale, $country), $scriptDataReviews);
109
110 19
        return AppInfo::newBuilder()
111 19
            ->setId($id)
112 19
            ->setLocale($locale)
113 19
            ->setCountry($country)
114 19
            ->setName($name)
115 19
            ->setDescription($description)
116 19
            ->setTranslatedFromLocale($translatedFromLocale)
117 19
            ->setSummary($summary)
118 19
            ->setIcon($icon)
119 19
            ->setCover($cover)
120 19
            ->setScreenshots($screenshots)
121 19
            ->setDeveloper($developer)
122 19
            ->setCategory($category)
123 19
            ->setCategoryFamily($categoryFamily)
124 19
            ->setVideo($video)
125 19
            ->setRecentChanges($recentChanges)
126 19
            ->setEditorsChoice($editorsChoice)
127 19
            ->setPrivacyPoliceUrl($privacyPoliceUrl)
128 19
            ->setInstalls($installs)
129 19
            ->setScore($score)
130 19
            ->setRecentChanges($recentChanges)
131 19
            ->setEditorsChoice($editorsChoice)
132 19
            ->setPrivacyPoliceUrl($privacyPoliceUrl)
133 19
            ->setInstalls($installs)
134 19
            ->setScore($score)
135 19
            ->setNumberVoters($numberVoters)
136 19
            ->setHistogramRating($histogramRating)
137 19
            ->setPrice($price)
138 19
            ->setCurrency($currency)
139 19
            ->setPriceText($priceText)
140 19
            ->setOffersIAPCost($offersIAPCost)
141 19
            ->setContainsAds($containsAds)
142 19
            ->setSize($size)
143 19
            ->setAppVersion($appVersion)
144 19
            ->setAndroidVersion($androidVersion)
145 19
            ->setMinAndroidVersion($minAndroidVersion)
146 19
            ->setContentRating($contentRating)
147 19
            ->setReleased($released)
148 19
            ->setUpdated($updated)
149 19
            ->setNumberReviews($numberReviews)
150 19
            ->setReviews($reviews)
151 19
            ->buildDetailInfo()
152
        ;
153
    }
154
155
    /**
156
     * @param RequestInterface  $request
157
     * @param ResponseInterface $response
158
     *
159
     * @throws GooglePlayException
160
     *
161
     * @return array
162
     */
163 19
    private function getScriptData(RequestInterface $request, ResponseInterface $response): array
164
    {
165 19
        $scriptData = ScraperUtil::extractScriptData($response->getBody()->getContents());
166
167 19
        $scriptDataInfo = null;
168 19
        $scriptDataRating = null;
169 19
        $scriptDataPrice = null;
170 19
        $scriptDataVersion = null;
171 19
        $scriptDataReviews = [];
172
173 19
        foreach ($scriptData as $key => $scriptValue) {
174 19
            if (isset($scriptValue[0][12][5][5][4][2])) { // ds:5
175 19
                $scriptDataInfo = $scriptValue;
176 19
            } elseif (isset($scriptValue[0][2][0][0][0][1][0][0])) { // ds:3
177 19
                $scriptDataPrice = $scriptValue;
178 19
            } elseif (isset($scriptValue[0][0][0])
179 19
                && \is_string($scriptValue[0][0][0])
180 19
                && strpos($scriptValue[0][0][0], 'gp:') === 0) { // ds:15
181 19
                $scriptDataReviews = $scriptValue;
182 19
            } elseif (isset($scriptValue[0][6][3][1])) { // ds:7
183 19
                $scriptDataRating = $scriptValue;
184 19
            } elseif (isset($scriptValue[0])
185 19
                && \is_string($scriptValue[0])
186 19
                && \count($scriptValue) === 3) { // ds:8
187 19
                $scriptDataVersion = $scriptValue;
188
            }
189
        }
190
191
        if (
192 19
            $scriptDataInfo === null ||
193 19
            $scriptDataRating === null ||
194 19
            $scriptDataPrice === null ||
195 19
            $scriptDataVersion === null
0 ignored issues
show
introduced by
The condition $scriptDataVersion === null is always true.
Loading history...
196
        ) {
197
            throw (new GooglePlayException('Unable to get data for this application.'))->setUrl(
198
                $request->getUri()->__toString()
199
            );
200
        }
201
202 19
        return [$scriptDataInfo, $scriptDataRating, $scriptDataPrice, $scriptDataVersion, $scriptDataReviews];
203
    }
204
205
    /**
206
     * @param        $scriptDataInfo
207
     * @param string $locale
208
     *
209
     * @return string|null
210
     */
211 19
    private function extractTranslatedFromLocale(array $scriptDataInfo, string $locale): ?string
212
    {
213 19
        return isset($scriptDataInfo[0][19][1]) ?
214 14
            LocaleHelper::findPreferredLanguage(
215 14
                $locale,
216 14
                $scriptDataInfo[0][19][1]
217
            ) :
218 19
            null;
219
    }
220
221
    /**
222
     * @param array $scriptDataInfo
223
     *
224
     * @return string
225
     */
226 19
    private function extractDescription(array $scriptDataInfo): string
227
    {
228 19
        if (isset($scriptDataInfo[0][19][0][0][1])) {
229 14
            return ScraperUtil::html2text($scriptDataInfo[0][19][0][0][1]);
230
        }
231
232 19
        return ScraperUtil::html2text($scriptDataInfo[0][10][0][1]);
233
    }
234
235
    /**
236
     * @param $scriptDataInfo
237
     *
238
     * @return string|null
239
     */
240 19
    private function extractSummary(array $scriptDataInfo): ?string
241
    {
242 19
        return empty($scriptDataInfo[0][10][1][1]) ?
243
            null :
244 19
            ScraperUtil::html2text($scriptDataInfo[0][10][1][1]);
245
    }
246
247
    /**
248
     * @param array $scriptDataInfo
249
     *
250
     * @return Developer
251
     */
252 19
    private function extractDeveloper(array $scriptDataInfo): Developer
253
    {
254 19
        $developerPage = GPlayApps::GOOGLE_PLAY_URL . $scriptDataInfo[0][12][5][5][4][2];
255 19
        $developerId = parse_query(parse_url($developerPage, \PHP_URL_QUERY))[GPlayApps::REQ_PARAM_ID];
256 19
        $developerName = $scriptDataInfo[0][12][5][1];
257 19
        $developerEmail = $scriptDataInfo[0][12][5][2][0] ?? null;
258 19
        $developerWebsite = $scriptDataInfo[0][12][5][3][5][2] ?? null;
259 19
        $developerAddress = $scriptDataInfo[0][12][5][4][0] ?? null;
260
//        $developerInternalID = (int)$scriptDataInfo[0][12][5][0][0];
261
262 19
        return new Developer(
263 19
            Developer::newBuilder()
264 19
                ->setId($developerId)
265 19
                ->setUrl($developerPage)
266 19
                ->setName($developerName)
267 19
                ->setEmail($developerEmail)
268 19
                ->setAddress($developerAddress)
269 19
                ->setWebsite($developerWebsite)
270
        );
271
    }
272
273
    /**
274
     * @param array $data
275
     *
276
     * @return Category|null
277
     */
278 19
    private function extractCategory(array $data): ?Category
279
    {
280 19
        if (isset($data[0]) && $data[0] !== null && $data[2] !== null) {
281 19
            $genreId = (string) $data[2];
282 19
            $genreName = (string) $data[0];
283
284 19
            return new Category($genreId, $genreName);
285
        }
286
287 17
        return null;
288
    }
289
290
    /**
291
     * @param array $scriptDataRating
292
     *
293
     * @return HistogramRating
294
     */
295 19
    private function extractHistogramRating(array $scriptDataRating): HistogramRating
296
    {
297 19
        return new HistogramRating(
298 19
            $scriptDataRating[0][6][1][5][1] ?? 0,
299 19
            $scriptDataRating[0][6][1][4][1] ?? 0,
300 19
            $scriptDataRating[0][6][1][3][1] ?? 0,
301 19
            $scriptDataRating[0][6][1][2][1] ?? 0,
302 19
            $scriptDataRating[0][6][1][1][1] ?? 0
303
        );
304
    }
305
306
    /**
307
     * @param $scriptDataPrice
308
     *
309
     * @return float
310
     */
311 19
    protected function extractPrice(array $scriptDataPrice): ?float
312
    {
313 19
        return isset($scriptDataPrice[0][2][0][0][0][1][0][0]) ?
314 19
            (float) ($scriptDataPrice[0][2][0][0][0][1][0][0] / 1000000) :
315 19
            0.0;
316
    }
317
318
    /**
319
     * @param array $scriptDataInfo
320
     *
321
     * @return GoogleImage|null
322
     */
323 19
    protected function extractIcon(array $scriptDataInfo): ?GoogleImage
324
    {
325 19
        return empty($scriptDataInfo[0][12][1][3][2]) ?
326
            null :
327 19
            new GoogleImage($scriptDataInfo[0][12][1][3][2]);
328
    }
329
330
    /**
331
     * @param array $scriptDataInfo
332
     *
333
     * @return GoogleImage|null
334
     */
335 19
    protected function extractCover(array $scriptDataInfo): ?GoogleImage
336
    {
337 19
        return empty($scriptDataInfo[0][12][2][3][2]) ?
338
            null :
339 19
            new GoogleImage($scriptDataInfo[0][12][2][3][2]);
340
    }
341
342
    /**
343
     * @param array $scriptDataInfo
344
     *
345
     * @return GoogleImage[]
346
     */
347 19
    private function extractScreenshots(array $scriptDataInfo): array
348
    {
349 19
        return !empty($scriptDataInfo[0][12][0]) ? array_map(
350
            static function (array $v) {
351 19
                return new GoogleImage($v[3][2]);
352 19
            },
353 19
            $scriptDataInfo[0][12][0]
354 19
        ) : [];
355
    }
356
357
    /**
358
     * @param array $scriptDataInfo
359
     *
360
     * @return Video|null
361
     */
362 19
    private function extractVideo(array $scriptDataInfo): ?Video
363
    {
364
        if (
365 19
            isset($scriptDataInfo[0][12][3][0][3][2]) &&
366 19
            $scriptDataInfo[0][12][3][0][3][2] !== null &&
367 19
            $scriptDataInfo[0][12][3][1][3][2] !== null
368
        ) {
369 8
            $videoThumb = (string) $scriptDataInfo[0][12][3][1][3][2];
370 8
            $videoUrl = (string) $scriptDataInfo[0][12][3][0][3][2];
371
372 8
            return new Video($videoThumb, $videoUrl);
373
        }
374
375 12
        return null;
376
    }
377
378
    /**
379
     * @param array  $scriptDataInfo
380
     * @param string $locale
381
     *
382
     * @return \DateTimeInterface|null
383
     */
384 19
    private function extractReleaseDate(array $scriptDataInfo, string $locale): ?\DateTimeInterface
385
    {
386 19
        if (isset($scriptDataInfo[0][12][36])) {
387 18
            return DateStringFormatter::formatted($locale, $scriptDataInfo[0][12][36]);
388
        }
389
390 1
        return null;
391
    }
392
393
    /**
394
     * @param array $scriptDataInfo
395
     *
396
     * @return \DateTimeInterface|null
397
     */
398 19
    private function extractUpdatedDate(array $scriptDataInfo): ?\DateTimeInterface
399
    {
400 19
        if (isset($scriptDataInfo[0][12][8][0])) {
401 19
            return DateStringFormatter::unixTimeToDateTime($scriptDataInfo[0][12][8][0]);
402
        }
403
404
        return null;
405
    }
406
407
    /**
408
     * @param $scriptDataInfo
409
     *
410
     * @return string|null
411
     */
412 19
    protected function extractRecentChanges($scriptDataInfo): ?string
413
    {
414 19
        return empty($scriptDataInfo[0][12][6][1]) ?
415 1
            null :
416 19
            ScraperUtil::html2text($scriptDataInfo[0][12][6][1]);
417
    }
418
419
    /**
420
     * @param AppId $appId
421
     * @param array $scriptDataReviews
422
     * @param int   $limit
423
     *
424
     * @return Review[]
425
     */
426 19
    private function extractReviews(AppId $appId, array $scriptDataReviews, int $limit = 4): array
427
    {
428 19
        if (empty($scriptDataReviews[0])) {
429 12
            return [];
430
        }
431
432 19
        return ReviewsExtractor::extractReviews(
433 19
            $appId,
434 19
            \array_slice($scriptDataReviews[0], 0, $limit)
435
        );
436
    }
437
}
438