File Coverage

blib/lib/WWW/Shorten/PunyURL.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package WWW::Shorten::PunyURL;
2              
3 4     4   92403 use 5.006;
  4         18  
  4         295  
4              
5             =head1 NAME
6              
7             WWW::Shorten::PunyURL - An interface to SAPO's URL shortening service
8              
9             =head1 VERSION
10              
11             Version 0.03
12              
13             =cut
14              
15             our $VERSION = '0.03';
16              
17              
18             =head1 SYNOPSIS
19              
20             PunyURL is a URL shortening service provided by SAPO (L). Given a URL, it replies with two versions of the short URL, one using Unicode and RFC3492-compliant (Punycode) and an ASCII-equivalent (lowercase).
21              
22             You can also provide the shortened URL and get back the original one.
23              
24             use WWW::Shorten::PunyURL;
25              
26             my $punyurl = WWW::Shorten::PunyURL->new( url => $long );
27             $punyurl->shorten;
28            
29             # or
30            
31             my $punyurl = WWW::Shorten::PunyURL->new( url => $short );
32             $punyurl->long;
33              
34             Optionally, you can give the constructor a timeout value (which defaults to 10 seconds):
35              
36             my $punyurl = WWW::Shorten::PunyURL->new(
37             url => $long,
38             timeout => 5
39             );
40              
41             =head1 TODO
42              
43             =over 4
44              
45             * Write conditional network tests
46              
47             * Report/fix bug in Regexp::Common::URI (doesn't handle Unicode)
48             UNTIL THIS IS FIXED, REQUESTING THE URL CORRESPONDING TO A PUNYCODE
49             SHORTENED ONE WILL BREAK HORRIBLY AND PROBABLY DESTROY THE WORLD. USE THE
50             ASCII SHORT VERSION FOR NOW.
51              
52             =back
53              
54             =cut
55              
56 4     4   4511 use Mouse;
  4         165408  
  4         27  
57              
58             extends 'WWW::Shorten::generic';
59             extends 'Exporter';
60              
61             our @EXPORT = qw( makeashorterlink makealongerlink );
62              
63 4     4   1787 use Mouse::Util::TypeConstraints;
  4         8  
  4         21  
64              
65 4     4   348 use Carp;
  4         9  
  4         335  
66              
67 4     4   6692 use Regexp::Common qw/ URI /;
  4         21735  
  4         23  
68              
69 4     4   148748 use URI::Escape qw/ uri_escape_utf8 /;
  4         5557  
  4         246  
70 4     4   4579 use LWP::UserAgent;
  4         203728  
  4         161  
71              
72 4     4   6371 use XML::LibXML;
  0            
  0            
73             use XML::LibXML::XPathContext;
74              
75             subtype 'URL'
76             => as 'Str'
77             => where { /$RE{URI}/ };
78              
79             has 'url' => (
80             is => 'ro',
81             isa => 'URL',
82             required => 1,
83             );
84              
85             has 'puny' => (
86             is => 'rw',
87             isa => 'Str',
88             default => '',
89             );
90              
91             has 'ascii' => (
92             is => 'rw',
93             isa => 'URL',
94             );
95              
96             has 'preview' => (
97             is => 'rw',
98             isa => 'URL',
99             );
100              
101             has 'original' => (
102             is => 'rw',
103             isa => 'Str',
104             default => '',
105             );
106              
107             has 'browser' => (
108             is => 'rw',
109             isa => 'LWP::UserAgent',
110             lazy_build => 1
111             );
112              
113             has 'timeout' => (
114             is => 'rw',
115             isa => 'Int',
116             default => 10,
117             );
118              
119             has 'error' => (
120             is => 'rw',
121             isa => 'Str',
122             default => '',
123             );
124              
125             has 'errstr' => (
126             is => 'rw',
127             isa => 'Str',
128             default => '',
129             );
130              
131             has 'parser' => (
132             is => 'rw',
133             isa => 'XML::LibXML',
134             lazy_build => 1,
135             );
136              
137             no Mouse;
138             no Mouse::Util::TypeConstraints;
139             __PACKAGE__->meta->make_immutable;
140              
141             =head1 CONSTANTS
142              
143             =head2 ENDPOINT
144              
145             The service endpoint for PunyURL
146              
147             =cut
148              
149             use constant ENDPOINT => 'http://services.sapo.pt/PunyURL';
150              
151             =head1 EXPORTS
152              
153             =head2 makeashorterlink
154              
155             This function will return the PunyURL corresponding to the original URL.
156              
157             =cut
158              
159             sub makeashorterlink {
160             my $url = shift or croak 'No URL passed to makeashorterlink()';
161            
162             my $puny = __PACKAGE__->new( $url );
163            
164             return $puny->shorten->ascii;
165             }
166              
167             =head2 makealongerlink
168              
169             This function does the reverse, finding the long URL corresponding to a PunyURL.
170              
171             =cut
172              
173             sub makealongerlink {
174             my $puny = shift or croak 'No PunyURL passed to makealongerlink()';
175            
176             my $url = __PACKAGE__->new( $puny );
177            
178             return $url->long->original;
179             }
180              
181             =head1 FUNCTIONS
182              
183             =head2 new
184              
185             Create a new WWW::Shorten::PunyURL object. Takes a string (containing a URL) as the argument (may also take an optional timeout, see SYNOPSIS):
186              
187             my $punyurl = WWW::Shorten::PunyURL->new( $url );
188              
189             =head2 shorten
190              
191             Give it a long url and you will get two shortened URLs, one using Unicode and its equivalent in lowercase ASCII. Returns undef on failure.
192              
193             my $result = $punyurl->shorten;
194            
195             if ( $result ) {
196             print $punyurl->url, "is now:\n";
197             print "\t", $punyurl->puny, "\n";
198             print "\t", $punyurl->ascii, "\n";
199             print "\t", $punyurl->preview, "\n";
200             } else {
201             print STDERR "Error:\n";
202             print STDERR $punyurl->errstr, "(", $punyurl->error, "\n";
203             }
204              
205             =cut
206              
207             sub shorten {
208             my $self = shift;
209            
210             my $request = ENDPOINT.'/GetCompressedURLByURL?url='.$self->_urlencode;
211            
212             my $xml = $self->_do_http( $request );
213             return undef unless $xml;
214            
215             my $xpc = $self->_get_xpc( $xml );
216             my $puny = $xpc->findvalue( '//p:puny' );
217             my $ascii = $xpc->findvalue( '//p:ascii' );
218             my $preview = $xpc->findvalue( '//p:preview' );
219            
220             $self->puny( $puny );
221             $self->ascii( $ascii );
222             $self->preview( $preview );
223            
224             return $self;
225             }
226              
227             =head2 long
228              
229             Given a short URL (that you previously got through shorten() or any other means), returns the original URL, or undef in case of failure.
230              
231             $punyurl->long;
232              
233             =cut
234              
235             sub long {
236             my $self = shift;
237            
238             my $request = ENDPOINT.'/GetURLByCompressedURL?url='.$self->_urlencode;
239              
240             my $xml = $self->_do_http( $request );
241             return undef unless $xml;
242            
243             my $xpc = $self->_get_xpc( $xml );
244             my $original = $xpc->findvalue( '//p:url' );
245              
246             $self->original( $original );
247            
248             return $self;
249             }
250              
251             =begin ignore
252              
253             =head1 INTERNAL FUNCTIONS
254              
255             =head2 _do_http
256              
257             =cut
258              
259             sub _do_http {
260             my $self = shift;
261             my $uri = shift;
262            
263             my $response = $self->browser->get( $uri );
264            
265             if ( ! $response->is_success ) {
266             $self->error( $response->code );
267             $self->errstr( $response->status_line );
268             return undef;
269             }
270            
271             if ( $response->content_type ne 'text/xml' ) {
272             $self->error( '501' );
273             $self->errstr(
274             'Wrong Content-Type received: ' .
275             $response->content_type
276             );
277             return undef;
278             }
279            
280             return $response->content;
281             }
282              
283             =head2 _get_xpc
284              
285             =cut
286              
287             sub _get_xpc {
288             my $self = shift;
289             my $xml = shift;
290            
291             my $doc = $self->parser->parse_string( $xml );
292             my $xpc = XML::LibXML::XPathContext->new( $doc );
293             $xpc->registerNs( 'p', 'http://services.sapo.pt/Metadata/PunyURL' );
294            
295             return $xpc;
296             }
297             =head2 _urlencode
298              
299             Lifted from Net::Amazon::S3. Thanks, Le'on!
300              
301             =cut
302              
303             sub _urlencode {
304             my $self = shift;
305            
306             return uri_escape_utf8( $self->url, '^A-Za-z0-9_-' );
307             }
308              
309             =head2 _build_browser
310              
311             =cut
312              
313             sub _build_browser {
314             my $self = shift;
315            
316             my $ua = LWP::UserAgent->new;
317             $ua->timeout( $self->timeout );
318             $ua->env_proxy;
319            
320             return $ua;
321             }
322              
323             =head2 _build_parser
324              
325             =cut
326              
327             sub _build_parser {
328             return XML::LibXML->new;
329             }
330              
331             =end ignore
332              
333             =head1 AUTHOR
334              
335             Pedro Figueiredo, C<< >>
336              
337             =head1 BUGS
338              
339             Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
340              
341              
342             =head1 SUPPORT
343              
344             You can find documentation for this module with the perldoc command.
345              
346             perldoc WWW::Shorten::PunyURL
347              
348              
349             You can also look for information at:
350              
351             =over 4
352              
353             =item * RT: CPAN's request tracker
354              
355             L
356              
357             =item * AnnoCPAN: Annotated CPAN documentation
358              
359             L
360              
361             =item * CPAN Ratings
362              
363             L
364              
365             =item * Search CPAN
366              
367             L
368              
369             =back
370              
371              
372             =head1 ACKNOWLEDGEMENTS
373              
374             =over 4
375              
376             =item * João Pedro, from SAPO, for pushing PunyURL.
377              
378             =item * Léon Brocard, for writing lots of code I can look at. My mistakes are my own, however.
379            
380             =item * and of course, SAPO :)
381              
382             =back
383              
384             =head1 COPYRIGHT & LICENSE
385              
386             Copyright 2009 Pedro Figueiredo, all rights reserved.
387              
388             This program is free software; you can redistribute it and/or modify it
389             under the same terms as Perl itself.
390              
391              
392             =cut
393              
394             1; # End of WWW::Shorten::PunyURL