File Coverage

blib/lib/Mojo/WebService/Twitter.pm
Criterion Covered Total %
statement 191 322 59.3
branch 47 146 32.1
condition 16 67 23.8
subroutine 42 80 52.5
pod 19 19 100.0
total 315 634 49.6


line stmt bran cond sub pod time code
1             package Mojo::WebService::Twitter;
2 2     2   778911 use Mojo::Base -base;
  2         14  
  2         15  
3              
4 2     2   317 use Carp 'croak';
  2         4  
  2         65  
5 2     2   8 use Scalar::Util 'blessed';
  2         4  
  2         67  
6 2     2   9 use Mojo::Collection;
  2         3  
  2         72  
7 2     2   8 use Mojo::UserAgent;
  2         2  
  2         17  
8 2     2   54 use Mojo::Util qw(b64_encode url_escape);
  2         2  
  2         111  
9 2     2   689 use Mojo::WebService::Twitter::Error 'twitter_tx_error';
  2         4  
  2         74  
10 2     2   638 use Mojo::WebService::Twitter::Tweet;
  2         4  
  2         8  
11 2     2   49 use Mojo::WebService::Twitter::User;
  2         3  
  2         5  
12 2     2   33 use Mojo::WebService::Twitter::Util;
  2         3  
  2         44  
13 2     2   796 use WWW::OAuth;
  2         22967  
  2         8384  
14              
15             our $VERSION = '1.000';
16              
17             has ['api_key','api_secret'];
18             has 'ua' => sub { Mojo::UserAgent->new };
19              
20             sub authentication {
21 11     11 1 1449 my $self = shift;
22 11 100 66     221 return $self->{authentication} // croak 'No authentication set' unless @_;
23 2         4 my $auth = shift;
24 2 50       15 if (ref $auth eq 'CODE') {
    50          
    100          
    50          
25 0         0 $self->{authentication} = $auth;
26             } elsif (ref $auth eq 'HASH') {
27 0 0 0     0 if (defined $auth->{access_token}) {
    0          
28 0         0 $self->{authentication} = $self->_oauth2($auth->{access_token});
29             } elsif (defined $auth->{oauth_token} and defined $auth->{oauth_token_secret}) {
30 0         0 $self->{authentication} = $self->_oauth($auth->{oauth_token}, $auth->{oauth_token_secret});
31             } else {
32 0         0 croak 'Unrecognized authentication hashref (no oauth_token or access_token)';
33             }
34             } elsif ($auth eq 'oauth') {
35 1         3 $self->{authentication} = $self->_oauth(@_);
36             } elsif ($auth eq 'oauth2') {
37 1         4 $self->{authentication} = $self->_oauth2(@_);
38             } else {
39 0         0 croak "Unknown authentication $auth";
40             }
41 2         4 return $self;
42             }
43              
44             sub request_oauth {
45 1 50   1 1 194 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
46 1         2 my $self = shift;
47 1         4 my $tx = $self->_build_request_oauth(@_);
48 1 50       12 if ($cb) {
49             $self->ua->start($tx, sub {
50 0     0   0 my ($ua, $tx) = @_;
51 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
52 0   0     0 my $res = $self->_from_request_oauth($tx) // return $self->$cb('OAuth callback was not confirmed');
53 0         0 $self->$cb(undef, $res);
54 0         0 });
55             } else {
56 1         4 $tx = $self->ua->start($tx);
57 1 50       13394 die twitter_tx_error($tx) if $tx->error;
58 1   50     18 return $self->_from_request_oauth($tx) // die "OAuth callback was not confirmed\n";
59             }
60             }
61              
62             sub request_oauth_p {
63 0     0 1 0 my $self = shift;
64 0         0 my $tx = $self->_build_request_oauth(@_);
65             return $self->ua->start_p($tx)->then(sub {
66 0     0   0 my ($tx) = @_;
67 0 0       0 die twitter_tx_error($tx) if $tx->error;
68 0   0     0 return $self->_from_request_oauth($tx) // die "OAuth callback was not confirmed\n";
69 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
70             }
71              
72             sub _build_request_oauth {
73 1     1   3 my ($self, $url) = @_;
74 1   50     7 $url //= 'oob';
75 1         6 my $tx = $self->ua->build_tx(POST => _oauth_url('request_token'));
76 1         434 $self->_oauth->($tx->req, { oauth_callback => $url });
77 1         7082 return $tx;
78             }
79              
80             sub _from_request_oauth {
81 1     1   3 my ($self, $tx) = @_;
82 1         5 my $params = Mojo::Parameters->new($tx->res->text)->to_hash;
83             return undef unless $params->{oauth_callback_confirmed} eq 'true'
84 1 50 33     252 and defined $params->{oauth_token} and defined $params->{oauth_token_secret};
      33        
85 1         5 $self->{request_token_secrets}{$params->{oauth_token}} = $params->{oauth_token_secret};
86 1         5 return $params;
87             }
88              
89             sub verify_oauth {
90 1 50   1 1 1278 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
91 1         3 my $self = shift;
92 1         5 my $tx = $self->_build_verify_oauth(@_);
93 1 50       11 if ($cb) {
94             $self->ua->start($tx, sub {
95 0     0   0 my ($ua, $tx) = @_;
96 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
97 0   0     0 my $res = $self->_from_verify_oauth($tx) // return $self->$cb('No OAuth token returned');
98 0         0 $self->$cb(undef, $res);
99 0         0 });
100             } else {
101 1         4 $tx = $self->ua->start($tx);
102 1 50       6336 die twitter_tx_error($tx) if $tx->error;
103 1   50     17 return $self->_from_verify_oauth($tx) // die "No OAuth token returned\n";
104             }
105             }
106              
107             sub verify_oauth_p {
108 0     0 1 0 my $self = shift;
109 0         0 my $tx = $self->_build_verify_oauth(@_);
110             return $self->ua->start_p($tx)->then(sub {
111 0     0   0 my ($tx) = @_;
112 0 0       0 die twitter_tx_error($tx) if $tx->error;
113 0   0     0 return $self->_from_verify_oauth($tx) // die "No OAuth token returned\n";
114 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
115             }
116              
117             sub _build_verify_oauth {
118 1     1   3 my ($self, $verifier, $request_token, $request_token_secret) = @_;
119 1   33     8 $request_token_secret //= delete $self->{request_token_secrets}{$request_token} // croak "Unknown request token";
      33        
120 1         5 my $tx = $self->ua->build_tx(POST => _oauth_url('access_token'));
121 1         259 $self->_oauth($request_token, $request_token_secret)->($tx->req, { oauth_verifier => $verifier });
122 1         1177 return $tx;
123             }
124              
125             sub _from_verify_oauth {
126 1     1   3 my ($self, $tx) = @_;
127 1         3 my $params = Mojo::Parameters->new($tx->res->text)->to_hash;
128 1 50 33     184 return undef unless defined $params->{'oauth_token'} and defined $params->{'oauth_token_secret'};
129 1         4 return $params;
130             }
131              
132             sub request_oauth2 {
133 1 50   1 1 1158 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
134 1         3 my $self = shift;
135 1         3 my $tx = $self->_build_request_oauth2(@_);
136 1 50       3 if ($cb) {
137             $self->ua->start($tx, sub {
138 0     0   0 my ($ua, $tx) = @_;
139 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
140 0   0     0 my $res = $self->_from_request_oauth2($tx) // return $self->$cb('No bearer token returned');
141 0         0 $self->$cb(undef, $res);
142 0         0 });
143             } else {
144 1         3 $tx = $self->ua->start($tx);
145 1 50       13909 die twitter_tx_error($tx) if $tx->error;
146 1   50     17 return $self->_from_request_oauth2($tx) // die "No bearer token returned\n";
147             }
148             }
149              
150             sub request_oauth2_p {
151 0     0 1 0 my $self = shift;
152 0         0 my $tx = $self->_build_request_oauth2(@_);
153             return $self->ua->start_p($tx)->then(sub {
154 0     0   0 my ($tx) = @_;
155 0 0       0 die twitter_tx_error($tx) if $tx->error;
156 0   0     0 return $self->_from_request_oauth2($tx) // die "No bearer token returned\n";
157 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
158             }
159              
160             sub _build_request_oauth2 {
161 1     1   2 my ($self) = @_;
162 1         3 my $tx = $self->ua->build_tx(POST => _oauth2_url('token'), form => { grant_type => 'client_credentials' });
163 1         619 $self->_oauth2_request->($tx->req);
164 1         14 return $tx;
165             }
166              
167             sub _from_request_oauth2 {
168 1     1   2 my ($self, $tx) = @_;
169 1   50     3 my $params = $tx->res->json // {};
170 1 50       178 return undef unless defined $params->{access_token};
171 1         4 return $params;
172             }
173              
174             sub get_tweet {
175 4 50   4 1 10184 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
176 4         6 my $self = shift;
177 4         11 my $tx = $self->_build_get_tweet(@_);
178 3 50       7 if ($cb) {
179             $self->ua->start($tx, sub {
180 0     0   0 my ($ua, $tx) = @_;
181 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
182 0         0 $self->$cb(undef, _tweet_object($tx->res->json));
183 0         0 });
184             } else {
185 3         7 $tx = $self->ua->start($tx);
186 3 50       17645 die twitter_tx_error($tx) if $tx->error;
187 3         41 return _tweet_object($tx->res->json);
188             }
189             }
190              
191             sub get_tweet_p {
192 0     0 1 0 my $self = shift;
193 0         0 my $tx = $self->_build_get_tweet(@_);
194             return $self->ua->start_p($tx)->then(sub {
195 0     0   0 my ($tx) = @_;
196 0 0       0 die twitter_tx_error($tx) if $tx->error;
197 0         0 return _tweet_object($tx->res->json);
198 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
199             }
200              
201             sub _build_get_tweet {
202 4     4   6 my ($self, $id) = @_;
203 4 50       9 croak 'Tweet ID is required for get_tweet' unless defined $id;
204 4 50       19 croak 'Invalid tweet ID to retrieve' unless $id =~ m/\A[0-9]+\z/;
205 4         13 my $tx = $self->ua->build_tx(GET => _api_url('statuses/show.json')->query(id => $id, tweet_mode => 'extended'));
206 4         1314 $self->authentication->($tx->req);
207 3         33 return $tx;
208             }
209              
210             sub get_user {
211 2 50   2 1 2386 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
212 2         3 my $self = shift;
213 2         7 my $tx = $self->_build_get_user(@_);
214 2 50       6 if ($cb) {
215             $self->ua->start($tx, sub {
216 0     0   0 my ($ua, $tx) = @_;
217 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
218 0         0 $self->$cb(undef, _user_object($tx->res->json));
219 0         0 });
220             } else {
221 2         5 $tx = $self->ua->start($tx);
222 2 50       11721 die twitter_tx_error($tx) if $tx->error;
223 2         28 return _user_object($tx->res->json);
224             }
225             }
226              
227             sub get_user_p {
228 0     0 1 0 my $self = shift;
229 0         0 my $tx = $self->_build_get_user(@_);
230             return $self->ua->start_p($tx)->then(sub {
231 0     0   0 my ($tx) = @_;
232 0 0       0 die twitter_tx_error($tx) if $tx->error;
233 0         0 return _user_object($tx->res->json);
234 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
235             }
236              
237             sub _build_get_user {
238 2     2   7 my ($self, %params) = @_;
239 2         3 my %query;
240 2 100       6 $query{user_id} = $params{user_id} if defined $params{user_id};
241 2 100       6 $query{screen_name} = $params{screen_name} if defined $params{screen_name};
242 2 50       6 croak 'user_id or screen_name is required for get_user' unless %query;
243 2         18 $query{tweet_mode} = 'extended';
244 2         8 my $tx = $self->ua->build_tx(GET => _api_url('users/show.json')->query(%query));
245 2         512 $self->authentication->($tx->req);
246 2         22 return $tx;
247             }
248              
249             sub post_tweet {
250 1 50   1 1 658 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
251 1         2 my $self = shift;
252 1         5 my $tx = $self->_build_post_tweet(@_);
253 1 50       3 if ($cb) {
254             $self->ua->start($tx, sub {
255 0     0   0 my ($ua, $tx) = @_;
256 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
257 0         0 $self->$cb(undef, _tweet_object($tx->res->json));
258 0         0 });
259             } else {
260 1         3 $tx = $self->ua->start($tx);
261 1 50       6035 die twitter_tx_error($tx) if $tx->error;
262 1         15 return _tweet_object($tx->res->json);
263             }
264             }
265              
266             sub post_tweet_p {
267 0     0 1 0 my $self = shift;
268 0         0 my $tx = $self->_build_post_tweet(@_);
269             return $self->ua->start_p($tx)->then(sub {
270 0     0   0 my ($tx) = @_;
271 0 0       0 die twitter_tx_error($tx) if $tx->error;
272 0         0 return _tweet_object($tx->res->json);
273 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
274             }
275              
276             sub _build_post_tweet {
277 1     1   3 my ($self, $status, %params) = @_;
278 1 50       3 croak 'Status text is required to post a tweet' unless defined $status;
279 1         2 my %form;
280 1         3 $form{status} = $status;
281 1         3 $form{$_} = $params{$_} for grep { defined $params{$_} }
  4         8  
282             qw(in_reply_to_status_id lat long place_id);
283 1 0       3 $form{$_} = $params{$_} ? 'true' : 'false' for grep { defined $params{$_} }
  1         3  
284             qw(display_coordinates);
285 1         3 $form{tweet_mode} = 'extended';
286 1         4 my $tx = $self->ua->build_tx(POST => _api_url('statuses/update.json'), form => \%form);
287 1         515 $self->authentication->($tx->req);
288 1         1203 return $tx;
289             }
290              
291             sub retweet {
292 0 0   0 1 0 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
293 0         0 my $self = shift;
294 0         0 my $tx = $self->_build_retweet(@_);
295 0 0       0 if ($cb) {
296             $self->ua->start($tx, sub {
297 0     0   0 my ($ua, $tx) = @_;
298 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
299 0         0 $self->$cb(undef, _tweet_object($tx->res->json));
300 0         0 });
301             } else {
302 0         0 $tx = $self->ua->start($tx);
303 0 0       0 die twitter_tx_error($tx) if $tx->error;
304 0         0 return _tweet_object($tx->res->json);
305             }
306             }
307              
308             sub retweet_p {
309 0     0 1 0 my $self = shift;
310 0         0 my $tx = $self->_build_retweet(@_);
311             return $self->ua->start_p($tx)->then(sub {
312 0     0   0 my ($tx) = @_;
313 0 0       0 die twitter_tx_error($tx) if $tx->error;
314 0         0 return _tweet_object($tx->res->json);
315 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
316             }
317              
318             sub _build_retweet {
319 0     0   0 my ($self, $id) = @_;
320 0 0       0 croak 'Tweet ID is required for retweet' unless defined $id;
321 0 0 0     0 $id = $id->id if blessed $id and $id->isa('Mojo::WebService::Twitter::Tweet');
322 0 0       0 croak 'Invalid tweet ID to retweet' unless $id =~ m/\A[0-9]+\z/;
323 0         0 my $tx = $self->ua->build_tx(POST => _api_url("statuses/retweet/$id.json")->query(tweet_mode => 'extended'));
324 0         0 $self->authentication->($tx->req);
325 0         0 return $tx;
326             }
327              
328             sub search_tweets {
329 1 50   1 1 3906 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
330 1         2 my $self = shift;
331 1         5 my $tx = $self->_build_search_tweets(@_);
332 1 50       3 if ($cb) {
333             $self->ua->start($tx, sub {
334 0     0   0 my ($ua, $tx) = @_;
335 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
336 0   0     0 $self->$cb(undef, Mojo::Collection->new(map { _tweet_object($_) } @{$tx->res->json->{statuses} // []}));
  0         0  
  0         0  
337 0         0 });
338             } else {
339 1         3 $tx = $self->ua->start($tx);
340 1 50       7262 die twitter_tx_error($tx) if $tx->error;
341 1   50     14 return Mojo::Collection->new(map { _tweet_object($_) } @{$tx->res->json->{statuses} // []});
  15         65224  
  1         2  
342             }
343             }
344              
345             sub search_tweets_p {
346 0     0 1 0 my $self = shift;
347 0         0 my $tx = $self->_build_search_tweets(@_);
348             return $self->ua->start_p($tx)->then(sub {
349 0     0   0 my ($tx) = @_;
350 0 0       0 die twitter_tx_error($tx) if $tx->error;
351 0   0     0 return Mojo::Collection->new(map { _tweet_object($_) } @{$tx->res->json->{statuses} // []});
  0         0  
  0         0  
352 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
353             }
354              
355             sub _build_search_tweets {
356 1     1   3 my ($self, $q, %params) = @_;
357 1 50       3 croak 'Search query is required for search_tweets' unless defined $q;
358 1         2 my %query;
359 1         3 $query{q} = $q;
360 1         2 my $geocode = $params{geocode};
361 1 50       3 if (ref $geocode) {
362 0         0 my ($lat, $long, $rad);
363 0 0       0 ($lat, $long, $rad) = @$geocode if ref $geocode eq 'ARRAY';
364 0 0       0 ($lat, $long, $rad) = @{$geocode}{'latitude','longitude','radius'} if ref $geocode eq 'HASH';
  0         0  
365 0 0 0     0 $geocode = "$lat,$long,$rad" if defined $lat and defined $long and defined $rad;
      0        
366             }
367 1 50       3 $query{geocode} = $geocode if defined $geocode;
368 1         3 $query{$_} = $params{$_} for grep { defined $params{$_} } qw(lang result_type count until since_id max_id);
  6         10  
369 1         3 $query{tweet_mode} = 'extended';
370 1         4 my $tx = $self->ua->build_tx(GET => _api_url('search/tweets.json')->query(%query));
371 1         308 $self->authentication->($tx->req);
372 1         12 return $tx;
373             }
374              
375             sub verify_credentials {
376 1 50   1 1 11 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
377 1         2 my $self = shift;
378 1         5 my $tx = $self->_build_verify_credentials(@_);
379 1 50       3 if ($cb) {
380             $self->ua->start($tx, sub {
381 0     0   0 my ($ua, $tx) = @_;
382 0 0       0 return $self->$cb(twitter_tx_error($tx)) if $tx->error;
383 0         0 $self->$cb(undef, Mojo::WebService::Twitter::User->new(twitter => $self)->from_source($tx->res->json));
384 0         0 });
385             } else {
386 1         4 $tx = $self->ua->start($tx);
387 1 50       6830 die twitter_tx_error($tx) if $tx->error;
388 1         23 return Mojo::WebService::Twitter::User->new(twitter => $self)->from_source($tx->res->json);
389             }
390             }
391              
392             sub verify_credentials_p {
393 0     0 1 0 my $self = shift;
394 0         0 my $tx = $self->_build_verify_credentials(@_);
395             return $self->ua->start_p($tx)->then(sub {
396 0     0   0 my ($tx) = @_;
397 0 0       0 die twitter_tx_error($tx) if $tx->error;
398 0         0 return Mojo::WebService::Twitter::User->new(twitter => $self)->from_source($tx->res->json);
399 0     0   0 }, sub { die Mojo::WebService::Twitter::Error->new(connection_error => $_[0]) });
  0         0  
400             }
401              
402             sub _build_verify_credentials {
403 1     1   17 my ($self) = @_;
404 1         4 my $tx = $self->ua->build_tx(GET => _api_url('account/verify_credentials.json')->query(tweet_mode => 'extended'));
405 1         280 $self->authentication->($tx->req);
406 1         1503 return $tx;
407             }
408              
409 9     9   56 sub _api_url { Mojo::WebService::Twitter::Util::_api_url(@_) }
410 2     2   16 sub _oauth_url { Mojo::WebService::Twitter::Util::_oauth_url(@_) }
411 1     1   8 sub _oauth2_url { Mojo::WebService::Twitter::Util::_oauth2_url(@_) }
412              
413             sub _oauth {
414 3     3   39 my $self = shift;
415 3         11 my ($api_key, $api_secret) = ($self->api_key, $self->api_secret);
416 3 50 33     33 croak 'Twitter API key and secret are required' unless defined $api_key and defined $api_secret;
417 3         6 my ($token, $token_secret) = @_;
418 3         26 my $oauth = WWW::OAuth->new(
419             client_id => $api_key,
420             client_secret => $api_secret,
421             token => $token,
422             token_secret => $token_secret,
423             );
424 3     4   180 return sub { $oauth->authenticate(@_) };
  4         16  
425             }
426              
427             sub _oauth2_request {
428 1     1   6 my $self = shift;
429 1         3 my ($api_key, $api_secret) = ($self->api_key, $self->api_secret);
430 1 50 33     11 croak 'Twitter API key and secret are required' unless defined $api_key and defined $api_secret;
431 1         4 my $token = b64_encode(url_escape($api_key) . ':' . url_escape($api_secret), '');
432 1     1   23 return sub { shift->headers->authorization("Basic $token") };
  1         4  
433             }
434              
435             sub _oauth2 {
436 1     1   2 my $self = shift;
437 1   33     3 my $token = shift // croak 'Access token is required for OAuth2 authentication';
438 1     6   5 return sub { shift->headers->authorization("Bearer $token") };
  6         12  
439             }
440              
441 19     19   16969 sub _tweet_object { Mojo::WebService::Twitter::Tweet->new->from_source(shift) }
442              
443 2     2   12002 sub _user_object { Mojo::WebService::Twitter::User->new->from_source(shift) }
444              
445             1;
446              
447             =head1 NAME
448              
449             Mojo::WebService::Twitter - Simple Twitter API client
450              
451             =head1 SYNOPSIS
452              
453             my $twitter = Mojo::WebService::Twitter->new(api_key => $api_key, api_secret => $api_secret);
454            
455             # Request and store access token
456             $twitter->authentication($twitter->request_oauth2);
457            
458             # Blocking API request
459             my $user = $twitter->get_user(screen_name => $name);
460             say $user->screen_name . ' was created on ' . $user->created_at->ymd;
461            
462             # Non-blocking API request
463             $twitter->get_tweet($tweet_id, sub {
464             my ($twitter, $err, $tweet) = @_;
465             print $err ? "Error: $err" : 'Tweet: ' . $tweet->text . "\n";
466             });
467             Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
468            
469             # Non-blocking API request using promises
470             $twitter->get_tweet_p($tweet_id)->then(sub {
471             my ($tweet) = @_;
472             print 'Tweet: ' . $tweet->text . "\n";
473             })->catch(sub {
474             my ($err) = @_;
475             print "Error: $err";
476             })->wait;
477            
478             # Some requests require authentication on behalf of a user
479             $twitter->authentication(oauth => $token, $secret);
480             my $authorizing_user = $twitter->verify_credentials;
481            
482             my $new_tweet = $twitter->post_tweet('Something new and exciting!');
483              
484             =head1 DESCRIPTION
485              
486             L is a L based
487             L API client that can perform requests
488             synchronously or asynchronously. An API key and secret for a
489             L are required.
490              
491             API requests are authenticated by the L coderef, which can
492             either use an OAuth 2.0 access token to authenticate requests on behalf of the
493             application itself, or OAuth 1.0 credentials (access token and secret) to
494             authenticate requests on behalf of a specific user. The L
495             script can be used to obtain Twitter OAuth credentials for a user from the
496             command-line. A web application may wish to implement its own OAuth
497             authorization flow, passing a callback URL back to the application in
498             L, then calling L with the passed verifier
499             code to retrieve the credentials. See the
500             L for
501             more details.
502              
503             All methods which query the Twitter API can be called with an optional trailing
504             callback argument to run a non-blocking API query. Alternatively, the C<_p>
505             variant will run a non-blocking API query and return a L, which
506             can simplify complex sequences of non-blocking queries. On connection, HTTP, or
507             API error, blocking API queries will throw a L
508             exception. Non-blocking API queries will pass this exception object to the
509             callback or reject the promise, and otherwise pass the results to the callback
510             or resolve the promise.
511              
512             Note that this distribution implements only a subset of the Twitter API.
513             Additional features may be added as requested. See L for a more
514             fully-featured lightweight and modern twitter client library.
515              
516             =head1 ATTRIBUTES
517              
518             L implements the following attributes.
519              
520             =head2 api_key
521              
522             my $api_key = $twitter->api_key;
523             $twitter = $twitter->api_key($api_key);
524              
525             API key for your L.
526              
527             =head2 api_secret
528              
529             my $api_secret = $twitter->api_secret;
530             $twitter = $twitter->api_secret($api_secret);
531              
532             API secret for your L.
533              
534             =head2 ua
535              
536             my $ua = $webservice->ua;
537             $webservice = $webservice->ua(Mojo::UserAgent->new);
538              
539             HTTP user agent object to use for synchronous and asynchronous requests,
540             defaults to a L object.
541              
542             =head1 METHODS
543              
544             L inherits all methods from L, and
545             implements the following new ones.
546              
547             =head2 authentication
548              
549             my $code = $twitter->authentication;
550             $twitter = $twitter->authentication($code);
551             $twitter = $twitter->authentication({oauth_token => $access_token, oauth_token_secret => $access_token_secret});
552             $twitter = $twitter->authentication(oauth => $access_token, $access_token_secret);
553             $twitter = $twitter->authentication({access_token => $access_token});
554             $twitter = $twitter->authentication(oauth2 => $access_token);
555              
556             Get or set coderef used to authenticate API requests. Passing C with
557             optional token and secret, or a hashref containing C and
558             C, will set a coderef which uses a L to
559             authenticate requests. Passing C with required token or a hashref
560             containing C will set a coderef which authenticates using the
561             passed access token. The coderef will receive the L
562             object as the first parameter, and an optional hashref of C parameters.
563              
564             =head2 request_oauth
565              
566             =head2 request_oauth_p
567              
568             my $res = $twitter->request_oauth;
569             my $res = $twitter->request_oauth($callback_url);
570             $twitter->request_oauth(sub {
571             my ($twitter, $error, $res) = @_;
572             });
573             my $p = $twitter->request_oauth_p;
574              
575             Send an OAuth 1.0 authorization request and return a hashref containing
576             C and C (request token and secret). An
577             optional OAuth callback URL may be passed; by default, C is passed to use
578             PIN-based authorization. The user should be directed to the authorization URL
579             which can be retrieved by passing the request token to
580             L. After
581             authorization, the user will either be redirected to the callback URL with the
582             query parameter C, or receive a PIN to return to the
583             application. Either the verifier string or PIN should be passed to
584             L to retrieve an access token and secret.
585              
586             =head2 verify_oauth
587              
588             =head2 verify_oauth_p
589              
590             my $res = $twitter->verify_oauth($verifier, $request_token, $request_token_secret);
591             $twitter->verify_oauth($verifier, $request_token, $request_token_secret, sub {
592             my ($twitter, $error, $res) = @_;
593             });
594             my $p = $twitter->verify_oauth_p($verifier, $request_token, $request_token_secret);
595              
596             Verify an OAuth 1.0 authorization request with the verifier string or PIN from
597             the authorizing user, and the previously obtained request token and secret. The
598             secret is cached by L and may be omitted. Returns a hashref
599             containing C and C (access token and secret)
600             which may be passed directly to L to authenticate requests
601             on behalf of the user.
602              
603             =head2 request_oauth2
604              
605             =head2 request_oauth2_p
606              
607             my $res = $twitter->request_oauth2;
608             $twitter->request_oauth2(sub {
609             my ($twitter, $error, $res) = @_;
610             });
611             my $p = $twitter->request_oauth2_p;
612              
613             Request OAuth 2 credentials and return a hashref containing an C
614             that can be passed directly to L to authenticate requests on
615             behalf of the application itself.
616              
617             =head2 get_tweet
618              
619             =head2 get_tweet_p
620              
621             my $tweet = $twitter->get_tweet($tweet_id);
622             $twitter->get_tweet($tweet_id, sub {
623             my ($twitter, $err, $tweet) = @_;
624             });
625             $twitter->get_tweet_p($tweet_id);
626              
627             Retrieve a L by tweet ID.
628              
629             =head2 get_user
630              
631             =head2 get_user_p
632              
633             my $user = $twitter->get_user(user_id => $user_id);
634             my $user = $twitter->get_user(screen_name => $screen_name);
635             $twitter->get_user(screen_name => $screen_name, sub {
636             my ($twitter, $err, $user) = @_;
637             });
638             my $p = $twitter->get_user_p(user_id => $user_id);
639              
640             Retrieve a L by user ID or screen name.
641              
642             =head2 post_tweet
643              
644             =head2 post_tweet_p
645              
646             my $tweet = $twitter->post_tweet($text, %options);
647             $twitter->post_tweet($text, %options, sub {
648             my ($twitter, $err, $tweet) = @_;
649             });
650             my $p = $twitter->post_tweet_p($text, %options);
651              
652             Post a status update (tweet) and retrieve the resulting
653             L. Requires OAuth 1.0 authentication. Accepts
654             the following options:
655              
656             =over
657              
658             =item in_reply_to_status_id
659              
660             in_reply_to_status_id => '12345'
661              
662             Indicates the tweet is in reply to an existing tweet ID. IDs should be
663             specified as a string to avoid issues with large integers. This parameter will
664             be ignored by the Twitter API unless the author of the referenced tweet is
665             mentioned within the status text as C<@username>.
666              
667             =item lat
668              
669             lat => '37.781157'
670              
671             The latitude of the location to attach to the tweet. This parameter will be
672             ignored by the Twitter API unless it is within the range C<-90.0> to C<90.0>,
673             and a corresponding C is specified. It is recommended to specify values
674             as strings to avoid issues with floating-point representations.
675              
676             =item long
677              
678             long => '-122.398720'
679              
680             The longitude of the location to attach to the tweet. This parameter will be
681             ignored by the Twitter API unless it is within the range C<-180.0> to C<180.0>,
682             and a corresponding C is specified. It is recommended to specify values as
683             strings to avoid issues with floating-point representations.
684              
685             =item place_id
686              
687             place_id => 'df51dec6f4ee2b2c'
688              
689             A Twitter L to attach to
690             the tweet.
691              
692             =item display_coordinates
693              
694             display_coordinates => 1
695              
696             If true, tweet will display the exact coordinates the tweet was sent from.
697              
698             =back
699              
700             =head2 retweet
701              
702             =head2 retweet_p
703              
704             my $tweet = $twitter->retweet($tweet_id);
705             $twitter->retweet($tweet_id, sub {
706             my ($twitter, $err, $tweet) = @_;
707             });
708             my $p = $twitter->retweet_p($tweet_id);
709              
710             Retweet the tweet ID or L object. Returns a
711             L representing the original tweet. Requires
712             OAuth 1.0 authentication.
713              
714             =head2 search_tweets
715              
716             =head2 search_tweets_p
717              
718             my $tweets = $twitter->search_tweets($query);
719             my $tweets = $twitter->search_tweets($query, %options);
720             $twitter->search_tweets($query, %options, sub {
721             my ($twitter, $err, $tweets) = @_;
722             });
723             my $p = $twitter->search_tweets_p($query, %options);
724              
725             Search Twitter and return a L of L
726             objects. Accepts the following options:
727              
728             =over
729              
730             =item geocode
731              
732             geocode => '37.781157,-122.398720,1mi'
733             geocode => ['37.781157','-122.398720','1mi']
734             geocode => {latitude => '37.781157', longitude => '-122.398720', radius => '1mi'}
735              
736             Restricts tweets to the given radius of the given latitude/longitude. Radius
737             must be specified as C (miles) or C (kilometers). It is recommended to
738             specify values as strings to avoid issues with floating-point representations.
739              
740             =item lang
741              
742             lang => 'eu'
743              
744             Restricts tweets to the given L
745             language code.
746              
747             =item result_type
748              
749             result_type => 'recent'
750              
751             Specifies what type of search results to receive. Valid values are C,
752             C, and C (default).
753              
754             =item count
755              
756             count => 5
757              
758             Limits the search results per page. Maximum C<100>, default C<15>.
759              
760             =item until
761              
762             until => '2015-07-19'
763              
764             Restricts tweets to those created before the given date, in the format
765             C.
766              
767             =item since_id
768              
769             since_id => '12345'
770              
771             Restricts results to those more recent than the given tweet ID. IDs should be
772             specified as a string to avoid issues with large integers. See
773             L for more information on
774             filtering results with C and C.
775              
776             =item max_id
777              
778             max_id => '54321'
779              
780             Restricts results to those older than (or equal to) the given tweet ID. IDs
781             should be specified as a string to avoid issues with large integers. See
782             L for more information on
783             filtering results with C and C.
784              
785             =back
786              
787             =head2 verify_credentials
788              
789             =head2 verify_credentials_p
790              
791             my $user = $twitter->verify_credentials;
792             $twitter->verify_credentials(sub {
793             my ($twitter, $error, $user) = @_;
794             });
795             my $p = $twitter->verify_credentials_p;
796              
797             Verify the authorizing user's credentials and return a representative
798             L object. Requires OAuth 1.0 authentication.
799              
800             =head1 BUGS
801              
802             Report any issues on the public bugtracker.
803              
804             =head1 AUTHOR
805              
806             Dan Book
807              
808             =head1 COPYRIGHT AND LICENSE
809              
810             This software is Copyright (c) 2015 by Dan Book.
811              
812             This is free software, licensed under:
813              
814             The Artistic License 2.0 (GPL Compatible)
815              
816             =head1 SEE ALSO
817              
818             L, L