line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Net::Twitter::Lite; |
2
|
|
|
|
|
|
|
our $VERSION = '0.12008'; |
3
|
11
|
|
|
11
|
|
179153
|
use 5.005; |
|
11
|
|
|
|
|
46
|
|
4
|
11
|
|
|
10
|
|
85
|
use warnings; |
|
10
|
|
|
|
|
20
|
|
|
10
|
|
|
|
|
381
|
|
5
|
10
|
|
|
9
|
|
57
|
use strict; |
|
9
|
|
|
|
|
13
|
|
|
9
|
|
|
|
|
237
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Net::Twitter::Lite - A perl library for Twitter's API v1 |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 VERSION |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
version 0.12008 |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=cut |
16
|
|
|
|
|
|
|
|
17
|
9
|
|
|
9
|
|
53
|
use Carp; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
769
|
|
18
|
9
|
|
|
9
|
|
3249
|
use URI::Escape; |
|
9
|
|
|
|
|
9779
|
|
|
9
|
|
|
|
|
600
|
|
19
|
9
|
|
|
9
|
|
5145
|
use JSON; |
|
9
|
|
|
|
|
93623
|
|
|
9
|
|
|
|
|
55
|
|
20
|
9
|
|
|
9
|
|
7255
|
use HTTP::Request::Common; |
|
9
|
|
|
|
|
137873
|
|
|
9
|
|
|
|
|
700
|
|
21
|
9
|
|
|
9
|
|
5133
|
use Net::Twitter::Lite::Error; |
|
9
|
|
|
|
|
51
|
|
|
9
|
|
|
|
|
283
|
|
22
|
9
|
|
|
9
|
|
5335
|
use Encode qw/encode_utf8/; |
|
9
|
|
|
|
|
80117
|
|
|
9
|
|
|
|
|
736
|
|
23
|
9
|
|
|
9
|
|
4551
|
use Net::Twitter::Lite::WrapResult; |
|
9
|
|
|
|
|
21
|
|
|
9
|
|
|
|
|
27600
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub twitter_api_def_from () { 'Net::Twitter::Lite::API::V1' } |
26
|
|
|
|
|
|
|
sub _default_api_url () { 'http://api.twitter.com/1' } |
27
|
|
|
|
|
|
|
sub _default_searchapiurl () { 'http://search.twitter.com' } |
28
|
|
|
|
|
|
|
sub _default_search_trends_api_url () { 'http://api.twitter.com/1' } |
29
|
|
|
|
|
|
|
sub _default_lists_api_url () { 'http://api.twitter.com/1' } |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
my $json_handler = JSON->new->utf8; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub new { |
34
|
11
|
|
|
11
|
1
|
136076
|
my ($class, %args) = @_; |
35
|
|
|
|
|
|
|
|
36
|
11
|
100
|
|
|
|
148
|
$class->can('verify_credentials') || $class->build_api_methods; |
37
|
|
|
|
|
|
|
|
38
|
11
|
|
|
|
|
35
|
my $netrc = delete $args{netrc}; |
39
|
|
|
|
|
|
|
my $new = bless { |
40
|
|
|
|
|
|
|
apiurl => $class->_default_api_url, |
41
|
|
|
|
|
|
|
searchapiurl => $class->_default_searchapiurl, |
42
|
|
|
|
|
|
|
search_trends_api_url => $class->_default_search_trends_api_url, |
43
|
|
|
|
|
|
|
lists_api_url => $class->_default_lists_api_url, |
44
|
|
|
|
|
|
|
apirealm => 'Twitter API', |
45
|
11
|
50
|
33
|
|
|
462
|
$args{identica} ? ( apiurl => 'http://identi.ca/api' ) : (), |
|
|
|
33
|
|
|
|
|
46
|
|
|
|
|
|
|
useragent => (ref $class || $class) . "/$VERSION (Perl)", |
47
|
|
|
|
|
|
|
clientname => (ref $class || $class), |
48
|
|
|
|
|
|
|
clientver => $VERSION, |
49
|
|
|
|
|
|
|
clienturl => 'http://search.cpan.org/dist/Net-Twitter-Lite/', |
50
|
|
|
|
|
|
|
source => 'twitterpm', |
51
|
|
|
|
|
|
|
useragent_class => 'LWP::UserAgent', |
52
|
|
|
|
|
|
|
useragent_args => {}, |
53
|
|
|
|
|
|
|
oauth_urls => { |
54
|
|
|
|
|
|
|
request_token_url => "https://api.twitter.com/oauth/request_token", |
55
|
|
|
|
|
|
|
authentication_url => "https://api.twitter.com/oauth/authenticate", |
56
|
|
|
|
|
|
|
authorization_url => "https://api.twitter.com/oauth/authorize", |
57
|
|
|
|
|
|
|
access_token_url => "https://api.twitter.com/oauth/access_token", |
58
|
|
|
|
|
|
|
xauth_url => "https://api.twitter.com/oauth/access_token", |
59
|
|
|
|
|
|
|
}, |
60
|
|
|
|
|
|
|
netrc_machine => 'api.twitter.com', |
61
|
|
|
|
|
|
|
%args |
62
|
|
|
|
|
|
|
}, $class; |
63
|
|
|
|
|
|
|
|
64
|
11
|
50
|
|
|
|
100
|
unless ( exists $new->{legacy_lists_api} ) { |
65
|
0
|
|
|
|
|
0
|
$new->{legacy_lists_api} = 1; |
66
|
0
|
|
|
|
|
0
|
carp |
67
|
0
|
|
|
|
|
0
|
"For backwards compatibility @{[ __PACKAGE__ ]} uses the deprecated Lists API |
68
|
|
|
|
|
|
|
endpoints and semantics. This default will be changed in a future version. |
69
|
|
|
|
|
|
|
Please update your code to use the new lists semantics and pass |
70
|
|
|
|
|
|
|
(legacy_lists_api => 0) to new. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
You can disable this warning, and keep backwards compatibility by passing |
73
|
|
|
|
|
|
|
(legacy_lists_api => 1) to new. Be warned, however, that support for the |
74
|
|
|
|
|
|
|
legacy endpoints will be removed in a future version and the default will |
75
|
|
|
|
|
|
|
change to (legacy_lists_api => 0)."; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
11
|
100
|
|
|
|
40
|
if ( delete $args{ssl} ) { |
80
|
|
|
|
|
|
|
$new->{$_} =~ s/^http:/https:/ |
81
|
4
|
|
|
|
|
75
|
for qw/apiurl searchapiurl search_trends_api_url lists_api_url/; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# get username and password from .netrc |
85
|
11
|
50
|
|
|
|
38
|
if ( $netrc ) { |
86
|
0
|
0
|
|
|
|
0
|
eval { require Net::Netrc; 1 } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
87
|
|
|
|
|
|
|
|| croak "Net::Netrc is required for the netrc option"; |
88
|
|
|
|
|
|
|
|
89
|
0
|
0
|
|
|
|
0
|
my $host = $netrc eq '1' ? $new->{netrc_machine} : $netrc; |
90
|
0
|
|
0
|
|
|
0
|
my $nrc = Net::Netrc->lookup($host) |
91
|
|
|
|
|
|
|
|| croak "No .netrc entry for $host"; |
92
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
0
|
@{$new}{qw/username password/} = $nrc->lpa; |
|
0
|
|
|
|
|
0
|
|
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
11
|
|
66
|
|
|
62
|
$new->{ua} ||= do { |
97
|
9
|
|
|
7
|
|
896
|
eval "use $new->{useragent_class}"; |
|
7
|
|
|
|
|
77
|
|
|
7
|
|
|
|
|
13
|
|
|
7
|
|
|
|
|
135
|
|
98
|
9
|
50
|
|
|
|
37
|
croak $@ if $@; |
99
|
|
|
|
|
|
|
|
100
|
9
|
|
|
|
|
47
|
$new->{useragent_class}->new(%{$new->{useragent_args}}); |
|
9
|
|
|
|
|
77
|
|
101
|
|
|
|
|
|
|
}; |
102
|
|
|
|
|
|
|
|
103
|
11
|
|
|
|
|
25959
|
$new->{ua}->agent($new->{useragent}); |
104
|
11
|
|
|
|
|
706
|
$new->{ua}->default_header('X-Twitter-Client' => $new->{clientname}); |
105
|
11
|
|
|
|
|
607
|
$new->{ua}->default_header('X-Twitter-Client-Version' => $new->{clientver}); |
106
|
11
|
|
|
|
|
509
|
$new->{ua}->default_header('X-Twitter-Client-URL' => $new->{clienturl}); |
107
|
11
|
|
|
|
|
504
|
$new->{ua}->env_proxy; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
$new->{_authenticator} = exists $new->{consumer_key} |
110
|
11
|
100
|
|
|
|
46040
|
? '_oauth_authenticated_request' |
111
|
|
|
|
|
|
|
: '_basic_authenticated_request'; |
112
|
|
|
|
|
|
|
|
113
|
4
|
|
|
|
|
22
|
$new->credentials(@{$new}{qw/username password/}) |
114
|
11
|
50
|
66
|
|
|
75
|
if exists $new->{username} && exists $new->{password}; |
115
|
|
|
|
|
|
|
|
116
|
11
|
|
|
|
|
161
|
return $new; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub credentials { |
120
|
5
|
|
|
5
|
1
|
759
|
my $self = shift; |
121
|
5
|
|
|
|
|
12
|
my ($username, $password) = @_; |
122
|
|
|
|
|
|
|
|
123
|
5
|
50
|
|
|
|
22
|
croak "exected a username and password" unless @_ == 2; |
124
|
5
|
50
|
|
|
|
18
|
croak "OAuth authentication is in use" if exists $self->{consumer_key}; |
125
|
|
|
|
|
|
|
|
126
|
5
|
|
|
|
|
14
|
$self->{username} = $username; |
127
|
5
|
|
|
|
|
7
|
$self->{password} = $password; |
128
|
|
|
|
|
|
|
|
129
|
5
|
|
|
|
|
38
|
my $uri = URI->new($self->{apiurl}); |
130
|
5
|
|
|
|
|
27699
|
my $netloc = join ':', $uri->host, $uri->port; |
131
|
|
|
|
|
|
|
|
132
|
5
|
|
|
|
|
604
|
$self->{ua}->credentials($netloc, $self->{apirealm}, $username, $password); |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# This is a hack. Rather than making Net::OAuth an install requirement for |
136
|
|
|
|
|
|
|
# Net::Twitter::Lite, require it at runtime if any OAuth methods are used. It |
137
|
|
|
|
|
|
|
# simply returns the string 'Net::OAuth' after successfully requiring |
138
|
|
|
|
|
|
|
# Net::OAuth. |
139
|
|
|
|
|
|
|
sub _oauth { |
140
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
141
|
|
|
|
|
|
|
|
142
|
1
|
|
33
|
|
|
5
|
return $self->{_oauth} ||= do { |
143
|
1
|
|
|
|
|
76
|
eval "use Net::OAuth 0.25"; |
144
|
1
|
50
|
|
|
|
4
|
croak "Install Net::OAuth 0.25 or later for OAuth support" if $@; |
145
|
|
|
|
|
|
|
|
146
|
1
|
|
|
|
|
51
|
eval '$Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A'; |
147
|
1
|
50
|
|
|
|
5
|
die $@ if $@; |
148
|
|
|
|
|
|
|
|
149
|
1
|
|
|
|
|
7
|
'Net::OAuth'; |
150
|
|
|
|
|
|
|
}; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# simple check to see if we have access tokens; does not check to see if they are valid |
154
|
|
|
|
|
|
|
sub authorized { |
155
|
1
|
|
|
1
|
1
|
2
|
my $self = shift; |
156
|
|
|
|
|
|
|
|
157
|
1
|
|
33
|
|
|
7
|
return defined $self->{access_token} && $self->{access_token_secret}; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# get the athorization or authentication url |
161
|
|
|
|
|
|
|
sub _get_auth_url { |
162
|
0
|
|
|
0
|
|
0
|
my ($self, $which_url, %params ) = @_; |
163
|
|
|
|
|
|
|
|
164
|
0
|
|
|
|
|
0
|
$self->_request_request_token(%params); |
165
|
|
|
|
|
|
|
|
166
|
0
|
|
|
|
|
0
|
my $uri = $self->$which_url; |
167
|
0
|
|
|
|
|
0
|
$uri->query_form(oauth_token => $self->request_token); |
168
|
0
|
|
|
|
|
0
|
return $uri; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# get the authentication URL from Twitter |
172
|
0
|
|
|
0
|
1
|
0
|
sub get_authentication_url { return shift->_get_auth_url(authentication_url => @_) } |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# get the authorization URL from Twitter |
175
|
0
|
|
|
0
|
1
|
0
|
sub get_authorization_url { return shift->_get_auth_url(authorization_url => @_) } |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# common portion of all oauth requests |
178
|
|
|
|
|
|
|
sub _make_oauth_request { |
179
|
1
|
|
|
1
|
|
6
|
my ($self, $type, %params) = @_; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
my $request = $self->_oauth->request($type)->new( |
182
|
|
|
|
|
|
|
version => '1.0', |
183
|
|
|
|
|
|
|
consumer_key => $self->{consumer_key}, |
184
|
|
|
|
|
|
|
consumer_secret => $self->{consumer_secret}, |
185
|
1
|
|
|
|
|
3
|
request_method => 'GET', |
186
|
|
|
|
|
|
|
signature_method => 'HMAC-SHA1', |
187
|
|
|
|
|
|
|
timestamp => time, |
188
|
|
|
|
|
|
|
nonce => time ^ $$ ^ int(rand 2**32), |
189
|
|
|
|
|
|
|
%params, |
190
|
|
|
|
|
|
|
); |
191
|
|
|
|
|
|
|
|
192
|
1
|
|
|
|
|
10686
|
$request->sign; |
193
|
|
|
|
|
|
|
|
194
|
1
|
|
|
|
|
7148
|
return $request; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# called by get_authorization_url to obtain request tokens |
198
|
|
|
|
|
|
|
sub _request_request_token { |
199
|
0
|
|
|
0
|
|
0
|
my ($self, %params) = @_; |
200
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
0
|
my $uri = $self->request_token_url; |
202
|
0
|
|
0
|
|
|
0
|
$params{callback} ||= 'oob'; |
203
|
0
|
|
|
|
|
0
|
my $request = $self->_make_oauth_request( |
204
|
|
|
|
|
|
|
'request token', |
205
|
|
|
|
|
|
|
request_url => $uri, |
206
|
|
|
|
|
|
|
%params, |
207
|
|
|
|
|
|
|
); |
208
|
|
|
|
|
|
|
|
209
|
0
|
|
|
|
|
0
|
my $res = $self->{ua}->get($request->to_url); |
210
|
0
|
0
|
|
|
|
0
|
die "GET $uri failed: ".$res->status_line |
211
|
|
|
|
|
|
|
unless $res->is_success; |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# reuse $uri to extract parameters from the response content |
214
|
0
|
|
|
|
|
0
|
$uri->query($res->content); |
215
|
0
|
|
|
|
|
0
|
my %res_param = $uri->query_form; |
216
|
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
0
|
$self->request_token($res_param{oauth_token}); |
218
|
0
|
|
|
|
|
0
|
$self->request_token_secret($res_param{oauth_token_secret}); |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# exchange request tokens for access tokens; call with (verifier => $verifier) |
222
|
|
|
|
|
|
|
sub request_access_token { |
223
|
0
|
|
|
0
|
1
|
0
|
my ($self, %params ) = @_; |
224
|
|
|
|
|
|
|
|
225
|
0
|
|
|
|
|
0
|
my $uri = $self->access_token_url; |
226
|
0
|
|
|
|
|
0
|
my $request = $self->_make_oauth_request( |
227
|
|
|
|
|
|
|
'access token', |
228
|
|
|
|
|
|
|
request_url => $uri, |
229
|
|
|
|
|
|
|
token => $self->request_token, |
230
|
|
|
|
|
|
|
token_secret => $self->request_token_secret, |
231
|
|
|
|
|
|
|
%params, # verifier => $verifier |
232
|
|
|
|
|
|
|
); |
233
|
|
|
|
|
|
|
|
234
|
0
|
|
|
|
|
0
|
my $res = $self->{ua}->get($request->to_url); |
235
|
0
|
0
|
|
|
|
0
|
die "GET $uri failed: ".$res->status_line |
236
|
|
|
|
|
|
|
unless $res->is_success; |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# discard request tokens, they're no longer valid |
239
|
0
|
|
|
|
|
0
|
delete $self->{request_token}; |
240
|
0
|
|
|
|
|
0
|
delete $self->{request_token_secret}; |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# reuse $uri to extract parameters from content |
243
|
0
|
|
|
|
|
0
|
$uri->query($res->content); |
244
|
0
|
|
|
|
|
0
|
my %res_param = $uri->query_form; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
return ( |
247
|
|
|
|
|
|
|
$self->access_token($res_param{oauth_token}), |
248
|
|
|
|
|
|
|
$self->access_token_secret($res_param{oauth_token_secret}), |
249
|
|
|
|
|
|
|
$res_param{user_id}, |
250
|
|
|
|
|
|
|
$res_param{screen_name}, |
251
|
0
|
|
|
|
|
0
|
); |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# exchange username and password for access tokens |
255
|
|
|
|
|
|
|
sub xauth { |
256
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $username, $password ) = @_; |
257
|
|
|
|
|
|
|
|
258
|
0
|
|
|
|
|
0
|
my $uri = $self->xauth_url; |
259
|
0
|
|
|
|
|
0
|
my $request = $self->_make_oauth_request( |
260
|
|
|
|
|
|
|
'XauthAccessToken', |
261
|
|
|
|
|
|
|
request_url => $uri, |
262
|
|
|
|
|
|
|
x_auth_username => $username, |
263
|
|
|
|
|
|
|
x_auth_password => $password, |
264
|
|
|
|
|
|
|
x_auth_mode => 'client_auth', |
265
|
|
|
|
|
|
|
); |
266
|
|
|
|
|
|
|
|
267
|
0
|
|
|
|
|
0
|
my $res = $self->{ua}->get($request->to_url); |
268
|
0
|
0
|
|
|
|
0
|
die "GET $uri failed: ".$res->status_line |
269
|
|
|
|
|
|
|
unless $res->is_success; |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# reuse $uri to extract parameters from content |
272
|
0
|
|
|
|
|
0
|
$uri->query($res->content); |
273
|
0
|
|
|
|
|
0
|
my %res_param = $uri->query_form; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
return ( |
276
|
|
|
|
|
|
|
$self->access_token($res_param{oauth_token}), |
277
|
|
|
|
|
|
|
$self->access_token_secret($res_param{oauth_token_secret}), |
278
|
|
|
|
|
|
|
$res_param{user_id}, |
279
|
|
|
|
|
|
|
$res_param{screen_name}, |
280
|
0
|
|
|
|
|
0
|
); |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# common call for both Basic Auth and OAuth |
284
|
|
|
|
|
|
|
sub _authenticated_request { |
285
|
155
|
|
|
155
|
|
192
|
my $self = shift; |
286
|
|
|
|
|
|
|
|
287
|
155
|
|
|
|
|
287
|
my $authenticator = $self->{_authenticator}; |
288
|
155
|
|
|
|
|
463
|
$self->$authenticator(@_); |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
sub _encode_args { |
292
|
154
|
|
|
154
|
|
168
|
my $args = shift; |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# Values need to be utf-8 encoded. Because of a perl bug, exposed when |
295
|
|
|
|
|
|
|
# client code does "use utf8", keys must also be encoded. |
296
|
|
|
|
|
|
|
# see: http://www.perlmonks.org/?node_id=668987 |
297
|
|
|
|
|
|
|
# and: http://perl5.git.perl.org/perl.git/commit/eaf7a4d2 |
298
|
154
|
50
|
|
|
|
3660
|
return { map { utf8::upgrade($_) unless ref($_); $_ } %$args }; |
|
256
|
|
|
|
|
733
|
|
|
256
|
|
|
|
|
557
|
|
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
sub _oauth_authenticated_request { |
302
|
1
|
|
|
1
|
|
3
|
my ($self, $http_method, $uri, $args, $authenticate) = @_; |
303
|
|
|
|
|
|
|
|
304
|
1
|
|
|
|
|
2
|
delete $args->{source}; # not necessary with OAuth requests |
305
|
|
|
|
|
|
|
|
306
|
1
|
|
50
|
|
|
7
|
my $content_type = delete $args->{-content_type} || ''; |
307
|
1
|
|
33
|
|
|
6
|
my $is_multipart = $content_type eq 'form-data' || grep { ref } %$args; |
308
|
|
|
|
|
|
|
|
309
|
1
|
|
|
|
|
2
|
my $msg; |
310
|
1
|
50
|
33
|
|
|
5
|
if ( $authenticate && $self->authorized ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
311
|
1
|
|
|
|
|
2
|
local $Net::OAuth::SKIP_UTF8_DOUBLE_ENCODE_CHECK = 1; |
312
|
|
|
|
|
|
|
|
313
|
1
|
50
|
|
|
|
5
|
my $request = $self->_make_oauth_request( |
314
|
|
|
|
|
|
|
'protected resource', |
315
|
|
|
|
|
|
|
request_url => $uri, |
316
|
|
|
|
|
|
|
request_method => $http_method, |
317
|
|
|
|
|
|
|
token => $self->access_token, |
318
|
|
|
|
|
|
|
token_secret => $self->access_token_secret, |
319
|
|
|
|
|
|
|
extra_params => $is_multipart ? {} : $args, |
320
|
|
|
|
|
|
|
); |
321
|
|
|
|
|
|
|
|
322
|
1
|
50
|
|
|
|
10
|
if ( $http_method =~ /^(?:GET|DELETE)$/ ) { |
|
|
0
|
|
|
|
|
|
323
|
1
|
|
|
|
|
7
|
$msg = HTTP::Request->new($http_method, $request->to_url); |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
elsif ( $http_method eq 'POST' ) { |
326
|
0
|
0
|
|
|
|
0
|
$msg = $is_multipart |
327
|
|
|
|
|
|
|
? POST($request->request_url, |
328
|
|
|
|
|
|
|
Authorization => $request->to_authorization_header, |
329
|
|
|
|
|
|
|
Content_Type => 'form-data', |
330
|
|
|
|
|
|
|
Content => [ %$args ], |
331
|
|
|
|
|
|
|
) |
332
|
|
|
|
|
|
|
: POST($$uri, Content => $request->to_post_body) |
333
|
|
|
|
|
|
|
; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
else { |
336
|
0
|
|
|
|
|
0
|
croak "unexpected http_method: $http_method"; |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
elsif ( $http_method eq 'GET' ) { |
340
|
0
|
|
|
|
|
0
|
$uri->query_form($args); |
341
|
0
|
|
|
|
|
0
|
$args = {}; |
342
|
0
|
|
|
|
|
0
|
$msg = GET($uri); |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
elsif ( $http_method eq 'POST' ) { |
345
|
0
|
|
|
|
|
0
|
my $encoded_args = { %$args }; |
346
|
0
|
|
|
|
|
0
|
_encode_args($encoded_args); |
347
|
0
|
|
|
|
|
0
|
$msg = $self->_mk_post_msg($uri, $args); |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
else { |
350
|
0
|
|
|
|
|
0
|
croak "unexpected http_method: $http_method"; |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
1
|
|
|
|
|
734
|
return $self->{ua}->request($msg); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub _basic_authenticated_request { |
357
|
154
|
|
|
154
|
|
260
|
my ($self, $http_method, $uri, $args, $authenticate) = @_; |
358
|
|
|
|
|
|
|
|
359
|
154
|
|
|
|
|
275
|
_encode_args($args); |
360
|
|
|
|
|
|
|
|
361
|
154
|
|
|
|
|
324
|
my $msg; |
362
|
154
|
100
|
|
|
|
632
|
if ( $http_method =~ /^(?:GET|DELETE)$/ ) { |
|
|
50
|
|
|
|
|
|
363
|
90
|
|
|
|
|
310
|
$uri->query_form($args); |
364
|
90
|
|
|
|
|
4805
|
$msg = HTTP::Request->new($http_method, $uri); |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
elsif ( $http_method eq 'POST' ) { |
367
|
64
|
|
|
|
|
173
|
$msg = $self->_mk_post_msg($uri, $args); |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
else { |
370
|
0
|
|
|
|
|
0
|
croak "unexpected HTTP method: $http_method"; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
154
|
50
|
66
|
|
|
17734
|
if ( $authenticate && $self->{username} && $self->{password} ) { |
|
|
|
66
|
|
|
|
|
374
|
48
|
|
|
|
|
105
|
$msg->headers->authorization_basic(@{$self}{qw/username password/}); |
|
48
|
|
|
|
|
299
|
|
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
154
|
|
|
|
|
8541
|
return $self->{ua}->request($msg); |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
sub _mk_post_msg { |
381
|
64
|
|
|
64
|
|
87
|
my ($self, $uri, $args) = @_; |
382
|
|
|
|
|
|
|
|
383
|
64
|
50
|
|
|
|
145
|
if ( grep { ref } values %$args ) { |
|
73
|
|
|
|
|
182
|
|
384
|
|
|
|
|
|
|
# if any of the arguments are (array) refs, use form-data |
385
|
0
|
|
|
|
|
0
|
return POST($uri, Content_Type => 'form-data', Content => [ %$args ]); |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
else { |
388
|
|
|
|
|
|
|
# There seems to be a bug introduced by Twitter about 2013-02-25: If |
389
|
|
|
|
|
|
|
# post arguments are uri encoded exactly the same way the OAuth spec |
390
|
|
|
|
|
|
|
# requires base signature string encoding, Twitter chokes and throws a |
391
|
|
|
|
|
|
|
# 401. This seems to be a violation of the OAuth spec on Twitter's |
392
|
|
|
|
|
|
|
# part. The specifically states the more stringent URI encoding is for |
393
|
|
|
|
|
|
|
# consistent signature generation and *only* applies to encoding the |
394
|
|
|
|
|
|
|
# base signature string and Authorization header. |
395
|
|
|
|
|
|
|
|
396
|
64
|
|
|
|
|
71
|
my @pairs; |
397
|
64
|
|
|
|
|
249
|
while ( my ($k, $v) = each %$args ) { |
398
|
73
|
|
|
|
|
3110
|
push @pairs, join '=', map URI::Escape::uri_escape_utf8($_, '^A-Za-z0-9\-\._~'), $k, $v; |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
64
|
|
|
|
|
3639
|
my $content = join '&', @pairs; |
402
|
64
|
|
|
|
|
251
|
return POST($uri, Content => $content); |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
sub build_api_methods { |
407
|
8
|
|
|
8
|
0
|
16
|
my $class = shift; |
408
|
|
|
|
|
|
|
|
409
|
8
|
|
|
|
|
27
|
my $api_def_module = $class->twitter_api_def_from; |
410
|
8
|
50
|
|
|
|
607
|
eval "require $api_def_module" or die $@; |
411
|
8
|
|
|
|
|
122
|
my $api_def = $api_def_module->api_def; |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
my $with_url_arg = sub { |
414
|
0
|
|
|
0
|
|
0
|
my ($path, $args) = @_; |
415
|
|
|
|
|
|
|
|
416
|
0
|
0
|
|
|
|
0
|
if ( defined(my $id = delete $args->{id}) ) { |
417
|
0
|
|
|
|
|
0
|
$path .= uri_escape($id); |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
else { |
420
|
0
|
|
|
|
|
0
|
chop($path); |
421
|
|
|
|
|
|
|
} |
422
|
0
|
|
|
|
|
0
|
return $path; |
423
|
8
|
|
|
|
|
211
|
}; |
424
|
|
|
|
|
|
|
|
425
|
8
|
|
|
|
|
76
|
while ( @$api_def ) { |
426
|
20
|
|
|
|
|
45
|
my $api = shift @$api_def; |
427
|
20
|
|
|
|
|
37
|
my $api_name = shift @$api; |
428
|
20
|
|
|
|
|
30
|
my $methods = shift @$api; |
429
|
|
|
|
|
|
|
|
430
|
20
|
|
|
|
|
41
|
for my $method ( @$methods ) { |
431
|
930
|
|
|
|
|
1183
|
my $name = shift @$method; |
432
|
930
|
|
|
|
|
784
|
my %options = %{ shift @$method }; |
|
930
|
|
|
|
|
5359
|
|
433
|
|
|
|
|
|
|
|
434
|
930
|
|
|
|
|
6044
|
my ($arg_names, $path) = @options{qw/required path/}; |
435
|
|
|
|
|
|
|
$arg_names = $options{params} |
436
|
930
|
100
|
100
|
|
|
1898
|
if @$arg_names == 0 && @{$options{params}} == 1; |
|
454
|
|
|
|
|
1452
|
|
437
|
|
|
|
|
|
|
|
438
|
930
|
50
|
|
0
|
|
2790
|
my $modify_path = $path =~ s,/id$,/, ? $with_url_arg : sub { $_[0] }; |
|
0
|
|
|
|
|
0
|
|
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
my $code = sub { |
441
|
176
|
|
|
176
|
|
153355
|
my $self = shift; |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# copy callers args since we may add ->{source} |
444
|
176
|
100
|
|
|
|
545
|
my $args = ref $_[-1] eq 'HASH' ? { %{pop @_} } : {}; |
|
99
|
|
|
|
|
314
|
|
445
|
176
|
50
|
|
|
|
586
|
if ( my $content_type = $options{content_type} ) { |
446
|
0
|
|
|
|
|
0
|
$args->{-content_type} = $options{content_type}; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
|
449
|
176
|
100
|
66
|
|
|
1614
|
if ( (my $legacy_method = $self->can("legacy_$name")) && ( |
|
|
100
|
|
|
|
|
|
450
|
|
|
|
|
|
|
exists $$args{-legacy_lists_api} |
451
|
|
|
|
|
|
|
? delete $$args{-legacy_lists_api} |
452
|
|
|
|
|
|
|
: $self->{legacy_lists_api} ) ) { |
453
|
21
|
|
|
|
|
57
|
return $self->$legacy_method(@_, $args); |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# just in case it's included where it shouldn't be: |
457
|
155
|
|
|
|
|
420
|
delete $args->{-legacy_lists_api}; |
458
|
|
|
|
|
|
|
|
459
|
155
|
50
|
|
|
|
433
|
croak sprintf "$name expected %d args", scalar @$arg_names |
460
|
|
|
|
|
|
|
if @_ > @$arg_names; |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# promote positional args to named args |
463
|
155
|
|
|
|
|
398
|
for ( my $i = 0; @_; ++$i ) { |
464
|
73
|
|
|
|
|
123
|
my $param = $arg_names->[$i]; |
465
|
|
|
|
|
|
|
croak "duplicate param $param: both positional and named" |
466
|
73
|
50
|
|
|
|
187
|
if exists $args->{$param}; |
467
|
|
|
|
|
|
|
|
468
|
73
|
|
|
|
|
247
|
$args->{$param} = shift; |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
|
471
|
155
|
100
|
33
|
|
|
406
|
$args->{source} ||= $self->{source} if $options{add_source}; |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
my $authenticate = exists $args->{authenticate} |
474
|
|
|
|
|
|
|
? delete $args->{authenticate} |
475
|
155
|
100
|
|
|
|
409
|
: $options{authenticate}; |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# promote boolean parameters |
478
|
155
|
|
|
|
|
159
|
for my $boolean_arg ( @{ $options{booleans} } ) { |
|
155
|
|
|
|
|
397
|
|
479
|
160
|
50
|
|
|
|
412
|
if ( exists $args->{$boolean_arg} ) { |
480
|
0
|
0
|
|
|
|
0
|
next if $args->{$boolean_arg} =~ /^true|false$/; |
481
|
0
|
0
|
|
|
|
0
|
$args->{$boolean_arg} = $args->{$boolean_arg} ? 'true' : 'false'; |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# Workaround Twitter bug: any value passed for skip_user is treated as true. |
486
|
|
|
|
|
|
|
# The only way to get 'false' is to not pass the skip_user at all. |
487
|
|
|
|
|
|
|
delete $args->{skip_user} if exists $args->{skip_user} |
488
|
155
|
50
|
33
|
|
|
414
|
&& $args->{skip_user} eq 'false'; |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
# replace placeholder arguments |
491
|
155
|
|
|
|
|
233
|
my $local_path = $path; |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
# remove optional trailing id |
494
|
155
|
100
|
|
|
|
470
|
$local_path =~ s,/:id$,, unless exists $args->{id}; |
495
|
155
|
50
|
|
|
|
378
|
$local_path =~ s/:(\w+)/delete $args->{$1} |
|
61
|
|
|
|
|
282
|
|
496
|
|
|
|
|
|
|
or croak "required arg '$1' missing"/eg; |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
# stringify lists |
499
|
155
|
|
|
|
|
287
|
for ( qw/screen_name user_id/ ) { |
500
|
3
|
|
|
|
|
11
|
$args->{$_} = join(',' => @{ $args->{$_} }) |
501
|
310
|
100
|
|
|
|
710
|
if ref $args->{$_} eq 'ARRAY'; |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
my $uri = URI->new($self->{$options{base_url_method}} |
505
|
155
|
|
|
|
|
1017
|
. "/$local_path.json"); |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
return $self->_parse_result( |
508
|
|
|
|
|
|
|
$self->_authenticated_request( |
509
|
155
|
|
|
|
|
56989
|
$options{method}, $uri, $args, $authenticate |
510
|
|
|
|
|
|
|
) |
511
|
|
|
|
|
|
|
); |
512
|
930
|
|
|
|
|
7968
|
}; |
513
|
|
|
|
|
|
|
|
514
|
9
|
|
|
9
|
|
87
|
no strict 'refs'; |
|
9
|
|
|
|
|
15
|
|
|
9
|
|
|
|
|
2006
|
|
515
|
930
|
|
|
|
|
837
|
$name = $_, *{"$class\::$_"} = $code for $name, @{$options{aliases}}; |
|
930
|
|
|
|
|
1672
|
|
|
1114
|
|
|
|
|
6735
|
|
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# catch expected error and promote it to an undef |
520
|
8
|
|
|
|
|
30
|
for ( qw/list_members is_list_member list_subscribers is_list_subscriber |
521
|
|
|
|
|
|
|
legacy_list_members legacy_is_list_member legacy_list_subscribers legacy_is_list_subscriber/ ) { |
522
|
64
|
100
|
|
|
|
323
|
my $orig = $class->can($_) or next; |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
my $code = sub { |
525
|
11
|
|
|
11
|
|
10666
|
my $r = eval { $orig->(@_) }; |
|
11
|
|
|
|
|
47
|
|
526
|
11
|
50
|
|
|
|
27
|
if ( $@ ) { |
527
|
0
|
0
|
|
|
|
0
|
return if $@ =~ /The specified user is not a (?:memb|subscrib)er of this list/; |
528
|
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
0
|
die $@; |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
11
|
|
|
|
|
19
|
return $r; |
533
|
56
|
|
|
|
|
132
|
}; |
534
|
|
|
|
|
|
|
|
535
|
9
|
|
|
9
|
|
131
|
no strict 'refs'; |
|
9
|
|
|
|
|
15
|
|
|
9
|
|
|
|
|
413
|
|
536
|
9
|
|
|
9
|
|
46
|
no warnings 'redefine'; |
|
9
|
|
|
|
|
12
|
|
|
9
|
|
|
|
|
771
|
|
537
|
56
|
|
|
|
|
72
|
*{"$class\::$_"} = $code; |
|
56
|
|
|
|
|
181
|
|
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# OAuth token accessors |
541
|
8
|
|
|
|
|
20
|
for my $method ( qw/ |
542
|
|
|
|
|
|
|
access_token |
543
|
|
|
|
|
|
|
access_token_secret |
544
|
|
|
|
|
|
|
request_token |
545
|
|
|
|
|
|
|
request_token_secret |
546
|
|
|
|
|
|
|
/ ) { |
547
|
9
|
|
|
9
|
|
40
|
no strict 'refs'; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
871
|
|
548
|
32
|
|
|
|
|
191
|
*{"$class\::$method"} = sub { |
549
|
4
|
|
|
4
|
|
106
|
my $self = shift; |
550
|
|
|
|
|
|
|
|
551
|
4
|
100
|
|
|
|
22
|
$self->{$method} = shift if @_; |
552
|
4
|
|
|
|
|
17
|
return $self->{$method}; |
553
|
32
|
|
|
|
|
101
|
}; |
554
|
|
|
|
|
|
|
} |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# OAuth url accessors |
557
|
8
|
|
|
|
|
27
|
for my $method ( qw/ |
558
|
|
|
|
|
|
|
request_token_url |
559
|
|
|
|
|
|
|
authentication_url |
560
|
|
|
|
|
|
|
authorization_url |
561
|
|
|
|
|
|
|
access_token_url |
562
|
|
|
|
|
|
|
xauth_url |
563
|
|
|
|
|
|
|
/ ) { |
564
|
9
|
|
|
9
|
|
62
|
no strict 'refs'; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
3456
|
|
565
|
40
|
|
|
|
|
210
|
*{"$class\::$method"} = sub { |
566
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
567
|
|
|
|
|
|
|
|
568
|
0
|
0
|
|
|
|
0
|
$self->{oauth_urls}{$method} = shift if @_; |
569
|
0
|
|
|
|
|
0
|
return URI->new($self->{oauth_urls}{$method}); |
570
|
40
|
|
|
|
|
94
|
}; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
sub _from_json { |
576
|
155
|
|
|
155
|
|
280
|
my ($self, $json) = @_; |
577
|
|
|
|
|
|
|
|
578
|
155
|
|
|
|
|
222
|
return eval { $json_handler->decode($json) }; |
|
155
|
|
|
|
|
1262
|
|
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
sub _parse_result { |
582
|
155
|
|
|
155
|
|
107758
|
my ($self, $res) = @_; |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
# workaround for Laconica API returning bools as strings |
585
|
|
|
|
|
|
|
# (Fixed in Laconi.ca 0.7.4) |
586
|
155
|
|
|
|
|
417
|
my $content = $res->content; |
587
|
155
|
|
|
|
|
1587
|
$content =~ s/^"(true|false)"$/$1/; |
588
|
|
|
|
|
|
|
|
589
|
155
|
|
|
|
|
374
|
my $obj = $self->_from_json($content); |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
# Twitter sometimes returns an error with status code 200 |
592
|
155
|
50
|
66
|
|
|
1232
|
if ( $obj && ref $obj eq 'HASH' && exists $obj->{error} ) { |
|
|
|
66
|
|
|
|
|
593
|
0
|
|
|
|
|
0
|
die Net::Twitter::Lite::Error->new(twitter_error => $obj, http_response => $res); |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
|
596
|
155
|
100
|
66
|
|
|
435
|
if ( $res->is_success && defined $obj ) { |
597
|
151
|
100
|
|
|
|
1501
|
if ( $self->{wrap_result} ) { |
598
|
17
|
|
|
|
|
52
|
$obj = Net::Twitter::Lite::WrapResult->new($obj, $res); |
599
|
|
|
|
|
|
|
} |
600
|
151
|
|
|
|
|
1040
|
return $obj; |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
|
603
|
4
|
|
|
|
|
80
|
my $error = Net::Twitter::Lite::Error->new(http_response => $res); |
604
|
4
|
50
|
|
|
|
12
|
$error->twitter_error($obj) if ref $obj; |
605
|
|
|
|
|
|
|
|
606
|
4
|
|
|
|
|
30
|
die $error; |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
1; |