File Coverage

blib/lib/WWW/BurrpTV.pm
Criterion Covered Total %
statement 142 161 88.2
branch 37 52 71.1
condition 3 6 50.0
subroutine 16 17 94.1
pod 4 5 80.0
total 202 241 83.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             WWW::BurrpTV - Parse tv.burrp.com for TV listings.
4              
5             =head1 SYNOPSIS
6              
7             use WWW::BurrpTV;
8              
9             my $tv = WWW::BurrpTV->new (
10             cache => '/tmp/module',
11             );
12              
13             $tv->timezone('Asia/Bahrain');
14              
15             # Get current playing show on Discovery Channel
16              
17             my $shows = $tv->get_shows(channel => 'Discovery Channel');
18              
19             my $current_playing_show = $$shows[0]; # First item in the array is the current playing show.
20              
21             print $current_playing_show->{_show};
22             print $current_playing_show->{_time12};
23             print $current_playing_show->{_link};
24              
25             =head1 DESCRIPTION
26              
27             =head2 Overview
28              
29             WWW::BurrpTV is an object oriented interface to parse TV listings from tv.burrp.com.
30              
31             =cut
32              
33             package WWW::BurrpTV;
34              
35 3     3   3304 use File::Path qw(make_path);
  3         7  
  3         212  
36 3     3   3023 use Path::Abstract qw(--no_0_093_warning);
  3         442690  
  3         22  
37 3     3   4651 use HTML::TreeBuilder;
  3         129804  
  3         47  
38 3     3   4657 use DateTime;
  3         1047686  
  3         124  
39 3     3   3487 use LWP::UserAgent;
  3         143476  
  3         99  
40 3     3   28 use Carp;
  3         6  
  3         247  
41 3     3   3254 use Data::Dumper;
  3         18463  
  3         224  
42              
43 3     3   24 use strict;
  3         6  
  3         100  
44 3     3   18 use warnings;
  3         4  
  3         197  
45              
46             our @ISA = qw();
47              
48             our $VERSION = '0.03';
49              
50 3         304 use vars qw (
51             %CHANNELS
52             $UA
53             $TIMEZONE
54 3     3   16 );
  3         6  
55              
56             BEGIN {
57 3     3   6 our %CHANNELS;
58 3         31 our $UA = LWP::UserAgent->new();
59 3         9540 $UA->env_proxy;
60 3         55387 our $TIMEZONE = 'Asia/Kolkata'; # Default timezone
61             }
62              
63             =head1 CONSTRUCTOR
64              
65             =over 4
66              
67             =item new()
68              
69             Object's constructor. Takes an optional hash argument which can be used to cache the list of channels. This does not cache the TV listings.
70              
71             my $tv = WWW::BurrpTV->new (
72             cache => '/tmp/module', # Path to use as cache (optional). Make sure the path exists.
73             );
74             =cut
75              
76             sub new {
77 4     4 1 2667 my ($class,%args) = @_;
78 4         9 my $cache = 0;
79 4         14 my $self = bless({}, $class);
80 4         9 my $write = 0;
81             # if ((exists $args{cache}) && (!-d $args{cache})) { carp 'Directory does not exist. Channel list will not be cached.'; }
82 4 50 66     116 if ((exists $args{cache}) && (!-d $args{cache})) { make_path($args{cache}); $write = 1; }
  0         0  
  0         0  
83 4         10 else { $write = 1; }
84              
85             #$cache = $args{cache}.'/cache' if exists $args{cache};
86 4 100       54 $cache = Path::Abstract->new($args{cache})->child('cache')->stringify if exists $args{cache};
87 4         516 delete $args{cache}; # No longed needed.
88              
89 4         19 for my $key (keys %args) { carp "Ignored invalid argument ($key)"; }
  0         0  
90              
91 4         8 my $html;
92              
93 4 100       24 if ($cache) {
94 3 50       155 open my $cachefile, '<', $cache or goto skip_read_cache;
95 3         89 while (<$cachefile>) { $html .= $_; }
  4170         8550  
96 3         79 close $cachefile;
97             }
98              
99             skip_read_cache:
100              
101 4 100       15 if (!$html) {
102 1         3 my $channel_list_url = 'http://tv.burrp.com/channels.html';
103 1         7 my $http = $UA->get($channel_list_url);
104 1         126430 $html = $http->decoded_content;
105 1 50       5411 croak $http->status_line unless $http->is_success;
106              
107 1 50 33     123 if (($cache) && ($write)) {
108 0         0 my $write_success = 0;
109 0 0       0 open my $cachefile, '>', $cache or carp $!;
110 0         0 print {$cachefile} $html;
  0         0  
111 0         0 close $cachefile;
112             }
113             }
114             ##
115            
116            
117 4         52 my $tree = HTML::TreeBuilder->new_from_content($html);
118             # my $parsed = $tree->parse($html);
119             # $tree->eof;
120              
121             # my $channel_array = $parsed->{'_content'}[1]{'_content'}[2]{'_content'}[0]{'_content'}[1]{'_content'}[1]{'_content'}[1]{'_content'}[1]{'_content'};
122              
123              
124             # for (@$channel_array) {
125             # my $channel_name = $_->{_content}[0]->{_content}[0];
126             # my $link = $_->{_content}[0]->{href};
127             # $channel_name =~ s/^\s*(.*?)\s*$/$1/;
128             # $CHANNELS{$channel_name} = 'http://tv.burrp.com'.$link;
129             # } print Dumper \%CHANNELS;
130              
131             # $tree = $tree->delete();
132             # $parsed = $parsed->delete();
133 4         1159201 for (@{$tree->extract_links('a', 'href')})
  4         69  
134             {
135 1464         101439 my($link, $el, $attr, $tag) = @$_;
136 1464 100       5247 next unless $link =~ /\/channel\/[a-z\-]+\/\d+\//;
137 1142         2927 my $channel_name = $el->as_text;
138 1142         26418 $channel_name =~ s/^\s*(.*?)\s*$/$1/;
139 1142 100       2798 next unless $channel_name;
140 638 100       1204 next if $channel_name eq 'New';
141 592         2352 $CHANNELS{$channel_name} = 'http://tv.burrp.com'.$link;
142             }
143 4         587 $tree->delete;
144 4         38770 return $self;
145             }
146              
147             =back
148              
149             =head1 METHODS
150              
151             =over 4
152              
153             =item channel_list()
154              
155             This method does not take any arguments, and returns a hashref.
156              
157             $tv->channel_list();
158              
159             =item timezone()
160              
161             Change the default timezone (Asia/Kolkata).
162              
163             $tv->timezone('Europe/Monaco');
164              
165             =item get_shows()
166              
167             Takes the channel name and an optional timezone as arguments, and returns an arrayref.
168              
169             $tv->get_shows (
170             channel => 'Discovery Channel',
171             timezone => 'Asia/Bahrain', # Optional
172             );
173             =cut
174              
175             sub channel_list {
176 3     3 1 1648 return \%CHANNELS;
177             }
178              
179              
180              
181              
182             sub timezone {
183 0     0 1 0 my ($class,$new_timezone) = @_;
184 0         0 my ($success,$error) = verify_timezone($new_timezone);
185              
186 0 0       0 if ($success) { $TIMEZONE = $new_timezone; }
  0         0  
187 0         0 else { carp $error; }
188 0         0 return;
189             }
190              
191             sub verify_timezone {
192 1     1 0 3 my ($timezone_to_verify) = @_;
193 1         4 my $dt_object = DateTime->now();
194 1         237 eval { $dt_object->set_time_zone($timezone_to_verify); };
  1         4  
195 1         2250 my $error = $@;
196 1         2 chomp $error;
197              
198 1 50       5 return (0,$error) if $@;
199 1         6 return 1;
200             }
201              
202             sub get_shows {
203              
204 2     2 1 912 my ($class,%args) = @_;
205             #my $self = bless({}, $class); #testing.
206 2 50       13 if (!exists $args{'channel'}) { croak 'Channel not specified.'; }
  0         0  
207 2         5 my $input_channel = $args{'channel'};
208 2         6 delete $args{'channel'}; # Don't need it anymore.
209 2         5 my $timezone_override = $TIMEZONE;
210              
211 2 100       8 if (exists $args{'timezone'}) {
212 1         5 my ($success,$error) = verify_timezone($args{'timezone'});
213 1 50       4 if ($success) { $timezone_override = $args{'timezone'}; }
  1         3  
214 0         0 else { carp $error; }
215 1         3 delete $args{'timezone'}; # Don't need it anymore.
216             }
217              
218 2         8 for my $key (keys %args) { carp "Ignored invalid argument ($key)"; }
  0         0  
219              
220 2 100       134 for (keys %CHANNELS) { if (lc($_) eq lc($input_channel)) { $input_channel = $_; } }
  190         316  
  2         6  
221              
222 2 50       17 croak 'Invalid channel' if !exists $CHANNELS{$input_channel};
223              
224 2         4 my $url = $CHANNELS{$input_channel};
225              
226 2         19 my $http = $UA->get($url);
227 2 50       140499 croak $http->status_line unless $http->is_success;
228 2         42 my $html = $http->decoded_content;
229              
230 2         8183 my $tree = HTML::TreeBuilder->new_from_content($html);
231             # my $parsed = $tree->parse($html);
232             # $tree->eof;
233              
234 2         790827 my $today = 1;
235 2         9 my @listing_today = qw();
236 2         4 my @listing_tomorrow = qw();
237 2         5 my @listing = qw();
238            
239 2 100   228   40 my @elements = $tree->look_down('_tag','td',sub { $_[0]->{class} =~ /resultTime/ if $_[0]->{class}});
  228         21659  
240 2         565 for my $element (@elements) {
241 68         10897 $element = $element->parent;
242             push @listing, {
243             _channel => $input_channel,
244             _original_time => eval {
245 68         166 my ($time) = $element->look_down('_tag','b','class','from')->as_text =~ /((?:\d{1,2}:?){2})/;
246 68         6233 return $time;
247             },
248             _am_or_pm => $element->look_down('_tag','sup','class','ap')->as_text,
249             _show_link => 'http://tv.burrp.com'.$element->look_down('_tag','a')->{href},
250 68         10725 _show => eval { my ($show) = $element->look_down('_tag','strong')->as_text =~ /(.*?) $/; return $show; },
  68         7844  
251 68         357 _episode => eval {
252 68         181 my $full_title = $element->look_down('_tag','a','class','title')->as_text;
253 68         8764 my $title = quotemeta($element->look_down('_tag','strong')->as_text);
254 68         8674 $full_title =~ s/\s+$title\s+:\s+//;
255 68         358 $full_title =~ s/\s+$//;
256 68         283 return $full_title;
257             },
258             _image => 'http://tv.burrp.com'.$element->look_down('_tag','img')->{src},
259             };
260 68         5879 $element->delete;
261             }
262            
263 2         239 for (@listing) {
264 68         165 my $show = $_->{_show};
265 68         174 my $time = $_->{_original_time};
266 68         105 my $am_or_pm = $_->{_am_or_pm};
267 68         94 my $show_link = $_->{_show_link};
268 68         115 my $episode = $_->{_episode};
269 68 50       137 undef $episode if !$episode;
270 68         52 my $season;
271 68 100       328 ($show,$season) = $show =~ /(.*?) \(Season (\d+)\)/ if $show =~ /Season \d+/;
272             ################## TIME CONVERSION ###############################
273 68         280 my ($hour,$minutes) = $time =~ /(\d+):(\d+)/;
274             #my $nt = normalize_hms($hour,$minutes,0,$am_or_pm);
275              
276 68         217 my $dt = DateTime->now(time_zone => 'Asia/Kolkata');
277              
278             $dt = DateTime->new (
279             time_zone => 'Asia/Kolkata', # Timezone used by tv.burrp.com
280             year => $dt->year,
281             month => $dt->month,
282             day => $dt->day,
283             #hour => $nt->{h24},
284 68         605884 hour => eval {
285 68 100       666 if ($am_or_pm eq 'PM') {
286 35 100       73 if ($hour == 12) { return $hour; }
  3         14  
287 32         34 else { $hour += 12; return $hour; }
  32         113  
288             }
289              
290             else {
291 33 100       64 if ($hour == 12) { return 0; }
  5         21  
292 28         104 else { return $hour; }
293             }
294             },
295              
296             minute => $minutes,
297             );
298              
299              
300 68         22536 $dt->set_time_zone($timezone_override); # Convert to required timezone
301             #my $minute = $dt->minute;
302             #$minute =~ s/^(\d)$/0$1/ if $minute =~ /^\d$/; # Prefix 0 for single digit numbers
303             #my $human_time = $dt->hour_12.':'.$minute.' '.$dt->am_or_pm;
304             ##################################################################
305              
306 68         8617 my $show_info = {
307             _channel => $input_channel,
308             _time24 => $dt->strftime('%H:%M'),
309             _time12 => $dt->strftime('%I:%M %p'),
310             _show => $show,
311             _link => $show_link,
312             _season => $season,
313             _episode => $episode,
314             };
315              
316 68 50       5423 if ($today) { push @listing_today,$show_info; }
  68         346  
317 0         0 else { push @listing_tomorrow,$show_info; }
318              
319             }
320              
321            
322            
323 2         12 $tree->delete;
324 2         12628 @listing = (@listing_today,@listing_tomorrow);
325 2         421 return \@listing;
326 0           exit;
327              
328             }
329              
330             1;
331             __END__