Codebase list libnet-twitter-lite-perl / 49b8e33
Added Lists API support (ported from Net::Twitter 3.13002_02) Marc Mims 14 years ago
7 changed file(s) with 484 addition(s) and 26 deletion(s). Raw diff Collapse all Expand all
0 0.9002_02 2010-05-12
1 - ported from Net::Twitter 3.13002_02
2 - Added Lists API support
3
04 0.09001 2010-05-10
15 - removed Try::Tiny from unicode test to avoid an additional dependency
26 - Fixed unicode test, aburch (Ansgar Burchardt)
00 use inc::Module::Install;
11
2 version '0.09001';
2 version '0.09002_02';
33
44 if ( $Module::Install::AUTHOR ) {
55 eval "use Net::Twitter::Core";
11 Net::Twitter::Lite - A perl interface to the Twitter API
22
33 VERSION
4 This document describes Net::Twitter::Lite version 0.09001
4 This document describes Net::Twitter::Lite version 0.09002_02
55
66 SYNOPSIS
77 use Net::Twitter::Lite;
253253
254254 useragent
255255 The value for "User-Agent" HTTP header. It defaults to
256 "Net::Twitter::Lite/0.09001 (Perl)".
256 "Net::Twitter::Lite/0.09002_02 (Perl)".
257257
258258 source
259259 The value used in the "source" parameter of API method calls. It
819819
820820 Returns: ArrayRef[Status]
821821
822 lookup_users
823
824 Parameters: user_id, screen_name
825 Required: *none*
826
827 Return up to 20 users worth of extended information, specified by
828 either ID, screen name, or combination of the two. The author's most
829 recent status (if the authenticating user has permission) will be
830 returned inline. This method is rate limited to 1000 calls per hour.
831
832 This method will accept user IDs or screen names as either a comma
833 delimited string, or as an ARRAY ref. It will also accept arguments
834 in the normal HASHREF form or as a simple list of named arguments.
835 I.e., any of the following forms are acceptable:
836
837 $nt->lookup_users({ user_id => '1234,6543,3333' });
838 $nt->lookup_users(user_id => '1234,6543,3333');
839 $nt->lookup_users({ user_id => [ 1234, 6543, 3333 ] });
840 $nt->lookup_users({ screen_name => 'fred,barney,wilma' });
841 $nt->lookup_users(screen_name => ['fred', 'barney', 'wilma']);
842
843 $nt->lookup_users(
844 screen_name => ['fred', 'barney' ],
845 user_id => '4321,6789',
846 );
847
848 Returns: ArrayRef[User]
849
822850 mentions
823851 alias: replies
824852
13531381 Required: *none*
13541382
13551383 Returns the top 30 trending topics for each day in a given week.
1384
1385 Returns: HashRef
1386
1387 Lists API Methods
1388 add_list_member
1389 add_list_member(user, list_id, id)
1390
1391 Parameters: user, list_id, id
1392 Required: user, list_id, id
1393
1394 Adds the user identified by "id" to the list.
1395
1396 Returns a reference the added user as a hash reference.
1397
1398 Returns: User
1399
1400 create_list
1401 create_list(user, name)
1402
1403 Parameters: user, name, mode, description
1404 Required: user, name
1405
1406 Creates a new list for the authenticated user. The "mode" parameter
1407 may be either "public" or "private". If not specified, it defaults
1408 to "public".
1409
1410 Returns: HashRef
1411
1412 delete_list
1413 delete_list(user, list_id)
1414
1415 Parameters: user, list_id
1416 Required: user, list_id
1417
1418 Deletes a list owned by the authenticating user. Returns the list as
1419 a hash reference.
1420
1421 Returns:
1422
1423 delete_list_member
1424 delete_list_member(user, list_id, id)
1425 alias: remove_list_member
1426
1427 Parameters: user, list_id, id
1428 Required: user, list_id, id
1429
1430 Deletes the user identified by "id" from the specified list.
1431
1432 Returns the deleted user as a hash reference.
1433
1434 Returns:
1435
1436 get_list
1437 get_list(user, list_id)
1438
1439 Parameters: user, list_id
1440 Required: user, list_id
1441
1442 Returns the specified list as a hash reference.
1443
1444 Returns: HashRef
1445
1446 get_lists
1447 get_lists(user)
1448 alias: list_lists
1449
1450 Parameters: user, cursor
1451 Required: user
1452
1453 Returns a reference to an array of lists owned by the specified
1454 user. If the user is the authenticated user, it returns both public
1455 and private lists. Otherwise, it only returns public lists.
1456
1457 When the "cursor" parameter is used, a hash reference is returned;
1458 the lists are returned in the "lists" element of the hash.
1459
1460 Returns: ArrayRef[List]
1461
1462 is_list_member
1463 is_list_member(user, list_id, id)
1464
1465 Parameters: user, list_id, id
1466 Required: user, list_id, id
1467
1468 Returns the list member as a HASH reference if "id" is a member of
1469 the list. Otherwise, returns undef.
1470
1471 Returns: ArrayRef[User]
1472
1473 is_list_subscriber
1474 is_list_subscriber(user, list_id, id)
1475 alias: is_subscribed_list
1476
1477 Parameters: user, list_id, id
1478 Required: user, list_id, id
1479
1480 Returns the subscriber as a HASH reference if "id" is a subscriber
1481 to the list. Otherwise, returns undef.
1482
1483 Returns: ArrayRef[User]
1484
1485 list_members
1486 list_members(user, list_id)
1487
1488 Parameters: user, list_id, id, cursor
1489 Required: user, list_id
1490
1491 Returns the list members as an array reference.
1492
1493 The optional "id" parameter can be used to determine if the user
1494 specified by "id" is a member of the list. If so, the user is
1495 returned as a hash reference; if not, "undef" is returned.
1496
1497 When the "cursor" parameter is used, a hash reference is returned;
1498 the members are returned in the "users" element of the hash.
1499
1500 Returns: ArrayRef[User]
1501
1502 list_memberships
1503 list_memberships(user)
1504
1505 Parameters: user, cursor
1506 Required: user
1507
1508 Returns the lists the specified user is a member of as an array
1509 reference.
1510
1511 When the "cursor" parameter is used, a hash reference is returned;
1512 the lists are returned in the "lists" element of the hash.
1513
1514 Returns:
1515
1516 list_statuses
1517 list_statuses(user, list_id)
1518
1519 Parameters: user, list_id, since_id, max_id, per_page, page
1520 Required: user, list_id
1521
1522 Returns a timeline of list member statuses as an array reference.
1523
1524 Returns: ArrayRef[Status]
1525
1526 list_subscribers
1527 list_subscribers(user, list_id)
1528
1529 Parameters: user, list_id, id, cursor
1530 Required: user, list_id
1531
1532 Returns the subscribers to a list as an array reference.
1533
1534 When the "cursor" parameter is used, a hash reference is returned;
1535 the subscribers are returned in the "users" element of the hash.
1536
1537 Returns: ArrayRef[User]
1538
1539 list_subscriptions
1540 list_subscriptions(user)
1541
1542 Parameters: user, cursor
1543 Required: user
1544
1545 Returns a lists to which the specified user is subscribed as an
1546 array reference.
1547
1548 When the "cursor" parameter is used, a hash reference is returned;
1549 the lists are returned in the "lists" element of the hash.
1550
1551 Returns:
1552
1553 subscribe_list
1554 subscribe_list(user, list_id)
1555
1556 Parameters: user, list_id
1557 Required: user, list_id
1558
1559 Subscribes the authenticated user to the specified list.
1560
1561 Returns: List
1562
1563 unsubscribe_list
1564 unsubscribe_list(user, list_id)
1565
1566 Parameters: user, list_id
1567 Required: user, list_id
1568
1569 Unsubscribes the authenticated user from the specified list.
1570
1571 Returns: List
1572
1573 update_list
1574 update_list(user, list_id)
1575
1576 Parameters: user, list_id, name, mode, description
1577 Required: user, list_id
1578
1579 Updates a list to change the name, mode, description, or any
1580 combination thereof.
13561581
13571582 Returns: HashRef
13581583
2222
2323 return
2424 sort { $a->{name} cmp $b->{name} }
25 grep { blessed $_ && $_->isa('Net::Twitter::Meta::Method') }
26 $nt->meta->get_all_methods;
25 grep {
26 $_->isa('Net::Twitter::Meta::Method')
27 }
28 map {
29 $_->isa('Class::MOP::Method::Wrapped') ? $_->get_original_method : $_
30 } $nt->meta->get_all_methods;
2731 }
2832
2933 sub get_base_url_for {
1717 sub new {
1818 my ($class, %args) = @_;
1919
20 my $ssl = delete $args{ssl};
21 if ( $ssl ) {
22 eval { require Crypt::SSLeay } && $Crypt::SSLeay::VERSION >= 0.5
23 || croak "Crypt::SSLeay version 0.50 is required for SSL support";
24 }
25
2620 my $netrc = delete $args{netrc};
2721 my $new = bless {
28 apiurl => 'http://api.twitter.com/1',
22 apiurl => 'http://api.twitter.com/1',
23 searchapiurl => 'http://search.twitter.com',
24 search_trends_api_url => 'http://api.twitter.com/1',
25 lists_api_url => 'http://api.twitter.com/1',
2926 apirealm => 'Twitter API',
3027 $args{identica} ? ( apiurl => 'http://identi.ca/api' ) : (),
31 searchurl => 'http://search.twitter.com',
3228 useragent => __PACKAGE__ . "/$VERSION (Perl)",
3329 clientname => __PACKAGE__,
3430 clientver => $VERSION,
4743 %args
4844 }, $class;
4945
50 $new->{apiurl} =~ s/http/https/ if $ssl;
46
47 if ( delete $args{ssl} ) {
48 eval { require Crypt::SSLeay } && $Crypt::SSLeay::VERSION >= 0.5
49 || croak "Crypt::SSLeay version 0.50 is required for SSL support";
50
51 $new->{$_} =~ s/http/https/
52 for qw/apiurl searchurl search_trends_api_url lists_api_url/;
53 }
5154
5255 # get username and password from .netrc
5356 if ( $netrc ) {
113116
114117 eval '$Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A';
115118 die $@ if $@;
116
119
117120 'Net::OAuth';
118121 };
119122 }
135138 no strict 'refs';
136139 *{__PACKAGE__ . "::$method"} = sub {
137140 my $self = shift;
138
141
139142 $self->{$method} = shift if @_;
140143 return $self->{$method};
141144 };
301304
302305 sub _oauth_authenticated_request {
303306 my ($self, $http_method, $uri, $args, $authenticate) = @_;
304
307
305308 delete $args->{source}; # not necessary with OAuth requests
306309
307310 my $is_multipart = grep { ref } %$args;
383386 : POST($uri, $args);
384387 }
385388
389 { ### scope $api_def
390
386391 my $api_def = [
387 [% FOREACH api IN [ 'REST', 'Search' ] -%]
392 [% FOREACH api IN [ 'REST', 'Search', 'Lists' ] -%]
388393 [ [% api %] => [
389394 [% FOREACH method IN get_methods_for(api) -%]
390395 [ '[% method.name %]', {
397402 deprecated => [% method.deprecated %],
398403 authenticate => [% method.authenticate %],
399404 booleans => [ qw/[% method.booleans.join(' ') %]/ ],
405 base_url_method => '[% method.base_url_method %]',
400406 } ],
401407 [% END -%]
402408 ] ],
420426 my $api_name = shift @$api;
421427 my $methods = shift @$api;
422428
423 my $url_attr = $api_name eq 'REST' ? 'apiurl' : 'searchurl';
424
425 my $base_url = sub { shift->{$url_attr} };
426
427429 for my $method ( @$methods ) {
428430 my $name = shift @$method;
429431 my %options = %{ shift @$method };
439441 # copy callers args since we may add ->{source}
440442 my $args = ref $_[-1] eq 'HASH' ? { %{pop @_} } : {};
441443
442 if ( @_ ) {
443 @_ == @$arg_names || croak "$name expected @{[ scalar @$arg_names ]} args";
444 @{$args}{@$arg_names} = @_;
444 croak sprintf "$name expected %d args", scalar @$arg_names if @_ > @$arg_names;
445
446 # promote positional args to named args
447 for ( my $i = 0; @_; ++$i ) {
448 my $param = $arg_names->[$i];
449 croak "duplicate param $param: both positional and named"
450 if exists $args->{$param};
451
452 $args->{$param} = shift;
445453 }
454
446455 $args->{source} ||= $self->{source} if $options{add_source};
447456
448457 my $authenticate = exists $args->{authenticate} ? delete $args->{authenticate}
465474 $local_path =~ s,/:id$,, unless exists $args->{id}; # remove optional trailing id
466475 $local_path =~ s/:(\w+)/delete $args->{$1} or croak "required arg '$1' missing"/eg;
467476
468 my $uri = URI->new($base_url->($self) . "/$local_path.json");
477 my $uri = URI->new($self->{$options{base_url_method}} . "/$local_path.json");
469478
470479 return $self->_parse_result(
471480 $self->_authenticated_request($options{method}, $uri, $args, $authenticate)
476485 *{__PACKAGE__ . "::$_"} = $code for $name, @{$options{aliases}};
477486 }
478487 }
488 $DB::single = 1;
489
490 # catch expected error and promote it to an undef
491 for ( qw/list_members is_list_member list_subscribers is_list_subscriber/ ) {
492 my $orig = __PACKAGE__->can($_) or die;
493
494 my $code = sub {
495 my $r = eval { $orig->(@_) };
496 if ( $@ ) {
497 return if $@ =~ /The specified user is not a (?:memb|subscrib)er of this list/;
498
499 die $@;
500 }
501
502 return $r;
503 };
504
505 no strict 'refs';
506 no warnings 'redefine';
507 *{__PACKAGE__ . "::$_"} = $code;
508 }
509
510 # special case parameter handling for lookup_users
511 for ( qw/lookup_users/ ) {
512 my $orig = __PACKAGE__->can($_) or die;
513
514 my $code = sub {
515 my $self = shift;
516
517 my $args = ref $_[-1] eq 'HASH' ? pop @_ : {};
518 $args = { %$args, @_ };
519
520 for ( qw/screen_name user_id/ ) {
521 $args->{$_} = join(',' => @{ $args->{$_} }) if ref $args->{$_} eq 'ARRAY';
522 }
523
524 return $orig->($self, $args);
525 };
526
527 no strict 'refs';
528 no warnings 'redefine';
529 *{__PACKAGE__ . "::$_"} = $code;
530 }
531
532
533
534 } ### end scope for $api_def
479535
480536 sub _from_json {
481537 my ($self, $json) = @_;
10331089
10341090 [% INCLUDE APIDOC class='Search' %]
10351091
1092 =head1 Lists API Methods
1093
1094 [% INCLUDE APIDOC class='Lists' %]
1095
10361096 =head1 ERROR HANDLING
10371097
10381098 When C<Net::Twitter::Lite> encounters a Twitter API error or a network error, it
0 #!perl
1 use warnings;
2 use strict;
3 use Test::More;
4 use Net::Twitter;
5
6 eval 'use LWP::UserAgent 5.819';
7 plan skip_all => 'LWP::UserAgent 5.819 required' if $@;
8
9 my $nt = Net::Twitter->new(traits => [qw/API::Lists/], username => 'fred', password => 'secret');
10
11 my $req;
12 my $res = HTTP::Response->new(200);
13 $res->content('{"response":"done"}');
14 $nt->ua->add_handler(request_send => sub { $req = shift; return $res });
15
16 my @tests = (
17 create_list => {
18 args => [ 'owner', { name => 'Test list', description => 'Just a test', mode => 'private' } ],
19 path => '/owner/lists',
20 params => { name => 'Test list', description => 'Just a test', mode => 'private' },
21 method => 'POST',
22 },
23 update_list => {
24 args => [ 'owner', 'test-list', { mode => 'public' } ],
25 path => '/owner/lists/test-list',
26 params => { mode => 'public' },
27 method => 'POST',
28 },
29 list_lists => {
30 args =>[ 'owner' ],
31 path => '/owner/lists',
32 params => {},
33 method => 'GET',
34 },
35 list_memberships => {
36 args => [ 'owner' ],
37 path => '/owner/lists/memberships',
38 params => {},
39 method => 'GET',
40 },
41 delete_list => {
42 args => [ 'owner', 'test-list' ],
43 path => '/owner/lists/test-list',
44 params => {},
45 method => 'DELETE',
46 },
47 list_statuses => {
48 args => [ 'owner', 'test-list' ],
49 path => '/owner/lists/test-list/statuses',
50 params => {},
51 method => 'GET',
52 },
53 get_list => {
54 args => [ 'owner', 'test-list' ],
55 path => '/owner/lists/test-list',
56 params => {},
57 method => 'GET',
58 },
59 add_list_member => {
60 args => [ 'owner', 'test-list', 1234 ],
61 path => '/owner/test-list/members',
62 params => { id => 1234 },
63 method => 'POST',
64 },
65 delete_list_member => {
66 args => [ 'owner', 'test-list', 1234 ],
67 path => '/owner/test-list/members',
68 params => { id => 1234 },
69 method => 'DELETE',
70 },
71 remove_list_member => {
72 args => [ 'owner', 'test-list', 1234 ],
73 path => '/owner/test-list/members',
74 params => { id => 1234 },
75 method => 'DELETE',
76 },
77 list_members => {
78 args => [ 'owner', 'test-list' ],
79 path => '/owner/test-list/members',
80 params => {},
81 method => 'GET',
82 },
83 is_list_member => {
84 args => [ 'owner', 'test-list', 1234 ],
85 path => '/owner/test-list/members/1234',
86 params => {},
87 method => 'GET',
88 },
89 subscribe_list => {
90 args => [ 'owner', 'some-list' ],
91 path => '/owner/some-list/subscribers',
92 params => {},
93 method => 'POST',
94 },
95 list_subscribers => {
96 args => [ 'owner', 'some-list' ],
97 path => '/owner/some-list/subscribers',
98 params => {},
99 method => 'GET',
100 },
101 list_subscriptions => {
102 args => [ 'owner' ],
103 path => '/owner/lists/subscriptions',
104 params => {},
105 method => 'GET',
106 },
107 unsubscribe_list => {
108 args => [ 'owner', 'test-list' ],
109 path => '/owner/test-list/subscribers',
110 params => {},
111 method => 'DELETE',
112 },
113 is_list_subscriber => {
114 args => [ 'owner', 'test-list', 1234 ],
115 path => '/owner/test-list/subscribers/1234',
116 params => {},
117 method => 'GET',
118 },
119 is_subscribed_list => {
120 args => [ 'owner', 'test-list', 1234 ],
121 path => '/owner/test-list/subscribers/1234',
122 params => {},
123 method => 'GET',
124 },
125 );
126
127 plan tests => scalar @tests / 2 * 3 + 2;
128
129 while ( @tests ) {
130 my $api_method = shift @tests;
131 my $t = shift @tests;
132
133 my $r = $nt->$api_method(@{ $t->{args} });
134 is $req->uri->path, "/1$t->{path}.json", "$api_method: path";
135 is $req->method, $t->{method}, "$api_method: HTTP method";
136 is_deeply extract_args($req), $t->{params},
137 "$api_method: parameters";
138 }
139
140 {
141 # unauthenticated call
142 my $r = $nt->list_statuses(twitter => 'team', { authenticate => 0 });
143 ok !$req->header('authorization'), 'unauthenticated call';
144
145 # authenticated call (default)
146 $r = $nt->list_statuses(twitter => 'team');
147 like $req->header('authorization'), qr/^Basic/, 'authenticated request (default)';
148 }
149
150 sub extract_args {
151 my $req = shift;
152
153 my $uri;
154 if ( $req->method eq 'POST' ) {
155 $uri = URI->new;
156 $uri->query($req->content);
157 }
158 else {
159 $uri = $req->uri;
160 }
161
162 return { $uri->query_form };
163 }
6565 Un
6666 un
6767 unfollow
68 Unsubscribes
6869 url
6970 useragent
7071 username