File Coverage

blib/lib/Mojo/Snoo/Base.pm
Criterion Covered Total %
statement 33 64 51.5
branch 5 22 22.7
condition 3 3 100.0
subroutine 8 49 16.3
pod 0 1 0.0
total 49 139 35.2


line stmt bran cond sub pod time code
1             package Mojo::Snoo::Base;
2 5     5   2988 use Moo;
  5         9  
  5         26  
3              
4 5     5   5656 use Mojo::UserAgent;
  5         779492  
  5         53  
5 5     5   184 use Mojo::URL;
  5         12  
  5         23  
6 5     5   117 use Mojo::Util ();
  5         10  
  5         80  
7              
8 5     5   26 use Carp ();
  5         10  
  5         4677  
9              
10             has agent => (
11             is => 'rw',
12             default => sub { Mojo::UserAgent->new() }
13             );
14              
15             has base_url => (
16             is => 'rw',
17             default => sub { Mojo::URL->new('https://www.reddit.com') }
18             );
19              
20             has [qw(username password client_id client_secret)] => (is => 'ro', predicate => 1);
21              
22             # TODO we will need to be able to "refresh" the token when authenticating users
23             has access_token => (is => 'rw', lazy => 1, builder => '_create_access_token');
24              
25             my %TOKEN_REQUIRED = map { $_ => 1 } (
26             qw(
27             /api/unsave
28             /api/save
29             /api/vote
30             /api/new_captcha
31             /api/compose
32             /api/subscribe
33             /api/submit
34             )
35             );
36              
37             sub _create_access_token {
38 0     0   0 my $self = shift;
39             # update base URL
40 0         0 my %form = (
41             grant_type => 'password',
42             username => $self->username,
43             password => $self->password,
44             );
45 0         0 my $access_url =
46             'https://'
47             . $self->client_id . ':'
48             . $self->client_secret
49             . '@www.reddit.com/api/v1/access_token';
50              
51 0         0 my $res = $self->agent->post($access_url => form => \%form)->res->json;
52              
53             # if a problem arises, it is most likely due to given auth being incorrect
54             # let the user know in this case
55 0 0       0 if (exists($res->{error})) {
56             my $msg =
57             $res->{error} == 401
58             ? '401 status code (Unauthorized)'
59 0 0       0 : 'error response of ' . $res->{error};
60 0         0 Carp::croak("Received $msg while attempting to create OAuth access token.");
61             }
62              
63             # update the base URL for future endpoint calls
64 0         0 $self->base_url->host('oauth.reddit.com');
65              
66             # TODO we will want to eventually keep track of token type, scope and expiration
67             # when dealing with user authentication (not just a personal script)
68 0         0 return $res->{access_token};
69             }
70              
71             sub BUILDARGS {
72 14     14 0 3668 my ($class, %args) = @_;
73              
74             # if the user wants oauth, make sure we have all required fields
75 14         42 my @oauth_required = (qw(username password client_id client_secret));
76 14         58 my @oauth_given = grep defined($args{$_}), @oauth_required;
77              
78 14 100 100     68 if (@oauth_given and @oauth_given < 4) {
79 1         21 Carp::croak( #
80             'OAuth requires the following fields to be defined: '
81             . join(', ', @oauth_required) . "\n"
82             . 'Fields defined: '
83             . join(', ', @oauth_given)
84             );
85             }
86              
87 13         302 \%args;
88             }
89              
90             sub _token_required {
91 0     0   0 my ($self, $path) = @_;
92 0 0       0 return $TOKEN_REQUIRED{$path} ? 1 : 0;
93             }
94              
95             sub _solve_captcha {
96 0     0   0 my $self = shift;
97 0         0 my $captcha_required = $self->_do_request('GET', '/api/needs_captcha');
98              
99             # do not proceed if user does not require a captcha
100 0 0       0 return unless $captcha_required;
101              
102 0         0 my $captcha = $self->_do_request('POST', '/api/new_captcha', api_type => 'json');
103 0         0 my $captcha_id = $captcha->{json}{data}{iden};
104              
105 0         0 my $url = "http://www.reddit.com/captcha/$captcha_id.png";
106 0         0 print("Type the CAPTCHA text from $url here (Get more karma to avoid captchas).\nCAPTCHA text: ");
107              
108 0         0 my $captcha_text = ;
109 0         0 return ($captcha_id, chomp($captcha_text));
110             }
111              
112             sub _do_request {
113 0     0   0 my ($self, $method, $path, %params) = @_;
114              
115 0         0 my %headers;
116 0 0       0 if ($self->_token_required($path)) {
117 0         0 $headers{Authorization} = 'bearer ' . $self->access_token;
118             }
119              
120 0         0 my $url = $self->base_url;
121              
122 0         0 $url->path("$path.json");
123              
124 0 0       0 if ($method eq 'GET') {
125 0 0       0 $url->query(%params) if %params;
126 0         0 return $self->agent->get($url => \%headers)->res;
127             }
128 0         0 return $self->agent->post($url => \%headers, form => \%params)->res;
129             }
130              
131             sub _create_object {
132 5     5   15 my ($self, $class, @args) = @_;
133              
134             # allow the user to pass in single strings, e.g. $object->subreddit(‘perl’)
135 5 50       49 my %args = @args > 1 ? @args : ($class->FIELD => $args[0]);
136              
137 5         11 for my $attr (qw(username password client_id client_secret)) {
138             ## allow user to override OAuth settings via constructor
139 20 50       50 next if exists($args{$attr});
140              
141 20         31 my $has_attr = "has_$attr";
142 20 50       85 $args{$attr} = $self->$attr if $self->$has_attr;
143             }
144 5         30 $class->new(%args);
145             }
146              
147             sub _monkey_patch {
148 3     3   1980 my ($self, $class, $patch) = @_;
149              
150             Mojo::Util::monkey_patch(
151             $class,
152             map {
153 3         18 my $key = $_;
  44         68  
154 0     0   0 $key => sub { $patch->{$key} }
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
155 44         138 } keys %$patch,
156             );
157 3         721 bless({}, $class);
158             }
159              
160             1;
161              
162             __END__