Resolver   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 26
c 9
b 0
f 0
lcom 1
cbo 12
dl 0
loc 301
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getAllLessons() 0 15 2
A getLatestLessons() 0 9 1
A getAllPage() 0 6 1
B doAuth() 0 38 4
A downloadSerieEpisode() 0 13 1
A downloadLesson() 0 12 1
A getPage() 0 6 1
A getRedirectUrl() 0 10 1
A getNameOfEpisode() 0 6 1
C downloadLessonFromPath() 0 69 12
1
<?php
2
/**
3
 * Http functions
4
 */
5
6
namespace App\Http;
7
8
use App\Downloader;
9
use App\Exceptions\NoDownloadLinkException;
10
use App\Exceptions\SubscriptionNotActiveException;
11
use App\Html\Parser;
12
use App\Utils\Utils;
13
use GuzzleHttp\Client;
14
use GuzzleHttp\Cookie\CookieJar;
15
use GuzzleHttp\Event\ProgressEvent;
16
use Ubench;
17
18
/**
19
 * Class Resolver
20
 * @package App\Http
21
 */
22
class Resolver
23
{
24
    /**
25
     * Guzzle client
26
     * @var Client
27
     */
28
    private $client;
29
30
    /**
31
     * Guzzle cookie
32
     * @var CookieJar
33
     */
34
    private $cookie;
35
36
    /**
37
     * Ubench lib
38
     * @var Ubench
39
     */
40
    private $bench;
41
42
    /**
43
     * Retry download on connection fail
44
     * @var int
45
     */
46
    private $retryDownload = false;
47
48
    /**
49
     * Receives dependencies
50
     *
51
     * @param Client $client
52
     * @param Ubench $bench
53
     * @param bool $retryDownload
54
     */
55
    public function __construct(Client $client, Ubench $bench, $retryDownload = false)
56
    {
57
        $this->client = $client;
58
        $this->cookie = new CookieJar();
59
        $this->bench = $bench;
60
        $this->retryDownload = $retryDownload;
0 ignored issues
show
Documentation Bug introduced by
The property $retryDownload was declared of type integer, but $retryDownload is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
61
    }
62
63
    /**
64
     * Grabs all lessons & series from the website.
65
     */
66
    public function getAllLessons()
67
    {
68
        $array = [];
69
        $html = $this->getAllPage();
70
        Parser::getAllLessons($html, $array);
71
72
        while ($nextPage = Parser::hasNextPage($html)) {
73
            $html = $this->client->get($nextPage, ['verify' => false])->getBody()->getContents();
74
            Parser::getAllLessons($html, $array);
75
        }
76
77
        Downloader::$currentLessonNumber = count($array['lessons']);
78
79
        return $array;
80
    }
81
82
    /**
83
     * Gets the latest lessons only.
84
     *
85
     * @return array
86
     *
87
     * @throws \Exception
88
     */
89
    public function getLatestLessons()
90
    {
91
        $array = [];
92
93
        $html = $this->getAllPage();
94
        Parser::getAllLessons($html, $array);
95
96
        return $array;
97
    }
98
99
    /**
100
     * Gets the html from the all page.
101
     *
102
     * @return string
103
     *
104
     * @throws \Exception
105
     */
106
    private function getAllPage()
107
    {
108
        $response = $this->client->get(LARACASTS_ALL_PATH, ['verify' => false]);
109
110
        return $response->getBody()->getContents();
111
    }
112
113
    /**
114
     * Tries to auth.
115
     *
116
     * @param $email
117
     * @param $password
118
     *
119
     * @return bool
120
     * @throws SubscriptionNotActiveException
121
     */
122
    public function doAuth($email, $password)
123
    {
124
        $response = $this->client->get(LARACASTS_LOGIN_PATH, [
125
            'cookies' => $this->cookie,
126
            'verify' => false
127
        ]);
128
129
        $token = Parser::getToken($response->getBody()->getContents());
130
131
        $response = $this->client->post(LARACASTS_POST_LOGIN_PATH, [
132
            'cookies' => $this->cookie,
133
            'body'    => [
134
                'email'    => $email,
135
                'password' => $password,
136
                '_token'   => $token,
137
                'remember' => 1,
138
            ],
139
            'verify' => false
140
        ]);
141
142
        $html = $response->getBody()->getContents();
143
144
        if (strpos($html, "Reactivate") !== FALSE) {
145
            throw new SubscriptionNotActiveException();
146
        }
147
148
        if(strpos($html, "The email must be a valid email address.") !== FALSE) {
149
            return false;
150
        }
151
152
        // user doesnt provided an email in the .env
153
        // laracasts redirects to login page again
154
        if(strpos($html, 'name="password"') !== FALSE) {
155
            return false;
156
        }
157
158
        return strpos($html, "verify your credentials.") === FALSE;
159
    }
160
161
    /**
162
     * Download the episode of the serie.
163
     *
164
     * @param $serie
165
     * @param $episode
166
     * @return bool
167
     */
168
    public function downloadSerieEpisode($serie, $episode)
169
    {
170
        $path = LARACASTS_SERIES_PATH . '/' . $serie . '/episodes/' . $episode;
171
        $episodePage = $this->getPage($path);
172
        $name = $this->getNameOfEpisode($episodePage, $path);
173
        $number = sprintf("%02d", $episode);
174
        $saveTo = BASE_FOLDER . '/' . SERIES_FOLDER . '/' . $serie . '/' . $number . '-' . $name . '.mp4';
175
        Utils::writeln(sprintf("Download started: %s . . . . Saving on " . SERIES_FOLDER . '/' . $serie . ' folder.',
176
            $number . ' - ' . $name
177
        ));
178
179
        return $this->downloadLessonFromPath($episodePage, $saveTo);
180
    }
181
182
    /**
183
     * Downloads the lesson.
184
     *
185
     * @param $lesson
186
     * @return bool
187
     */
188
    public function downloadLesson($lesson)
189
    {
190
        $path = LARACASTS_LESSONS_PATH . '/' . $lesson;
191
        $number = sprintf("%04d", Downloader::$totalLocalLessons + Downloader::$currentLessonNumber--);
192
        $saveTo = BASE_FOLDER . '/' . LESSONS_FOLDER . '/' . $number . '-' . $lesson . '.mp4';
193
194
        Utils::writeln(sprintf("Download started: %s . . . . Saving on " . LESSONS_FOLDER . ' folder.',
195
            $lesson
196
        ));
197
        $html = $this->getPage($path);
198
        return $this->downloadLessonFromPath($html, $saveTo);
199
    }
200
201
    /**
202
     * Helper function to get html of a page
203
     * @param $path
204
     * @return string
205
     */
206
    private function getPage($path) {
207
        return $this->client
208
            ->get($path, ['cookies' => $this->cookie, 'verify' => false])
209
            ->getBody()
210
            ->getContents();
211
    }
212
213
    /**
214
     * Helper to get the Location header.
215
     *
216
     * @param $url
217
     *
218
     * @return string
219
     */
220
    private function getRedirectUrl($url)
221
    {
222
        $response = $this->client->get($url, [
223
            'cookies'         => $this->cookie,
224
            'allow_redirects' => FALSE,
225
            'verify' => false
226
        ]);
227
228
        return $response->getHeader('Location');
229
    }
230
231
    /**
232
     * Gets the name of the serie episode.
233
     *
234
     * @param $html
235
     *
236
     * @param $path
237
     * @return string
238
     */
239
    private function getNameOfEpisode($html, $path)
240
    {
241
        $name = Parser::getNameOfEpisode($html, $path);
242
243
        return Utils::parseEpisodeName($name);
244
    }
245
246
    /**
247
     * Helper to download the video.
248
     *
249
     * @param $html
250
     * @param $saveTo
251
     * @return bool
252
     */
253
    private function downloadLessonFromPath($html, $saveTo)
254
    {
255
        try {
256
            $downloadUrl = Parser::getDownloadLink($html);
257
            $viemoUrl = $this->getRedirectUrl($downloadUrl);
258
            $finalUrl = $this->getRedirectUrl($viemoUrl);
259
        } catch(NoDownloadLinkException $e) {
260
            Utils::write(sprintf("Can't download this lesson! :( No download button"));
261
262
            try {
263
                Utils::write(sprintf("Tring to find a Wistia.net video"));
264
                $Wistia = new Wistia($html,$this->bench);
265
                $finalUrl = $Wistia->getDownloadUrl();
266
            } catch(NoDownloadLinkException $e) {
267
                return false;
268
            }
269
270
        }
271
272
        $this->bench->start();
273
274
        $retries = 0;
275
        while (true) {
276
            try {
277
                $downloadedBytes = file_exists($saveTo) ? filesize($saveTo) : 0;
278
                $req = $this->client->createRequest('GET', $finalUrl, [
279
                    'save_to' => fopen($saveTo, 'a'),
280
                    'verify' => false,
281
                    'headers' => [
282
                        'Range' => 'bytes=' . $downloadedBytes . '-'
283
                    ]
284
                ]);
285
286
                if (php_sapi_name() == "cli") { //on cli show progress
287
                    $req->getEmitter()->on('progress', function (ProgressEvent $e) use ($downloadedBytes) {
288
                        printf("> Total: %d%% Downloaded: %s of %s     \r",
289
                            Utils::getPercentage($e->downloaded + $downloadedBytes, $e->downloadSize),
290
                            Utils::formatBytes($e->downloaded + $downloadedBytes),
291
                            Utils::formatBytes($e->downloadSize));
292
                    });
293
                }
294
295
                $response = $this->client->send($req); 
296
297
                if(strpos($response->getHeader('Content-Type'), 'text/html') !== FALSE) {
298
                    Utils::writeln(sprintf("Got HTML instead of the video file, the subscription is probably inactive"));
299
                    throw new SubscriptionNotActiveException();
300
                } 
301
302
                break;  
303
            } catch (\Exception $e) {
304
                if (is_a($e, SubscriptionNotActiveException::class) || !$this->retryDownload || ($this->retryDownload && $retries >= 3)) {
305
                    throw $e;
306
                }
307
                ++$retries;
308
                Utils::writeln(sprintf("Retry download after connection fail!     "));
309
                continue;
310
            }
311
        }
312
313
        $this->bench->end();
314
315
        Utils::write(sprintf("Elapsed time: %s, Memory: %s         ",
316
            $this->bench->getTime(),
317
            $this->bench->getMemoryUsage()
318
        ));
319
320
        return true;
321
    }
322
}
323