File Coverage

blib/lib/WebService/LiveJournal/Client.pm
Criterion Covered Total %
statement 38 332 11.4
branch 0 128 0.0
condition 0 31 0.0
subroutine 13 51 25.4
pod 24 34 70.5
total 75 576 13.0


line stmt bran cond sub pod time code
1             package WebService::LiveJournal::Client;
2              
3 12     12   310663 use strict;
  12         61  
  12         404  
4 12     12   76 use warnings;
  12         24  
  12         256  
5 12     12   136 use v5.10;
  12         41  
6 12     12   14194 use overload '""' => \&as_string;
  12         11139  
  12         100  
7 12     12   830 use Digest::MD5 qw(md5_hex);
  12         24  
  12         805  
8 12     12   8742 use RPC::XML;
  12         7467622  
  12         740  
9 12     12   8152 use RPC::XML::Client;
  12         1583816  
  12         594  
10 12     12   6299 use WebService::LiveJournal::FriendList;
  12         46  
  12         385  
11 12     12   5194 use WebService::LiveJournal::FriendGroupList;
  12         35  
  12         390  
12 12     12   5722 use WebService::LiveJournal::Event;
  12         49  
  12         446  
13 12     12   5849 use WebService::LiveJournal::EventList;
  12         33  
  12         441  
14 12     12   5257 use WebService::LiveJournal::Tag;
  12         40  
  12         378  
15 12     12   6891 use HTTP::Cookies;
  12         89893  
  12         47618  
16              
17             # ABSTRACT: (Deprecated) Interface to the LiveJournal API
18             our $VERSION = '0.09'; # VERSION
19              
20              
21             my $zero = new RPC::XML::int(0);
22             my $one = new RPC::XML::int(1);
23             our $lineendings_unix = new RPC::XML::string('unix');
24             my $challenge = new RPC::XML::string('challenge');
25             our $error;
26             our $error_request;
27              
28             $RPC::XML::ENCODING = 'utf-8'; # uh... and WHY??? is this a global???
29              
30              
31             sub new # arg: server, port, username, password, mode
32             {
33 0     0 1   my $ob = shift;
34 0   0       my $class = ref($ob) || $ob;
35 0           my $self = bless {}, $class;
36            
37 0           my %arg = @_;
38            
39 0   0       my $server = $self->{server} = $arg{server} // 'www.livejournal.com';
40 0           my $domain = $server;
41 0           $domain =~ s/^([A-Za-z0-9]+)//;
42 0           $self->{domain} = $domain;
43 0   0       my $port = $self->{port} = $arg{port} || 80;
44 0 0         $server .= ":$port" if $port != 80;
45 0           my $client = $self->{client} = new RPC::XML::Client("http://$server/interface/xmlrpc");
46 0           $self->{flat_url} = "http://$server/interface/flat";
47 0           my $cookie_jar = $self->{cookie_jar} = new HTTP::Cookies;
48 0           $client->useragent->cookie_jar($cookie_jar);
49 0           $client->useragent->default_headers->push_header('X-LJ-Auth' => 'cookie');
50              
51 0   0       $self->{mode} = $arg{mode} // 'cookie'; # can be cookie or challenge
52              
53 0           my $username = $self->{username} = $arg{username};
54 0           my $password = $arg{password};
55 0 0         $self->{password} = $password if $self->{mode} ne 'cookie';
56            
57 0           $self->{auth} = [ ver => $one ];
58 0           $self->{flat_auth} = [ ver => 1 ];
59            
60 0 0         if($self->{mode} eq 'cookie')
    0          
61             {
62            
63 0           my $response = $self->send_request('getchallenge');
64 0 0         return unless defined $response;
65 0           my $auth_challenge = $response->value->{challenge};
66 0           my $auth_response = md5_hex($auth_challenge, md5_hex($password));
67            
68 0           push @{ $self->{auth} }, username => new RPC::XML::string($username);
  0            
69 0           push @{ $self->{flat_auth} }, user => $username;
  0            
70              
71 0           $response = $self->send_request('sessiongenerate',
72             auth_method => $challenge,
73             auth_challenge => new RPC::XML::string($auth_challenge),
74             auth_response => new RPC::XML::string($auth_response),
75             );
76              
77 0 0         return unless defined $response;
78              
79 0           my $ljsession = $self->{ljsession} = $response->value->{ljsession};
80 0           $self->set_cookie(ljsession => $ljsession);
81 0           push @{ $self->{auth} }, auth_method => new RPC::XML::string('cookie');
  0            
82 0           push @{ $self->{flat_auth} }, auth_method => 'cookie';
  0            
83            
84             }
85             elsif($self->{mode} eq 'challenge')
86             {
87 0           push @{ $self->{auth} }, username => new RPC::XML::string($username);
  0            
88 0           push @{ $self->{flat_auth} }, user => $username;
  0            
89             }
90              
91 0           my $response = $self->send_request('login'
92             #getmoods => $zero,
93             #getmenus => $one,
94             #getpickws => $one,
95             #getpickwurls => $one,
96             );
97            
98 0 0         return unless defined $response;
99            
100 0           my $h = $response->value;
101 0 0         return $self->_set_error($h->{faultString}) if defined $h->{faultString};
102 0 0         return $self->_set_error("unknown LJ error " . $h->{faultCode}->value) if defined $h->{faultCode};
103            
104 0           $self->{userid} = $h->{userid};
105 0           $self->{fullname} = $h->{fullname};
106 0   0       $self->{usejournals} = $h->{usejournals} || [];
107 0           my $fastserver = $self->{fastserver} = $h->{fastserver};
108            
109 0 0         if($fastserver)
110             {
111 0           $self->set_cookie(ljfastserver => 1);
112             }
113            
114 0 0         if($h->{friendgroups})
115             {
116 0           my $fg = $self->{cachefriendgroups} = new WebService::LiveJournal::FriendGroupList(response => $response);
117             }
118            
119 0           $self->{message} = $h->{message};
120 0           return $self;
121             }
122              
123              
124             foreach my $name (qw( server username port userid fullname usejournals fastserver cachefriendgroups message cookie_jar ))
125             {
126 0     0 0   eval qq{ sub $name { shift->{$name} } };
  0     0 1    
  0     0 1    
  0     0 1    
  0     0 1    
  0     0 1    
  0     0 1    
  0     0 1    
  0     0 1    
  0     0 1    
127             die $@ if $@;
128             }
129              
130 0     0 1   sub useragent { $_[0]->{client}->useragent }
131              
132              
133             sub create_event
134             {
135 0     0 1   my $self = shift;
136 0           my $event = new WebService::LiveJournal::Event(client => $self, @_);
137 0           $event;
138             }
139              
140             # legacy
141 0     0 0   sub create { shift->create_event(@_) }
142              
143              
144             sub get_events
145             {
146 0     0 1   my $self = shift;
147 0           my @list;
148 0   0       my $selecttype = shift || 'lastn';
149 0           push @list, selecttype => new RPC::XML::string($selecttype);
150              
151 0           my %arg = @_;
152              
153 0 0         if($selecttype eq 'syncitems')
    0          
    0          
    0          
154             {
155 0 0         push @list, lastsync => new RPC::XML::string($arg{lastsync}) if defined $arg{lastsync};
156             }
157             elsif($selecttype eq 'day')
158             {
159 0 0 0       unless(defined $arg{day} && defined $arg{month} && defined $arg{year})
      0        
160             {
161 0           return $self->_set_error('attempt to use selecttype=day without providing day!');
162             }
163             push @list,
164             day => new RPC::XML::int($arg{day}),
165             month => new RPC::XML::int($arg{month}),
166 0           year => new RPC::XML::int($arg{year});
167             }
168             elsif($selecttype eq 'lastn')
169             {
170 0 0         push @list, howmany => new RPC::XML::int($arg{howmany}) if defined $arg{howmany};
171 0 0         push @list, howmany => new RPC::XML::int($arg{max}) if defined $arg{max};
172 0 0         push @list, beforedate => new RPC::XML::string($arg{beforedate}) if defined $arg{beforedate};
173             }
174             elsif($selecttype eq 'one')
175             {
176 0   0       my $itemid = $arg{itemid} || -1;
177 0           push @list, itemid => new RPC::XML::int($itemid);
178             }
179             else
180             {
181 0           return $self->_set_error("unknown selecttype: $selecttype");
182             }
183            
184 0 0         push @list, truncate => new RPC::XML::int($arg{truncate}) if $arg{truncate};
185 0 0         push @list, prefersubject => $one if $arg{prefersubject};
186 0           push @list, lineendings => $lineendings_unix;
187 0 0         push @list, usejournal => RPX::XML::string($arg{usejournal}) if $arg{usejournal};
188 0 0         push @list, usejournal => RPX::XML::string($arg{journal}) if $arg{journal};
189              
190 0           my $response = $self->send_request('getevents', @list);
191 0 0         return unless defined $response;
192 0 0         if($selecttype eq 'one')
193             {
194 0 0         return unless @{ $response->value->{events} } > 0;
  0            
195 0           return new WebService::LiveJournal::Event(client => $self, %{ $response->value->{events}->[0] });
  0            
196             }
197             else
198             {
199 0           return new WebService::LiveJournal::EventList(client => $self, response => $response);
200             }
201             }
202              
203             # legacy
204 0     0 0   sub getevents { shift->get_events(@_) }
205              
206              
207             sub get_event
208             {
209 0     0 1   my $self = shift;
210 0 0         my %args = @_ == 1 ? (itemid => shift) : (@_);
211 0           $self->get_events('one', %args);
212             }
213              
214             # legacy
215 0     0 0   sub getevent { shift->get_event(@_) }
216              
217              
218             sub sync_items
219             {
220 0     0 1   my $self = shift;
221 0     0     my $cb = sub {};
222 0 0         $cb = pop if ref($_[-1]) eq 'CODE';
223 0           my %arg = @_;
224            
225 0           my $return_time;
226            
227 0           my @req_args = ();
228 0 0         if(defined $arg{last_sync})
229             {
230 0           @req_args = ( lastsync => $arg{last_sync} );
231 0           $return_time = $arg{last_sync};
232             }
233            
234 0           eval {
235 0           while(1)
236             {
237 0           my $response = $self->send_request('syncitems', @req_args);
238 0 0         last unless defined $response;
239 0           my $count = $response->value->{count};
240 0           my $total = $response->value->{total};
241 0           foreach my $item (@{ $response->value->{syncitems} })
  0            
242             {
243 0 0         unless($item->{item} =~ /^(.)-(\d+)$/)
244             {
245 0           die 'internal error: ' . $item->{item} . ' does not match';
246             }
247 0           $cb->($item->{action}, $1, $2);
248 0           $return_time = $item->{time};
249             }
250 0 0         last if $count == $total;
251 0           @req_args = ( lastsync => $arg{return_time} );
252             };
253             };
254 0 0         $WebService::LiveJournal::Client::error = $@ if $@;
255 0           return $return_time;
256             }
257              
258              
259             sub get_friends
260             {
261 0     0 1   my $self = shift;
262 0           my %arg = @_;
263 0           my @list;
264 0 0         push @list, friendlimit => new RPC::XML::int($arg{friendlimit}) if defined $arg{friendlimit};
265 0 0         push @list, friendlimit => new RPC::XML::int($arg{limit}) if defined $arg{limit};
266 0 0         push @list, includefriendof => 1, includegroups => 1 if $arg{complete};
267 0           my $response = $self->send_request('getfriends', @list);
268 0 0         return unless defined $response;
269 0 0         if($arg{complete})
270             {
271             return (new WebService::LiveJournal::FriendList(response_list => $response->value->{friends}),
272 0           new WebService::LiveJournal::FriendList(response_list => $response->value->{friendofs}),
273             new WebService::LiveJournal::FriendGroupList(response => $response),
274             );
275             }
276             else
277             {
278 0           return new WebService::LiveJournal::FriendList(response => $response);
279             }
280             }
281              
282 0     0 0   sub getfriends { shift->get_friends(@_) }
283              
284              
285             sub get_friend_of
286             {
287 0     0 0   my $self = shift;
288 0           my %arg = @_;
289 0           my @list;
290 0 0         push @list, friendoflimit => new RPC::XML::int($arg{friendoflimit}) if defined $arg{friendoflimit};
291 0 0         push @list, friendoflimit => new RPC::XML::int($arg{limit}) if defined $arg{limit};
292 0           my $response = $self->send_request('friendof', @list);
293 0 0         return unless defined $response;
294 0           return new WebService::LiveJournal::FriendList(response => $response);
295             }
296              
297 0     0 0   sub friendof { shift->get_friend_of(@_) }
298              
299              
300             sub get_friend_groups
301             {
302 0     0 1   my $self = shift;
303 0           my $response = $self->send_request('getfriendgroups');
304 0 0         return unless defined $response;
305 0           return new WebService::LiveJournal::FriendGroupList(response => $response);
306             }
307              
308 0     0 0   sub getfriendgroups { shift->get_friend_groups(@_) }
309              
310              
311             sub get_user_tags
312             {
313 0     0 1   my($self, $journal_name) = @_;
314 0           my @request = ('getusertags');
315 0 0         push @request, usejournal => RPC::XML::string->new($journal_name)
316             if defined $journal_name;
317 0           my $response = $self->send_request(@request);
318 0 0         return unless defined $response;
319 0           return map { WebService::LiveJournal::Tag->new($_) } @{ $response->value->{tags} };
  0            
  0            
320             }
321              
322              
323             sub console_command
324             {
325 0     0 1   my $self = shift;
326            
327             my $response = $self->send_request('consolecommand',
328             commands => RPC::XML::array->new(
329             RPC::XML::array->new(
330 0           map { RPC::XML::string->new($_) } @_
  0            
331             ),
332             ),
333             );
334 0 0         return unless defined $response;
335 0           return $response->value->{results}->[0]->{output};
336             }
337              
338              
339             sub batch_console_commands
340             {
341 0     0 1   my $self = shift;
342 0           my @commands;
343             my @cb;
344 0           for(0..$#_)
345             {
346 0 0         if($_ % 2)
347 0           { push @cb, $_[$_] }
348             else
349 0           { push @commands, RPC::XML::array->new(map { RPC::XML::string->new($_) } @{ $_[$_] }) }
  0            
  0            
350             }
351            
352 0           my $response = $self->send_request('consolecommand',
353             commands => RPC::XML::array->new(@commands)
354             );
355 0 0         return unless defined $response;
356              
357             # also returned is 'success' but as far as I can tell it is always
358             # 1, even if the command doesn't exist. so we are ignoring it.
359            
360 0           foreach my $output (map { $_->{output} } @{ $response->value->{results} })
  0            
  0            
361             {
362 0           my $cb = shift @cb;
363 0           $cb->(@$output);
364             }
365            
366 0           return 1;
367             }
368              
369              
370             sub set_cookie
371             {
372 0     0 1   my $self = shift;
373 0           my $key = shift;
374 0           my $value = shift;
375              
376             $self->cookie_jar->set_cookie(
377             0, # version
378             $key => $value, # key => value
379             '/', # path
380             $self->{domain}, # domain
381 0           $self->port, # port
382             1, # path_spec
383             0, # secure
384             60*60*24, # maxage
385             0, # discard
386             );
387             }
388              
389              
390             sub send_request
391             {
392 0     0 1   my $self = shift;
393 0           $self->_clear_error;
394 0   0       my $count = $self->{count} || 1;
395 0           my $procname = shift;
396            
397 0           my @challenge;
398 0 0         if($self->{mode} eq 'challenge')
399             {
400 0           my $response = $self->{client}->send_request('LJ.XMLRPC.getchallenge');
401 0 0         if(ref $response)
402             {
403 0 0         if($response->is_fault)
404             {
405 0           my $string = $response->value->{faultString};
406 0           my $code = $response->value->{faultCode};
407 0           $self->_set_error("$string ($code) on LJ.XMLRPC.getchallenge");
408 0           return;
409             }
410             # else, stuff worked fall through
411             }
412             else
413             {
414 0 0 0       if($count < 5 && $response =~ /HTTP server error: Method Not Allowed/i)
415             {
416 0           $self->{count} = $count+1;
417 0           print STDERR "retry ($count)\n";
418 0           sleep 10;
419 0           my $response = $self->send_request($procname, @_);
420 0           $self->{count} = $count;
421 0           return $response;
422             }
423 0           return $self->_set_error($response);
424             }
425              
426             # this is where we fall through down to from above
427 0           my $auth_challenge = $response->value->{challenge};
428 0           my $auth_response = md5_hex($auth_challenge, md5_hex($self->{password}));
429 0           @challenge = (
430             auth_method => $challenge,
431             auth_challenge => new RPC::XML::string($auth_challenge),
432             auth_response => new RPC::XML::string($auth_response),
433             );
434             }
435              
436             my $request = new RPC::XML::request(
437             "LJ.XMLRPC.$procname",
438             new RPC::XML::struct(
439 0           @{ $self->{auth} },
  0            
440             @challenge,
441             @_,
442             ),
443             );
444              
445             #use Test::More;
446             #use XML::LibXML;
447             #use XML::LibXML::PrettyPrint;
448             #my $xml = XML::LibXML->new->parse_string($request->as_string);
449             #my $pp = XML::LibXML::PrettyPrint->new(indent_string => ' ')->pretty_print($xml);
450             #note 'send request:';
451             #note $xml->toString;
452              
453 0           my $response = $self->{client}->send_request($request);
454            
455             #my $xml = XML::LibXML->new->parse_string($response->as_string);
456             #my $pp = XML::LibXML::PrettyPrint->new(indent_string => ' ')->pretty_print($xml);
457             #note 'recv response:';
458             #note $xml->toString;
459            
460 0 0         if(ref $response)
461             {
462 0 0         if($response->is_fault)
463             {
464 0           my $string = $response->value->{faultString};
465 0           my $code = $response->value->{faultCode};
466 0           $self->_set_error("$string ($code) on LJ.XMLRPC.$procname");
467 0           $error_request = $request;
468 0           return;
469             }
470 0           return $response;
471             }
472             else
473             {
474 0 0 0       if($count < 5 && $response =~ /HTTP server error: Method Not Allowed/i)
475             {
476 0           $self->{count} = $count+1;
477 0           print STDERR "retry ($count)\n";
478 0           sleep 10;
479 0           my $response = $self->send_request($procname, @_);
480 0           $self->{count} = $count;
481 0           return $response;
482             }
483 0           return $self->_set_error($response);
484             }
485             }
486              
487             sub _post
488             {
489 0     0     my $self = shift;
490 0           my $ua = $self->{client}->useragent;
491 0           my %arg = @_;
492             #use Test::More;
493             #note "====\nOUT:\n";
494             #foreach my $key (keys %arg)
495             #{
496             # note "$key=$arg{$key}\n";
497             #}
498 0           my $http_response = $ua->post($self->{flat_url}, \@_);
499 0 0         return $self->_set_error("HTTP Error: " . $http_response->status_line) unless $http_response->is_success;
500            
501 0           my $response_text = $http_response->content;
502 0           my @list = split /\n/, $response_text;
503 0           my %h;
504             #note "====\nIN:\n";
505 0           while(@list > 0)
506             {
507 0           my $key = shift @list;
508 0           my $value = shift @list;
509             #note "$key=$value\n";
510 0           $h{$key} = $value;
511             }
512            
513 0 0         return $self->_set_error("LJ Protocol error, server didn't return a success value") unless defined $h{success};
514 0 0         return $self->_set_error("LJ Protocol error: $h{errmsg}") if $h{success} ne 'OK';
515            
516 0           return \%h;
517             }
518              
519             sub as_string
520             {
521 0     0 0   my $self = shift;
522 0           my $username = $self->username;
523 0           my $server = $self->server;
524 0           "[ljclient $username\@$server]";
525             }
526              
527             sub findallitemid
528             {
529 0     0 0   my $self = shift;
530 0           my %arg = @_;
531 0           my $response = $self->send_request('syncitems');
532 0 0         die $error unless defined $response;
533 0           my $count = $response->value->{count};
534 0           my $total = $response->value->{total};
535 0           my $time;
536             my @list;
537 0           while(1)
538             {
539             #print "$count/$total\n";
540 0           foreach my $item (@{ $response->value->{syncitems} })
  0            
541             {
542 0           $time = $item->{time};
543 0           my $id = $item->{item};
544 0           my $action = $item->{action};
545 0 0         if($id =~ /^L-(\d+)$/)
546             {
547 0           push @list, $1;
548             }
549             }
550            
551 0 0         last if $count == $total;
552              
553 0           $response = $self->send_request('syncitems', lastsync => $time);
554 0 0         die $error unless defined $response;
555 0           $count = $response->value->{count};
556 0           $total = $response->value->{total};
557             }
558              
559 0           return @list;
560             }
561              
562              
563             sub send_flat_request
564             {
565 0     0 1   my $self = shift;
566 0           $self->_clear_error;
567 0   0       my $count = $self->{count} || 1;
568 0           my $procname = shift;
569 0           my $ua = $self->{client}->useragent;
570              
571 0           my @challenge;
572 0 0         if($self->{mode} eq 'challenge')
573             {
574 0           my $h = _post($self, mode => 'getchallenge');
575 0 0         return unless defined $h;
576 0           my %h = %{ $h };
  0            
577              
578 0           my $auth_challenge = $h{challenge};
579              
580 0           my $auth_response = md5_hex($auth_challenge, md5_hex($self->{password}));
581 0           @challenge = (
582             auth_method => 'challenge',
583             auth_challenge => $auth_challenge,
584             auth_response => $auth_response,
585             );
586             }
587            
588             return _post($self,
589             mode => $procname,
590 0           @{ $self->{flat_auth} },
  0            
591             @challenge,
592             @_
593             );
594             }
595              
596             sub _set_error
597             {
598 0     0     my($self, $value) = @_;
599 0           $error = $value;
600 0           return;
601             }
602              
603             sub _clear_error
604             {
605 0     0     undef $error;
606             }
607              
608              
609 0     0 1   sub error { $error }
610              
611             1;
612              
613             __END__
614              
615             =pod
616              
617             =encoding UTF-8
618              
619             =head1 NAME
620              
621             WebService::LiveJournal::Client - (Deprecated) Interface to the LiveJournal API
622              
623             =head1 VERSION
624              
625             version 0.09
626              
627             =head1 SYNOPSIS
628              
629             new interface
630              
631             use WebService::LiveJournal;
632             my $client = WebService::LiveJournal->new( username => 'foo', password => 'bar' );
633              
634             same thing with the old interface
635              
636             use WebService::LiveJournal::Client;
637             my $client = WebService::LiveJournal::Client->new( username => 'foo', password => 'bar' );
638             die "connection error: $WebService::LiveJournal::Client::error" unless defined $client;
639              
640             See L<WebService::LiveJournal::Event> for creating/updating LiveJournal events.
641              
642             See L<WebService::LiveJournal::Friend> for making queries about friends.
643              
644             See L<WebService::LiveJournal::FriendGroup> for getting your friend groups.
645              
646             =head1 DESCRIPTION
647              
648             B<NOTE>: This distribution is deprecated. It uses the outmoded XML-RPC protocol.
649             LiveJournal has also been compromised. I recommend using DreamWidth instead
650             (L<https://www.dreamwidth.org/>) which is in keeping with the original philosophy
651             LiveJournal regarding advertising.
652              
653             This is a client class for communicating with LiveJournal using its API. It is different
654             from the other LJ modules on CPAN in that it originally used the XML-RPC API. It now
655             uses a hybrid of the flat and XML-RPC API to avoid bugs in some LiveJournal deployments.
656              
657             There are two interfaces:
658              
659             =over 4
660              
661             =item L<WebService::LiveJournal>
662              
663             The new interface, where methods throw an exception on error.
664              
665             =item L<WebService::LiveJournal::Client>
666              
667             The legacy interface, where methods return undef on error and
668             set $WebService::LiveJournal::Client::error
669              
670             =back
671              
672             It is recommended that for any new code that you use the new interface.
673              
674             =head1 CONSTRUCTOR
675              
676             =head2 new
677              
678             my $client = WebService::LiveJournal::Client->new( %options )
679              
680             Connects to a LiveJournal server using the host and user information
681             provided by C<%options>.
682              
683             Signals an error depending on the interface
684             selected by throwing an exception or returning undef.
685              
686             =head3 options
687              
688             =over 4
689              
690             =item server
691              
692             The server hostname, defaults to www.livejournal.com
693              
694             =item port
695              
696             The server port, defaults to 80
697              
698             =item username [required]
699              
700             The username to login as
701              
702             =item password [required]
703              
704             The password to login with
705              
706             =item mode
707              
708             One of either C<cookie> or C<challenge>, defaults to C<cookie>.
709              
710             =back
711              
712             =head1 ATTRIBUTES
713              
714             These attributes are read-only.
715              
716             =head2 server
717              
718             The name of the LiveJournal server
719              
720             =head2 port
721              
722             The port used to connect to LiveJournal with
723              
724             =head2 username
725              
726             The username used to connect to LiveJournal
727              
728             =head2 userid
729              
730             The LiveJournal userid of the user used to connect to LiveJournal.
731             This is an integer.
732              
733             =head2 fullname
734              
735             The fullname of the user used to connect to LiveJournal as LiveJournal understands it
736              
737             =head2 usejournals
738              
739             List of shared/news/community journals that the user has permission to post in.
740              
741             =head2 message
742              
743             Message that should be displayed to the end user, if present.
744              
745             =head2 useragent
746              
747             Instance of L<LWP::UserAgent> used to connect to LiveJournal
748              
749             =head2 cookie_jar
750              
751             Instance of L<HTTP::Cookies> used to connect to LiveJournal with
752              
753             =head2 fastserver
754              
755             True if you have a paid account and are entitled to use the
756             fast server mode.
757              
758             =head1 METHODS
759              
760             =head2 create_event
761              
762             $client->create_event( %options )
763              
764             Creates a new event and returns it in the form of an instance of
765             L<WebService::LiveJournal::Event>. This does not create the
766             event on the LiveJournal server itself, until you use the
767             C<update> methods on the event.
768              
769             C<%options> contains a hash of attribute key, value pairs for
770             the new L<WebService::LiveJournal::Event>. The only required
771             attributes are C<subject> and C<event>, though you may set these
772             values after the event is created as long as you set them
773             before you try to C<update> the event. Thus this:
774              
775             my $event = $client->create(
776             subject => 'a new title',
777             event => 'some content',
778             );
779             $event->update;
780              
781             is equivalent to this:
782              
783             my $event = $client->create;
784             $event->subject('a new title');
785             $event->event('some content');
786             $event->update;
787              
788             This method signals an error depending on the interface
789             selected by throwing an exception or returning undef.
790              
791             =head2 get_events
792              
793             $client->get_events( $select_type, %query )
794              
795             Selects events from the LiveJournal server. The actual C<%query>
796             parameter requirements depend on the C<$select_type>.
797              
798             Returns an instance of L<WebService::LiveJournal::EventList>.
799              
800             Select types:
801              
802             =over 4
803              
804             =item syncitems
805              
806             This query mode can be used to sync all entries with multiple calls.
807              
808             =over 4
809              
810             =item lastsync
811              
812             The date of the last sync in the format of C<yyyy-mm-dd hh:mm:ss>
813              
814             =back
815              
816             =item day
817              
818             This query can be used to fetch all the entries for a particular day.
819              
820             =over 4
821              
822             =item year
823              
824             4 digit integer
825              
826             =item month
827              
828             1 or 2 digit integer, 1-31
829              
830             =item day
831              
832             integer 1-12
833              
834             =back
835              
836             =item lastn
837              
838             Fetch the last n events from the LiveJournal server.
839              
840             =over 4
841              
842             =item howmany
843              
844             integer, default = 20, max = 50
845              
846             =item beforedate
847              
848             date of the format C<yyyy-mm-dd hh:mm:ss>
849              
850             =back
851              
852             =back
853              
854             This method signals an error depending on the interface
855             selected by throwing an exception or returning undef.
856              
857             =head2 get_event
858              
859             $client->get_event( $itemid )
860              
861             Given an C<itemid> (the internal LiveJournal identifier for an event).
862              
863             This method signals an error depending on the interface
864             selected by throwing an exception or returning undef.
865              
866             =head2 sync_items
867              
868             $client->sync_items( $cb )
869             $client->sync_items( last_sync => $time, $cb )
870              
871             Fetch all of the items which have been created/modified since the last sync.
872             If C<last_sync =E<gt> $time> is not provided then it will fetch all events.
873             For each item that has been changed it will call the code reference C<$cb>
874             with three arguments:
875              
876             $cb->($action, $type, $id)
877              
878             =over 4
879              
880             =item action
881              
882             One of C<create> or C<update>
883              
884             =item type
885              
886             For "events" (journal entries) this is C<L>
887              
888             =item id
889              
890             The internal LiveJournal server id for the item. An integer.
891             For events, the actual event can be fetched using the C<get_event>
892             method.
893              
894             =back
895              
896             If the callback throws an exception, then no more entries will be processed.
897             If the callback does not throw an exception, then the next item will be
898             processed.
899              
900             This method returns the time of the last entry successfully processed, which
901             can be passed into C<sync_item> the next time to only get the items that have
902             changed since the first time.
903              
904             Here is a broad example:
905              
906             # first time:
907             my $time = $client->sync_items(sub {
908             my($action, $type, $id) = @_;
909             if($type eq 'L')
910             {
911             my $event = $client->get_item($id);
912             # ...
913             if(error condition)
914             {
915             die 'error happened';
916             }
917             }
918             });
919            
920             # if an error happened during the sync
921             my $error = $client->error;
922            
923             # next time:
924             $time = $client->sync_items(last_sync => $time, sub {
925             ...
926             });
927              
928             Because the C<syncitems> rpc that this method depends on
929             can make several requests before it completes it can fail
930             half way through. If this happens, you can restart where
931             the last successful item was processed by passing the
932             return value back into C<sync_items> again. You can tell
933             that C<sync_item> completed without error because the
934             C<$client-E<gt>error> accessor should return a false value.
935              
936             =head2 get_friends
937              
938             $client->get_friends( %options )
939              
940             Returns friend information associated with the account with which you are logged in.
941              
942             =over 4
943              
944             =item complete
945              
946             If true returns your friends, stalkers (users who have you as a friend) and friend groups
947              
948             # $friends is a WS::LJ::FriendList containing your friends
949             # $friend_of is a WS::LJ::FriendList containing your stalkers
950             # $groups is a WS::LJ::FriendGroupList containing your friend groups
951             my($friends, $friend_of, $groups) = $client-E<gt>get_friends( complete => 1 );
952              
953             If false (the default) only your friends will be returned
954              
955             # $friends is a WS::LJ::FriendList containing your friends
956             my $friends = $client-E<gt>get_friends;
957              
958             =item friendlimit
959              
960             If set to a numeric value greater than zero, this mode will only return the number of results indicated.
961              
962             =back
963              
964             =head2 get_friends_of
965              
966             $client->get_friend_of( %options )
967              
968             Returns the list of users that are a friend of the logged in account.
969              
970             Returns an instance of L<WebService::LiveJournal::FriendList>, a list of
971             L<WebService::LiveJournal::Friend>.
972              
973             Options:
974              
975             =over 4
976              
977             =item friendoflimit
978              
979             If set to a numeric value greater than zero, this mode will only return the number of results indicated
980              
981             =back
982              
983             =head2 get_friend_groups
984              
985             $client->get_friend_groups
986              
987             Returns your friend groups. This comes as an instance of
988             L<WebService::LiveJournal::FriendGroupList> that contains
989             zero or more instances of L<WebService::LiveJournal::FriendGroup>.
990              
991             =head2 get_user_tags
992              
993             $client->get_user_tags;
994             $client->get_user_tags( $journal_name );
995              
996             Fetch the tags associated with the given journal, or the users journal
997             if not specified. This method returns a list of zero or more
998             L<WebService::LiveJournal::Tag> objects.
999              
1000             =head2 console_command
1001              
1002             $client->console_command( $command, @arguments )
1003              
1004             Execute the given console command with the given arguments on the
1005             LiveJournal server. Returns the output as a list reference.
1006             Each element in the list represents a line out output and consists
1007             of a list reference containing the type of output and the text
1008             of the output. For example:
1009              
1010             my $ret = $client->console_command( 'print', 'hello world' );
1011              
1012             returns:
1013              
1014             [
1015             [ 'info', "Welcome to 'print'!" ],
1016             [ 'success', "hello world" ],
1017             ]
1018              
1019             =head2 batch_console_commands
1020              
1021             $client->batch_console_commands( $command1, $callback);
1022             $client->batch_console_commands( $command1, $callback, [ $command2, $callback, [ ... ] );
1023              
1024             Execute a list of commands on the LiveJournal server in one request. Each command is a list reference. Each callback
1025             associated with each command will be called with the results of that command (in the same format returned by
1026             C<console_command> mentioned above, except it is passed in as a list instead of a list reference). Example:
1027              
1028             $client->batch_console_commands(
1029             [ 'print', 'something to print' ],
1030             sub {
1031             my @output = @_;
1032             ...
1033             },
1034             [ 'print', 'something else to print' ],
1035             sub {
1036             my @output = @_;
1037             ...
1038             },
1039             );
1040              
1041             =head2 set_cookie
1042              
1043             $client->set_cookie( $key => $value )
1044              
1045             This method allows you to set a cookie for the appropriate security and expiration information.
1046             You shouldn't need to call it directly, but is available here if necessary.
1047              
1048             =head2 send_request
1049              
1050             $client->send_request( $procname, @arguments )
1051              
1052             Make a low level request to LiveJournal with the given
1053             C<$procname> (the rpc procedure name) and C<@arguments>
1054             (should be L<RPC::XML> types).
1055              
1056             On success returns the appropriate L<RPC::XML> type
1057             (usually RPC::XML::struct).
1058              
1059             This method signals an error depending on the interface
1060             selected by throwing an exception or returning undef.
1061              
1062             =head2 send_flat_request
1063              
1064             $client->send_flat_request( $procname, @arguments )
1065              
1066             Sends a low level request to the LiveJournal server using the flat API,
1067             with the given C<$procname> (the rpc procedure name) and C<@arguments>.
1068              
1069             On success returns the appropriate response.
1070              
1071             This method signals an error depending on the interface
1072             selected by throwing an exception or returning undef.
1073              
1074             =head2 error
1075              
1076             $client->error
1077              
1078             Returns the last error. This just returns
1079             $WebService::LiveJournal::Client::error, so it
1080             is still a global, but is a slightly safer shortcut.
1081              
1082             my $event = $client->get_event($itemid) || die $client->error;
1083              
1084             It is still better to use the newer interface which throws
1085             an exception for any error.
1086              
1087             =head1 EXAMPLES
1088              
1089             These examples are included with the distribution in its 'example' directory.
1090              
1091             Here is a simple example of how you would login/authenticate with a
1092             LiveJournal server:
1093              
1094             use strict;
1095             use warnings;
1096             use WebService::LiveJournal;
1097            
1098             print "user: ";
1099             my $user = <STDIN>;
1100             chomp $user;
1101             print "pass: ";
1102             my $password = <STDIN>;
1103             chomp $password;
1104            
1105             my $client = WebService::LiveJournal->new(
1106             server => 'www.livejournal.com',
1107             username => $user,
1108             password => $password,
1109             );
1110            
1111             print "$client\n";
1112            
1113             if($client->fastserver)
1114             {
1115             print "fast server\n";
1116             }
1117             else
1118             {
1119             print "slow server\n";
1120             }
1121              
1122             Here is a simple example showing how you can post an entry to your
1123             LiveJournal:
1124              
1125             use strict;
1126             use warnings;
1127             use WebService::LiveJournal;
1128            
1129             print "user: ";
1130             my $user = <STDIN>;
1131             chomp $user;
1132             print "pass: ";
1133             my $password = <STDIN>;
1134             chomp $password;
1135            
1136             my $client = WebService::LiveJournal->new(
1137             server => 'www.livejournal.com',
1138             username => $user,
1139             password => $password,
1140             );
1141            
1142             print "subject: ";
1143             my $subject = <STDIN>;
1144             chomp $subject;
1145            
1146             print "content: (^D or EOF when done)\n";
1147             my @lines = <STDIN>;
1148             chomp @lines;
1149            
1150             my $event = $client->create(
1151             subject => $subject,
1152             event => join("\n", @lines),
1153             );
1154            
1155             $event->update;
1156            
1157             print "posted $event with $client\n";
1158             print "itemid = ", $event->itemid, "\n";
1159             print "url = ", $event->url, "\n";
1160             print "anum = ", $event->anum, "\n";
1161              
1162             Here is an example of a script that will remove all entries from a
1163             LiveJournal. Be very cautious before using this script, once the
1164             entries are removed they cannot be brought back from the dead:
1165              
1166             use strict;
1167             use warnings;
1168             use WebService::LiveJournal;
1169            
1170             print "WARNING WARNING WARNING\n";
1171             print "this will remove all entries in your LiveJournal account\n";
1172             print "this probably cannot be undone\n";
1173             print "WARNING WARNING WARNING\n";
1174            
1175             print "user: ";
1176             my $user = <STDIN>;
1177             chomp $user;
1178             print "pass: ";
1179             my $password = <STDIN>;
1180             chomp $password;
1181            
1182             my $client = WebService::LiveJournal->new(
1183             server => 'www.livejournal.com',
1184             username => $user,
1185             password => $password,
1186             );
1187            
1188             print "$client\n";
1189            
1190             my $count = 0;
1191             while(1)
1192             {
1193             my $event_list = $client->get_events('lastn', howmany => 50);
1194             last unless @{ $event_list } > 0;
1195             foreach my $event (@{ $event_list })
1196             {
1197             print "rm: ", $event->subject, "\n";
1198             $event->delete;
1199             $count++;
1200             }
1201             }
1202            
1203             print "$count entries deleted\n";
1204              
1205             Here is a really simple command line interface to the LiveJournal
1206             admin console. Obvious improvements like better parsing of the commands
1207             and not displaying the password are left as an exercise to the reader.
1208              
1209             use strict;
1210             use warnings;
1211             use WebService::LiveJournal;
1212            
1213             my $client = WebService::LiveJournal->new(
1214             server => 'www.livejournal.com',
1215             username => do {
1216             print "user: ";
1217             my $user = <STDIN>;
1218             chomp $user;
1219             $user;
1220             },
1221             password => do {
1222             print "pass: ";
1223             my $pass = <STDIN>;
1224             chomp $pass;
1225             $pass;
1226             },
1227             );
1228            
1229             while(1)
1230             {
1231             print "> ";
1232             my $command = <STDIN>;
1233             unless(defined $command)
1234             {
1235             print "\n";
1236             last;
1237             }
1238             chomp $command;
1239             $client->batch_console_commands(
1240             [ split /\s+/, $command ],
1241             sub {
1242             foreach my $line (@_)
1243             {
1244             my($type, $text) = @$line;
1245             printf "%8s : %s\n", $type, $text;
1246             }
1247             }
1248             );
1249             }
1250              
1251             =head1 HISTORY
1252              
1253             The code in this distribution was written many years ago to sync my website
1254             with my LiveJournal. It has some ugly warts and its interface was not well
1255             planned or thought out, it has many omissions and contains much that is apocryphal
1256             (or at least wildly inaccurate), but it (possibly) scores over the older
1257             LiveJournal modules on CPAN in that it has been used in production for
1258             many many years with very little maintenance required, and at the time of
1259             its original writing the documentation for those modules was sparse or misleading.
1260              
1261             =head1 SEE ALSO
1262              
1263             =over 4
1264              
1265             =item
1266              
1267             L<http://www.livejournal.com/doc/server/index.html>,
1268              
1269             =item
1270              
1271             L<Net::LiveJournal>,
1272              
1273             =item
1274              
1275             L<LJ::Simple>
1276              
1277             =back
1278              
1279             =head1 AUTHOR
1280              
1281             Graham Ollis <plicease@cpan.org>
1282              
1283             =head1 COPYRIGHT AND LICENSE
1284              
1285             This software is copyright (c) 2013 by Graham Ollis.
1286              
1287             This is free software; you can redistribute it and/or modify it under
1288             the same terms as the Perl 5 programming language system itself.
1289              
1290             =cut