File Coverage

blib/lib/WWW/TV/Series.pm
Criterion Covered Total %
statement 44 131 33.5
branch 13 58 22.4
condition 4 14 28.5
subroutine 11 18 61.1
pod 12 12 100.0
total 84 233 36.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             WWW::TV::Series - Parse TV.com for TV Series information.
4              
5             =head1 SYNOPSIS
6              
7             use WWW::TV::Series qw();
8             my $series = WWW::TV::Series->new(name => 'Prison Break');
9              
10             my @episodes = $series->episodes;
11             print $series->summary;
12              
13             =head1 DESCRIPTION
14              
15             The L module parses TV.com series information using
16             L.
17              
18             =head1 METHODS
19              
20             =cut
21              
22             package WWW::TV::Series;
23 1     1   827 use strict;
  1         2  
  1         32  
24 1     1   5 use warnings;
  1         1  
  1         46  
25              
26             our $VERSION = '0.14';
27              
28 1     1   5 use Carp qw(croak);
  1         2  
  1         52  
29 1     1   4 use LWP::UserAgent qw();
  1         2  
  1         1992  
30              
31             =head2 new
32              
33             The new() method is the constructor. It takes the id of the show if
34             you have previously looked that up, or the name of the show which
35             will be used to perform a search and the id will be taken from the
36             first result.
37              
38             Optional parameters let you set the season number or LWP user agent.
39              
40             # default usage
41             my $series = WWW::TV::Series->new(name => 'Prison Break');
42             my $series = WWW::TV::Series->new(id => 31635);
43              
44             # change user-agent from the default of "libwww-perl/#.##"
45             my $series = WWW::TV::Series->new(id => 31635, agent => 'WWW::TV');
46              
47             It is recommended that you lookup the show first and use the ID,
48             otherwise you just don't know what will be returned.
49              
50             The constructor also takes a single scalar as an argument and does
51             it's best to figure out what you want. But due to some shows being
52             all digits as a name (e.g. "24"), use of this is not recommended
53             (and in future may be deprecated).
54              
55             =cut
56              
57             sub new {
58 1 50   1 1 778 my $class = ref $_[0] ? ref(shift) : shift;
59              
60 1         3 my %data;
61              
62 1 50       8 if (@_ == 1) {
    50          
63             # If they gave us a plain scalar argument, try our best to figure out
64             # what it is. Of course this dies in the arse if you want to search
65             # for a program with a name like '24'.
66 0 0       0 if ($_[0] =~ /^\d+$/) {
67 0         0 $data{id} = shift;
68             }
69             else {
70 0         0 $data{name} = shift;
71             }
72             }
73             elsif (scalar(@_) % 2 == 0) {
74 1         3 %data = @_;
75             }
76              
77 1         5 $data{agent} = $class->agent($data{agent});
78 1         14 $data{site} = $class->site ($data{site});
79            
80 1 50       5 $data{id} = $class->_get_first_search_result($data{name}, $data{agent}, $data{site})
81             if exists $data{name};
82              
83 1 50       3 croak 'No id or name given to constructor' unless exists $data{id};
84 1 50       8 croak "Invalid id: $data{id}" unless $data{id} =~ /^\d+$/;
85              
86 1   50     17 return bless {
87             id => $data{id},
88             _season => $data{season} || 0,
89             _agent => $data{agent},
90             _site => $data{site},
91             filled => { id => 1 },
92             }, $class;
93             }
94              
95             sub _get_first_search_result {
96 0     0   0 my ($class, $name, $agent, $site) = @_;
97              
98 0         0 my $ua = LWP::UserAgent->new( agent => $agent );
99 0         0 my $rc = $ua->get(
100             sprintf("http://%s.tv.com/search.php?type=Search&stype=ajax_search&search_type=program&qs=%s",
101             $site, $name)
102             );
103 0 0       0 croak "Unable to get search results for $name" unless $rc->is_success;
104              
105 0         0 for (split /\n/, $rc->content) {
106 0 0       0 next unless m{ 107 0         0 return $1;
108             }
109 0         0 croak 'Unable to find a show in the search results.';
110             }
111              
112             =head2 summary
113              
114             Returns a string containing basic information about this series.
115              
116             =cut
117              
118             sub summary {
119 0     0 1 0 my $self = shift;
120              
121 0 0       0 unless (exists $self->{filled}->{summary}) {
122 0         0 ($self->{summary}) = $self->_html =~ m{
123             (.*?)
124             }smx;
125 0         0 $self->{summary} =~ s/
/\n/g;
126 0         0 $self->{summary} =~ s/.*?<\/a>//g;
127 0         0 $self->{summary} =~ s/^\s*//;
128 0         0 $self->{summary} =~ s/\s*$//;
129 0         0 $self->{filled}->{summary} = 1;
130             }
131              
132 0         0 return $self->{summary};
133             }
134              
135             =head2 genres
136              
137             Returns a list of all the genres that TV.com have categorised this series as.
138              
139             # in scalar context, returns a comma-delimited string
140             my $genres = $series->genres;
141              
142             # in array context, returns an array
143             my @genres = $series->genres;
144              
145             =cut
146              
147             sub genres {
148 0     0 1 0 my $self = shift;
149              
150 0 0       0 unless (exists $self->{filled}->{genres}) {
151 0         0 my ($genres_row) = $self->_html =~ m{
152             ()
153             }x;
154              
155 0         0 $self->{genres} =
156             join(
157             ', ',
158 0         0 map { s/\s*(.*?)<\/a>\s*/$1/; $_ }
  0         0  
159             split(/,/, $genres_row)
160             );
161              
162 0         0 my @genres = split(/, /, $self->{genres});
163 0         0 $self->{genres} = \@genres;
164 0         0 $self->{filled}->{genres} = 1;
165             }
166              
167 0 0       0 return wantarray ? @{$self->{genres}} : join(', ', @{$self->{genres}});
  0         0  
  0         0  
168             }
169              
170             =head2 cast
171              
172             Returns a list of the cast members. The order is the same as they
173             appear on TV.com, which is most likely nothing to go by, but
174             in most cases is the main cast order.
175              
176             # in scalar context, returns a comma-delimited string
177             my $cast = $series->cast;
178              
179             # in array context, returns an array
180             my @cast = $series->cast;
181              
182             =cut
183              
184             sub cast {
185 0     0 1 0 my $self = shift;
186              
187 0 0       0 unless (exists $self->{filled}->{cast}) {
188 0         0 my ($cast_line) = $self->_html =~ m{
    (.*?)
};
189 0         0 my @cast;
190 0         0 for my $person (split /<\/li>/, $cast_line) {
191 0 0       0 next unless $person =~ m{(.*?)};
192 0         0 push @cast, $1;
193             }
194 0         0 $self->{cast} = \@cast;
195 0         0 $self->{filled}->{cast} = 1;
196             }
197              
198 0 0       0 return wantarray ? @{$self->{cast}} : join(', ', @{$self->{cast}});
  0         0  
  0         0  
199             }
200              
201             =head2 name
202              
203             Returns a string containing the name of the series.
204              
205             =cut
206              
207             sub name {
208 1     1 1 3 my $self = shift;
209              
210 1 50       9 unless (exists $self->{filled}->{name}) {
211 1         3 ($self->{name}) = $self->_html =~ m{
212             \n\n?
213            

(.*?):\s*Summary

\n
214             }x;
215 0         0 $self->{filled}->{name} = 1;
216             }
217              
218 0         0 return $self->{name};
219             }
220              
221             =head2 image
222              
223             Returns the url of an image that can be used to identify this series.
224              
225             =cut
226              
227             sub image {
228 0     0 1 0 my $self = shift;
229              
230 0 0       0 unless (exists $self->{filled}->{image}) {
231 0         0 ($self->{image}) = $self->_html =~ m{
232             \s*\n
233             (?:\n)?
234             \s*
235             }x;
236 0         0 $self->{filled}->{image} = 1;
237             }
238              
239 0         0 return $self->{image};
240             }
241              
242             =head2 episodes
243              
244             Returns an array of L objects in order.
245              
246             # All episodes
247             my @episodes = $series->episodes;
248              
249             # Episodes for season 2 only
250             my @episodes = $series->episodes( season => 2 );
251              
252             =cut
253              
254             sub episodes {
255 0     0 1 0 my $self = shift;
256              
257 0         0 my %args;
258 0 0       0 if (scalar(@_) % 2 == 0) {
259 0         0 %args = @_;
260             }
261              
262 0 0       0 my $season = exists $args{season} ? $args{season} : $self->{_season};
263              
264 0 0       0 unless ($self->{filled}->{episodes}->{$season}) {
265 0         0 my $ua = LWP::UserAgent->new(agent => $self->agent);
266 0         0 my $rc = $ua->get($self->episode_url($season));
267 0 0       0 croak sprintf('Unable to fetch episodes for series %d, season %d', $self->id, $season)
268             unless $rc->is_success;
269              
270 0         0 require WWW::TV::Episode;
271 0         0 my ($episode_line) = $rc->content =~ m{(.*)\n};
272 0         0 my @episodes =
273 0         0 grep { defined }
274             map {
275 0         0 my $ep;
276 0 0       0 if (m#(.*?)#) {
277 0         0 $ep = WWW::TV::Episode->new(id => $1, name => $2, agent => $self->agent);
278             }
279 0         0 $ep;
280             } split /<\/div>/, $episode_line;
281              
282 0         0 $self->{episodes}->{$season} = \@episodes;
283 0         0 $self->{filled}->{episodes}->{$season} = 1;
284             }
285              
286 0         0 return @{$self->{episodes}->{$season}};
  0         0  
287             }
288              
289             sub _html {
290 1     1   2 my $self = shift;
291              
292 1 50       5 unless ($self->{filled}->{html}) {
293 1         4 my $ua = LWP::UserAgent->new (agent => $self->agent);
294 1         3344 my $rc = $ua->get($self->url);
295 1 50       1271456 croak sprintf('Unable to fetch page for series %s', $self->id)
296             unless $rc->is_success;
297              
298 0         0 $self->{html} =
299             join(
300             "\n",
301 0         0 map { s/^\s*//; s/\s*$//; $_ }
  0         0  
  0         0  
302             split /\n/, $rc->content
303             );
304              
305 0         0 $self->{filled}->{html} = 1;
306             }
307              
308 0         0 return $self->{html};
309             }
310              
311             =head2 id
312              
313             The ID of this series, according to TV.com
314              
315             =cut
316              
317             sub id {
318 2     2 1 19 my $self = shift;
319              
320 2         240 return $self->{id};
321             }
322              
323             =head2 agent ($value)
324              
325             Returns the current user agent setting, and sets to $value if provided.
326              
327             =cut
328              
329             sub agent {
330 2     2 1 4 my $self = shift; # may be called as $self or $class
331 2         4 my $value = shift;
332              
333 2 100       6 if (ref $self) {
334 1 50       6 if (defined $value) {
335 0         0 $self->{_agent} = $value;
336             }
337 1   33     11 return ($self->{_agent} || LWP::UserAgent::_agent);
338             } else {
339 1   33     6 return ($value || LWP::UserAgent::_agent);
340             }
341             }
342              
343             =head2 site ($value)
344              
345             Returns the current mirror site setting, and sets to $value if provided.
346              
347             Default site is "www"; other options include: us, uk, au
348              
349             =cut
350              
351             sub site {
352 1     1 1 1 my $self = shift; # may be called as $self or $class
353 1         2 my $value = shift;
354              
355 1 50       4 if (ref $self) {
356 0 0       0 if (defined $value) {
357 0 0       0 if ($value =~ m#^(au|uk|us|www|)$#i) {
358 0         0 $self->{_site} = $value;
359             } else {
360 0         0 warn "Ignoring unknown site value: [$value]\n";
361             }
362             }
363 0   0     0 return ($self->{_site} || 'www');
364             } else {
365 1   50     8 return ($value || 'www');
366             }
367             }
368              
369             =head2 url
370              
371             Returns the url that was used to create this object.
372              
373             =cut
374              
375             sub url {
376 1     1 1 3 my $self = shift;
377              
378 1         4 return sprintf('http://%s.tv.com/show/%d/summary.html', $self->{_site}, $self->id);
379             }
380              
381             =head2 episode_url ($season)
382              
383             Returns the url that is used to get the episode listings for this
384             series.
385              
386             $season is optional ; defaults to "all"
387              
388             =cut
389              
390             sub episode_url {
391 0     0 1   my $self = shift;
392 0   0       my $season = shift || 'All'; # 0 == ALL seasons
393              
394 0           return sprintf(
395             'http://%s.tv.com/show/%d/episode_listings.html?season=%s',
396             $self->{_site}, $self->id, $season
397             );
398             }
399              
400             1;
401              
402             __END__