File Coverage

blib/lib/MojoX/Twitter.pm
Criterion Covered Total %
statement 32 88 36.3
branch 0 22 0.0
condition 0 14 0.0
subroutine 11 16 68.7
pod 0 2 0.0
total 43 142 30.2


line stmt bran cond sub pod time code
1             package MojoX::Twitter;
2              
3 1     1   12990 use strict;
  1         23  
  1         27  
4 1     1   3 use warnings;
  1         1  
  1         22  
5 1     1   10 use v5.10;
  1         2  
6 1     1   4 use Carp qw/croak/;
  1         1  
  1         61  
7 1     1   437 use Mojo::Base -base;
  1         6827  
  1         6  
8 1     1   748 use Mojo::UserAgent;
  1         164119  
  1         8  
9 1     1   31 use Mojo::URL;
  1         1  
  1         3  
10 1     1   18 use Mojo::JSON 'j';
  1         1  
  1         41  
11 1     1   4 use Digest::SHA 'hmac_sha1';
  1         1  
  1         39  
12 1     1   14 use MIME::Base64 'encode_base64';
  1         2  
  1         34  
13 1     1   457 use URI::Escape 'uri_escape_utf8';
  1         969  
  1         796  
14              
15             our $VERSION = '0.06';
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 0         if (my $res = $tx->success) {
55             # check Rate Limit
56             # print Dumper(\$res); use Data::Dumper;
57              
58 0           return $res->json;
59             } else {
60 0           my $err = $tx->error;
61              
62             # for 429 response: Too Many Requests
63 0 0 0       if ( ($err->{code} || 0) == 429 ) {
64 0           return $self->request($method, $command, $params); # REDO
65             }
66              
67 0 0         croak "$err->{code} response: $err->{message}" if $err->{code};
68 0           croak "Connection error: $err->{message}";
69             }
70             }
71              
72             sub streaming {
73 0     0 0   my ($self, $url, $params, $callback) = @_;
74              
75 0           my $auth_str = $self->__build_auth_header('GET', $url, $params);
76              
77 0 0         if ($params) {
78 0           my $uri = Mojo::URL->new($url);
79 0           $uri->query($params);
80 0           $url = $uri->to_string();
81             }
82              
83             # The Streaming API will send a keep-alive newline every 30 seconds
84             # to prevent your application from timing out the connection.
85 0           $self->ua->inactivity_timeout(61);
86              
87 0           my $tx = $self->ua->build_tx(GET => $url => {
88             Authorization => "OAuth $auth_str"
89             });
90 0           $tx->res->max_message_size(0);
91              
92             # Replace "read" events to disable default content parser
93 0           my $input;
94             $tx->res->content->unsubscribe('read')->on(read => sub {
95 0     0     my ($content, $bytes) = @_;
96              
97             # https://dev.twitter.com/streaming/overview/processing
98             # 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.
99 0           $input .= $bytes;
100 0           while ($input =~ s/^(.*?)\r\n//) {
101 0           my ($json_raw) = $1;
102 0 0         if (length($json_raw)) {
103 0           $callback->(j($json_raw));
104             }
105             }
106 0           });
107              
108             # Process transaction
109 0           $self->ua->start($tx);
110             }
111              
112             sub __build_auth_header {
113 0     0     my ($self, $method, $url, $params) = @_;
114              
115 0           my ($consumer_key, $consumer_secret, $access_token, $access_token_secret) =
116             ($self->consumer_key, $self->consumer_secret, $self->access_token, $self->access_token_secret);
117              
118 0 0 0       croak 'consumer_key, consumer_secret, access_token and access_token_secret are all required'
      0        
      0        
119             unless $consumer_key and $consumer_secret and $access_token and $access_token_secret;
120              
121 0           my %oauth_params = (
122             oauth_consumer_key => $consumer_key,
123             oauth_nonce => __nonce(),
124             oauth_signature_method => 'HMAC-SHA1',
125             oauth_timestamp => time(),
126             oauth_token => $access_token,
127             oauth_version => '1.0',
128             );
129              
130             ## sign
131 0 0         my %params = ( %{$params || {}}, %oauth_params );
  0            
132 0           my $params_str = join('&', map { $_ . '=' . uri_escape_utf8($params{$_}) } sort keys %params);
  0            
133 0           my $base_str = uc($method) . '&' . uri_escape_utf8($url) . '&' . uri_escape_utf8($params_str);
134 0           my $signing_key = uri_escape_utf8($consumer_secret) . '&' . uri_escape_utf8($access_token_secret);
135 0           my $sign = encode_base64(hmac_sha1($base_str, $signing_key), '');
136              
137 0           $oauth_params{oauth_signature} = $sign;
138 0           return join ', ', map { $_ . '="' . uri_escape_utf8($oauth_params{$_}) . '"' } sort keys %oauth_params;
  0            
139             }
140              
141             sub __nonce {
142 0     0     return time ^ $$ ^ int(rand 2**32);
143             }
144              
145             1;
146             __END__