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 13 61.5
pod 0 1 0.0
total 49 103 47.5


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