File Coverage

blib/lib/Twitter/API.pm
Criterion Covered Total %
statement 178 185 96.2
branch 37 42 88.1
condition 28 46 60.8
subroutine 49 50 98.0
pod 7 28 25.0
total 299 351 85.1


line stmt bran cond sub pod time code
1             package Twitter::API;
2             # ABSTRACT: A Twitter REST API library for Perl
3              
4             our $VERSION = '1.0006';
5 13     13   1772996 use 5.14.1;
  13         165  
6 13     13   6344 use Moo;
  13         129252  
  13         61  
7 13     13   17332 use Carp;
  13         31  
  13         617  
8 13     13   6174 use Digest::SHA;
  13         31076  
  13         731  
9 13     13   6689 use Encode qw/encode_utf8/;
  13         170354  
  13         1022  
10 13     13   5619 use HTTP::Request;
  13         101113  
  13         415  
11 13     13   6300 use HTTP::Request::Common qw/POST/;
  13         26549  
  13         771  
12 13     13   5435 use JSON::MaybeXS ();
  13         61348  
  13         354  
13 13     13   93 use Module::Runtime qw/use_module/;
  13         24  
  13         126  
14 13     13   5653 use Ref::Util qw/is_arrayref is_ref/;
  13         15712  
  13         865  
15 13     13   3766 use Try::Tiny;
  13         9571  
  13         617  
16 13     13   5614 use Twitter::API::Context;
  13         47  
  13         631  
17 13     13   7229 use Twitter::API::Error;
  13         43  
  13         476  
18 13     13   108 use URI;
  13         27  
  13         344  
19 13     13   5111 use URL::Encode ();
  13         51446  
  13         409  
20 13     13   6474 use WWW::OAuth;
  13         104348  
  13         505  
21 13     13   107 use namespace::clean;
  13         35  
  13         112  
22              
23             with qw/MooX::Traits/;
24 70     70   158126 sub _trait_namespace { 'Twitter::API::Trait' }
25              
26             has api_version => (
27             is => 'ro',
28             default => sub { '1.1' },
29             );
30              
31             has api_ext => (
32             is => 'ro',
33             default => sub { '.json' },
34             );
35              
36             has [ qw/consumer_key consumer_secret/ ] => (
37             is => 'ro',
38             required => 1,
39             );
40              
41             has [ qw/access_token access_token_secret/ ] => (
42             is => 'rw',
43             predicate => 1,
44             clearer => 1,
45             );
46              
47             # The secret is no good without the token.
48             after clear_access_token => sub {
49             shift->clear_access_token_secret;
50             };
51              
52             has api_url => (
53             is => 'ro',
54             default => sub { 'https://api.twitter.com' },
55             );
56              
57             has upload_url => (
58             is => 'ro',
59             default => sub { 'https://upload.twitter.com' },
60             );
61              
62             has agent => (
63             is => 'ro',
64             default => sub {
65             (join('/', __PACKAGE__, $VERSION) =~ s/::/-/gr) . ' (Perl)';
66             },
67             );
68              
69             has timeout => (
70             is => 'ro',
71             default => sub { 10 },
72             );
73              
74             has default_headers => (
75             is => 'ro',
76             default => sub {
77             my $agent = shift->agent;
78             {
79             user_agent => $agent,
80             x_twitter_client => $agent,
81             x_twitter_client_version => $VERSION,
82             x_twitter_client_url => 'https://github.com/semifor/Twitter-API',
83             };
84             },
85             );
86              
87             has user_agent => (
88             is => 'ro',
89             lazy => 1,
90             default => sub {
91             my $self = shift;
92              
93             use_module 'HTTP::Thin';
94             HTTP::Thin->new(
95             timeout => $self->timeout,
96             agent => $self->agent,
97             );
98             },
99             handles => {
100             send_request => 'request',
101             },
102             );
103              
104             has json_parser => (
105             is => 'ro',
106             lazy => 1,
107             default => sub { JSON::MaybeXS->new(utf8 => 1) },
108             handles => {
109             from_json => 'decode',
110             to_json => 'encode',
111             },
112             );
113              
114             around BUILDARGS => sub {
115             my ( $next, $class ) = splice @_, 0, 2;
116              
117             my $args = $class->$next(@_);
118             croak 'use new_with_traits' if exists $args->{traits};
119              
120             return $args;
121             };
122              
123 34     34 1 7699 sub get { shift->request( get => @_ ) }
124 7     7 1 1748 sub post { shift->request( post => @_ ) }
125              
126             sub request {
127 78     78 0 839 my $self = shift;
128              
129             my $c = Twitter::API::Context->new({
130             http_method => uc shift,
131             url => shift,
132             args => shift || {},
133             # shallow copy so we don't spoil the defaults
134             headers => {
135 78   100     420 %{ $self->default_headers },
  78         2380  
136             accept => 'application/json',
137             content_type => 'application/json;charset=utf8',
138             },
139             extra_args => \@_,
140             });
141              
142 78         694 $self->extract_options($c);
143 78         281 $self->preprocess_args($c);
144 78         289 $self->preprocess_url($c);
145 78         265 $self->prepare_request($c);
146 78         488 $self->add_authorization($c);
147              
148             # Allow early exit for things like Twitter::API::AnyEvent
149 77   100     131395 $c->set_http_response($self->send_request($c) // return $c);
150              
151 58         6746 $self->inflate_response($c);
152 53 100       853 return wantarray ? ( $c->result, $c ) : $c->result;
153             }
154              
155             sub extract_options {
156 78     78 0 201 my ( $self, $c ) = @_;
157              
158 78         198 my $args = $c->args;
159 78         262 for ( keys %$args ) {
160 112 100       508 $c->set_option($1, delete $$args{$_}) if /^-(.+)/;
161             }
162             }
163              
164             sub preprocess_args {
165 78     78 0 165 my ( $self, $c ) = @_;
166              
167 78 100       271 if ( $c->http_method eq 'GET' ) {
168 38         127 $self->flatten_array_args($c->args);
169             }
170              
171             # If any of the args are arrayrefs, we'll infer it's multipart/form-data
172             $c->set_option(multipart_form_data => 1) if
173 78 100 100     327 $c->http_method eq 'POST' && !!grep is_ref($_), values %{ $c->args };
  39         271  
174             }
175              
176             sub preprocess_url {
177 78     78 0 151 my ( $self, $c ) = @_;
178              
179 78         216 my $url = $c->url;
180 78         150 my $args = $c->args;
181 78   0     198 $url =~ s[:(\w+)][delete $$args{$1} // croak "missing arg $1"]eg;
  0         0  
182 78         259 $c->set_url($self->api_url_for($url));
183             }
184              
185             sub prepare_request {
186 78     78 0 160 my ( $self, $c ) = @_;
187              
188             # possibly override Accept header
189 78 100       307 $c->set_header(accept => $c->get_option('accept'))
190             if $c->has_option('accept');
191              
192             # dispatch on HTTP method
193 78         221 my $http_method = $c->http_method;
194 78         242 my $prepare_method = join '_', 'mk', lc($http_method), 'request';
195 78   33     356 my $dispatch = $self->can($prepare_method)
196             || croak "unexpected HTTP method: $http_method";
197              
198 78         200 my $req = $self->$dispatch($c);
199 78         69112 $c->set_http_request($req);
200             }
201              
202             sub mk_get_request {
203 38     38 0 104 shift->mk_simple_request(GET => @_);
204             }
205              
206             sub mk_delete_request {
207 1     1 0 3 shift->mk_simple_request(DELETE => @_);
208             }
209              
210             sub mk_post_request {
211 39     39 0 95 my ( $self, $c ) = @_;
212              
213 39 100       160 if ( $c->get_option('multipart_form_data') ) {
214 10         46 return $self->mk_multipart_post($c);
215             }
216              
217 29 100       99 if ( $c->has_option('to_json') ) {
218 5         19 return $self->mk_json_post($c);
219             }
220              
221 24         99 return $self->mk_form_urlencoded_post($c);
222             }
223              
224             sub mk_multipart_post {
225 10     10 0 30 my ( $self, $c ) = @_;
226              
227 10         45 $c->set_header(content_type => 'multipart/form-data;charset=utf-8');
228             POST $c->url,
229 10         47 %{ $c->headers },
230             Content => [
231 10 100       31 map { is_ref($_) ? $_ : encode_utf8 $_ } %{ $c->args },
  30         231  
  10         68  
232             ];
233             }
234              
235             sub mk_json_post {
236 5     5 0 12 my ( $self, $c ) = @_;
237              
238             POST $c->url,
239 5         13 %{ $c->headers },
  5         22  
240             Content => $self->to_json($c->get_option('to_json'));
241             }
242              
243             sub mk_form_urlencoded_post {
244 24     24 0 84 my ( $self, $c ) = @_;
245              
246 24         80 $c->set_header(
247             content_type => 'application/x-www-form-urlencoded;charset=utf-8');
248             POST $c->url,
249 24         51 %{ $c->headers },
  24         141  
250             Content => $self->encode_args_string($c->args);
251             }
252              
253             sub mk_simple_request {
254 39     39 0 76 my ( $self, $http_method, $c ) = @_;
255              
256 39         206 my $uri = URI->new($c->url);
257 39 100       30061 if ( my $encoded = $self->encode_args_string($c->args) ) {
258 6         26 $uri->query($encoded);
259             }
260              
261             # HTTP::Message expects an arrayref, so transform
262 39         224 my $headers = [ %{ $c->headers } ];
  39         209  
263              
264 39         259 return HTTP::Request->new($http_method, $uri, $headers);
265             }
266              
267             sub add_authorization {
268 84     84 0 172 my ( $self, $c ) = @_;
269              
270 84         185 my $req = $c->http_request;
271              
272 84         460 my %cred = (
273             client_id => $self->consumer_key,
274             client_secret => $self->consumer_secret,
275             );
276              
277 84         145 my %oauth;
278             # only the token management methods set 'oauth_args'
279 84 100       236 if ( my $opt = $c->get_option('oauth_args') ) {
280 12         45 %oauth = %$opt;
281 12         39 $cred{token} = delete $oauth{oauth_token};
282 12         27 $cred{token_secret} = delete $oauth{oauth_token_secret};
283             }
284             else {
285             # protected request; requires tokens
286 72   100     203 $cred{token} = $c->get_option('token')
      66        
287             // $self->access_token
288             // croak 'requires an oauth token';
289 71   66     174 $cred{token_secret} = $c->get_option('token_secret')
      33        
290             // $self->access_token_secret
291             // croak 'requires an oauth token secret';
292             }
293              
294 83         652 WWW::OAuth->new(%cred)->authenticate($req, \%oauth);
295             }
296              
297             around send_request => sub {
298             my ( $orig, $self, $c ) = @_;
299              
300             $self->$orig($c->http_request);
301             };
302              
303             sub inflate_response {
304 56     56 0 116 my ( $self, $c ) = @_;
305              
306 56         144 my $res = $c->http_response;
307 56         78 my $data;
308             try {
309 56 100 100 56   2621 if ( $res->content_type eq 'application/json' ) {
    100 50        
    50          
310 20         599 $data = $self->from_json($res->content);
311             }
312             elsif ( ( $res->content_length // 0 ) == 0 ) {
313             # E.g., 200 OK from media/metadata/create
314 24         1308 $data = '';
315             }
316             elsif ( ($c->get_option('accept') // '') eq 'application/x-www-form-urlencoded' ) {
317              
318             # Twitter sets Content-Type: text/html for /oauth/request_token and
319             # /oauth/access_token even though they return url encoded form
320             # data. So we'll decode based on what we expected when we set the
321             # Accept header. We don't want to assume form data when we didn't
322             # request it, because sometimes twitter returns 200 OK with actual
323             # HTML content. We don't want to decode and return that. It's an
324             # error. We'll just leave $data unset if we don't have a reasonable
325             # expectation of the content type.
326              
327 12         35 $data = URL::Encode::url_params_mixed($res->content, 1);
328             }
329             }
330             catch {
331             # Failed to decode the response body, synthesize an error response
332 0     0   0 s/ at .* line \d+.*//s; # remove file/line number
333 0         0 $res->code(500);
334 0         0 $res->status($_);
335 56         510 };
336              
337 56         3231 $c->set_result($data);
338 56 100 66     323 return if defined($data) && $res->is_success;
339              
340 5         70 $self->process_error_response($c);
341             }
342              
343             sub flatten_array_args {
344 38     38 0 62 my ( $self, $args ) = @_;
345              
346             # transform arrays to comma delimited strings
347 38         84 for my $k ( keys %$args ) {
348 9         18 my $v = $$args{$k};
349 9 50       25 $$args{$k} = join ',' => @$v if is_arrayref($v);
350             }
351             }
352              
353             sub encode_args_string {
354 63     63 0 147 my ( $self, $args ) = @_;
355              
356 63         87 my @pairs;
357 63         233 for my $k ( sort keys %$args ) {
358 38         309 push @pairs, join '=', map $self->uri_escape($_), $k, $$args{$k};
359             }
360              
361 63         538 join '&', @pairs;
362             }
363              
364 76     76 0 722 sub uri_escape { URL::Encode::url_encode_utf8($_[1]) }
365              
366             sub process_error_response {
367 5     5 0 36 Twitter::API::Error->throw({ context => $_[1] });
368             }
369              
370             sub api_url_for {
371 80     80 0 139 my $self = shift;
372              
373 80         504 $self->_url_for($self->api_ext, $self->api_url, $self->api_version, @_);
374             }
375              
376             sub upload_url_for {
377 9     9 0 23 my $self = shift;
378              
379 9         92 $self->_url_for($self->api_ext, $self->upload_url, $self->api_version, @_);
380             }
381              
382             sub oauth_url_for {
383 42     42 0 63 my $self = shift;
384              
385 42         144 $self->_url_for('', $self->api_url, 'oauth', @_);
386             }
387              
388             sub _url_for {
389 137     137   398 my ( $self, $ext, @parts ) = @_;
390              
391             # If we already have a fully qualified URL, just return it
392 137 100       546 return $_[-1] if $_[-1] =~ m(^https?://);
393              
394 111         309 my $url = join('/', @parts);
395 111 100       308 $url .= $ext if $ext;
396              
397 111         625 return $url;
398             }
399              
400             # OAuth handshake
401              
402             sub oauth_request_token {
403 4     4 1 1498 my $self = shift;
404 4 50 33     24 my %args = @_ == 1 && is_ref($_[0]) ? %{ $_[0] } : @_;
  0         0  
405              
406 4         9 my %oauth_args;
407 4   100     19 $oauth_args{oauth_callback} = delete $args{callback} // 'oob';
408 4         17 return $self->request(post => $self->oauth_url_for('request_token'), {
409             -accept => 'application/x-www-form-urlencoded',
410             -oauth_args => \%oauth_args,
411             %args, # i.e. ( x_auth_access_type => 'read' )
412             });
413             }
414              
415             sub _auth_url {
416 26     26   53 my ( $self, $endpoint ) = splice @_, 0, 2;
417 26 50 33     117 my %args = @_ == 1 && is_ref($_[0]) ? %{ $_[0] } : @_;
  0         0  
418              
419 26         73 my $uri = URI->new($self->oauth_url_for($endpoint));
420 26         17767 $uri->query_form(%args);
421 26         2018 return $uri;
422             };
423              
424 4     4 1 1130 sub oauth_authentication_url { shift->_auth_url(authenticate => @_) }
425 4     4 1 1055 sub oauth_authorization_url { shift->_auth_url(authorize => @_) }
426              
427             sub oauth_access_token {
428 8     8 1 1186 my $self = shift;
429 8 100 66     52 my %args = @_ == 1 && is_ref($_[0]) ? %{ $_[0] } : @_;
  6         24  
430              
431             # We'll take 'em with or without the oauth_ prefix :)
432 8         13 my %oauth_args;
433 8         72 @oauth_args{map s/^(?!oauth_)/oauth_/r, keys %args} = values %args;
434              
435 8         33 $self->request(post => $self->oauth_url_for('access_token'), {
436             -accept => 'application/x-www-form-urlencoded',
437             -oauth_args => \%oauth_args,
438             });
439             }
440              
441             sub xauth {
442 3     3 1 2183 my ( $self, $username, $password ) = splice @_, 0, 3;
443 3 50 33     22 my %extra_args = @_ == 1 && is_ref($_[0]) ? %{ $_[0] } : @_;
  0         0  
444              
445 3         13 $self->request(post => $self->oauth_url_for('access_token'), {
446             -accept => 'application/x-www-form-urlencoded',
447             -oauth_args => {},
448             x_auth_mode => 'client_auth',
449             x_auth_password => $password,
450             x_auth_username => $username,
451             %extra_args,
452             });
453             }
454              
455             1;
456              
457             __END__
458              
459             =pod
460              
461             =encoding UTF-8
462              
463             =head1 NAME
464              
465             Twitter::API - A Twitter REST API library for Perl
466              
467             =for html <a href="https://travis-ci.org/semifor/Twitter-API"><img src="https://travis-ci.org/semifor/Twitter-API.svg?branch=master" alt="Build Status" /></a>
468              
469             =head1 VERSION
470              
471             version 1.0006
472              
473             =head1 SYNOPSIS
474              
475             ### Common usage ###
476              
477             use Twitter::API;
478             my $client = Twitter::API->new_with_traits(
479             traits => 'Enchilada',
480             consumer_key => $YOUR_CONSUMER_KEY,
481             consumer_secret => $YOUR_CONSUMER_SECRET,
482             access_token => $YOUR_ACCESS_TOKEN,
483             access_token_secret => $YOUR_ACCESS_TOKEN_SECRET,
484             );
485              
486             my $me = $client->verify_credentials;
487             my $user = $client->show_user('twitter');
488              
489             # In list context, both the Twitter API result and a Twitter::API::Context
490             # object are returned.
491             my ($r, $context) = $client->home_timeline({ count => 200, trim_user => 1 });
492             my $remaning = $context->rate_limit_remaining;
493             my $until = $context->rate_limit_reset;
494              
495              
496             ### No frills ###
497              
498             my $client = Twitter::API->new(
499             consumer_key => $YOUR_CONSUMER_KEY,
500             consumer_secret => $YOUR_CONSUMER_SECRET,
501             );
502              
503             my $r = $client->get('account/verify_credentials', {
504             -token => $an_access_token,
505             -token_secret => $an_access_token_secret,
506             });
507              
508             ### Error handling ###
509              
510             use Twitter::API::Util 'is_twitter_api_error';
511             use Try::Tiny;
512              
513             try {
514             my $r = $client->verify_credentials;
515             }
516             catch {
517             die $_ unless is_twitter_api_error($_);
518              
519             # The error object includes plenty of information
520             say $_->http_request->as_string;
521             say $_->http_response->as_string;
522             say 'No use retrying right away' if $_->is_permanent_error;
523             if ( $_->is_token_error ) {
524             say "There's something wrong with this token."
525             }
526             if ( $_->twitter_error_code == 326 ) {
527             say "Oops! Twitter thinks you're spam bot!";
528             }
529             };
530              
531             =head1 DESCRIPTION
532              
533             Twitter::API provides an interface to the Twitter REST API for perl.
534              
535             Features:
536              
537             =over 4
538              
539             =item *
540              
541             full support for all Twitter REST API endpoints
542              
543             =item *
544              
545             not dependent on a new distribution for new endpoint support
546              
547             =item *
548              
549             optionally specify access tokens per API call
550              
551             =item *
552              
553             error handling via an exception object that captures the full request/response context
554              
555             =item *
556              
557             full support for OAuth handshake and Xauth authentication
558              
559             =back
560              
561             Additional features are available via optional traits:
562              
563             =over 4
564              
565             =item *
566              
567             convenient methods for API endpoints with simplified argument handling via L<ApiMethods|Twitter::API::Trait::ApiMethods>
568              
569             =item *
570              
571             normalized booleans (Twitter likes 'true' and 'false', except when it doesn't) via L<NormalizeBooleans|Twitter::API::Trait::NormalizeBooleans>
572              
573             =item *
574              
575             automatic decoding of HTML entities via L<DecodeHtmlEntities|Twitter::API::Trait::DecodeHtmlEntities>
576              
577             =item *
578              
579             automatic retry on transient errors via L<RetryOnError|Twitter::API::Trait::RetryOnError>
580              
581             =item *
582              
583             "the whole enchilada" combines all the above traits via L<Enchilada|Twitter::API::Trait::Enchilada>
584              
585             =item *
586              
587             app-only (OAuth2) support via L<AppAuth|Twitter::API::Trait::AppAuth>
588              
589             =item *
590              
591             automatic rate limiting via L<RateLimiting|Twitter::API::Trait::RateLimiting>
592              
593             =back
594              
595             Some features are provided by separate distributions to avoid additional
596             dependencies most users won't want or need:
597              
598             =over 4
599              
600             =item *
601              
602             async support via subclass L<Twitter::API::AnyEvent|https://github.com/semifor/Twitter-API-AnyEvent>
603              
604             =item *
605              
606             inflate API call results to objects via L<Twitter::API::Trait::InflateObjects|https://github.com/semifor/Twitter-API-Trait-InflateObjects>
607              
608             =back
609              
610             =head1 OVERVIEW
611              
612             =head2 Migration from Net::Twitter and Net::Twitter::Lite
613              
614             Migration support is included to assist users migrating from L<Net::Twitter>
615             and L<Net::Twitter::Lite>. It will be removed from a future release. See
616             L<Migration|Twitter::API::Trait::Migration> for details about migrating your
617             existing Net::Twitter/::Lite applications.
618              
619             =head2 Normal usage
620              
621             Normally, you will construct a Twitter::API client with some traits, primarily
622             B<ApiMethods>. It provides methods for each known Twitter API endpoint.
623             Documentation is provided for each of those methods in
624             L<ApiMethods|Twitter::API::Trait::ApiMethods>.
625              
626             See the list of traits in the L</DESCRIPTION> and refer to the documentation
627             for each.
628              
629             =head2 Minimalist usage
630              
631             Without any traits, Twitter::API provides access to API endpoints with the
632             L<get|get-url-args> and L<post|post-url-args> methods described below, as well
633             as methods for managing OAuth authentication. API results are simply perl data
634             structures decoded from the JSON responses. Refer to the L<Twitter API
635             Documentation|https://dev.twitter.com/rest/public> for available endpoints,
636             parameters, and responses.
637              
638             =head2 Twitter API V2 Beta Support
639              
640             Twitter intends to replace the current public API, version 1.1, with version 2.
641              
642             See L<https://developer.twitter.com/en/docs/twitter-api/early-access>.
643              
644             You can use Twitter::API for the V2 beta with the minimalist usage described
645             just above by passing values in the constructor for C<api_version> and
646             C<api_ext>.
647              
648             my $client = Twitter::API->new_with_traits(
649             api_version => '2',
650             api_ext => '',
651             %oauth_credentials,
652             );
653              
654             my $user = $client->get("users/by/username/$username");
655              
656             More complete V2 support is anticipated in a future release.
657              
658             =head1 ATTRIBUTES
659              
660             =head2 consumer_key, consumer_secret
661              
662             Required. Every application has it's own application credentials.
663              
664             =head2 access_token, access_token_secret
665              
666             Optional. If provided, every API call will be authenticated with these user
667             credentials. See L<AppAuth|Twitter::API::Trait::AppAuth> for app-only (OAuth2)
668             support, which does not require user credentials. You can also pass options
669             C<-token> and C<-token_secret> to specify user credentials on each API call.
670              
671             =head2 api_url
672              
673             Optional. Defaults to C<https://api.twitter.com>.
674              
675             =head2 upload_url
676              
677             Optional. Defaults to C<https://upload.twitter.com>.
678              
679             =head2 api_version
680              
681             Optional. Defaults to C<1.1>.
682              
683             =head2 api_ext
684              
685             Optional. Defaults to C<.json>.
686              
687             =head2 agent
688              
689             Optional. Used for both the User-Agent and X-Twitter-Client identifiers.
690             Defaults to C<Twitter-API-$VERSION (Perl)>.
691              
692             =head2 timeout
693              
694             Optional. Request timeout in seconds. Defaults to C<10>.
695              
696             =head1 METHODS
697              
698             =head2 get($url, [ \%args ])
699              
700             Issues an HTTP GET request to Twitter. If C<$url> is just a path part, e.g.,
701             C<account/verify_credentials>, it will be expanded to a full URL by prepending
702             the C<api_url>, C<api_version> and appending C<.json>. A full URL can also be
703             specified, e.g. C<https://api.twitter.com/1.1/account/verify_credentials.json>.
704              
705             This should accommodate any new API endpoints Twitter adds without requiring an
706             update to this module.
707              
708             =head2 post($url, [ \%args ])
709              
710             See C<get> above, for a discussion C<$url>. For file upload, pass an array
711             reference as described in
712             L<https://metacpan.org/pod/distribution/HTTP-Message/lib/HTTP/Request/Common.pm#POST-url-Header-Value-...-Content-content>.
713              
714             =head2 oauth_request_token([ \%args ])
715              
716             This is the first step in the OAuth handshake. The only argument expected is
717             C<callback>, which defaults to C<oob> for PIN based verification. Web
718             applications will pass a callback URL.
719              
720             Returns a hashref that includes C<oauth_token> and C<oauth_token_secret>.
721              
722             See L<https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token>.
723              
724             =head2 oauth_authentication_url(\%args)
725              
726             This is the second step in the OAuth handshake. The only required argument is
727             C<oauth_token>. Use the value returned by C<get_request_token>. Optional
728             arguments: C<force_login> and C<screen_name> to pre-fill Twitter's
729             authentication form.
730              
731             See L<https://developer.twitter.com/en/docs/basics/authentication/api-reference/authenticate>.
732              
733             =head2 oauth_authorization_url(\%args)
734              
735             Identical to C<oauth_authentication_url>, but uses authorization flow, rather
736             than authentication flow.
737              
738             See L<https://developer.twitter.com/en/docs/basics/authentication/api-reference/authorize>.
739              
740             =head2 oauth_access_token(\%ags)
741              
742             This is the third and final step in the OAuth handshake. Pass the request
743             C<token>, request C<token_secret> obtained in the C<get_request_token> call,
744             and either the PIN number if you used C<oob> for the callback value in
745             C<get_request_token> or the C<verifier> parameter returned in the web callback,
746             as C<verfier>.
747              
748             See L<https://developer.twitter.com/en/docs/basics/authentication/api-reference/access_token>.
749              
750             =head2 xauth(\%args)
751              
752             Requires per application approval from Twitter. Pass C<username> and
753             C<password>.
754              
755             =head1 SEE ALSO
756              
757             =over 4
758              
759             =item *
760              
761             L<API::Twitter> - Twitter.com API Client
762              
763             =item *
764              
765             L<AnyEvent::Twitter::Stream> - Receive Twitter streaming API in an event loop
766              
767             =item *
768              
769             L<AnyEvent::Twitter> - A thin wrapper for Twitter API using OAuth
770              
771             =item *
772              
773             L<Mojo::WebService::Twitter> - Simple Twitter API client
774              
775             =item *
776              
777             L<Net::Twitter> - Twitter::API's predecessor (also L<Net::Twitter::Lite>)
778              
779             =back
780              
781             =head1 AUTHOR
782              
783             Marc Mims <marc@questright.com>
784              
785             =head1 COPYRIGHT AND LICENSE
786              
787             This software is copyright (c) 2015-2021 by Marc Mims.
788              
789             This is free software; you can redistribute it and/or modify it under
790             the same terms as the Perl 5 programming language system itself.
791              
792             =cut