File Coverage

blib/lib/WWW/Facebook/FQL.pm
Criterion Covered Total %
statement 64 109 58.7
branch 7 30 23.3
condition 2 9 22.2
subroutine 16 24 66.6
pod 7 10 70.0
total 96 182 52.7


line stmt bran cond sub pod time code
1             package WWW::Facebook::FQL;
2              
3             =head1 NAME
4              
5             WWW::Facebook::FQL - Simple interface to Facebook's FQL query language
6              
7             =head1 SYNOPSIS
8              
9             use WWW::Facebook::FQL;
10              
11             ## Connect and log in:
12             my $fb = new WWW::Facebook::FQL key => $public_key, private => $private_key;
13             $fb->login($email, $password);
14              
15             ## Get your own name and pic back:
16             $fb->query("SELECT name, pic FROM user WHERE uid=$fb->{uid}");
17              
18             ## Get your friends' names and pics:
19             $fb->query("SELECT name, pic FROM user WHERE uid IN "
20             . "(SELECT uid2 FROM friend WHERE uid1 = $fb->{uid})");
21              
22             ## Get results in manageable form:
23             use JSON::Syck; # or whatever...
24             $fb->format = 'JSON';
25             my $arrayref = JSON::Syck::Load $fb->query("...");
26              
27             =head1 DESCRIPTION
28              
29             WWW::Facebook::FQL aims to make it easy to perform Facebook Query
30             Language (FQL) queries from a Perl program, rather than to reflect the
31             whole PHP Facebook API. For those comfortable with SQL, this may be a
32             more comfortable interface. Results are currently returned in the raw
33             JSON or XML format, but more palatable options may be available in the
34             future.
35              
36             =cut
37              
38 1     1   1311 use URI::Escape;
  1         1276  
  1         63  
39 1     1   1647 use WWW::Mechanize;
  1         495342  
  1         48  
40 1     1   25 use Digest::MD5 qw(md5_hex);
  1         3  
  1         81  
41             require Exporter;
42 1     1   4 use strict;
  1         2  
  1         37  
43              
44 1     1   4 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         102  
45             $VERSION = '0.03';
46              
47             @EXPORT_OK = qw(%FIELDS %IXFIELDS);
48             %EXPORT_TAGS = (all => \@EXPORT_OK);
49             @ISA = qw(Exporter);
50              
51 1     1   4 use vars qw($rest %FIELDS %IXFIELDS);
  1         2  
  1         1138  
52             $rest = 'http://api.facebook.com/restserver.php';
53              
54             sub dprint
55             {
56 1     1 0 2 my $self = shift;
57 1         2 my $lev = shift;
58 1 50       16 if ($lev <= $self->{verbose}) {
59 0         0 print STDERR @_;
60             }
61             }
62              
63             sub _sig
64             {
65 1     1   2 my $secret = shift;
66 1         14 md5_hex uri_unescape(join '', sort(@_), $secret);
67             }
68              
69             sub get
70             {
71 1     1 0 2 my $self = shift;
72 1   33     15 ($self->{mech} ||= new WWW::Mechanize)->get(@_);
73             }
74              
75             sub _request_nofail
76             {
77 1     1   2 my $self = shift;
78 1         3 my $resp = $self->_request(@_);
79 1 50       4 die "Request failed:\n", $resp->decoded_content unless $resp->is_success;
80 1         10 $resp;
81             }
82              
83             sub _request
84             {
85 1     1   3 my ($self, $method, %o) = @_;
86 1   33     5 $o{format} ||= $self->{format};
87 1         3 $method = "facebook.$method";
88 1         6 my @params = ("api_key=$self->{key}",
89             "method=$method",
90             'v=1.0',
91             $self->{session_key} ? ('session_key='.$self->{session_key},
92             'call_id='.(++$self->{callid})) : (),
93 1 50       7 map { "$_=".uri_escape($o{$_}) } keys %o);
94 1         41 my $sig = _sig($self->{secret}, @params);
95 1         20 my $url = "$rest?".join '&', @params, "sig=$sig\n";
96 1         5 $self->dprint(1, $url);
97 1         7 my $resp = $self->get("$rest?".join '&', @params, "sig=$sig");
98 1 50       459763 if (!$resp->is_success) {
99 0         0 $self->dprint(0, "Request '$url' failed.\n");
100             }
101             ## avoid decoding content unless printed
102 1 50       23 if ($self->{verbose} > 2) {
103 0         0 $self->dprint(2, "RESPONSE ", '=' x 50, "\n", $resp->decoded_content,
104             "\n", '=' x 70, "\n");
105             }
106 1         8 $resp;
107             }
108              
109             sub _get_auth_token
110             {
111 1     1   1 my ($self) = @_;
112 1         6 $self->{secret} = $self->{private};
113 1         3 my $resp = $self->_request_nofail('auth.createToken', format => 'JSON');
114 1         36 $self->{auth_token} = eval $resp->decoded_content;
115             }
116              
117             sub _get_session
118             {
119 0     0   0 my $self = shift;
120 0         0 my $resp;
121             {
122 0         0 local $rest = $rest;
  0         0  
123 0         0 $rest =~ s/^http/https/;
124 0         0 $resp = $self->_request_nofail('auth.getSession', format => 'XML',
125             auth_token => $self->{auth_token});
126             }
127 0         0 local $_ = $resp->decoded_content;
128 0         0 for my $word (qw(uid session_key expires secret)) {
129 0         0 ($self->{$word}) = /<$word>(.*?)<\/$word>/;
130             }
131 0         0 $self->dprint(1, "Session expires at ",
132             scalar localtime($self->{expires}), "\n");
133             }
134              
135             =head2 C<$fb = new WWW::Facebook::FQL key =E value, ...>
136              
137             Create a new Facebook FQL session for user $EMAIL with password $PASS.
138             Keyword arguments include
139              
140             =over 4
141              
142             =item email -- the email address of your Facebook account.
143              
144             =item pass -- your password.
145              
146             =item verbose -- A number controlling debugging information.
147              
148             =item key -- The public part of your API key.
149              
150             You need to sign up for this on Facebook by joining the "Developers"
151             group and requesting an API key.
152              
153             =item private -- The private part of your API key.
154              
155             =item format -- Data return format, either 'XML' (the default) or 'JSON'.
156              
157             =back
158              
159             WWW::Facebook::FQL reads default values from the file $HOME/.fqlrc if
160             it exists. It should contain the innards of an argument list, and
161             will be evaluated like C<@args = eval "($FILE_CONTENTS)">. The
162             constructor will I prompt for any parameters; it is the calling
163             program's responsibility to get sensitive information from the user in
164             an appropriate way.
165              
166             =cut
167              
168             sub new
169             {
170 1     1 1 421 my $class = shift;
171 1         4 my @def = (format => 'XML', verbose => 0);
172 1 50       31 if (-f "$ENV{HOME}/.fqlrc") {
173 0         0 local $/;
174 0 0       0 if (open IN, "$ENV{HOME}/.fqlrc") {
175 0         0 my @tmp = eval '('..')';
176 0 0       0 push @def, @tmp unless $@;
177 0         0 close IN;
178             }
179             }
180 1         7 my %o = (@def, @_);
181 1         3 my $self = bless \%o, $class;
182 1 50       5 return undef unless $self->_get_auth_token;
183 0           $self
184             }
185              
186             sub login
187             {
188 0     0 0   my $self = shift;
189 0 0         ($self->{email}, $self->{pass}) = @_ if @_;
190 0           my $mech = $self->{mech};
191 0           $mech->get("http://www.facebook.com/login.php?api_key=$self->{key}&v=1.0&auth_token=$self->{auth_token}&hide_checkbox=1&skipcookie=1");
192 0 0         die "Can't access login form:\n", $mech->res->decoded_content
193             unless $mech->success;
194              
195 0           my $resp = $mech->submit_form(with_fields => {
196             email => $self->{email},
197             pass => $self->{pass}
198             });
199 0 0         die "Login failed:\n", $resp->decoded_content
200             unless $resp->is_success;
201 0           $self->dprint(2, "Logged in as $self->{email}\n");
202             ## XXX check response
203 0 0         if ($mech->content =~ /Terms of Service/) {
204 0           $mech->submit_form(form_name => 'confirm_grant_form');
205 0 0         die "TOS failed:\n", $mech->res->decoded_content
206             unless $mech->res->is_success;
207 0           $self->dprint(2, "Agreed to terms of service.");
208             }
209             ## Get session key
210 0           $self->_get_session;
211 0           $self;
212             }
213              
214             =head2 C<$fb-Elogout>
215              
216             Log the current user out.
217              
218             =cut
219              
220             sub logout
221             {
222 0     0 1   my $self = shift;
223 0           $self->{mech}->get("http://www.facebook.com/logout.php?api_key=$self->{key}&v=1.0&auth_token=$self->{auth_token}&confirm=1");
224 0           delete $self->{secret};
225             }
226              
227             =head2 C<$result = $fb-Equery($QUERY)
228              
229             Perform FQL query $QUERY, returning the result in format $FORMAT
230             (either XML or JSON, JSON by default). FQL is a lot like SQL, but
231             with its own set of weird and privacy-related restrictions; for a
232             description, see
233             L.
234              
235             =cut
236              
237             sub query
238             {
239 0     0 1   my ($self, $q) = @_;
240 0 0 0       if (!$self->{secret} || $self->{private} eq $self->{secret}) {
241 0           print STDERR "Must log in before querying.\n";
242 0           return;
243             }
244 0           $self->_request('fql.query', query => $q)->decoded_content;
245             }
246              
247             =head2 ACCESSORS
248              
249             =over
250              
251             =item C<$fb-Euid> (read-only)
252              
253             =item C<$fb-Eemail> (read-only)
254              
255             =item C<$fb-Everbose> (read-write)
256              
257             =item C<$fb-Eformat> (read-write)
258              
259             =back
260              
261             =cut
262              
263             BEGIN {
264 1     1   5 no strict;
  1         7  
  1         76  
265 1     1   3 for (qw(uid email)) {
266 2     0 1 151 eval "sub $_\n{ shift->{$_} }";
  0     0 1    
  0            
267             }
268 1         2 for (qw(verbose format)) {
269 2     0 1 277 eval "sub $_ :lvalue { shift->{$_} }";
  0     0 1    
  0            
270             }
271             }
272              
273             BEGIN {
274 1     1   33 %FIELDS = (
275             user => [qw(uid* first_name last_name name* pic_small pic_big
276             pic_square pic affiliations profile_update_time timezone religion
277             birthday sex hometown_location meeting_sex meeting_for
278             relationship_status significant_other_id political
279             current_location activities interests is_app_user music tv movies
280             books quotes about_me hs_info education_history work_history
281             notes_count wall_count status has_added_app)],
282              
283             friend => [qw(uid1* uid2*)],
284              
285             group => [qw(gid* name nid pic_small pic_big pic description
286             group_type group_subtype recent_news creator update_time office
287             website venue)],
288              
289             group_member => [qw(uid* gid* positions)],
290              
291             event => [qw(eid* name tagline nid pic_small pic_big pic host
292             description event_type event_subtype start_time end_time creator
293             update_time location venue)],
294              
295             event_member => [qw(uid* eid* rsvp_status)],
296              
297             photo => [qw(pid* aid* owner src_small src_big src link caption
298             created)],
299              
300             album => [qw(aid* cover_pid* owner* name created modified
301             description location size)],
302              
303             photo_tag => [qw(pid* subject* xcoord ycoord)],
304             );
305              
306 1         6 for (keys %FIELDS) {
307 9         10 $IXFIELDS{$_} = [grep /\*$/, @{$FIELDS{$_}}];
  9         61  
308 9         12 s/\*$// for @{$FIELDS{$_}};
  9         135  
309             }
310              
311             } ## END BEGIN
312              
313             1;
314             __END__