File Coverage

blib/lib/MojoX/Twitter.pm
Criterion Covered Total %
statement 32 89 35.9
branch 0 22 0.0
condition 0 17 0.0
subroutine 11 16 68.7
pod 0 2 0.0
total 43 146 29.4


line stmt bran cond sub pod time code
1             package MojoX::Twitter;
2              
3 1     1   54300 use strict;
  1         9  
  1         23  
4 1     1   3 use warnings;
  1         2  
  1         19  
5 1     1   13 use v5.10;
  1         3  
6 1     1   3 use Carp qw/croak/;
  1         2  
  1         36  
7 1     1   496 use Mojo::Base -base;
  1         164681  
  1         7  
8 1     1   669 use Mojo::UserAgent;
  1         189155  
  1         10  
9 1     1   47 use Mojo::URL;
  1         2  
  1         4  
10 1     1   31 use Mojo::JSON 'j';
  1         2  
  1         39  
11 1     1   15 use Digest::SHA 'hmac_sha1';
  1         1  
  1         33  
12 1     1   5 use MIME::Base64 'encode_base64';
  1         1  
  1         41  
13 1     1   481 use URI::Escape 'uri_escape_utf8';
  1         1140  
  1         943  
14              
15             our $VERSION = '0.07';
16              
17             has 'ua' => sub {
18             my $ua = Mojo::UserAgent->new;
19             $ua->transactor->name("MojoX-Twitter $VERSION");
20             return $ua;
21             };
22              
23             has 'consumer_key';
24             has 'consumer_secret';
25             has 'access_token';
26             has 'access_token_secret';
27              
28             sub request {
29 0     0 0   my ($self, $method, $command, $params) = @_;
30              
31 0 0         $command = '/' . $command if $command !~ m{^/};
32 0           my $url = "https://api.twitter.com/1.1" . $command . ".json";
33              
34 0           my $auth_str = $self->__build_auth_header($method, $url, $params);
35              
36 0           my @extra;
37 0 0         if ($method eq 'GET') {
    0          
38 0           my $uri = Mojo::URL->new($url);
39 0           $uri->query($params);
40 0           $url = $uri->to_string();
41             } elsif ($method eq 'POST') {
42 0           @extra = (form => $params);
43             }
44              
45 0           my $tx = $self->ua->build_tx($method => $url => { Authorization => "OAuth $auth_str" } => @extra );
46 0           $tx = $self->ua->start($tx);
47              
48 0           my $remaing = $tx->res->headers->header('X-Rate-Limit-Remaining');
49 0 0 0       if (defined $remaing and $remaing < 1) {
50 0           my $sleep = $tx->res->headers->header('X-Rate-Limit-Reset') - time();
51 0           sleep $sleep; # wait until limit reset
52             }
53              
54 0           my $err = $tx->error;
55 0           my $res = $tx->res;
56 0 0 0       if ($err || $res->is_error) {
57             # for 429 response: Too Many Requests
58 0 0 0       if ( ($err->{code} || 0) == 429 ) {
59 0           return $self->request($method, $command, $params); # REDO
60             }
61              
62 0 0         croak "$err->{code} response: $err->{message}" if $err->{code};
63 0           croak "Connection error: $err->{message}";
64             }
65              
66 0           return $res->json;
67             }
68              
69             sub streaming {
70 0     0 0   my ($self, $url, $params, $callback) = @_;
71              
72 0           my $auth_str = $self->__build_auth_header('GET', $url, $params);
73              
74 0 0         if ($params) {
75 0           my $uri = Mojo::URL->new($url);
76 0           $uri->query($params);
77 0           $url = $uri->to_string();
78             }
79              
80             # The Streaming API will send a keep-alive newline every 30 seconds
81             # to prevent your application from timing out the connection.
82 0           $self->ua->inactivity_timeout(61);
83              
84 0           my $tx = $self->ua->build_tx(GET => $url => {
85             Authorization => "OAuth $auth_str"
86             });
87 0           $tx->res->max_message_size(0);
88              
89             # Replace "read" events to disable default content parser
90 0           my $input;
91             $tx->res->content->unsubscribe('read')->on(read => sub {
92 0     0     my ($content, $bytes) = @_;
93              
94             # https://dev.twitter.com/streaming/overview/processing
95             # The body of a streaming API response consists of a series of newline-delimited messages, where “newline” is considered to be \r\n (in hex, 0x0D 0x0A) and “message” is a JSON encoded data structure or a blank line.
96 0           $input .= $bytes;
97 0           while ($input =~ s/^(.*?)\r\n//) {
98 0           my ($json_raw) = $1;
99 0 0         if (length($json_raw)) {
100 0           $callback->(j($json_raw));
101             }
102             }
103 0           });
104              
105             # Process transaction
106 0           $self->ua->start($tx);
107             }
108              
109             sub __build_auth_header {
110 0     0     my ($self, $method, $url, $params) = @_;
111              
112 0           my ($consumer_key, $consumer_secret, $access_token, $access_token_secret) =
113             ($self->consumer_key, $self->consumer_secret, $self->access_token, $self->access_token_secret);
114              
115 0 0 0       croak 'consumer_key, consumer_secret, access_token and access_token_secret are all required'
      0        
      0        
116             unless $consumer_key and $consumer_secret and $access_token and $access_token_secret;
117              
118 0           my %oauth_params = (
119             oauth_consumer_key => $consumer_key,
120             oauth_nonce => __nonce(),
121             oauth_signature_method => 'HMAC-SHA1',
122             oauth_timestamp => time(),
123             oauth_token => $access_token,
124             oauth_version => '1.0',
125             );
126              
127             ## sign
128 0 0         my %params = ( %{$params || {}}, %oauth_params );
  0            
129 0           my $params_str = join('&', map { $_ . '=' . uri_escape_utf8($params{$_}) } sort keys %params);
  0            
130 0           my $base_str = uc($method) . '&' . uri_escape_utf8($url) . '&' . uri_escape_utf8($params_str);
131 0           my $signing_key = uri_escape_utf8($consumer_secret) . '&' . uri_escape_utf8($access_token_secret);
132 0           my $sign = encode_base64(hmac_sha1($base_str, $signing_key), '');
133              
134 0           $oauth_params{oauth_signature} = $sign;
135 0           return join ', ', map { $_ . '="' . uri_escape_utf8($oauth_params{$_}) . '"' } sort keys %oauth_params;
  0            
136             }
137              
138             sub __nonce {
139 0     0     return time ^ $$ ^ int(rand 2**32);
140             }
141              
142             1;
143             __END__