File Coverage

blib/lib/WWW/Coursera.pm
Criterion Covered Total %
statement 30 93 32.2
branch 0 30 0.0
condition n/a
subroutine 10 19 52.6
pod n/a
total 40 142 28.1


line stmt bran cond sub pod time code
1             package WWW::Coursera;
2              
3 1     1   29382 use strict;
  1         3  
  1         74  
4 1     1   7 use warnings;
  1         2  
  1         34  
5              
6 1     1   110 use 5.010;
  1         10  
  1         45  
7 1     1   1108 use Moo;
  1         579608  
  1         11  
8 1     1   12100 use Mojo::DOM;
  1         1359686  
  1         42  
9 1     1   1269 use Mojo::UserAgent;
  1         493659  
  1         19  
10 1     1   1980 use AnyEvent;
  1         6521  
  1         43  
11 1     1   1114 use AnyEvent::Util 'fork_call';
  1         7953  
  1         165  
12             my $cv = AE::cv;
13 1     1   14 use File::Path qw( make_path );
  1         2  
  1         65  
14 1     1   7 use Carp qw(croak) ;
  1         2  
  1         1557  
15              
16             $ENV{MOJO_MAX_MESSAGE_SIZE} = 1073741824;
17              
18             =head1 NAME
19              
20             WWW::Coursera - Downloading parallel material (video, text, pdf ...) from Coursera.org online classes.
21              
22             =head1 VERSION
23              
24             version 0.08
25              
26             =cut
27              
28             our $VERSION = '0.08';
29              
30             =head2 username
31              
32             set username
33              
34             =cut
35              
36             has username => (
37             is => 'ro',
38             required => 1,
39             );
40              
41             =head2 password
42              
43             set password
44              
45             =cut
46              
47             has password => (
48             is => 'ro',
49             required => 1,
50             );
51              
52             =head2 course_id
53              
54             set course id
55              
56             =cut
57              
58             has course_id => (
59             is => 'ro',
60             required => 1,
61             );
62              
63             =head2 debug
64              
65             debug option
66              
67             =cut
68              
69             has debug => (
70             is => 'rw',
71             default => 0,
72             );
73              
74             =head2 max_parallel_download
75              
76             set max parallel http requests
77              
78             =cut
79              
80             has max_parallel_download => (
81             is => 'rw',
82             default => 2,
83             );
84              
85             =head2 override_existing_files
86              
87             set option ro override existing files
88              
89             =cut
90              
91             has override_existing_files => (
92             is => 'rw',
93             default => 0,
94             );
95              
96              
97             =head1 SYNOPSIS
98              
99             Scrape video materials from lectures area and download paralell related files.
100             The default download directory is set to the course_id.
101            
102             The only one requirement is to enroll the course online.
103              
104              
105             use WWW::Coursera;
106             my $init = WWW::Coursera->new(
107             username => 'xxxx', #is required
108             password => 'xxxx', #is required
109             course_id => "xxxx", #is required
110             debug => 1, #default disabled
111             max_parallel_download => 2, #default 2
112             override_existing_files => 1, #default false
113             );
114             $init->run;
115              
116             =head1 SUBROUTINES/METHODS
117              
118             =head2 directory
119              
120             Create new directory
121              
122             =cut
123              
124             sub directory {
125 0     0     my $self = shift;
126 0 0         unless ( -d $self->{course_id} ) {
127 0 0         make_path $self->{course_id} or die "Failed to create path:
128             $self->{course_id}";
129             }
130             }
131              
132             =head2 extentions
133              
134             Definition of downoading extentions
135              
136             =cut
137              
138             sub extentions {
139 0     0     my $self = shift;
140 0           my @extention = ( "mp4", "txt", "pdf", "pptx", "srt" );
141 0           return @extention;
142             }
143              
144             =head2 UserAgent
145              
146             Create UserAgent object
147              
148             =cut
149              
150             sub UserAgent {
151 0     0     my $self = shift;
152 0           my $ua = Mojo::UserAgent->new;
153 0           $ua = $ua->max_redirects(1);
154 0           $self->{ua} = $ua;
155             }
156              
157              
158             =head2 csrf
159              
160             Save csrf token for authentication
161              
162             =cut
163              
164             sub csrf {
165 0     0     my $self = shift;
166 0           $self->UserAgent;
167 0           my $tx =
168             $self->{ua}
169             ->get("https://class.coursera.org/$self->{course_id}/lecture/index");
170 0           my $csrf = $tx->res->cookies->[0]->{value};
171 0 0         croak "Error: No CSRF key available my be the couse is not available"
172             unless $csrf;
173 0           $self->{csrf} = $csrf;
174 0 0         say "The CSRF key is : $csrf" if $self->debug;
175             }
176              
177             =head2 login
178              
179             Login with username, password and csrftoken
180              
181             =cut
182              
183             sub login {
184 0     0     my $self = shift;
185 0           $self->csrf;
186 0           my $tx = $self->{ua}->post(
187             'https://accounts.coursera.org/api/v1/login' => {
188             'Cookie' => "csrftoken=$self->{csrf}",
189             'X-CSRFToken' => "$self->{csrf}"
190             } => form =>
191             { email => "$self->{username}", password => "$self->{password}" }
192             );
193 0 0         say "The http response code from login page is :" . $tx->res->code
194             if $self->debug;
195 0 0         unless ( $tx->res->code == 200 ) {
196 0           my ( $err1, $code1 ) = $tx->error;
197 0 0         say $code1 ? "$code1 response: $err1" : "Connection error: $err1";
198             }
199             }
200              
201             =head2 convert_filename
202              
203             Replace all non word chars with underscore
204              
205             =cut
206              
207             sub convert_filename {
208 0     0     my ( $self, $string, $ext ) = @_;
209 0           $string =~ s/\W/_/g;
210 0           $string =~ s/__/_/g;
211 0           $string = "$string" . ".$ext";
212 0           $string =~ s/_\././g;
213 0 0         say "Convert string $string" if $self->debug;
214 0           return $string;
215             }
216              
217             =head2 extract_urls
218              
219             Scrape urls from lectures
220              
221             =cut
222              
223             sub extract_urls {
224 0     0     my $self = shift;
225 0           $self->login;
226 0           my %urls;
227 0           my $r =
228             $self->{ua}->get("https://class.coursera.org/$self->{course_id}/lecture");
229 0 0         if ( my $res = $r->success ) {
230 0           my $dom = $r->res->dom->html->body;
231             $dom->find('div.course-lecture-item-resource')->each(
232             sub {
233 0     0     my ( $e, $count ) = @_;
234             my $title = $e->find('a[data-if-linkable=modal-lock]')->each(
235             sub {
236 0           my ( $b, $cnt ) = @_;
237 0           my $file = $b->find('div.hidden')->text;
238 0           my $url = $b->attr('href');
239 0           foreach my $ext ( $self->extentions ) {
240 0 0         if ( "$url" =~ m/$ext/ ) {
241 0           my $conv_name =
242             $self->convert_filename( $file, $ext );
243 0           $urls{$conv_name} = "$url";
244             }
245             }
246             }
247 0           );
248             }
249 0           );
250 0           $self->{urls} = \%urls;
251             }
252             else {
253 0           my ( $err, $code ) = $res->error;
254 0 0         say $code ? "$code response: $err" : "Connection error: $err";
255             }
256             }
257              
258             =head2 download
259              
260             Download lectures in the course_id folder
261              
262             =cut
263              
264             sub download {
265 0     0     my ( $self, $file ) = @_;
266 0           say "Start download $file in $self->{course_id}";
267 0           my $url = $self->{urls}->{$file};
268 0           $self->directory;
269 0           my $path = "$self->{course_id}/$file";
270              
271 0 0         if ( $self->override_existing_files ) {
272 0           my $response = $self->{ua}->get( $url, { Accept => '*/*' } )->res;
273 0 0         open my $fh, '>', $path or die "Could not open [$file]: $!";
274 0           print $fh $response->body;
275             }
276             else {
277 0 0         if ( !-e $path ) {
278 0           my $response = $self->{ua}->get( $url, { Accept => '*/*' } )->res;
279 0 0         open my $fh, '>', $path or die "Could not open [$file]: $!";
280 0           print $fh $response->body;
281             }
282             }
283             }
284              
285              
286              
287             =head2 run
288              
289             Entry point of the package
290              
291             =cut
292              
293             sub run {
294             my $self = shift;
295             $AnyEvent::Util::MAX_FORKS = $self->max_parallel_download;
296             $self->extract_urls;
297             my @arr = keys $self->{urls};
298             foreach my $file (@arr) {
299             $cv->begin;
300             fork_call {
301             $self->download($file);
302             }
303             sub {
304             $cv->end;
305             }
306             }
307             $cv->recv;
308             }
309              
310              
311              
312              
313             =head1 AUTHOR
314              
315             Ovidiu N. Tatar, C<< >>
316              
317             =head1 BUGS
318              
319             Please report any bugs or feature requests to C, or through
320             the web interface at L. I will be notified, and then you'll
321             automatically be notified of progress on your bug as I make changes.
322              
323              
324             =head1 REQUIREMENT
325              
326             perl 5.010 or higher
327             Enrol course before start downloding
328             For more info regarding requires modules (see Build.PL)
329              
330             =head1 INSTALLATION
331              
332             To install this module, run the following commands:
333              
334             git clone https://github.com/ovntatar/WWW-Coursera.git
335             cd WWW-Coursera
336            
337             perl Build.PL
338             ./Build
339             ./Build test
340             ./Build install
341              
342             OR (if you don't have write permissions to create man3) use cpanminus:
343              
344             cpanm WWW-Coursera
345              
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc WWW::Coursera
352            
353             or
354            
355             https://github.com/ovntatar/WWW-Coursera/issues
356              
357              
358             You can also look for information at:
359              
360             =over 4
361              
362             =item * RT: CPAN's request tracker (report bugs here)
363              
364             L
365              
366             =item * AnnoCPAN: Annotated CPAN documentation
367              
368             L
369              
370             =item * CPAN Ratings
371              
372             L
373              
374             =item * Search CPAN
375              
376             L
377              
378             =back
379              
380              
381             =head1 ACKNOWLEDGEMENTS
382              
383              
384             =head1 LICENSE AND COPYRIGHT
385              
386             Copyright 2013 Ovidiu N. Tatar.
387              
388             This program is free software; you can redistribute it and/or modify it
389             under the terms of either: the GNU General Public License as published
390             by the Free Software Foundation; or the Artistic License.
391              
392             See http://dev.perl.org/licenses/ for more information.
393              
394              
395             =cut
396              
397             1; # End of WWW::Coursera
398              
399              
400              
401              
402              
403              
404