File Coverage

blib/lib/Net/Async/WebSearch/Provider/Google.pm
Criterion Covered Total %
statement 27 67 40.3
branch 0 30 0.0
condition 0 13 0.0
subroutine 9 15 60.0
pod 4 4 100.0
total 40 129 31.0


line stmt bran cond sub pod time code
1             package Net::Async::WebSearch::Provider::Google;
2             our $VERSION = '0.002';
3             # ABSTRACT: Google Programmable Search (CSE) JSON API provider
4 1     1   1169 use strict;
  1         2  
  1         30  
5 1     1   4 use warnings;
  1         2  
  1         45  
6 1     1   3 use parent 'Net::Async::WebSearch::Provider';
  1         3  
  1         6  
7              
8 1     1   66 use Carp qw( croak );
  1         2  
  1         48  
9 1     1   3 use Future;
  1         1  
  1         85  
10 1     1   5 use JSON::MaybeXS qw( decode_json );
  1         1  
  1         34  
11 1     1   4 use URI;
  1         1  
  1         22  
12 1     1   4 use HTTP::Request::Common qw( GET );
  1         2  
  1         29  
13 1     1   4 use Net::Async::WebSearch::Result;
  1         1  
  1         560  
14              
15             sub _init {
16 0     0     my ( $self ) = @_;
17 0 0         croak "Google provider requires 'api_key'" unless $self->{api_key};
18 0 0         croak "Google provider requires 'cx' (Programmable Search engine id)" unless $self->{cx};
19 0   0       $self->{endpoint} ||= 'https://www.googleapis.com/customsearch/v1';
20 0   0       $self->{name} ||= 'google';
21             }
22              
23 0     0 1   sub endpoint { $_[0]->{endpoint} }
24 0     0 1   sub api_key { $_[0]->{api_key} }
25 0     0 1   sub cx { $_[0]->{cx} }
26              
27             sub search {
28 0     0 1   my ( $self, $http, $query, $opts ) = @_;
29 0   0       $opts ||= {};
30 0   0       my $limit = $opts->{limit} || 10;
31              
32 0           my $uri = URI->new( $self->endpoint );
33 0 0         my %q = (
34             q => $query,
35             key => $self->api_key,
36             cx => $self->cx,
37             num => ( $limit > 10 ? 10 : $limit ), # CSE hard-caps num to 10
38             );
39 0 0         $q{gl} = $opts->{region} if defined $opts->{region};
40 0 0         $q{hl} = $opts->{language} if defined $opts->{language};
41 0 0         $q{safe} = $opts->{safesearch} if defined $opts->{safesearch};
42 0 0         $q{dateRestrict} = $opts->{date_restrict} if defined $opts->{date_restrict};
43 0           $uri->query_form(%q);
44              
45 0           my $req = GET( $uri->as_string );
46 0           $req->header( 'User-Agent' => $self->user_agent_string );
47 0           $req->header( 'Accept' => 'application/json' );
48              
49             return $http->do_request( request => $req )->then(sub {
50 0     0     my ( $resp ) = @_;
51 0 0         unless ( $resp->is_success ) {
52 0           return Future->fail(
53             $self->name.": HTTP ".$resp->status_line, 'websearch', $self->name,
54             );
55             }
56 0           my $data = eval { decode_json( $resp->decoded_content ) };
  0            
57 0 0         if ( my $e = $@ ) {
58 0           return Future->fail( $self->name.": invalid JSON: $e", 'websearch', $self->name );
59             }
60 0           my @out;
61 0           my $rank = 0;
62 0 0         for my $r ( @{ $data->{items} || [] } ) {
  0            
63 0           $rank++;
64 0   0       my $pagemap = $r->{pagemap} || {};
65 0 0         my ($metatags) = @{ $pagemap->{metatags} || [] };
  0            
66             push @out, Net::Async::WebSearch::Result->new(
67             url => $r->{link},
68             title => $r->{title},
69             snippet => $r->{snippet},
70             provider => $self->name,
71             rank => $rank,
72             published_at => (
73             $metatags && ( $metatags->{'article:published_time'}
74             // $metatags->{'og:article:published_time'}
75             // $metatags->{'date'} )
76             ),
77             raw => $r,
78             extra => {
79             ( defined $r->{displayLink} ? ( displayLink => $r->{displayLink} ) : () ),
80             ( defined $r->{mime} ? ( mime => $r->{mime} ) : () ),
81 0 0 0       ( defined $r->{fileFormat} ? ( fileFormat => $r->{fileFormat} ) : () ),
    0          
    0          
82             },
83             );
84 0 0         last if $rank >= $limit;
85             }
86 0           return Future->done(\@out);
87 0           });
88             }
89              
90             1;
91              
92             __END__