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 |
|||||||||||||
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 |
|||||||||||||
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__ |