File Coverage

blib/lib/Net/Async/WebSearch/Provider/Brave.pm
Criterion Covered Total %
statement 27 62 43.5
branch 0 22 0.0
condition 0 13 0.0
subroutine 9 14 64.2
pod 3 3 100.0
total 39 114 34.2


line stmt bran cond sub pod time code
1             package Net::Async::WebSearch::Provider::Brave;
2             our $VERSION = '0.002';
3             # ABSTRACT: Brave Search API provider
4 1     1   1516 use strict;
  1         2  
  1         69  
5 1     1   5 use warnings;
  1         1  
  1         54  
6 1     1   4 use parent 'Net::Async::WebSearch::Provider';
  1         1  
  1         7  
7              
8 1     1   55 use Carp qw( croak );
  1         1  
  1         35  
9 1     1   4 use Future;
  1         10  
  1         26  
10 1     1   348 use JSON::MaybeXS qw( decode_json );
  1         5601  
  1         52  
11 1     1   5 use URI;
  1         1  
  1         18  
12 1     1   4 use HTTP::Request::Common qw( GET );
  1         1  
  1         32  
13 1     1   8 use Net::Async::WebSearch::Result;
  1         1  
  1         464  
14              
15             sub _init {
16 0     0     my ( $self ) = @_;
17 0 0         croak "Brave provider requires 'api_key'" unless $self->{api_key};
18 0   0       $self->{endpoint} ||= 'https://api.search.brave.com/res/v1/web/search';
19 0   0       $self->{name} ||= 'brave';
20             }
21              
22 0     0 1   sub endpoint { $_[0]->{endpoint} }
23 0     0 1   sub api_key { $_[0]->{api_key} }
24              
25             sub search {
26 0     0 1   my ( $self, $http, $query, $opts ) = @_;
27 0   0       $opts ||= {};
28 0   0       my $limit = $opts->{limit} || 10;
29              
30 0           my $uri = URI->new( $self->endpoint );
31 0           my %q = ( q => $query, count => $limit );
32 0 0         $q{country} = $opts->{region} if defined $opts->{region};
33 0 0         $q{search_lang}= $opts->{language} if defined $opts->{language};
34 0 0         $q{safesearch} = $opts->{safesearch} if defined $opts->{safesearch};
35 0           $uri->query_form(%q);
36              
37 0           my $req = GET( $uri->as_string );
38 0           $req->header( 'User-Agent' => $self->user_agent_string );
39 0           $req->header( 'Accept' => 'application/json' );
40 0           $req->header( 'X-Subscription-Token' => $self->api_key );
41              
42             return $http->do_request( request => $req )->then(sub {
43 0     0     my ( $resp ) = @_;
44 0 0         unless ( $resp->is_success ) {
45 0           return Future->fail(
46             $self->name.": HTTP ".$resp->status_line, 'websearch', $self->name,
47             );
48             }
49 0           my $data = eval { decode_json( $resp->decoded_content ) };
  0            
50 0 0         if ( my $e = $@ ) {
51 0           return Future->fail( $self->name.": invalid JSON: $e", 'websearch', $self->name );
52             }
53 0           my @out;
54 0           my $rank = 0;
55 0 0 0       for my $r ( @{ ($data->{web} || {})->{results} || [] } ) {
  0            
56 0           $rank++;
57             push @out, Net::Async::WebSearch::Result->new(
58             url => $r->{url},
59             title => $r->{title},
60             snippet => $r->{description},
61             provider => $self->name,
62             rank => $rank,
63             # Brave gives both age (human-readable like "3 days ago") and
64             # page_age (ISO 8601). Prefer the ISO one.
65             published_at => $r->{page_age} // $r->{age},
66             language => $r->{language},
67             raw => $r,
68             extra => {
69             ( defined $r->{age} ? ( age => $r->{age} ) : () ),
70             ( defined $r->{profile} ? ( profile => $r->{profile} ) : () ),
71 0 0 0       ( defined $r->{subtype} ? ( subtype => $r->{subtype} ) : () ),
    0          
    0          
72             },
73             );
74 0 0         last if $rank >= $limit;
75             }
76 0           return Future->done(\@out);
77 0           });
78             }
79              
80             1;
81              
82             __END__