Passed
Push — master ( 81593c...33129c )
by Alexey
10:12 queued 12s
created

AppInfoScraper::extractUpdatedDate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
cc 2
nc 2
nop 1
crap 2.0625
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright (c) Ne-Lexa
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 *
11
 * @see https://github.com/Ne-Lexa/google-play-scraper
12
 */
13
14
namespace Nelexa\GPlay\Scraper;
15
16
use GuzzleHttp\Psr7\Query;
17
use Nelexa\GPlay\Exception\GooglePlayException;
18
use Nelexa\GPlay\GPlayApps;
19
use Nelexa\GPlay\HttpClient\ParseHandlerInterface;
20
use Nelexa\GPlay\Model\AppId;
21
use Nelexa\GPlay\Model\AppInfo;
22
use Nelexa\GPlay\Model\Category;
23
use Nelexa\GPlay\Model\Developer;
24
use Nelexa\GPlay\Model\GoogleImage;
25
use Nelexa\GPlay\Model\HistogramRating;
26
use Nelexa\GPlay\Model\Review;
27
use Nelexa\GPlay\Model\Video;
28
use Nelexa\GPlay\Scraper\Extractor\ReviewsExtractor;
29
use Nelexa\GPlay\Util\DateStringFormatter;
30
use Nelexa\GPlay\Util\ScraperUtil;
31
use Psr\Http\Message\RequestInterface;
32
use Psr\Http\Message\ResponseInterface;
33
34
/**
35
 * @internal
36
 */
37
class AppInfoScraper implements ParseHandlerInterface
38
{
39
    /**
40
     * @param RequestInterface  $request
41
     * @param ResponseInterface $response
42
     * @param array             $options
43
     *
44
     * @throws \Nelexa\GPlay\Exception\GooglePlayException
45
     *
46
     * @return AppInfo
47
     */
48 10
    public function __invoke(RequestInterface $request, ResponseInterface $response, array &$options = []): AppInfo
49
    {
50 10
        $scriptData = ScraperUtil::extractScriptData($response->getBody()->getContents());
51
52 10
        $appInfo = ScraperUtil::getValue($scriptData, 'ds:4.1.2');
53 10
        if (!\is_array($appInfo)) {
54
            throw (new GooglePlayException('Unable to get data for this application.'))->setUrl(
55
                $request->getUri()->__toString()
56
            );
57
        }
58
59 10
        $query = Query::parse($request->getUri()->getQuery());
60 10
        $id = $query[GPlayApps::REQ_PARAM_ID];
61 10
        $locale = $query[GPlayApps::REQ_PARAM_LOCALE] ?? GPlayApps::DEFAULT_LOCALE;
62 10
        $country = $query[GPlayApps::REQ_PARAM_COUNTRY] ?? GPlayApps::DEFAULT_COUNTRY;
63
64 10
        $name = $appInfo[0][0];
65 10
        $description = ScraperUtil::html2text($appInfo[72][0][1]);
66 10
        $developer = $this->extractDeveloper($appInfo);
67
68 10
        $category = $this->extractCategory($appInfo[79][0][0] ?? []);
69 10
        $summary = $this->extractSummary($appInfo);
70 10
        $installsText = $appInfo[13][0] ?? null;
71 10
        $installs = $appInfo[13][2] ?? 0;
72 10
        $score = (float) ($appInfo[51][0][1] ?? 0);
73 10
        $numberVoters = (int) ($appInfo[51][2][1] ?? 0);
74 10
        $numberReviews = (int) ($appInfo[51][3][1] ?? 0);
75 10
        $histogramRating = $this->extractHistogramRating($appInfo[51][1]);
76
77 10
        $scriptDataPrice = $appInfo[57][0][0][0][0][1] ?? null;
78 10
        $price = $scriptDataPrice !== null ? $this->extractPrice($scriptDataPrice) : 0.0;
79 10
        $currency = $scriptDataPrice[0][1] ?? 'USD';
80 10
        $priceText = $scriptDataPrice[0][2] ?? null;
81
82 10
        $offersIAPCost = $appInfo[19][0] ?? null;
83 10
        $containsAds = (bool) ($appInfo[48][0] ?? false);
84
85 10
        $androidVersion = $appInfo[140][1][1][0][0][1] ?? null;
86 10
        $appVersion = $appInfo[140][0][0][0] ?? null;
87
88 10
        if ($androidVersion !== null) {
89 4
            $minAndroidVersion = preg_replace('~.*?(\d+(\.\d+)*).*~', '$1', $androidVersion);
90
        } else {
91 7
            $minAndroidVersion = null;
92
        }
93
94 10
        $editorsChoice = (bool) ScraperUtil::getValue($appInfo, 'ds:5.1.2.136.0.1.0');
95 10
        $privacyPoliceUrl = $appInfo[99][0][5][2] ?? '';
96 10
        $categoryFamily = $this->extractCategory($appInfo[118][0][0][0] ?? []);
97 10
        $icon = $this->extractIcon($appInfo);
98 10
        $cover = $this->extractCover($appInfo);
99 10
        $screenshots = $this->extractScreenshots($appInfo);
100 10
        $video = $this->extractVideo($appInfo);
101 10
        $contentRating = $appInfo[111][1] ?? '';
102 10
        $released = $this->convertDate($appInfo[10][1][0] ?? null);
103 10
        $updated = $this->convertDate($appInfo[145][0][1][0] ?? null);
104 10
        $recentChanges = $this->extractRecentChanges($appInfo);
105
106 10
        $reviewsInfo = ScraperUtil::getValue($scriptData, 'ds:8.0');
107 10
        $reviews = $this->extractReviews(new AppId($id, $locale, $country), $reviewsInfo);
0 ignored issues
show
Bug introduced by
It seems like $reviewsInfo can also be of type null; however, parameter $data of Nelexa\GPlay\Scraper\App...raper::extractReviews() does only seem to accept array, 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

107
        $reviews = $this->extractReviews(new AppId($id, $locale, $country), /** @scrutinizer ignore-type */ $reviewsInfo);
Loading history...
108
109 10
        return AppInfo::newBuilder()
110 10
            ->setId($id)
111 10
            ->setLocale($locale)
112 10
            ->setCountry($country)
113 10
            ->setName($name)
114 10
            ->setDescription($description)
115 10
            ->setSummary($summary)
116 10
            ->setIcon($icon)
117 10
            ->setCover($cover)
118 10
            ->setScreenshots($screenshots)
119 10
            ->setDeveloper($developer)
120 10
            ->setCategory($category)
121 10
            ->setCategoryFamily($categoryFamily)
122 10
            ->setVideo($video)
123 10
            ->setRecentChanges($recentChanges)
124 10
            ->setEditorsChoice($editorsChoice)
125 10
            ->setPrivacyPoliceUrl($privacyPoliceUrl)
126 10
            ->setInstalls($installs)
127 10
            ->setInstallsText($installsText)
128 10
            ->setScore($score)
129 10
            ->setRecentChanges($recentChanges)
130 10
            ->setEditorsChoice($editorsChoice)
131 10
            ->setPrivacyPoliceUrl($privacyPoliceUrl)
132 10
            ->setInstalls($installs)
133 10
            ->setScore($score)
134 10
            ->setNumberVoters($numberVoters)
135 10
            ->setHistogramRating($histogramRating)
136 10
            ->setPrice($price)
137 10
            ->setCurrency($currency)
138 10
            ->setPriceText($priceText)
139 10
            ->setOffersIAPCost($offersIAPCost)
140 10
            ->setContainsAds($containsAds)
141 10
            ->setAppVersion($appVersion)
142 10
            ->setAndroidVersion($androidVersion)
143 10
            ->setMinAndroidVersion($minAndroidVersion)
144 10
            ->setContentRating($contentRating)
145 10
            ->setReleased($released)
146 10
            ->setUpdated($updated)
147 10
            ->setNumberReviews($numberReviews)
148 10
            ->setReviews($reviews)
149 10
            ->buildDetailInfo()
150
        ;
151
    }
152
153
    /**
154
     * @param array $appInfo
155
     *
156
     * @return string|null
157
     */
158 10
    private function extractSummary(array $appInfo): ?string
159
    {
160 10
        return empty($appInfo[73][0][1])
161
            ? null
162 10
            : ScraperUtil::html2text(str_replace("\n", ' ', $appInfo[73][0][1]));
163
    }
164
165
    /**
166
     * @param array $appInfo
167
     *
168
     * @return Developer
169
     */
170 10
    private function extractDeveloper(array $appInfo): Developer
171
    {
172 10
        $developerPage = GPlayApps::GOOGLE_PLAY_URL . $appInfo[68][1][4][2];
173 10
        $developerId = Query::parse(parse_url($developerPage, \PHP_URL_QUERY))[GPlayApps::REQ_PARAM_ID];
174 10
        $developerName = $appInfo[68][0];
175 10
        $developerEmail = $appInfo[69][1][0] ?? null;
176 10
        $developerWebsite = $appInfo[69][0][5][2] ?? null;
177 10
        $developerAddress = $appInfo[69][2][0] ?? null;
178
179 10
        return new Developer(
180 10
            Developer::newBuilder()
181 10
                ->setId($developerId)
182 10
                ->setUrl($developerPage)
183 10
                ->setName($developerName)
184 10
                ->setEmail($developerEmail)
185 10
                ->setAddress($developerAddress)
186 10
                ->setWebsite($developerWebsite)
187
        );
188
    }
189
190
    /**
191
     * @param array $data
192
     *
193
     * @return Category|null
194
     */
195 10
    private function extractCategory(array $data): ?Category
196
    {
197 10
        if (isset($data[1][4][2], $data[0], $data[2])) {
198 10
            $categorySlug = (string) $data[2];
199 10
            $categoryName = (string) $data[0];
200
201 10
            return new Category($categorySlug, $categoryName);
202
        }
203
204 9
        return null;
205
    }
206
207
    /**
208
     * @param array|null $data
209
     *
210
     * @return HistogramRating
211
     */
212 10
    private function extractHistogramRating(array $data): HistogramRating
213
    {
214 10
        return new HistogramRating(
215 10
            $data[1][1] ?? 0,
216 10
            $data[2][1] ?? 0,
217 10
            $data[3][1] ?? 0,
218 10
            $data[4][1] ?? 0,
219 10
            $data[5][1] ?? 0
220
        );
221
    }
222
223
    /**
224
     * @param array $scriptDataPrice
225
     *
226
     * @return float
227
     */
228 10
    protected function extractPrice(array $scriptDataPrice): ?float
229
    {
230 10
        return isset($scriptDataPrice[0][0])
231 10
            ? (float) ($scriptDataPrice[0][0] / 1000000)
232 10
            : 0.0;
233
    }
234
235
    /**
236
     * @param array $data
237
     *
238
     * @return GoogleImage|null
239
     */
240 10
    protected function extractIcon(array $data): ?GoogleImage
241
    {
242 10
        return empty($data[95][0][3][2])
243
            ? null
244 10
            : new GoogleImage($data[95][0][3][2]);
245
    }
246
247
    /**
248
     * @param array $data
249
     *
250
     * @return GoogleImage|null
251
     */
252 10
    protected function extractCover(array $data): ?GoogleImage
253
    {
254 10
        return empty($data[96][0][3][2])
255
            ? null
256 10
            : new GoogleImage($data[96][0][3][2]);
257
    }
258
259
    /**
260
     * @param array $data
261
     *
262
     * @return GoogleImage[]
263
     */
264 10
    private function extractScreenshots(array $data): array
265
    {
266 10
        return !empty($data[78][0][0][3][2]) ? array_map(
267 10
            static function (array $v) {
268 10
                return new GoogleImage($v[3][2]);
269
            },
270 10
            $data[78][0]
271 10
        ) : [];
272
    }
273
274
    /**
275
     * @param array $data
276
     *
277
     * @return Video|null
278
     */
279 10
    private function extractVideo(array $data): ?Video
280
    {
281 10
        if (isset($data[100][0][0][4], $data[100][0][1][3][3])) {
282
            $videoThumb = (string) $data[100][0][1][3][2];
283
            $youtubeId = (string) $data[100][0][4];
284
            $youtubeId = str_replace('yt:', '', strtok($youtubeId, '?'));
285
            $videoUrl = 'https://www.youtube.com/embed/' . $youtubeId . '?ps=play&vq=large&rel=0&autohide=1&showinfo=0';
286
287
            return new Video($videoThumb, $videoUrl);
288
        }
289
290 10
        return null;
291
    }
292
293
    /**
294
     * @param ?int $timestamp
295
     *
296
     * @return \DateTimeInterface|null
297
     */
298 10
    private function convertDate(?int $timestamp): ?\DateTimeInterface
299
    {
300 10
        if ($timestamp !== null) {
301 10
            return DateStringFormatter::unixTimeToDateTime($timestamp);
302
        }
303
304 2
        return null;
305
    }
306
307
    /**
308
     * @param array $data
309
     *
310
     * @return string|null
311
     */
312 10
    protected function extractRecentChanges(array $data): ?string
313
    {
314 10
        return empty($data[144][1][1])
315 2
            ? null
316 10
            : ScraperUtil::html2text($data[144][1][1]);
317
    }
318
319
    /**
320
     * @param AppId $appId
321
     * @param array $data
322
     *
323
     * @return Review[]
324
     */
325 10
    private function extractReviews(AppId $appId, array $data): array
326
    {
327 10
        if (empty($data[0])) {
328 1
            return [];
329
        }
330
331 10
        return ReviewsExtractor::extractReviews($appId, $data);
332
    }
333
}
334