File Coverage

blib/lib/App/Maisha/Shell.pm
Criterion Covered Total %
statement 103 465 22.1
branch 10 206 4.8
condition 1 60 1.6
subroutine 68 104 65.3
pod 84 84 100.0
total 266 919 28.9


line stmt bran cond sub pod time code
1             package App::Maisha::Shell;
2              
3 9     9   90398 use strict;
  9         22  
  9         353  
4 9     9   49 use warnings;
  9         18  
  9         506  
5              
6             our $VERSION = '0.19';
7              
8             #----------------------------------------------------------------------------
9              
10             =head1 NAME
11              
12             App::Maisha::Shell - A command line social micro-blog networking tool.
13              
14             =head1 SYNOPSIS
15              
16             use App::Maisha::Shell;
17             my $shell = App::Maisha::Shell->new;
18              
19             =head1 DESCRIPTION
20              
21             This distribution provides the ability to micro-blog via social networking
22             websites and services, such as Identica and Twitter.
23              
24             =cut
25              
26             #----------------------------------------------------------------------------
27             # Library Modules
28              
29 9     9   179 use base qw(Term::Shell);
  9         18  
  9         39873  
30              
31 9     9   318441 use File::Basename;
  9         24  
  9         1013  
32 9     9   55 use File::Path;
  9         17  
  9         710  
33 9     9   8997 use IO::File;
  9         104438  
  9         1809  
34 9     9   10303 use Module::Pluggable instantiate => 'new', search_path => ['App::Maisha::Plugin'];
  9         40117  
  9         80  
35 9     9   10226 use Text::Wrap;
  9         36819  
  9         86922  
36              
37             #----------------------------------------------------------------------------
38             # Variables
39              
40             $Text::Wrap::columns = 80;
41              
42             my %plugins; # contains all available plugins
43              
44             my %months = (
45             'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4,
46             'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8,
47             'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12,
48             );
49              
50             my %max = (
51             user_timeline => { limit => 3200, count => 200 }, # specific user timelines
52             search_tweets => { limit => 800, count => 100 }, # search lists
53             home_timeline => { limit => 800, count => 200 }, # generic timelines
54             user_lists => { limit => 5000, count => 5000 }, # user lists
55             );
56              
57             #----------------------------------------------------------------------------
58             # Accessors
59              
60 1     1 1 28144 sub networks { shift->_elem('networks', @_) }
61 4     4 1 1461 sub prompt_str { shift->_elem('prompt_str', @_) }
62 4     4 1 1635 sub tag_str { shift->_elem('tag_str', @_) }
63 4     4 1 1546 sub order { shift->_elem('order', @_) }
64 4     4 1 1581 sub limit { shift->_elem('limit', @_) }
65 3     3 1 11471 sub services { shift->_elem('services', @_) }
66 4     4 1 1376 sub pager { shift->_elem('pager', @_) }
67 4     4 1 1276 sub format { shift->_elem('format', @_) }
68 4     4 1 1412 sub chars { shift->_elem('chars', @_) }
69 4     4 1 6446 sub history { shift->_elem('historyfile', @_) }
70 4     4 1 20922 sub debug { shift->_elem('debug', @_) }
71 0     0 1 0 sub error { shift->_elem('error', @_) }
72              
73             #----------------------------------------------------------------------------
74             # Public API
75              
76             #
77             # Connect/Disconnect
78             #
79              
80             sub connect {
81 2     2 1 36 my ($self,$plug,$config) = @_;
82              
83 2 50       18 unless($plug) { warn "No plugin supplied\n"; return }
  0         0  
  0         0  
84              
85 2 100       16 $self->_load_plugins unless(%plugins);
86 2         21 my $plugin = $self->_get_plugin($plug);
87 2 50       79 if(!$plugin) {
88 0         0 warn "Unable to establish plugin '$plug'\n";
89 0         0 return;
90             }
91              
92 2         100 my $status = $plugin->login($config);
93 2 50       8 if(!$status) {
94 2         1126 warn "Login to '$plug' failed\n";
95 2         41 return;
96             }
97              
98 0         0 my $services = $self->services;
99 0         0 push @$services, $plugin;
100 0         0 $self->services($services);
101 0         0 $self->_reset_networks;
102             }
103              
104             *run_connect = \&connect;
105 2     2 1 1505 sub smry_connect { "connect to a service" }
106             sub help_connect {
107 2     2 1 2516 <<'END';
108              
109             Connects to a named service. Requires the name of the service, together with
110             the username and password to access the service.
111             END
112             }
113              
114              
115             sub run_disconnect {
116 0     0 1 0 my ($self,$plug) = @_;
117              
118 0 0       0 unless($plug) { warn "No plugin supplied\n"; return }
  0         0  
  0         0  
119              
120 0         0 my $services = $self->services;
121 0         0 my @new = grep {ref($_) !~ /^App::Maisha::Plugin::$plug$/} @$services;
  0         0  
122 0         0 $self->services(\@new);
123 0         0 $self->_reset_networks;
124             }
125 2     2 1 2894 sub smry_disconnect { "disconnect from a service" }
126             sub help_disconnect {
127 2     2 1 2215 <<'END';
128              
129             Disconnects from the named service.
130             END
131             }
132              
133              
134             #
135             # Use
136             #
137              
138             sub run_use {
139 0     0 1 0 my ($self,$plug) = @_;
140 0 0       0 if(my $p = $self->_get_plugin($plug)) {
141 0         0 my $services = $self->services;
142 0         0 my @new = grep {ref($_) !~ /^App::Maisha::Plugin::$plug$/} @$services;
  0         0  
143 0         0 unshift @new, $self->_get_plugin($plug);
144 0         0 $self->services(\@new);
145 0         0 $self->_reset_networks;
146             } else {
147 0         0 warn "Unknown plugin\n";
148             }
149             }
150 2     2 1 1310 sub smry_use { "set primary service" }
151             sub help_use {
152 2     2 1 763 <<'END';
153              
154             Set the primary service for message list commands.
155             END
156             }
157              
158             sub comp_use {
159 0     0 1 0 my ($self, $word, $line, $start_index) = @_;
160 0         0 my $services = $self->services;
161 0         0 my @networks = map {
162 0         0 ref($_) =~ /^App::Maisha::Plugin::(.+)/; $1;
  0         0  
163             } @$services;
164 0         0 return grep { /^$word/ } @networks;
  0         0  
165             }
166              
167             #
168             # Followers
169             #
170              
171             sub run_followers {
172 0     0 1 0 my $self = shift;
173 0         0 $self->_run_snapshot('followers',$max{user_lists},@_);
174             }
175 2     2 1 47190 sub smry_followers { "display followers' status" }
176             sub help_followers {
177 2     2 1 1640 <<'END';
178              
179             Displays the most recent status messages from each of your followers.
180             END
181             }
182              
183              
184             #
185             # Friends
186             #
187              
188             sub run_friends {
189 0     0 1 0 my $self = shift;
190 0         0 $self->_run_snapshot('friends',$max{user_lists},@_);
191             }
192 2     2 1 1998 sub smry_friends { "display friends' status" }
193             sub help_friends {
194 2     2 1 1157 <<'END';
195              
196             Displays the most recent status messages from each of your friends.
197             END
198             }
199              
200              
201             #
202             # Show User
203             #
204              
205             sub run_user {
206 0     0 1 0 my ($self,$user) = @_;
207              
208 0 0       0 $user =~ s/^\@// if($user);
209 0 0       0 unless($user) {
210 0         0 print "no user specified\n\n";
211 0         0 return;
212             }
213              
214 0         0 my $ref = { screen_name => $user };
215 0         0 my $ret = $self->_command('user', $ref);
216              
217 0         0 print "\n";
218 0 0       0 print "user: $ret->{screen_name}\n" if($ret->{screen_name});
219 0 0       0 print "name: $ret->{name}\n" if($ret->{name});
220 0 0       0 print "location: $ret->{location}\n" if($ret->{location});
221 0 0       0 print "description: $ret->{description}\n" if($ret->{description});
222 0 0       0 print "url: $ret->{url}\n" if($ret->{url});
223 0 0       0 print "friends: $ret->{friends_count}\n" if($ret->{friends_count});
224 0 0       0 print "followers: $ret->{followers_count}\n" if($ret->{followers_count});
225 0 0       0 print "statuses: $ret->{statuses_count}\n" if($ret->{statuses_count});
226 0 0       0 print "status: $ret->{status}{text}\n" if($ret->{status}{text});
227              
228             #use Data::Dumper;
229             #print Dumper($ret);
230             }
231 2     2 1 821 sub smry_user { "display a user profile" }
232             sub help_user {
233 2     2 1 832 <<'END';
234              
235             Displays a user profile.
236             END
237             }
238              
239             sub comp_user {
240 0     0 1 0 my ($self, $word, $line, $start_index) = @_;
241 0         0 my $services = $self->services;
242 0   0     0 my $service = $services->[0] || return;
243 0 0 0     0 return unless($service && $service->can('users'));
244              
245 0         0 my $users = $service->users;
246 0         0 return grep { /^$word/ } keys %$users;
  0         0  
247             }
248              
249              
250             #
251             # Follow/Unfollow
252             #
253              
254             sub run_follow {
255 0     0 1 0 my ($self,$user) = @_;
256              
257 0 0       0 $user =~ s/^\@// if($user);
258 0 0       0 unless($user) {
259 0         0 print "no user specified\n\n";
260 0         0 return;
261             }
262              
263 0         0 my $ref = { screen_name => $user };
264 0         0 my $ret = $self->_command('follow', $ref);
265             }
266 2     2 1 923 sub smry_follow { "follow a named user" }
267             sub help_follow {
268 2     2 1 884 <<'END';
269              
270             Sends a follow request to the name user. If status updates are not protected
271             you can start seeing that user's updates immediately. Otherwise you will have
272             to wait until the user accepts your request.
273             END
274             }
275              
276              
277             sub run_unfollow {
278 0     0 1 0 my ($self,$user) = @_;
279              
280 0 0       0 $user =~ s/^\@// if($user);
281 0 0       0 unless($user) {
282 0         0 print "no user specified\n\n";
283 0         0 return;
284             }
285              
286 0         0 my $ref = { screen_name => $user };
287 0         0 my $ret = $self->_command('unfollow', $ref);
288             }
289 2     2 1 4659 sub smry_unfollow { "unfollow a named user" }
290             sub help_unfollow {
291 2     2 1 1069 <<'END';
292              
293             Allows you to unfollow a user.
294             END
295             }
296              
297              
298             #
299             # Friends Timeline
300             #
301              
302             sub run_friends_timeline {
303 0     0 1 0 my $self = shift;
304 0         0 $self->_run_timeline('friends_timeline',$max{home_timeline},undef,@_);
305             }
306              
307 2     2 1 999 sub smry_friends_timeline { "display friends' status as a timeline" }
308             sub help_friends_timeline {
309 4     4 1 3249 <<'END';
310              
311             Displays the most recent status messages within your friends timeline.
312             END
313             }
314              
315             *run_ft = \&run_friends_timeline;
316 2     2 1 1143 sub smry_ft { "alias to friends_timeline" }
317             *help_ft = \&help_friends_timeline;
318              
319              
320             #
321             # Public Timeline
322             #
323              
324             sub run_public_timeline {
325 0     0 1 0 my $self = shift;
326 0         0 $self->_run_timeline('public_timeline',$max{home_timeline},undef,@_);
327             }
328              
329 2     2 1 1029 sub smry_public_timeline { "display public status as a timeline" }
330             sub help_public_timeline {
331 4     4 1 2542 <<'END';
332              
333             Displays the most recent status messages within the public timeline.
334             END
335             }
336              
337             *run_pt = \&run_public_timeline;
338 2     2 1 1356 sub smry_pt { "alias to public_timeline" }
339             *help_pt = \&help_public_timeline;
340              
341              
342             #
343             # User Timeline
344             #
345              
346             sub run_user_timeline {
347 0     0 1 0 my $self = shift;
348 0         0 my $user = shift;
349              
350 0 0       0 $user =~ s/^\@// if($user);
351 0 0       0 unless($user) {
352 0         0 print "no user specified\n\n";
353 0         0 return;
354             }
355              
356 0         0 $self->_run_timeline('user_timeline',$max{user_timeline},$user,@_);
357             }
358              
359 2     2 1 907 sub smry_user_timeline { "display named user statuses as a timeline" }
360             sub help_user_timeline {
361 4     4 1 1897 <<'END';
362              
363             Displays the most recent status messages for a specified user.
364             END
365             }
366             *comp_user_timeline = \&comp_user;
367              
368             *run_ut = \&run_user_timeline;
369 2     2 1 806 sub smry_ut { "alias to user_timeline" }
370             *help_ut = \&help_user_timeline;
371             *comp_ut = \&comp_user;
372              
373              
374             #
375             # Replies
376             #
377              
378             sub run_replies {
379 0     0 1 0 my $self = shift;
380 0         0 $self->_run_timeline('replies',$max{home_timeline},undef,@_);
381             }
382              
383 2     2 1 892 sub smry_replies { "display reply messages that refer to you" }
384             sub help_replies {
385 4     4 1 3002 <<'END';
386              
387             Displays the most recent reply messages that refer to you.
388             END
389             }
390              
391             *run_re = \&run_replies;
392 2     2 1 875 sub smry_re { "alias to replies" }
393             *help_re = \&help_replies;
394              
395              
396             #
397             # Direct Messages
398             #
399              
400             sub run_direct_messages {
401 0     0 1 0 my $self = shift;
402 0 0 0     0 my $frto = @_ && $_[0] =~ /^from|to$/ ? shift : 'to';
403 0         0 my $num = shift;
404              
405 0         0 my ($limit,$pages,$count) = $self->_get_limits($max{home_timeline},$num,$self->limit);
406              
407 0         0 my (@pages,@results,$max_id);
408 0         0 for my $page (1 .. $pages) {
409 0         0 my $ref = {};
410 0 0       0 $ref->{max_id} = $max_id-1 if($max_id);
411 0 0       0 $ref->{count} = $count if($count);
412              
413 0         0 my $ret;
414 0         0 eval { $ret = $self->_command('direct_messages_' . $frto,$ref) };
  0         0  
415              
416 0 0 0     0 if(($@ || !$ret) && $self->error =~ /This application is not allowed to access or delete your direct messages/) {
      0        
417 0         0 print "WARNING: Your OAuth keys need updating.\n";
418 0         0 eval { $ret = $self->_command('reauthorize') };
  0         0  
419 0 0       0 return unless($ret);
420              
421             # okay retry
422 0         0 eval { $ret = $self->_command('direct_messages_' . $frto,$ref) };
  0         0  
423 0 0       0 return if($@);
424             }
425              
426 0 0       0 last unless($ret);
427 0 0 0     0 last if($max_id && $max_id == $ret->[-1]{id});
428 0         0 unshift @pages, $ret;
429 0         0 $max_id = $ret->[-1]{id};
430             }
431              
432 0 0       0 return unless(@pages);
433 0         0 for my $page (@pages) {
434 0         0 push @results, @$page;
435             }
436 0 0       0 $self->_print_messages($limit,($frto eq 'to' ? 'sender' : 'recipient'),\@results);
437             }
438              
439 2     2 1 2220 sub smry_direct_messages { "display direct messages that have been sent to you" }
440             sub help_direct_messages {
441 4     4 1 2867 <<'END';
442              
443             Displays the direct messages that have been sent to you.
444             END
445             }
446              
447             *run_dm = \&run_direct_messages;
448 2     2 1 4263 sub smry_dm { "alias to direct_messages" }
449             *help_dm = \&help_direct_messages;
450              
451              
452             #
453             # Send Message
454             #
455              
456             sub run_send_message {
457 0     0 1 0 my $self = shift;
458 0         0 my (undef,$user,$mess) = split(/\s+/,$self->line(),3);
459              
460 0 0       0 $user =~ s/^\@// if($user);
461 0 0       0 unless($user) {
462 0         0 print "no user specified\n\n";
463 0         0 return;
464             }
465              
466 0 0 0     0 unless(defined $mess && $mess =~ /\S/) {
467 0         0 print "cannot send an empty message\n\n";
468 0         0 return;
469             }
470              
471 0         0 $mess =~ s/^\s+//;
472 0         0 $mess =~ s/\s+$//;
473 0         0 my $len = length $mess;
474 0 0       0 if($len > 140) {
475 0         0 print "message too long: $len/140\n\n";
476 0         0 return;
477             }
478              
479 0         0 my $ref = { screen_name => $user, text => $mess };
480 0         0 $self->_command('send_message', $ref);
481             }
482              
483 2     2 1 937 sub smry_send_message { "send a direct message" }
484             sub help_send_message {
485 6     6 1 4091 <<'END';
486              
487             Posts a message (upto 140 characters), to a named user.
488             END
489             }
490              
491             *comp_send_message = \&comp_user;
492             *comp_send = \&comp_user;
493             *comp_sm = \&comp_user;
494              
495             *run_send = \&run_send_message;
496 4     4 1 3040 sub smry_send { "alias to send_message" }
497             *help_send = \&help_send_message;
498              
499             *run_sm = \&run_send_message;
500             *smry_sm = \&smry_send;
501             *help_sm = \&help_send_message;
502              
503              
504             #
505             # Update/Say
506             #
507              
508             sub run_update {
509 0     0 1 0 my $self = shift;
510 0         0 my (undef,$text) = split(' ',$self->line(),2);
511 0         0 $text =~ s/^\s+//;
512 0         0 $text =~ s/\s+$//;
513              
514 0 0       0 unless($text) {
515 0         0 print "cannot send an empty message\n\n";
516 0         0 return;
517             }
518              
519 0         0 my $len = length $text;
520 0 0       0 if($len < 140) {
    0          
521 0         0 my $tag = $self->tag_str;
522 0 0       0 $text .= " " if ($text =~ /\S/);
523 0 0       0 $text .= $tag if(length "$text$tag" <= 140);
524             } elsif($len > 140) {
525 0         0 print "message too long: $len/140\n\n";
526 0         0 return;
527             }
528              
529 0         0 $self->_commands('update', $text);
530             }
531 2     2 1 4826 sub smry_update { "post a message" }
532             sub help_update {
533 4     4 1 2907 <<'END';
534              
535             Posts a message (upto 140 characters).
536             END
537             }
538              
539             *comp_update = \&comp_user;
540              
541             # help
542             *run_say = \&run_update;
543 2     2 1 1868 sub smry_say { "alias to 'update'" }
544             *help_say = \&help_update;
545             *comp_say = \&comp_user;
546              
547              
548             #
549             # Search
550             #
551              
552             sub run_search {
553 0     0 1 0 my $self = shift;
554 0         0 my (undef,$text) = split(' ',$self->line(),2);
555 0         0 $text =~ s/^\s+//;
556 0         0 $text =~ s/\s+$//;
557              
558 0         0 my $limit;
559 0 0       0 if($text =~ /^(\d+)\s+(.*)/) {
560 0         0 ($limit,$text) = ($1,$2);
561             }
562              
563 0 0       0 unless($text) {
564 0         0 print "cannot search for an empty string\n\n";
565 0         0 return;
566             }
567              
568 0         0 my $len = length $text;
569 0 0       0 if($len > 1000) {
570 0         0 print "search term is too long: $len/1000\n\n";
571 0         0 return;
572             }
573             #print "limit=$limit, text=$text\n";
574 0         0 $self->_run_results('search',$max{search_tweets},$limit,$text);
575             }
576 0     0 1 0 sub smry_search { "search for messages" }
577             sub help_search {
578 0     0 1 0 <<'END';
579              
580             Search for messages with a given search term. The given search term can
581             contain up to 1000 characters.
582             END
583             }
584              
585              
586              
587             #
588             # About
589             #
590              
591 2     2 1 826 sub smry_about { "brief summary of maisha" }
592             sub help_about {
593 2     2 1 767 <<'END';
594             Provides a brief summary about maisha.
595             END
596             }
597             sub run_about {
598 0     0 1 0 print <<ABOUT;
599              
600             Maisha is a command line application that can interface with a number of online
601             social networking sites. Referred to as micro-blogging, users can post status
602             updates to the likes of Twitter and Identi.ca. Maisha provides the abilty to
603             follow the status updates of friends and see who is following you, as well as
604             allowing you to send updates and send and receive direct messages too.
605              
606             Maisha means "life" in Swahili, and as the application is not tied to any
607             particular online service, it seemed an appropriate choice of name. After all
608             you are posting status updates about your life :)
609              
610             Maisha is written in Perl, and freely available as Open Source under the Perl
611             Artistic license. Copyright (c) 2009 Barbie for Grango.org, the Open Source
612             development outlet of Miss Barbell Productions. See http://maisha.grango.org.
613              
614             Version: $VERSION
615              
616             ABOUT
617             }
618              
619              
620             #
621             # Version
622             #
623              
624 2     2 1 839 sub smry_version { "display the current version of maisha" }
625             sub help_version {
626 2     2 1 891 <<'END';
627              
628             Displays the current version of maisha.
629             END
630             }
631             sub run_version {
632 0     0 1 0 print "\nVersion: $VERSION";
633             }
634              
635              
636             #
637             # Debugging
638             #
639              
640 2     2 1 915 sub smry_debug { "turn on/off debugging" }
641             sub help_debug {
642 2     2 1 891 <<'END';
643              
644             Some commands may return unexpected results. More verbose output can be
645             returned when debugging is turned on. Set 'debug on' or 'debug off' to
646             turn the debugging functionality on or off respectively.
647             END
648             }
649             sub run_debug {
650 0     0 1 0 my ($self,$state) = @_;
651              
652 0 0       0 if(!$state) {
    0          
    0          
653 0         0 print "Please use 'on' or 'off' with debug command\n\n";
654             } elsif($state eq 'on') {
655 0         0 $self->debug(1);
656 0         0 print "Debugging is ON\n\n";
657             } elsif($state eq 'off') {
658 0         0 $self->debug(0);
659 0         0 print "Debugging is OFF\n\n";
660             } else {
661 0         0 print "Please use 'on' or 'off' with debug command\n\n";
662             }
663             };
664              
665              
666             #
667             # Quit/Exit
668             #
669              
670 4     4 1 6391 sub smry_quit { "alias to exit" }
671             sub help_quit {
672 4     4 1 4363 <<'END';
673              
674             Exits the program.
675             END
676             }
677             sub run_quit {
678 0     0 1 0 my $self = shift;
679 0         0 $self->stoploop;
680             }
681              
682             *run_q = \&run_quit;
683             *smry_q = \&smry_quit;
684             *help_q = \&help_quit;
685              
686             sub postcmd {
687 0     0 1 0 my ($self, $handler, $cmd, $args) = @_;
688             #print "$$handler - $$cmd\n" if($self->debug);
689 0 0 0     0 return if($handler && $$handler =~ /^(comp|help|smry)_/);
690 0 0 0     0 return if($cmd && $$cmd =~ /^(q|quit)$/);
691              
692 0         0 push @{$self->{history}}, $self->line;
  0         0  
693 0         0 print $self->networks;
694             }
695              
696             sub preloop {
697 0     0 1 0 my $self = shift;
698 0         0 my $file = $self->history;
699 0 0 0     0 if($file && -f $file) {
700 0 0       0 my $fh = IO::File->new($file,'r') or return;
701 0         0 while(<$fh>) {
702 0         0 s/\s+$//;
703 0 0       0 next unless($_);
704 0         0 $self->term->addhistory($_);
705 0         0 push @{$self->{history}}, $_;
  0         0  
706             }
707             }
708             }
709              
710             sub postloop {
711 0     0 1 0 my $self = shift;
712 0 0       0 if(my $file = $self->history) {
713 0 0       0 my @history = grep { $_ && $_ !~ /^(q|quit)$/ } @{$self->{history}};
  0         0  
  0         0  
714 0 0       0 if(@history) {
715 0         0 mkpath(dirname($file));
716 0 0       0 my $fh = IO::File->new($file,'w+') or return;
717 0 0       0 splice( @history, 0, (scalar(@history) - 100)) if(@history > 100);
718 0         0 print $fh join("\n", @history);
719 0         0 $fh->close;
720             }
721             }
722             }
723              
724             #----------------------------------------------------------------------------
725             # Private Methods
726              
727             sub _reset_networks {
728 0     0   0 my $self = shift;
729 0         0 my $first = 1;
730 0         0 my $str = "\nNetworks: ";
731 0         0 my $services = $self->services;
732 0         0 for my $item (@$services) {
733 0         0 my $ref = ref($item);
734 0         0 $ref =~ s/^App::Maisha::Plugin:://;
735 0 0       0 $str .= $first ? "[$ref]" : " $ref";
736 0         0 $first = 0;
737             }
738 0         0 $str .= "\n";
739 0         0 $self->networks($str);
740             }
741              
742             sub _elem {
743 40     40   131 my $self = shift;
744 40         72 my $name = shift;
745 40         73 my $value = $self->{$name};
746 40 100       106 if (@_) {
747 20         68 $self->{$name} = shift;
748             }
749 40         179 return $value;
750             }
751              
752             sub _run_snapshot {
753 0     0   0 my ($self,$cmd,$max,$num) = @_;
754 0         0 my (@pages,@results,$max_id);
755 0         0 my ($limit,$pages,$count) = $self->_get_limits($max,$num);
756              
757 0 0       0 if($pages) {
758 0         0 for my $page (1 .. $pages) {
759 0         0 my $ref = {};
760 0 0       0 $ref->{max_id} = $max_id-1 if($max_id);
761 0 0       0 $ref->{count} = $count if($count);
762              
763 0         0 my $ret;
764 0         0 eval { $ret = $self->_command($cmd,$ref) };
  0         0  
765 0 0       0 last unless($ret);
766 0 0 0     0 last if($max_id && $max_id == $ret->[-1]{id});
767 0         0 unshift @pages, @$ret;
768 0         0 $max_id = $ret->[-1]{id};
769             }
770             } else {
771 0         0 my $ret;
772 0         0 eval { $ret = $self->_command($cmd) };
  0         0  
773 0 0       0 push @pages, @$ret if($ret);
774             }
775              
776 0 0       0 return unless(@pages);
777 0         0 for my $page (@pages) {
778 0         0 push @results, @$page;
779             }
780 0         0 $self->_print_messages($limit,undef,\@results);
781             }
782              
783             sub _run_results {
784 0     0   0 my ($self,$cmd,$max,$num,$term) = @_;
785 0         0 my (@pages,@results,$max_id);
786 0         0 my ($limit,$pages,$count) = $self->_get_limits($max,$num);
787              
788 0 0       0 if($pages) {
789 0         0 for my $page (1 .. $pages) {
790 0         0 my $ref = {};
791 0 0       0 $ref->{max_id} = $max_id-1 if($max_id);
792 0 0       0 $ref->{count} = $count if($count);
793              
794 0         0 my $ret;
795 0         0 eval { $ret = $self->_command($cmd,$term,$ref) };
  0         0  
796 0 0       0 last unless($ret);
797 0 0 0     0 last if($max_id && $max_id == $ret->[-1]{id});
798 0         0 unshift @pages, [ @{$ret->{results}} ];
  0         0  
799 0         0 $max_id = $ret->{results}[-1]{id};
800             }
801             } else {
802 0         0 my $ret;
803 0         0 eval { $ret = $self->_command($cmd,$term) };
  0         0  
804 0 0       0 push @pages, [ @{$ret->{results}} ] if($ret);
  0         0  
805             }
806              
807 0 0       0 return unless(@pages);
808 0         0 for my $page (@pages) {
809 0         0 push @results, @$page;
810             }
811 0         0 $self->_print_messages($limit,undef,\@results);
812             }
813              
814             sub _run_timeline {
815 0     0   0 my ($self,$cmd,$max,$user,$num) = @_;
816 0         0 my ($limit,$pages,$count) = $self->_get_limits($max,$num,$self->limit);
817              
818 0         0 my (@pages,@results,$max_id);
819 0         0 for my $page (1 .. $pages) {
820 0         0 my $ref = {};
821 0 0       0 $ref->{screen_name} = $user if($user);
822 0 0       0 $ref->{max_id} = $max_id-1 if($max_id);
823 0 0       0 $ref->{count} = $count if($count);
824              
825 0         0 my $ret;
826 0         0 eval { $ret = $self->_command($cmd,$ref) };
  0         0  
827 0 0       0 last unless($ret);
828 0 0 0     0 last if($max_id && $max_id == $ret->[-1]{id});
829 0         0 unshift @pages, $ret;
830 0         0 $max_id = $ret->[-1]{id};
831             }
832              
833 0 0       0 return unless(@pages);
834 0         0 for my $page (@pages) {
835 0         0 push @results, @$page;
836             }
837 0         0 $self->_print_messages($limit,'user',\@results);
838             }
839              
840             sub _command {
841 0     0   0 my $self = shift;
842 0         0 my $cmd = shift;
843              
844 0         0 $self->error('');
845              
846 0         0 my $services = $self->services;
847 0 0 0     0 return unless(defined $services && @$services);
848              
849 0         0 my $service = $services->[0];
850 0 0       0 return unless(defined $service);
851              
852 0         0 my $method = "api_$cmd";
853 0         0 my $ret;
854 0         0 eval { $ret = $service->$method(@_) };
  0         0  
855              
856 0 0       0 if ($@) {
    0          
857 0 0       0 print "Command $cmd failed :(" . ($self->debug ? " [$@]" : '') . "\n";
858 0         0 $self->error($@);
859             } elsif(!$ret) {
860 0         0 print "Command $cmd failed :(\n";
861             } else {
862             #print "$cmd ok\n";
863             }
864              
865 0         0 return $ret;
866             }
867              
868             sub _commands {
869 0     0   0 my $self = shift;
870 0         0 my $cmd = shift;
871              
872 0         0 $self->error('');
873              
874 0         0 my $services = $self->services;
875 0 0 0     0 return unless(defined $services && @$services);
876              
877 0         0 for my $service (@$services) {
878 0 0       0 next unless(defined $service);
879              
880 0         0 my $class = ref($service);
881 0         0 $class =~ s/^App::Maisha::Plugin:://;
882              
883 0         0 my $method = "api_$cmd";
884 0         0 my $ret;
885 0         0 eval { $ret = $service->$method(@_) };
  0         0  
886              
887 0 0       0 if ($@) {
    0          
888 0 0       0 print "[$class] Command $cmd failed :(" . ($self->debug ? " [$@]" : '') . "\n";
889 0         0 $self->error("[$class] $@");
890             } elsif(!$ret) {
891 0         0 print "[$class] Command $cmd failed :(\n";
892             } else {
893 0         0 print "[$class] $cmd ok\n";
894             }
895             }
896              
897 0         0 return;
898             }
899              
900             sub _print_messages {
901 0     0   0 my ($self,$limit,$who,$ret) = @_;
902              
903 0         0 $Text::Wrap::columns = $self->chars;
904              
905 0 0       0 my @recs = $self->order =~ /^asc/i ? @$ret : reverse @$ret;
906 0 0 0     0 splice(@recs,$limit) if($limit && $limit < scalar(@recs));
907 0         0 @recs = reverse @recs;
908              
909 0         0 my $msgs = "\n" .
910             join("\n", map {
911 0         0 wrap('',' ',$self->_format_message($_,$who))
912             } @recs ) . "\n";
913 0 0       0 if ($self->pager) {
914 0         0 $self->page($msgs);
915             } else {
916 0         0 print $msgs;
917             }
918             }
919              
920             sub _format_message {
921 0     0   0 my ($self,$mess,$who) = @_;
922 0         0 my ($user,$text);
923              
924 0         0 my $network = $self->networks();
925 0         0 $network =~ s!^.*?\[([^\]]+)\].*!$1!s;
926              
927 0         0 my $timestamp = $mess->{created_at};
928 0         0 my ($M,$D,$T,$Y) = $timestamp =~ /\w+\s+(\w+)\s+(\d+)\s+([\d:]+)\s+\S+\s+(\d+)/; # Sat Oct 13 19:01:19 +0000 2012
929 0 0       0 ($D,$M,$Y,$T) = $timestamp =~ /\w+,\s+(\d+)\s+(\w+)\s+(\d+)\s+([\d:]+)/ unless($M); # Sat, 13 Oct 2012 19:01:19 +0000
930              
931 0         0 my $datetime = sprintf "%02d/%02d/%04d %s", $D, $months{$M}, $Y, $T;
932 0         0 my $date = sprintf "%02d/%02d/%04d", $D, $months{$M}, $Y;
933 0         0 my $time = $T;
934              
935 0 0       0 if($who) {
936 0         0 $user = $mess->{$who}{screen_name};
937 0         0 $text = $mess->{text};
938             } else {
939 0   0     0 $user = $mess->{screen_name} || $mess->{from_user};
940 0   0     0 $text = $mess->{status}{text} || $mess->{text};
941 0   0     0 $text ||= '';
942             }
943              
944 0         0 my $format = $self->format;
945 0         0 $format =~ s!\%U!$user!g;
946 0         0 $format =~ s!\%M!$text!g;
947 0         0 $format =~ s!\%T!$timestamp!g;
948 0         0 $format =~ s!\%D!$datetime!g;
949 0         0 $format =~ s!\%t!$time!g;
950 0         0 $format =~ s!\%d!$date!g;
951 0         0 $format =~ s!\%N!$network!g;
952 0         0 return $format;
953             }
954              
955             sub _get_limits {
956 0     0   0 my ($self,$max,$limit,$default) = @_;
957 0   0     0 $limit ||= $default;
958 0 0       0 return unless($limit);
959              
960 0 0       0 return unless($limit =~ /^\d+$/);
961 0 0       0 $limit = $max->{limit} if($limit > $max->{limit});
962 0         0 my $count = $max->{count};
963              
964 0 0       0 return($limit,1,$limit) if($limit <= $count);
965              
966 0 0       0 my $pages = int($limit / $count) + ($limit % $count ? 1 : 0);
967 0         0 return($limit,$pages,$count);
968             }
969              
970             sub _load_plugins {
971 1     1   2 my $self = shift;
972 1         432 for my $plugin ($self->plugins()) {
973 4         99 my $class = ref($plugin);
974 4         19 $class =~ s/^App::Maisha::Plugin:://;
975             #print STDERR "CLASS=$class, PLUGIN=$plugin\n";
976 4 100       26 $plugins{$class} = $plugin unless($class eq 'Base');
977             }
978             }
979              
980             sub _get_plugin {
981 2     2   4 my $self = shift;
982 2 50       8 my $class = shift or return;
983 2   50     40 return $plugins{$class} || undef;
984             }
985              
986             1;
987              
988             __END__
989              
990             =head1 METHODS
991              
992             =head2 Constructor
993              
994             =over 4
995              
996             =item * new
997              
998             =back
999              
1000             =head2 Configuration Methods
1001              
1002             =over 4
1003              
1004             =item * context
1005              
1006             Used internally to reference the current shell for command handlers.
1007              
1008             =item * limit
1009              
1010             Used by timeline commands to limit the number of messages displayed. The
1011             default setting will display the last 20 messages.
1012              
1013             =item * order
1014              
1015             Used by timeline commands to order the messages displayed. The default is to
1016             display messages in descending order, with the most recent first and the oldest
1017             last.
1018              
1019             To reverse this order, set the 'order' as 'ascending' (or 'asc') in your
1020             configuration file. (case insensitive).
1021              
1022             =item * networks
1023              
1024             Sets the networks list that will appear above the command line.
1025              
1026             =item * prompt_str
1027              
1028             Sets the prompt string that will appear on the command line.
1029              
1030             =item * tag_str
1031              
1032             Sets the text that will appear at the end of your message.
1033              
1034             In order to suppress the tag string set the 'tag' option to '.' in your
1035             configuration file.
1036              
1037             =item * services
1038              
1039             Provides the order of services available, the first is always the primary
1040             service.
1041              
1042             =item * pager
1043              
1044             Enables the use of a pager when viewing timelines. Defaults to true
1045             if not specified.
1046              
1047             =item * format
1048              
1049             When printing a list of status messages, the default format of printing the
1050             username followed by the status message is not always suitable for everyone. As
1051             such you can define your own formatting.
1052              
1053             The default format is "[%U] %M", with the available formatting patterns defined
1054             as:
1055              
1056             %U - username or screen name
1057             %M - status message
1058             %T - timestamp (e.g. Sat Oct 13 19:29:17 +0000 2012)
1059             %D - datetime (e.g. 13/10/2012 19:29:17)
1060             %d - date only (e.g. 13/10/2012)
1061             %t - time only (e.g. 19:29:17)
1062             %N - network
1063              
1064             =item * chars
1065              
1066             As Maisha is run from the command line, it is most likely being run within a
1067             terminal window. Unfortunately there isn't currently a detection method for
1068             knowing the exact screen width being used. As such you can specify a width for
1069             the wrapper to use to ensure the messages are correctly line wrapped. The
1070             default setting is 80.
1071              
1072             =item * history
1073              
1074             Provides the history file, if available.
1075              
1076             =item * debug
1077              
1078             Boolean setting for debugging messages.
1079              
1080             =item * error
1081              
1082             The last error message received from a failing command.
1083              
1084             =back
1085              
1086             =head2 Run Methods
1087              
1088             The run methods are handlers to run the specific command requested.
1089              
1090             =head2 Help Methods
1091              
1092             The help methods are handlers to provide additional information about the named
1093             command when the 'help' command is used, with the name of a command as an
1094             argument.
1095              
1096             =head2 Summary Methods
1097              
1098             When the 'help' command is requested, with no additonal arguments, a summary
1099             of the available commands is display, with the text from each specific command
1100             summary method handler.
1101              
1102             =head2 Completion Methods
1103              
1104             For some commands completion methods are available to help complete the command
1105             request. for example with the 'use' command, pressing <TAB> will attempt to
1106             complete the name of the Network plugin name for you.
1107              
1108             =head2 Connect Methods
1109              
1110             The connect methods provide the handlers to connect to a service. This is
1111             performed automatically on startup for all the services provided in your
1112             configuration file.
1113              
1114             =over 4
1115              
1116             =item * connect
1117              
1118             =item * run_connect
1119              
1120             =item * help_connect
1121              
1122             =item * smry_connect
1123              
1124             =back
1125              
1126             =head2 Disconnect Methods
1127              
1128             The disconnect methods provide the handlers to disconnect from a service.
1129              
1130             =over 4
1131              
1132             =item * run_disconnect
1133              
1134             =item * help_disconnect
1135              
1136             =item * smry_disconnect
1137              
1138             =back
1139              
1140             =head2 Use Methods
1141              
1142             The use methods provide the handlers change the primary service. The primary
1143             service is used by the main messaging commands. All available services are
1144             used when 'update' or 'say' are used.
1145              
1146             =over 4
1147              
1148             =item * run_use
1149              
1150             =item * help_use
1151              
1152             =item * smry_use
1153              
1154             =item * comp_use
1155              
1156             =back
1157              
1158             =head2 Followers Methods
1159              
1160             The followers methods provide the handlers for the 'followers' command.
1161              
1162             =over 4
1163              
1164             =item * run_followers
1165              
1166             =item * help_followers
1167              
1168             =item * smry_followers
1169              
1170             =back
1171              
1172             =head2 Follow Methods
1173              
1174             The follow methods provide the handlers for the 'follow' command.
1175              
1176             =over 4
1177              
1178             =item * run_follow
1179              
1180             =item * help_follow
1181              
1182             =item * smry_follow
1183              
1184             =back
1185              
1186             =head2 Unfollow Methods
1187              
1188             The unfollow methods provide the handlers for the 'unfollow' command.
1189              
1190             =over 4
1191              
1192             =item * run_unfollow
1193              
1194             =item * help_unfollow
1195              
1196             =item * smry_unfollow
1197              
1198             =back
1199              
1200             =head2 User Methods
1201              
1202             The user methods provide the handlers display the profile of a named user.
1203              
1204             =over 4
1205              
1206             =item * run_user
1207              
1208             =item * help_user
1209              
1210             =item * smry_user
1211              
1212             =item * comp_user
1213              
1214             =back
1215              
1216             =head2 User Timeline Methods
1217              
1218             The user timeline methods provide the handlers for the 'user_timeline'
1219             command. Note that the 'ut' is an alias to 'user_timeline'.
1220              
1221             The user_timeline command has one optional parameter:
1222              
1223             maisha> ut [limit]
1224              
1225             =over 4
1226              
1227             =item * run_user_timeline
1228              
1229             =item * help_user_timeline
1230              
1231             =item * smry_user_timeline
1232              
1233             =item * comp_user_timeline
1234              
1235             =item * run_ut
1236              
1237             =item * help_ut
1238              
1239             =item * smry_ut
1240              
1241             =item * comp_ut
1242              
1243             =back
1244              
1245             =head2 Friends Methods
1246              
1247             The friends methods provide the handlers for the 'friends' command.
1248              
1249             =over 4
1250              
1251             =item * run_friends
1252              
1253             =item * help_friends
1254              
1255             =item * smry_friends
1256              
1257             =back
1258              
1259             =head2 Friends Timeline Methods
1260              
1261             The friends timeline methods provide the handlers for the 'friends_timeline'
1262             command. Note that the 'ft' is an alias to 'friends_timeline'.
1263              
1264             The friends_timeline command has one optional parameter:
1265              
1266             maisha> ft [limit]
1267              
1268             =over 4
1269              
1270             =item * run_friends_timeline
1271              
1272             =item * help_friends_timeline
1273              
1274             =item * smry_friends_timeline
1275              
1276             =item * run_ft
1277              
1278             =item * help_ft
1279              
1280             =item * smry_ft
1281              
1282             =back
1283              
1284             =head2 Public Timeline Methods
1285              
1286             The public timeline methods provide the handlers for the 'public_timeline'
1287             command. Note that the 'pt' is an alias to 'public_timeline'.
1288              
1289             The public_timeline command has one optional parameter:
1290              
1291             maisha> pt [limit]
1292              
1293             =over 4
1294              
1295             =item * run_public_timeline
1296              
1297             =item * help_public_timeline
1298              
1299             =item * smry_public_timeline
1300              
1301             =item * run_pt
1302              
1303             =item * help_pt
1304              
1305             =item * smry_pt
1306              
1307             =back
1308              
1309             =head2 Update Methods
1310              
1311             The update methods provide the handlers for the 'update' command. Note that
1312             'say' is an alias for 'update'.
1313              
1314             =over 4
1315              
1316             =item * run_update
1317              
1318             =item * help_update
1319              
1320             =item * smry_update
1321              
1322             =item * comp_update
1323              
1324             =item * run_say
1325              
1326             =item * help_say
1327              
1328             =item * smry_say
1329              
1330             =item * comp_say
1331              
1332             =back
1333              
1334             =head2 Reply Methods
1335              
1336             The reply methods provide the handlers for the 'replies' command. Note that
1337             're' is an aliases for 'replies'
1338              
1339             The replies command has one optional parameter:
1340              
1341             maisha> re [limit]
1342              
1343             =over 4
1344              
1345             =item * run_replies
1346              
1347             =item * help_replies
1348              
1349             =item * smry_replies
1350              
1351             =item * run_re
1352              
1353             =item * help_re
1354              
1355             =item * smry_re
1356              
1357             =back
1358              
1359             =head2 Direct Message Methods
1360              
1361             The direct message methods provide the handlers for the 'direct_message'
1362             command. Note that 'dm' is an aliases for 'direct_message'.
1363              
1364             The direct_message command has two optional parameters:
1365              
1366             maisha> dm [from|to] [limit]
1367              
1368             maisha> dm from
1369             maisha> dm to 10
1370             maisha> dm 5
1371             maisha> dm
1372              
1373             The first above is the usage, with the keywords 'from' and 'to' both being
1374             optional. If neither is specified, 'to' is assumed. In addition a limit for
1375             the number of message can be provided. If no limit is given, your configured
1376             default, or the system default (20) is used.
1377              
1378             =over 4
1379              
1380             =item * run_direct_messages
1381              
1382             =item * help_direct_messages
1383              
1384             =item * smry_direct_messages
1385              
1386             =item * run_dm
1387              
1388             =item * help_dm
1389              
1390             =item * smry_dm
1391              
1392             =back
1393              
1394             =head2 Send Message Methods
1395              
1396             The send message methods provide the handlers for the 'send_message' command.
1397             Note that both 'send' and 'sm' are aliases to 'send_message'
1398              
1399             =over 4
1400              
1401             =item * run_send_message
1402              
1403             =item * help_send_message
1404              
1405             =item * smry_send_message
1406              
1407             =item * comp_send_message
1408              
1409             =item * run_send
1410              
1411             =item * help_send
1412              
1413             =item * smry_send
1414              
1415             =item * comp_send
1416              
1417             =item * run_sm
1418              
1419             =item * help_sm
1420              
1421             =item * smry_sm
1422              
1423             =item * comp_sm
1424              
1425             =back
1426              
1427             =head2 Search Methods
1428              
1429             These methods provide the handlers for the 'search' command.
1430              
1431             The search command has one optional, and one mandatory parameter:
1432              
1433             maisha> search [limit] term [term ...]
1434              
1435             maisha> search term
1436             maisha> search 10 term
1437             maisha> search a really long search term
1438             maisha> search 20 a really long search term
1439              
1440             If the first parameter is a number, this will be treated as the limit value,
1441             used to limit the number of messages displayed.
1442              
1443             =over 4
1444              
1445             =item * run_search
1446              
1447             =item * help_search
1448              
1449             =item * smry_search
1450              
1451             =back
1452              
1453             =head2 About Methods
1454              
1455             These methods provide the handlers for the 'about' command.
1456              
1457             =over 4
1458              
1459             =item * run_about
1460              
1461             =item * help_about
1462              
1463             =item * smry_about
1464              
1465             =back
1466              
1467             =head2 Version Methods
1468              
1469             The quit methods provide the handlers for the 'version' command.
1470              
1471             =over 4
1472              
1473             =item * run_version
1474              
1475             =item * help_version
1476              
1477             =item * smry_version
1478              
1479             =back
1480              
1481             =head2 Debug Methods
1482              
1483             The debug methods provide more verbose error mesages if commands fail.
1484              
1485             The debug command has two optional parameters:
1486              
1487             maisha> debug on|off
1488              
1489             maisha> debug on
1490             maisha> debug off
1491              
1492             =over 4
1493              
1494             =item * run_debug
1495              
1496             =item * help_debug
1497              
1498             =item * smry_debug
1499              
1500             =back
1501              
1502             =head2 Quit Methods
1503              
1504             The quit methods provide the handlers for the 'quit' command. Note that both
1505             'quit' and 'q' are aliases to 'exit'
1506              
1507             =over 4
1508              
1509             =item * run_quit
1510              
1511             =item * help_quit
1512              
1513             =item * smry_quit
1514              
1515             =item * run_q
1516              
1517             =item * help_q
1518              
1519             =item * smry_q
1520              
1521             =back
1522              
1523             =head2 Internal Shell Methods
1524              
1525             Used internally to interface with the underlying shell application.
1526              
1527             =over 4
1528              
1529             =item * postcmd
1530              
1531             =item * preloop
1532              
1533             =item * postloop
1534              
1535             =back
1536              
1537             =head1 SEE ALSO
1538              
1539             For further information regarding the commands and configuration, please see
1540             the 'maisha' script included with this distribution.
1541              
1542             L<App::Maisha>
1543              
1544             L<Term::Shell>
1545              
1546             =head1 WEBSITES
1547              
1548             =over 4
1549              
1550             =item * Main Site: L<http://maisha.grango.org>
1551              
1552             =item * Git Repo: L<http://github.com/barbie/maisha/tree/master>
1553              
1554             =item * RT Queue: L<RT: http://rt.cpan.org/Public/Dist/Display.html?Name=App-Maisha>
1555              
1556             =back
1557              
1558             =head1 AUTHOR
1559              
1560             Barbie, <barbie@cpan.org>
1561             for Miss Barbell Productions <http://www.missbarbell.co.uk>.
1562              
1563             =head1 COPYRIGHT AND LICENSE
1564              
1565             Copyright (C) 2009-2014 by Barbie
1566              
1567             This distribution is free software; you can redistribute it and/or
1568             modify it under the Artistic License v2.
1569              
1570             =cut