File Coverage

blib/lib/WWW/Twitpic/Fetch.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package WWW::Twitpic::Fetch;
2 6     6   241285 use Moose;
  0            
  0            
3             use LWP::UserAgent;
4             use Web::Scraper;
5             use URI;
6             use Carp;
7             use List::MoreUtils qw/each_array/;
8             use Text::Trim;
9             use Encode;
10             use utf8;
11              
12             =head1 NAME
13              
14             WWW::Twitpic::Fetch - Moose-based information scraper/fetcher for Twitpic
15              
16             =head1 VERSION
17              
18             Version 0.07
19              
20             =cut
21              
22             our $VERSION = '0.07';
23              
24              
25             =head1 SYNOPSIS
26              
27             use WWW::Twitpic::Fetch;
28            
29             my $twitpic = WWW::Twitpic::Fetch->new();
30             my $list = $twitpic->list($username, $page);
31             my $photoinfo = $twitpic->photo_info($list->[0]{id}, 0);
32             ...
33              
34             =head1 ATTRIBUTES
35              
36             attributes can be specified by parameter of C<new> like
37              
38             WWW::Twitpic::Fetch->new(
39             ua => $my_ua
40             );
41              
42             =head2 ua
43              
44             LWP::UserAgent compatible UserAgent object.
45             default is an instance of LWP::UserAgent.
46              
47             =head2 username
48              
49             username for twitter (and also twitpic).
50             B<UNUSED for this version>
51              
52             =head2 password
53              
54             password for twitter (and also twitpic).
55             B<UNUSED for this version>
56              
57             =cut
58              
59             has ua => (
60             is => q/rw/,
61             isa => q/Ref/,
62             default => sub {
63             my $ua = LWP::UserAgent->new;
64             $ua->env_proxy;
65             $ua;
66             },
67             );
68              
69             has username => (
70             is => q/ro/,
71             isa => q/Str/,
72             #required => 1,
73             );
74              
75             has password => (
76             is => q/ro/,
77             isa => q/Str/,
78             #required => 1,
79             );
80              
81             # private attributes
82              
83             has _list_scraper => (
84             is => q/ro/,
85             lazy => 1,
86             default => sub {
87             scraper {
88             process 'div.user-photo>a' => 'id[]' => '@href';
89             process 'div.user-photo>a>img' => 'thumb[]' => '@src';
90             process 'div.user-tweet>p.' => 'message[]' => 'TEXT';
91             };
92             },
93             );
94              
95             has _photo_full_scraper => (
96             is => q/ro/,
97             lazy => 1,
98             default => sub {
99             scraper {
100             process 'body>img' => 'url' => '@src';
101             };
102             },
103             );
104              
105             has _photo_scaled_scraper => (
106             is => q/ro/,
107             lazy => 1,
108             default => sub {
109             my $each_comment = scraper {
110             process 'div.photo-comment-info>a' => 'username' => 'TEXT';
111             process 'div.photo-comment-info>span.photo-comment-date' => 'date' => 'TEXT';
112             process 'div.photo-comment-message' => 'comment' => 'TEXT';
113             process 'div.photo-comment-avatar>img' => 'avatar' => '@src';
114             };
115             scraper {
116             process 'img#photo-display' => 'url' => '@src';
117             process 'div#view-photo-views>div' => 'views' => 'TEXT';
118             process 'div#view-photo-caption' => 'message' => 'TEXT';
119             process 'div.photo-comment' => 'comments[]' => $each_comment;
120             process 'div#view-photo-tags>span>a.nav-link' => 'tags[]' => 'TEXT';
121             };
122             },
123             );
124              
125             has _public_timeline_scraper => (
126             is => q/ro/,
127             lazy => 1,
128             default => sub {
129             my $each = scraper {
130             process 'img.avatar' => 'avatar' => '@src';
131             process 'a.nav' => 'username' => 'TEXT';
132             process 'td>div>a' => 'id[]' => '@href';
133             process 'td>div' => 'message[]' => 'TEXT';
134             process 'div>a>img' => 'mini' => '@src';
135             };
136             scraper {
137             process 'div.comment>table>tr' => 'photos[]' => $each;
138             };
139             },
140             );
141              
142             has _tagged_scraper => (
143             is => q/ro/,
144             lazy => 1,
145             default => sub {
146             my $each = scraper {
147             process '.' => 'id' => ['@href', sub { s{^/}{}; $_ } ];
148             process 'img' => 'mini' => '@src';
149             };
150             scraper {
151             process 'div#tagged-photos>div>a' => 'tagged[]' => $each;
152             };
153             },
154             );
155              
156             =head1 FUNCTIONS
157              
158             =head2 list I<username> [, I<page>]
159              
160             get list of photo informations for I<username>.
161              
162             returns arrayref of hashref containing following keys
163             C<'id'>, C<'message'>, C<'thumb'> when success.
164             (C<'id'> is a photo id, and C<'thumb'> is for url of thumbnail image of photo)
165              
166             returns undef if failed to fetch list.
167              
168             =over 1
169              
170             =item I<username> (required)
171              
172             specifies whose photo list.
173              
174             =item I<page>
175              
176             specifies page of list. can be omitted. (default = 1)
177              
178             =back
179              
180             =cut
181              
182             sub list
183             {
184             my ($self, $username, $page) = @_;
185             croak "invalid username: @{[$username?$username:'']}" if !$username || $username !~ m{^[[:alnum:]_]+$};
186             $page += 0 if $page;
187             $page = 1 if !defined $page || $page < 1;
188              
189             my $ua = $self->ua;
190              
191             my $uri = URI->new('http://twitpic.com/photos/'.$username);
192             if ( $page > 1 ) {
193             $uri->query_form(page => $page);
194             }
195             my $res = $ua->get($uri);
196             if ( !$res->is_success ) {
197             return undef;
198             }
199              
200             my $sres = $self->_list_scraper->scrape(decode_utf8($res->content));
201              
202             my ($ids, $messages, $thumbs) = map { $sres->{$_} } qw/id message thumb/;
203              
204             return [] if !($ids && $messages && $thumbs);
205              
206             warn 'mismatch found for photo ids and messages. return value may be wrong'
207             if !(scalar @$ids == scalar @$messages && scalar @$ids == scalar @$thumbs);
208              
209             $_ =~ s#^/## for @$ids;
210             trim for @$messages;
211              
212             my $ea = each_array(@$ids, @$messages, @$thumbs);
213             my @list;
214             while (my ($id, $message, $thumb) = $ea->() ) {
215             push @list, +{ id => $id, message => $message, thumb => $thumb };
216             }
217              
218             \@list;
219             }
220              
221             =head2 photo_info I<photo ID or URL of photo page> [, I<full?>]
222              
223             get informations of photo file.
224              
225             returns hashref containing following keys ..
226              
227             C<'url'>, C<'message'>, C<'comments'>, C<'views'> and C<'tags'> for scaled.
228              
229             just C<'url'> for fullsize.
230              
231             return undef if failed to fetch.
232              
233             =over 1
234              
235             =item I<photo ID or url of photo page> (required)
236              
237             photo id. you can get photo id by list() or public_timeline().
238              
239             or you can just pass an url of certain photo page.
240              
241             =item I<full?>
242              
243             FALSE for scaled photo. TRUE for full-size photo.
244             (default = FALSE).
245              
246             =back
247              
248             =cut
249              
250             sub photo_info {
251             my ($self, $id, $full) = @_;
252              
253             if ( $id && $id =~ m{http://(?:www\.)?twitpic\.com/([[:alnum:]]+)} ) {
254             $id = $1;
255             }
256             elsif ( !$id || $id !~ m{^[[:alnum:]]+$} ) {
257             croak "invalid photo id: @{[$id?$id:'']}";
258             }
259              
260             my $url = URI->new('http://twitpic.com/' . $id . ($full ? '/full' : ''));
261             my $res = $self->ua->get($url);
262              
263             return undef if !$res->is_success;
264              
265             my $sres =
266             ($full ? $self->_photo_full_scraper : $self->_photo_scaled_scraper)
267             ->scrape(decode_utf8($res->content));
268             return undef if !$sres;
269              
270             if ( $full ) {
271             return $sres;
272             }
273              
274             $sres->{views} =~ s/[^\d]*(\d+).*/$1/;
275             trim $sres->{message};
276             trim $_->{comment} for @{$sres->{comments}};
277             $sres->{tags} = [] if !exists $sres->{tags};
278              
279             $sres;
280             }
281              
282             =head2 public_timeline
283              
284             get information of photos on public_timeline
285              
286             returns arrayref of hashref containing following.
287             C<'avatar'>, C<'username'>, C<'mini'> and C<'message'> ('mini' is for mini-thumbnail).
288              
289             returns undef if failed to fetch
290              
291             =cut
292              
293             sub public_timeline
294             {
295             my ($self) = @_;
296              
297             my $res = $self->ua->get('http://twitpic.com/public_timeline/');
298             return undef if !$res->is_success;
299              
300             my $sres = $self->_public_timeline_scraper->scrape(decode_utf8($res->content));
301             return undef if !$sres;
302              
303             for (@{$sres->{photos}}) {
304             $_->{id} = pop @{$_->{id}};
305             $_->{message} = pop @{$_->{message}};
306              
307             $_->{id} =~ s#^/##;
308             trim $_->{message};
309             }
310              
311             $sres->{photos};
312             }
313              
314             =head2 tagged I<tag name>
315              
316             get list of photos that tagged certain name.
317              
318             returns arrayref of hashref containing following keys,
319             C<'id'>, C<'mini'>
320              
321             =over 1
322              
323             =item I<tag name>
324              
325             =back
326              
327             =cut
328              
329             sub tagged
330             {
331             my ($self, $tagname) = @_;
332              
333             croak "invalid tag name @{[$tagname?$tagname:'']}" if !$tagname;
334              
335             my $url = URI->new('http://twitpic.com/tag/' . $tagname);
336             my $res = $self->ua->get($url);
337              
338             return undef if !$res->is_success;
339              
340             my $sres = $self->_tagged_scraper->scrape($res->content);
341              
342             my $ret = $sres->{tagged};
343              
344             $_->{id} =~ s{^/}{} for @$ret;
345              
346             $ret;
347             }
348              
349             =head1 SEEALSO
350              
351             L<http://twitpic.com/> - Twitpic web site
352              
353             L<WWW::Twitpic> - Diego Kuperman's Twitpic API client
354              
355             =head1 AUTHOR
356              
357             turugina, C<< <turugina at cpan.org> >>
358              
359             =head1 BUGS
360              
361             Please report any bugs or feature requests to C<bug-www-twitpic-fetch at rt.cpan.org>, or through
362             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=WWW-Twitpic-Fetch>. I will be notified, and then you'll
363             automatically be notified of progress on your bug as I make changes.
364              
365              
366             =head1 SUPPORT
367              
368             You can find documentation for this module with the perldoc command.
369              
370             perldoc WWW::Twitpic::Fetch
371              
372              
373             You can also look for information at:
374              
375             =over 4
376              
377             =item * RT: CPAN's request tracker
378              
379             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=WWW-Twitpic-Fetch>
380              
381             =item * AnnoCPAN: Annotated CPAN documentation
382              
383             L<http://annocpan.org/dist/WWW-Twitpic-Fetch>
384              
385             =item * CPAN Ratings
386              
387             L<http://cpanratings.perl.org/d/WWW-Twitpic-Fetch>
388              
389             =item * Search CPAN
390              
391             L<http://search.cpan.org/dist/WWW-Twitpic-Fetch/>
392              
393             =back
394              
395              
396             =head1 ACKNOWLEDGEMENTS
397              
398              
399             =head1 COPYRIGHT & LICENSE
400              
401             Copyright 2009 turugina, all rights reserved.
402              
403             This program is free software; you can redistribute it and/or modify it
404             under the same terms as Perl itself.
405              
406              
407             =cut
408              
409             1; # End of WWW::Twitpic::Fetch