--- /dev/null
+name = WWW::EZTV
+author = Diego Kuperman <diego@freekeylabs.com>
+license = Perl_5
+copyright_holder = Diego Kuperman
+copyright_year = 2012
+
+[@Basic]
+[@Git]
+
+[AutoPrereqs]
+
+[Git::NextVersion]
+first_version = 0.01
+
+[PkgVersion]
+[MetaConfig]
+[MetaJSON]
+[NextRelease]
+[PodSyntaxTests]
+[PodCoverageTests]
+[PodWeaver]
+
+[MetaResources]
+repository.web = http://github.com/diegok/www-eztv
+repository.url = http://github.com/diegok/www-eztv
+repository.type = git
+
+[GithubMeta]
+user = diegok
+issues = 1
+
+;[ReadmeAnyFromPod]
+; Default is plaintext README in build dir
+
+;[ReadmeAnyFromPod / ReadmePodInRoot]
+;type = pod
+;filename = README.pod
+;location = root
+
+[Prereqs / TestRequires]
+Test::More = 0.96
--- /dev/null
+package WWW::EZTV;
+use Moose;
+with 'WWW::EZTV::UA';
+use WWW::EZTV::Show;
+
+has url => ( is => 'ro', lazy => 1, default => sub { Mojo::URL->new('http://eztv.it/') } );
+has url_shows => ( is => 'ro', lazy => 1, default => sub { shift->url->clone->path('/showlist/') } );
+has shows =>
+ is => 'ro',
+ lazy => 1,
+ default => \&build_shows,
+ handles => {
+ find_show => 'first',
+ has_shows => 'size',
+ };
+
+sub build_shows {
+ my $self = shift;
+
+ $self->get_response( $self->url_shows )->dom->find('table.forum_header_border tr[name="hover"]')->map(sub {
+ my $tr = shift;
+ my $link = $tr->at('td:nth-child(1) a');
+ WWW::EZTV::Show->new(
+ title => $link->all_text,
+ url => $self->url->clone->path($link->attrs('href')),
+ status => lc($tr->at('td:nth-child(2)')->all_text),
+ rating => $tr->at('td:nth-child(3)')->all_text
+ );
+ });
+}
+
+1;
--- /dev/null
+package WWW::EZTV::Episode;
+use Moose;
+with 'WWW::EZTV::UA';
+
+# ABSTRACT: EZTV single episode
+
+has show => is => 'ro', isa => 'WWW::EZTV::Show', required => 1;
+has title => is => 'ro', isa => 'Str', required => 1;
+has url => is => 'ro', isa => 'Mojo::URL', required => 1;
+has links => is => 'rw';
+
+has _parsed => is => 'ro', lazy => 1, builder => '_parse';
+
+has name => is => 'ro', lazy => 1,
+ default => sub { shift->_parsed->{name} };
+
+has season => is => 'ro', lazy => 1,
+ default => sub { shift->_parsed->{season} };
+
+has number => is => 'ro', lazy => 1,
+ default => sub { shift->_parsed->{number} };
+
+has version => is => 'ro', lazy => 1,
+ default => sub { shift->_parsed->{version} };
+
+has quality => is => 'ro', lazy => 1,
+ default => sub { shift->_parsed->{quality} || 'standard' };
+
+has size => is => 'ro', lazy => 1,
+ default => sub { shift->_parsed->{size} };
+
+sub _parse {
+ my $title = shift->title;
+
+ $title =~ /^\s*
+ (?<name>.+?)
+ \s+
+ (?<chapter>
+ S (?<season>\d+) E (?<number>\d+)
+ |(?<season>\d+) x (?<number>\d+)
+ |(?<number>\d+) of (?<total>\d+)
+ )
+ \s+
+ (?<version>
+ ((?<quality>\d+p)\s+)?
+ (?<team>.*?)
+ )
+ (?:
+ \s+
+ \((?<size>
+ \d+
+ [^\)]+
+ )\)
+ )?
+ \s*$/xi;
+
+ return {
+ name => $+{name} || $title,
+ chapter => $+{chapter},
+ number => $+{number} +0,
+ season => ($+{season}||0) +0,
+ total => ($+{total}||0) +0,
+ version => $+{version} || '',
+ quality => $+{quality} || 'standard',
+ team => $+{team},
+ size => $+{size}
+ };
+}
+
+1;
--- /dev/null
+package WWW::EZTV::Link;
+use Moose;
+with 'WWW::EZTV::UA';
+
+# ABSTRACT: EZTV episode link
+
+has url => is => 'ro', isa => 'Str', required => 1;
+
+1;
--- /dev/null
+package WWW::EZTV::Show;
+use Moose;
+with 'WWW::EZTV::UA';
+use WWW::EZTV::Link;
+use WWW::EZTV::Episode;
+
+# ABSTRACT: EZTV show object
+
+has title => is => 'ro', isa => 'Str', required => 1;
+has name => is => 'ro', lazy => 1, default => \&_name;
+has year => is => 'ro', lazy => 1, default => \&_year;
+has url => is => 'ro', isa => 'Mojo::URL', required => 1;
+has status => is => 'ro', isa => 'Str', required => 1;
+has rating => is => 'ro', isa => 'Int', default => sub {0};
+has episodes =>
+ is => 'ro',
+ lazy => 1,
+ builder => '_build_episodes',
+ handles => {
+ find_episode => 'first',
+ has_episodes => 'size',
+ };
+
+sub _build_episodes {
+ my $self = shift;
+ $self->get_response($self->url)->dom->find('table.forum_header_noborder tr[name="hover"]')->map(sub{
+ my $tr = shift;
+ my $a = $tr->at('td:nth-child(2) a');
+
+ WWW::EZTV::Episode->new(
+ title => $a->attrs('title'),
+ url => $self->url->clone->path($a->attrs('href')),
+ links => $tr->find('td:nth-child(3) a')->map(sub{
+ WWW::EZTV::Link->new( url => shift->attrs('href') )
+ }),
+ released => $tr->at('td:nth-child(4)')->all_text,
+ show => $self
+ );
+ });
+}
+
+sub _name {
+ my $self = shift;
+ my $name = $self->title;
+
+ # Chasers War on Everything, The
+ if ( $name =~ /^(.+),\s*([^,]+)$/ ) { $name = "$2 $1" }
+
+ # Remove year: Castle (2009)
+ $name =~ s/\s* \(\d{4}\) \s*/ /x;
+
+ # Trim and cleanup spaces
+ $self->_cleanup_str($name);
+}
+
+sub _year {
+ my $self = shift;
+ if ( $self->title =~ /\((\d{4})\)/ ) {
+ return $1;
+ }
+}
+
+sub _cleanup_str {
+ my $str = pop;
+ $str =~ s/^\s+|\s+$//g;
+ $str =~ s/\s+/ /g;
+ $str;
+}
+
+1;
+
+=attr title
+=cut
+
+=attr name
+=cut
+
+=attr year
+=cut
+
+=attr url
+=cut
+
+=attr status
+=cut
+
+=attr rating
+=cut
+
+=attr episodes
+Collection of episodes fetched for this show.
+=cut
+
+=attr has_episodes
+How many episodes has this show.
+=cut
+
+=method find_episode
+Find first L<WWW::EZTV::Episode> object matching the given criteria.
+This method accept an anon function.
+=cut
+
--- /dev/null
+package WWW::EZTV::UA;
+use Moose::Role;
+use Mojo::UserAgent;
+
+has ua => ( is => 'ro', lazy => 1, default => sub { $EZTV::Global::UA || ($EZTV::Global::UA = Mojo::UserAgent->new) } );
+
+sub get_response {
+ my ($self, $url) = (shift, shift);
+
+ my $tx = $self->ua->get( $url );
+ if ( my $res = $tx->success ) {
+ return $res;
+ }
+ else {
+ my ($err, $code) = $tx->error;
+ my $message = shift || 'User agent error';
+ die "$message: $err ($code)";
+ }
+}
+
+1;
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use Test::More;
+
+BEGIN { use_ok( 'WWW::EZTV' ); }
+
+ok( my $eztv = WWW::EZTV->new, 'Build eztv crawler' );
+isa_ok( $eztv->shows, 'Mojo::Collection' );
+isa_ok( $eztv->shows->[0], 'WWW::EZTV::Show' );
+
+subtest 'All shows has name and URL' => sub {
+ my $has_year = 0;
+ $eztv->shows->each(sub {
+ my $show = shift;
+ ok( $show->name, 'Found show ' . $show->name );
+ ok( $show->url, 'Has url ' . $show->url );
+ ok( $show->status, 'Has status ' . $show->status );
+ $has_year++ if $show->year;
+ });
+ ok( $has_year, "Some shows has year info" );
+};
+
+subtest 'Show object' => sub {
+ ok( my $show = $eztv->shows->[0], 'Pick first show' );
+ diag( $show->name . ' was choosen!' );
+ ok( $show->has_episodes, 'Has episodes' );
+ ok( $show->episodes, 'Retrieve episodes' );
+ $show->episodes->each(sub{
+ my $ep = shift;
+ diag( 'Title: '. $ep->title );
+ diag( 'Name: '. $ep->name );
+ diag( 'Season: '. $ep->season );
+ diag( 'Number: '. $ep->number );
+ diag( 'Version: '. $ep->version );
+ diag( 'Size: '. $ep->size );
+ ok( $ep->season >= 1, 'Has season' );
+ ok( $ep->number >= 1, 'Has number' );
+
+ ok( my $link = $ep->links->[0], 'Get first link' );
+ ok( $link->url, 'Link has url' );
+ });
+};
+
+done_testing();
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use Test::More;
+
+BEGIN { use_ok( 'WWW::EZTV' ); }
+
+ok( my $eztv = WWW::EZTV->new, 'Build eztv crawler' );
+
+my $show;
+
+subtest 'Find a show' => sub {
+ ok( $eztv->has_shows, 'Can fetch shows list' );
+ ok( $show = $eztv->find_show(sub{ $_->name =~ /Walking dead/i }), 'Find a known show' );
+ is( $show->name, 'The Walking Dead', 'Name looks good' );
+ ok( $show->url, 'Has url ' . $show->url );
+ ok( $show->status, 'Has status ' . $show->status );
+};
+
+subtest 'Find episodes' => sub {
+ ok( $show->has_episodes, 'Show fetch show episodes' );
+ ok( my $ep = $show->find_episode(sub{ $_->season == 3 && $_->number == 8 && $_->quality eq 'standard' }), 'Find a known episode' );
+ diag( 'Title: ' . $ep->title );
+ diag( 'Version: ' . $ep->version );
+ diag( 'Size: ' . $ep->size );
+ is( $ep->name, 'The Walking Dead', 'Name looks good' );
+};
+
+done_testing();