File Coverage

blib/lib/Net/FriendFeed.pm
Criterion Covered Total %
statement 178 180 98.8
branch 52 54 96.3
condition 13 14 92.8
subroutine 43 43 100.0
pod 28 28 100.0
total 314 319 98.4


line stmt bran cond sub pod time code
1             package Net::FriendFeed;
2              
3 7     7   112044 use warnings;
  7         18  
  7         331  
4 7     7   37 use strict;
  7         12  
  7         316  
5              
6             =head1 NAME
7              
8             Net::FriendFeed - Perl interface to FriendFeed.com API
9              
10             =cut
11              
12             our $VERSION = '0.93';
13              
14 7     7   5767 use Encode;
  7         61599  
  7         563  
15 7     7   48 use File::Spec;
  7         14  
  7         165  
16 7     7   7551 use HTTP::Request::Common;
  7         59530  
  7         473  
17 7     7   1062 use LWP::UserAgent;
  7         42013  
  7         171  
18 7     7   6425 use MIME::Base64 qw/encode_base64/;
  7         5633  
  7         504  
19 7     7   55 use URI::Escape;
  7         14  
  7         396  
20              
21 7     7   40 use base qw(Class::Accessor);
  7         9  
  7         6933  
22             Net::FriendFeed->mk_accessors(qw/login remotekey ua return_feeds_as
23             last_error/);
24              
25             our $API_ENTRYPOINT = 'http://friendfeed.com/api/';
26              
27             our $Last_Http_Response;
28              
29             =head1 SYNOPSIS
30              
31             FriendFeed is a social feed agregator with a clean public REST-based
32             API. This package allows easy access to FriendFeed from Perl.
33              
34             Methods are named in accordance with the official Python package.
35              
36             use Net::FriendFeed;
37              
38             my $frf = Net::FriendFeed->new;
39             $frf->publish_message('Hello, world!');
40             ...
41              
42             =cut
43              
44             =head1 GENERAL FUNCTIONS
45              
46             =head2 new(\%opts)
47              
48             This is a constructor for FriendFeed object. It takes an optional
49             hashref parameter with auth credentials.
50              
51             Example:
52              
53             my $frf_anon = Net::FriendFeed->new;
54             my $frf = Net::FriendFeed->new({login => 'kkapp', remotekey => 'hfytr38'});
55              
56             The remotekey is a kind of easily regeneratable password used
57             only in API functions. A user can get his remotekey here:
58             L
59              
60             Authentication is needed only to post or to read private feeds.
61              
62             =head2 last_error()
63              
64             Returns machine-readable code of the last error. You should consult
65             this code if an API call returns C.
66              
67             This is a list of FriendFeed error codes:
68             * bad-id-format - Bad format for UUID argument.
69             * bad-url-format - Bad format for URL argument.
70             * entry-not-found - Entry with the specified UUID was not found.
71             * entry-required - Entry UUID argument is required.
72             * forbidden - User does not have access to entry, room or other entity specified in the request.
73             * image-format-not-supported - Unsupported image format.
74             * internal-server-error - Internal error on FriendFeed server.
75             * limit-exceeded - Request limit exceeded.
76             * room-not-found - Room with specified name not found.
77             * room-required - Room name argument is required.
78             * title-required - Entry title argument is required.
79             * unauthorized - The request requires authentication.
80             * user-not-found - User with specified nickname not found.
81             * user-required - User nickname argument is required.
82             * error - Other unspecified error.
83              
84             These error codes are generated inside Net::FriendFeed wrapper code:
85             * need-auth - The request was not made because it requires authentication.
86             * failed-req - The request was not made because of unknown reason.
87              
88             =cut
89              
90             sub new {
91 8     8 1 2351 my ($proto, $fields) = @_;
92 8   66     60 my $class = ref $proto || $proto;
93              
94 8 100       26 $fields = {} unless defined $fields;
95              
96 8         28 my $self = { %$fields };
97              
98 8   100     60 $self->{return_feeds_as} ||= 'structure';
99              
100             # make a copy of $fields.
101 8         39 bless $self, $class;
102             }
103              
104             =head2 login([$login])
105              
106             Read/Write accessor for login name. You can either get current login
107             or set it if you'd like to do it after calling C.
108              
109             =head2 remotekey([$remotekey])
110              
111             Read/Write accessor for remotekey. You can either get current remotekey
112             or set it if you'd like to do it after calling C.
113              
114             Remotekey is a special password valid only for use via API calls.
115             A user can get his remotekey here: L
116              
117             =cut
118              
119             sub _connect {
120 42     42   907 my $self = shift;
121              
122 42 100       137 unless ($self->ua) {
123 1 50       24 $self->ua(new LWP::UserAgent)
124             or die;
125             }
126             }
127              
128             sub _has_auth {
129 71     71   827 my $self = shift;
130              
131 71   100     287 return $self->login && $self->remotekey;
132             }
133              
134             =head2 validate()
135              
136             Validates the current combination of login and remotekey.
137              
138             =cut
139              
140             sub validate {
141 1     1 1 9 my $self = shift;
142 1         7 $self->_http_req('GET', 'validate', 'need auth');
143             }
144              
145             sub _api_url {
146 41     41   64 my $self = shift;
147 41         63 my $uri = shift;
148              
149 41         340 return $API_ENTRYPOINT . $uri;
150             }
151              
152             sub _http_req {
153 44     44   141 my ($self, $method, $uri, $needauth, @args) = @_;
154              
155             # all posts should be authenticated
156 44 100 100     206 if ($needauth && !$self->_has_auth) {
157 3         72 $self->last_error('need-auth');
158 3         59 return;
159             }
160              
161 41         718 $self->_connect();
162              
163 41         887 my ($needs_parsing, $format) = ($self->return_feeds_as eq 'structure', $self->return_feeds_as);
164 41 100       1508 $format = 'json' if $needs_parsing;
165              
166 41         65 my $req;
167 41 100       118 if ($method eq 'GET') {
168 20         70 my $get_uri = URI->new($self->_api_url($uri));
169 20 100       70342 $get_uri->query_form(format => $format) unless $format eq 'json';
170 20 100       549 $get_uri->query_form(@args) if @args;
171              
172 20         881 $req = GET $get_uri->as_string;
173             }
174             else { # $method eq 'POST'
175 21         105 my $post_uri = URI->new($self->_api_url($uri));
176 21 100       12056 $post_uri->query_form(format => $format) unless $format eq 'json';
177 21         163 $req = POST $post_uri,
178             @args;
179             }
180              
181 41 100       45619 if ($self->_has_auth) {
182 31         840 $req->header(Authorization => 'Basic ' . encode_base64($self->login . ':' . $self->remotekey, q{}));
183             }
184              
185 41 50       2450 if ($Last_Http_Response = $self->ua->request($req)) {
186 41 100       7154 unless ($Last_Http_Response->is_success) {
187 1         14 require JSON; # should die if absent
188 1         17 JSON->VERSION(2.0); # we need newer JSON
189             # do some JSON magic
190 1         6 $self->last_error(
191             JSON::from_json($Last_Http_Response->content, { utf8 => 1})->{errorCode}
192             );
193 1         49 return;
194             }
195             }
196             else {
197 0         0 $self->last_error('failed-req');
198 0         0 return;
199             }
200              
201 40 100       573 if ($needs_parsing) {
202 35         322 require JSON; # should die if absent
203 35         3188 JSON->VERSION(2.0); # we need newer JSON
204             # do some JSON magic
205 35         245 return JSON::from_json($Last_Http_Response->content, { utf8 => 1});
206             }
207             else {
208 5         20 return $Last_Http_Response->content;
209             }
210             }
211              
212             sub _fetch_feed {
213 19     19   716 my $self = shift;
214 19         29 my $uri = shift;
215              
216 19         69 $self->_http_req('GET', $uri, undef, @_);
217             }
218              
219             sub _post {
220 24     24   339 my $self = shift;
221 24         51 my $uri = shift;
222              
223 24         81 $self->_http_req('POST', $uri, 'need auth', @_);
224             }
225              
226             =head2 list_services
227              
228             Returns the list of all services supported by FriendFeed.
229              
230             The returned JSON has the format:
231              
232             * services[]
233             o url - the official URL of the service, e.g., http://picasaweb.google.com/
234             o iconUrl - the URL of the favicon for this service
235             o id - the service's FriendFeed ID, e.g., "picasa"
236             o name - the service's official name, e.g., "Picasa Web Albums"
237              
238             =cut
239              
240             sub list_services {
241 1     1 1 7 my $self = shift;
242              
243 1         6 $self->_fetch_feed('services', @_);
244             }
245              
246             =head1 FEED FUNCTIONS
247              
248             A number of methods fetch arrays of data or "feeds" from FriendFeed.
249             All feeds are available in four different formats. They are
250             C, C, C and C. JSON and XML formats are custom
251             FriendFeed structures, while Atom and RSS are standard XML formats
252             with all FriendFeed specific features represented as custom XML tags.
253              
254             Net::FriendFeed adds one additional format called C which
255             means the feeds will be fetched as C and then deserialized into
256             Perl structures using JSON.pm module. The format C is set by
257             default.
258              
259             The feeds have the following structure (JSON-like notation is used):
260              
261             * entries[]
262             o id - the FriendFeed entry UUID, used to add comments/likes to the entry
263             o title
264             o link
265             o published
266             o updated
267             o hidden - if true, this entry should be hidden based on the user's preferences
268             o anonymous - if true, user will be present but should not be shown
269             o user{} - the user who shared this entry
270             + id - the user's FriendFeed UUID
271             + name - the user's full name
272             + nickname - the user's FriendFeed nickname, used in FriendFeed URLs
273             + profileUrl - the user's profile URL on FriendFeed
274             o service{} - the service from which the entry came
275             + id - the service's FriendFeed ID, e.g., "picasa"
276             + name - the service's official name, e.g., "Picasa Web Albums"
277             + iconUrl - the URL of the favicon for this service
278             + profileUrl - the user's profile URL on this service
279             o comments[]
280             + date
281             + id - the UUID of the comment
282             + user{} - same structure as the user{} structure above
283             + body - the textual body of the comment
284             o via{}? - present if this comment came from an API client
285             + name - the name of the API client, e.g., "fftogo"
286             + url - the official URL of the API client, e.g., http://www.fftogo.com
287             o likes[]
288             + date
289             + user{} - same structure as the user{} structure above
290             o media[] - the videos/images associated with the entry
291             + title? - the title of the media file
292             + player? - the player for this media file (e.g., the YouTube.com URL with the embedded video)
293             + thumbnails[] - the thumbnails for this media file
294             # url
295             # width
296             # height
297             + content[] - the different versions of the media file
298             # url
299             # type - the MIME type of the media file
300             # width
301             # height
302             o via{}? - present if this entry came from an API client
303             + name - the name of the API client, e.g., "Alert Thingy"
304             + url - the official URL of the API client, e.g., http://www.alertthingy.com/
305             o room{}? - if the entry is in a room, the room the entry is in
306              
307             + id - the room's FriendFeed UUID
308             + name - the room's display name
309             + nickname - the room's FriendFeed nickname, used in FriendFeed URLs
310             + url - the room's URL on FriendFeed
311              
312             The simple XML format has the same structure as the JSON. The RSS and Atom formats use the standard RSS and Atom attributes for title, link, published, and updated, and include extension elements for all of the other meta-data.
313              
314             Dates in JSON and dates in the FriendFeed extension elements in the Atom and RSS feeds are in RFC 3339 format in UTC. You can parse them with the strptime string "%Y-%m-%dT%H:%M:%SZ".
315              
316             All feed-fetching methods support additional parameters:
317              
318             =over
319              
320             =item service
321              
322             only return entries from the service with the given ID, e.g., service=twitter
323              
324             =item start
325              
326             return entries starting with the given index, e.g., start=30
327              
328             =item num
329              
330             return num entries starting from start, e.g., num=10
331              
332             =back
333              
334             They can be passed as key => value pairs after all the other arguments.
335              
336             $frf->fetch_user_feed('kkapp', num => 50, service => 'twitter');
337              
338             =cut
339              
340             =head2 return_feeds_as($type)
341              
342             Gets or sets the type of return feeds.
343              
344             This can be one of C and defaults to
345             C<'structure'> which is a parsed Perl data structure. Other types are
346             string scalars.
347              
348             =cut
349              
350             =head2 fetch_public_feed
351              
352             Fetches the most recent 30 public entries published to FriendFeed.
353              
354             =cut
355              
356             sub fetch_public_feed {
357 1     1 1 8 my $self = shift;
358              
359 1         7 $self->_fetch_feed('feed/public', @_);
360             }
361              
362             =head2 fetch_user_feed($nickname)
363              
364             Fetches the most recent entries from a user feed.
365             If the user has a private feed, authentication is required.
366              
367             =cut
368              
369             sub fetch_user_feed {
370 2     2 1 14 my $self = shift;
371 2         4 my $nickname = shift;
372              
373 2         11 $self->_fetch_feed('feed/user/' . uri_escape($nickname), @_);
374             }
375              
376             =head2 fetch_user_comments_feed($nickname)
377              
378             Returns the most recent entries the user has commented on, ordered by the date of that user's comments.
379              
380             =cut
381              
382             sub fetch_user_comments_feed {
383 1     1 1 6 my $self = shift;
384 1         3 my $nickname = shift;
385              
386 1         6 $self->_fetch_feed('feed/user/' . uri_escape($nickname) . '/comments', @_);
387             }
388              
389             =head2 fetch_user_likes_feed($nickname)
390              
391             Returns the most recent entries the user has "liked," ordered by the date of that user's "likes".
392              
393             =cut
394              
395             sub fetch_user_likes_feed {
396 1     1 1 6 my $self = shift;
397 1         2 my $nickname = shift;
398              
399 1         4 $self->_fetch_feed('feed/user/' . uri_escape($nickname) . '/likes', @_);
400             }
401              
402             =head2 fetch_user_discussion_feed($nickname)
403              
404             Returns the most recent entries the user has commented on or "liked".
405              
406             =cut
407              
408             sub fetch_user_discussion_feed {
409 1     1 1 6 my $self = shift;
410 1         3 my $nickname = shift;
411              
412 1         5 $self->_fetch_feed('feed/user/' . uri_escape($nickname) . '/discussion', @_);
413             }
414              
415             =head2 fetch_user_friends_feed($nickname)
416              
417             Fetch a users "friends" feed. This feed contains entries
418             from the user and her friends. No private feeds will be included
419             unless you are authenticated and have access to that feed.
420              
421             =cut
422              
423             sub fetch_user_friends_feed {
424 1     1 1 6 my $self = shift;
425 1         3 my $nickname = shift;
426              
427 1         7 $self->_fetch_feed('feed/user/' . uri_escape($nickname) . '/friends', @_);
428             }
429              
430             =head2 fetch_multi_user_feed(@nicknames)
431              
432             Returns the most recent entries from a list of users, specified by nickname:
433              
434             If more than one nickname is specified, the feed most recent entries
435             from all of the given users. If any one of the users has a private
436             feed, authentication is required.
437              
438             User nicknames may also be passed as an arrayref.
439              
440             $frf->fetch_multi_user_feed(qw/kkapp mihun/);
441              
442             =cut
443              
444             sub fetch_multi_user_feed {
445 3     3 1 20 my $self = shift;
446 3         9 my @nicknames = @_;
447              
448 2         7 ref $nicknames[0] eq 'ARRAY'
449 3 100       14 and @nicknames = @{$nicknames[0]};
450              
451 3         16 $self->_fetch_feed('feed/user', nickname => join(',', @nicknames), @_);
452             }
453              
454             =head2 fetch_room_feed($room)
455              
456             Returns the most recent entries in the room with the given nickname.
457              
458             If the room is private, authentication is required.
459              
460             =cut
461              
462             sub fetch_room_feed {
463 1     1 1 6 my $self = shift;
464 1         2 my $room = shift;
465              
466 1         6 $self->_fetch_feed('feed/room/' . uri_escape($room), @_);
467             }
468              
469             =head2 fetch_home_feed
470              
471             Returns the entries the authenticated user would see on their FriendFeed homepage - all of their subscriptions and friend-of-a-friend entries.
472              
473             Authentication is always required.
474              
475             =cut
476              
477             sub fetch_home_feed {
478 2     2 1 472 my $self = shift;
479              
480 2 100       6 $self->_has_auth and
481             $self->_fetch_feed('feed/home', @_);
482             }
483              
484             =head2 fetch_entry($uuid[, @uuids])
485              
486             Fetches a single or a list of entries by UUIDs. Needs authentication to read
487             private entries.
488              
489             You can request up to 100 entries on each call. The entries you don't
490             have permission to read will be filtered out from the result feed.
491              
492             =cut
493              
494             sub fetch_entry {
495 2     2 1 20 my $self = shift;
496 2         5 my @entry_ids = @_;
497              
498 2 100       9 if (@entry_ids == 1) {
499 1         10 $self->_fetch_feed('feed/entry/' . uri_escape($entry_ids[0]));
500             }
501             else {
502 1         8 $self->_fetch_feed('feed/entry', entry_id =>
503             Encode::encode('UTF-8', join(',', @entry_ids)));
504             }
505             }
506              
507             =head2 search($query)
508              
509             Executes a search over the entries in FriendFeed. If the request is
510             authenticated, the default scope is over all of the entries in the
511             authenticated user's Friends Feed. If the request is not
512             authenticated, the default scope is over all public entries.
513              
514             $frf->search('rambler service:twitter');
515              
516             The query syntax is the same syntax as http://friendfeed.com/search/advanced. The query operators are:
517              
518             =over
519              
520             =item who:
521              
522             restricts the search to a specific user, e.g., who:bret
523              
524             =item service:
525              
526             restricts the search to a specific service ID, e.g., service:twitter
527              
528             =back
529              
530             =cut
531              
532             sub search {
533 1     1 1 6 my $self = shift;
534 1         3 my $q = shift;
535              
536 1         7 $self->_fetch_feed('feed/search', q => Encode::encode('UTF-8', $q), @_);
537             }
538              
539             =head1 PUBLISHING FUNCTIONS
540              
541             You can perform test calls from a web browser using the HTTP Basic
542             Authentication built into your browser at
543             L.
544              
545             Requests to FriendFeed are rate limited, which, e.g., limits the
546             number and size of thumbnails you can upload in a day. Normal uses
547             should fall well within our rate limits. If you encounter HTTP 403
548             errors because of rate limits, and you think the limit is erroneous,
549             please let us know in the developer forum.
550              
551             =cut
552              
553             =head2 publish_link($title, $link, $comment, [@images, [$imgN, $linkN]], $room, $via)
554              
555             Share a link with a title, images and other possible options.
556             Requires authentication.
557              
558             All non-ASCII input data should be clean Perl Unicode (that is, decoded from
559             any encoding). FriendFeed API is strictly UTF-8 so we unconditionally
560             encode strings into UTF-8 via Encode::encode('UTF-8', $data) call.
561              
562             Full signature looks like:
563             $frf->publish_link($title, $link, $comment, [@images, [$imgN, $linkN]], $room, $via)
564              
565             =over
566              
567             =item $title
568              
569             Mandatory title of the shared item.
570              
571             =item $links
572              
573             URL to refer to. If absent, the shared link reduces to text.
574              
575             =item $comment
576              
577             Automatically add 1st comment to the item.
578              
579             =item $images
580              
581             This one is an arrayref of image items. Each image item is either an image PURL or a
582             pair (taken as arrayrefs of two elements) of PURL => URL. PURL in the
583             pair points to the image and URL is used as a href to follow when the
584             user clicks on this very image. URL defaults to the main $link.
585              
586             Each PURL may be either an (http|https|ftp) URL or a PATH to a local
587             file in which case that file gets uploaded directly to FriendFeed.
588              
589             =item $room
590              
591             This is a room nickname to which the link should be published.
592              
593             =item $via
594              
595             This is an identifier of your software. It's ignored unless you
596             register it with FriendFeed administration.
597              
598             =back
599              
600             =cut
601              
602             sub publish_link {
603 13     13 1 68 my $self = shift;
604 13         28 my ($msg, $link, $comment, $imgs, $room, $via) = @_;
605              
606 13         41 my @args = ();
607              
608 13         83 push @args, title => Encode::encode('UTF-8', $msg);
609 13 100       859 push @args, 'link' => $link if defined $link;
610 13 100       46 push @args, comment => Encode::encode('UTF-8', $comment) if defined $comment;
611 13 100       95 push @args, room => $room if defined $room;
612 13 100       34 push @args, via => $via if defined $via;
613              
614 13         16 my $multipart;
615              
616 13 100 100     59 if ($imgs && ref $imgs eq 'ARRAY') {
617 3         12 foreach (0 .. $#$imgs) {
618 5 100       13 if (ref $imgs->[$_]) { # image AND link
619              
620 2 100       13 if ($imgs->[$_]->[0] =~ m{^(?:http|https|ftp)://}) { # remote image
621 1         10 push @args, ("image${_}_url" => $imgs->[$_]->[0], "image${_}_link" => $imgs->[$_]->[1]);
622             }
623             else {
624 1         2 $multipart = 1;
625 1         27 my $filename = (File::Spec->splitpath($imgs->[$_]->[0]))[2]; # kinda basename
626 1         8 push @args, ("image${_}" => [$imgs->[$_]->[0], $filename], "${filename}_link" => $imgs->[$_]->[1]);
627             }
628             }
629             else {
630 3 100       24 if ($imgs->[$_] =~ m{^(?:http|https|ftp)://}) { # remote image
631 2         14 push @args, ("image${_}_url" => $imgs->[$_]);
632             }
633             else {
634 1         3 $multipart = 1;
635 1         5 push @args, ("image${_}" => [$imgs->[$_]]);
636             }
637             }
638             }
639             }
640              
641 13 100       74 $self->_post('share', Content => \@args,
642             $multipart ? (Content_Type => 'form-data') : ());
643             }
644              
645             =head2 publish_message($msg)
646              
647             Share a piece of text. The simplest form of FriendFeed sharing.
648             Requires authentication.
649              
650             This is actually a special case of publish_link with only $title set.
651              
652             =cut
653              
654             sub publish_message {
655 5     5 1 632 my $self = shift;
656 5         8 my $msg = shift;
657              
658 5         17 $self->publish_link($msg);
659             }
660              
661             =head2 delete_entry($entry_id)
662              
663             Delete an entry. The arguments are:
664              
665             =over
666              
667             =item $entry_id
668              
669             required - The FriendFeed UUID of the entry.
670              
671             =back
672              
673             =cut
674              
675             sub delete_entry {
676 1     1 1 7 my $self = shift;
677 1         2 my $entry_id = shift;
678              
679 1         16 $self->_post('entry/delete', [entry => $entry_id]);
680             }
681              
682             =head2 undelete_entry($entry_id)
683              
684             Undelete a deleted entry. The arguments are:
685              
686             =over
687              
688             =item $entry_id
689              
690             required - The FriendFeed UUID of the entry.
691              
692             =back
693              
694             =cut
695              
696             sub undelete_entry {
697 1     1 1 5 my $self = shift;
698 1         3 my $entry_id = shift;
699              
700 1         4 $self->_post('entry/delete', [
701             entry => $entry_id,
702             undelete => 1,
703             ]);
704             }
705              
706             =head2 hide_entry($entry_id)
707              
708             Hides an entry for current user. The arguments are:
709              
710             =over
711              
712             =item $entry_id
713              
714             required - The FriendFeed UUID of the entry.
715              
716             =back
717              
718             =cut
719              
720             sub hide_entry {
721 1     1 1 7 my $self = shift;
722 1         2 my $entry_id = shift;
723              
724 1         5 $self->_post('entry/hide', [entry => $entry_id]);
725             }
726              
727             =head2 unhide_entry($entry_id)
728              
729             Unhide a hidden entry. The arguments are:
730              
731             =over
732              
733             =item $entry_id
734              
735             required - The FriendFeed UUID of the entry.
736              
737             =back
738              
739             =cut
740              
741             sub unhide_entry {
742 1     1 1 5 my $self = shift;
743 1         2 my $entry_id = shift;
744              
745 1         5 $self->_post('entry/hide', [
746             entry => $entry_id,
747             unhide => 1,
748             ]);
749             }
750              
751             =head1 COMMENT AND LIKE FUNCTIONS
752              
753             =head2 add_comment($entry_id, $body, $via)
754              
755             Add a comment on a FriendFeed entry. The arguments are:
756              
757             =over
758              
759             =item $entry_id
760              
761             required - The FriendFeed UUID of the entry to which this comment is attached.
762              
763             =item $body
764              
765             required - The textual body of the comment.
766              
767             =item $via
768              
769             This is an identifier of your software. It's ignored unless you
770             register it with FriendFeed administration.
771              
772             =back
773              
774             $frf->add_comment('550e8400-e29b-41d4-a716-446655440000', 'Testing the FriendFeed API', 'Microsoft Word');
775              
776             =cut
777              
778             sub add_comment {
779 2     2 1 12 my $self = shift;
780 2         5 my ($entry_id, $comment_text, $via) = @_;
781              
782 2 100       14 $self->_post('comment', [entry => $entry_id, body => Encode::encode('UTF-8', $comment_text), defined $via ? (via => $via) : ()]);
783             }
784              
785             =head2 edit_comment($entry_id, $body, $comment_id)
786              
787             Edit an existing comment on a FriendFeed entry. The arguments are:
788              
789             =over
790              
791             =item $entry_id
792              
793             required - The FriendFeed UUID of the entry to which this comment is attached.
794              
795             =item $body
796              
797             required - The textual body of the comment.
798              
799             =item $comment_id
800              
801             The FriendFeed UUID of the comment to edit. If not given, the request will create a new comment.
802              
803             =back
804              
805             =cut
806              
807             sub edit_comment {
808 1     1 1 6 my $self = shift;
809 1         4 my ($entry_id, $comment_text, $comment_id) = @_;
810              
811 1         8 $self->_post('comment', [
812             entry => $entry_id,
813             comment => $comment_id,
814             body => Encode::encode('UTF-8', $comment_text),
815             ]);
816             }
817              
818             =head2 delete_comment($entry_id, $comment_id)
819              
820             Delete an existing comment. The arguments are:
821              
822             =over
823              
824             =item $entry_id
825              
826             required - The FriendFeed UUID of the entry to which this comment is attached.
827              
828             =item $comment_id
829              
830             required - The FriendFeed UUID of the comment to delete.
831              
832             =back
833              
834             =cut
835              
836             sub delete_comment {
837 1     1 1 7 my $self = shift;
838 1         3 my ($entry_id, $comment_id) = @_;
839              
840 1         5 $self->_post('comment/delete', [entry => $entry_id, comment => $comment_id]);
841             }
842              
843             =head2 undelete_comment($entry_id, $comment_id)
844              
845             Undelete a deleted comment. The arguments are:
846              
847             =over
848              
849             =item $entry_id
850              
851             required - The FriendFeed UUID of the entry to which this comment is attached.
852              
853             =item $comment_id
854              
855             required - The FriendFeed UUID of the comment to undelete.
856              
857             =back
858              
859             =cut
860              
861             sub undelete_comment {
862 1     1 1 7 my $self = shift;
863 1         2 my ($entry_id, $comment_id) = @_;
864              
865 1         6 $self->_post('comment/delete', [
866             entry => $entry_id,
867             comment => $comment_id,
868             undelete => 1,
869             ]);
870             }
871              
872             =head2 add_like($entry_id)
873              
874             Add a "Like" to a FriendFeed entry for the authenticated user.
875              
876             =over
877              
878             =item $entry_id
879              
880             required - The FriendFeed UUID of the entry to which this comment is attached
881              
882             =back
883              
884             $frf->add_like("550e8400-e29b-41d4-a716-446655440000");
885              
886             =cut
887              
888             sub add_like {
889 1     1 1 7 my $self = shift;
890 1         3 my $entry_id = shift;
891              
892 1         5 $self->_post('like', [entry => $entry_id]);
893             }
894              
895             =head2 delete_like($entry_id)
896              
897             Delete an existing "Like". The arguments are:
898              
899             =over
900              
901             =item $entry_id
902              
903             required - The FriendFeed UUID of the entry to which this comment is attached.
904              
905             =back
906              
907             =cut
908              
909             sub delete_like {
910 1     1 1 7 my $self = shift;
911 1         2 my $entry_id = shift;
912              
913 1         5 $self->_post('like/delete', [entry => $entry_id]);
914             }
915              
916             =head1 PROFILE FUNCTIONS
917              
918             =head2 fetch_user_profile($nickname)
919              
920             Returns list of all of the user's subscriptions (people) and services connected to their account.
921              
922             The returned data has this structure:
923              
924             * id - the user's FriendFeed UUID
925             * name - the user's full name
926             * nickname - the user's FriendFeed nickname, used in FriendFeed URLs
927             * profileUrl - the user's profile URL on FriendFeed
928             * services[] - the services connected to the user's account
929             o id - the service's FriendFeed ID, e.g., "picasa"
930             o name - the service's official name, e.g., "Picasa Web Albums"
931             o url - the official URL of the service, e.g., http://picasaweb.google.com/
932             o iconUrl - the URL of the favicon for this service
933             o profileUrl? - the user's profile URL on this service, if any
934             o username? - the user's username for this service, if any
935             * subscriptions[] - the users this user is subscribed to
936             o id
937             o name
938             o nickname
939             o profileUrl
940             * rooms[] - the rooms this user is a member of
941             o id - the room's FriendFeed UUID
942             o name - the room's display name
943             o nickname - the room's FriendFeed nickname, used in FriendFeed URLs
944             o url - the room's URL on FriendFeed
945              
946             =cut
947              
948             sub fetch_user_profile {
949 1     1 1 6 my $self = shift;
950 1         3 my $nickname = shift;
951              
952 1         8 $self->_fetch_feed('user/' . uri_escape($nickname) . '/profile', @_);
953             }
954              
955             =head2 fetch_user_profiles(@nicknames)
956              
957             Returns profiles for multiple users. The returned structure has one
958             element C which is an array of profile structures described
959             alongside C method.
960              
961             User nicknames may also be passed as an arrayref.
962              
963             =cut
964              
965             sub fetch_user_profiles {
966 2     2 1 12 my $self = shift;
967 2         6 my @nicknames = @_;
968              
969 1         3 ref $nicknames[0] eq 'ARRAY'
970 2 100       9 and @nicknames = @{$nicknames[0]};
971              
972 2         12 $self->_fetch_feed('profiles', nickname => join(',', @nicknames), @_);
973             }
974              
975             =head1 AUTHOR
976              
977             Alex Kapranoff, C<< >>
978              
979             =head1 BUGS
980              
981             Please report any bugs or feature requests to C, or through
982             the web interface at L. I will be notified, and then you'll
983             automatically be notified of progress on your bug as I make changes.
984              
985              
986             =head1 SUPPORT
987              
988             You can find documentation for this module with the perldoc command.
989              
990             perldoc Net::FriendFeed
991              
992              
993             You can also look for information at:
994              
995             =over 4
996              
997             =item * RT: CPAN's request tracker
998              
999             L
1000              
1001             =item * AnnoCPAN: Annotated CPAN documentation
1002              
1003             L
1004              
1005             =item * CPAN Ratings
1006              
1007             L
1008              
1009             =item * Search CPAN
1010              
1011             L
1012              
1013             =back
1014              
1015              
1016             =head1 ACKNOWLEDGEMENTS
1017              
1018             Mark Carey prompted to implement C for comments.
1019              
1020             =head1 COPYRIGHT & LICENSE
1021              
1022             Copyright 2008 Alex Kapranoff, all rights reserved.
1023              
1024             This program is released under the following license: GPLv3
1025              
1026             =cut
1027              
1028             1; # End of Net::FriendFeed