]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/command.php
Merge branch 'testing' of git@gitorious.org:statusnet/mainline into 0.9.x
[quix0rs-gnu-social.git] / lib / command.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, 2010 StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
21
22 require_once(INSTALLDIR.'/lib/channel.php');
23
24 class Command
25 {
26
27     var $user = null;
28
29     function __construct($user=null)
30     {
31         $this->user = $user;
32     }
33
34     /**
35      * Execute the command and send success or error results
36      * back via the given communications channel.
37      *
38      * @param Channel
39      */
40     public function execute($channel)
41     {
42         try {
43             $this->handle($channel);
44         } catch (CommandException $e) {
45             $channel->error($this->user, $e->getMessage());
46         } catch (Exception $e) {
47             common_log(LOG_ERR, "Error handling " . get_class($this) . ": " . $e->getMessage());
48             $channel->error($this->user, $e->getMessage());
49         }
50     }
51
52     
53     /**
54      * Override this with the meat!
55      *
56      * An error to send back to the user may be sent by throwing
57      * a CommandException with a formatted message.
58      *
59      * @param Channel
60      * @throws CommandException
61      */
62     function handle($channel)
63     {
64         return false;
65     }
66
67     /**
68      * Look up a notice from an argument, by poster's name to get last post
69      * or notice_id prefixed with #.
70      *
71      * @return Notice
72      * @throws CommandException
73      */
74     function getNotice($arg)
75     {
76         $notice = null;
77         if (Event::handle('StartCommandGetNotice', array($this, $arg, &$notice))) {
78             if(substr($this->other,0,1)=='#'){
79                 // A specific notice_id #123
80
81                 $notice = Notice::staticGet(substr($arg,1));
82                 if (!$notice) {
83                     throw new CommandException(_('Notice with that id does not exist'));
84                 }
85             }
86             
87             if (Validate::uri($this->other)) {
88                 // A specific notice by URI lookup
89                 $notice = Notice::staticGet('uri', $arg);
90             }
91             
92             if (!$notice) {
93                 // Local or remote profile name to get their last notice.
94                 // May throw an exception and report 'no such user'
95                 $recipient = $this->getProfile($arg);
96
97                 $notice = $recipient->getCurrentNotice();
98                 if (!$notice) {
99                     throw new CommandException(_('User has no last notice'));
100                 }
101             }
102         }
103         Event::handle('EndCommandGetNotice', array($this, $arg, &$notice));
104         if (!$notice) {
105             throw new CommandException(_('Notice with that id does not exist'));
106         }
107         return $notice;
108     }
109
110     /**
111      * Look up a local or remote profile by nickname.
112      *
113      * @return Profile
114      * @throws CommandException
115      */
116     function getProfile($arg)
117     {
118         $profile = null;
119         if (Event::handle('StartCommandGetProfile', array($this, $arg, &$profile))) {
120             $profile =
121               common_relative_profile($this->user, common_canonical_nickname($arg));
122         }
123         Event::handle('EndCommandGetProfile', array($this, $arg, &$profile));
124         if (!$profile) {
125             throw new CommandException(sprintf(_('Could not find a user with nickname %s'), $arg));
126         }
127         return $profile;
128     }
129
130     /**
131      * Get a local user by name
132      * @return User
133      * @throws CommandException
134      */
135     function getUser($arg)
136     {
137         $user = null;
138         if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
139             $user = User::staticGet('nickname', $arg);
140         }
141         Event::handle('EndCommandGetUser', array($this, $arg, &$user));
142         if (!$user){
143             throw new CommandException(sprintf(_('Could not find a local user with nickname %s'),
144                                $arg));
145         }
146         return $user;
147     }
148
149     /**
150      * Get a local or remote group by name.
151      * @return User_group
152      * @throws CommandException
153      */
154     function getGroup($arg)
155     {
156         $group = null;
157         if (Event::handle('StartCommandGetGroup', array($this, $arg, &$group))) {
158             $group = User_group::getForNickname($arg, $this->user->getProfile());
159         }
160         Event::handle('EndCommandGetGroup', array($this, $arg, &$group));
161         if (!$group) {
162             throw new CommandException(_('No such group.'));
163         }
164         return $group;
165     }
166 }
167
168 class CommandException extends Exception
169 {
170 }
171
172 class UnimplementedCommand extends Command
173 {
174     function handle($channel)
175     {
176         $channel->error($this->user, _("Sorry, this command is not yet implemented."));
177     }
178 }
179
180 class TrackingCommand extends UnimplementedCommand
181 {
182 }
183
184 class TrackOffCommand extends UnimplementedCommand
185 {
186 }
187
188 class TrackCommand extends UnimplementedCommand
189 {
190     var $word = null;
191     function __construct($user, $word)
192     {
193         parent::__construct($user);
194         $this->word = $word;
195     }
196 }
197
198 class UntrackCommand extends UnimplementedCommand
199 {
200     var $word = null;
201     function __construct($user, $word)
202     {
203         parent::__construct($user);
204         $this->word = $word;
205     }
206 }
207
208 class NudgeCommand extends Command
209 {
210     var $other = null;
211     function __construct($user, $other)
212     {
213         parent::__construct($user);
214         $this->other = $other;
215     }
216
217     function handle($channel)
218     {
219         $recipient = $this->getUser($this->other);
220         if ($recipient->id == $this->user->id) {
221             throw new CommandException(_('It does not make a lot of sense to nudge yourself!'));
222         } else {
223             if ($recipient->email && $recipient->emailnotifynudge) {
224                 mail_notify_nudge($this->user, $recipient);
225             }
226             // XXX: notify by IM
227             // XXX: notify by SMS
228             $channel->output($this->user, sprintf(_('Nudge sent to %s'),
229                            $recipient->nickname));
230         }
231     }
232 }
233
234 class InviteCommand extends UnimplementedCommand
235 {
236     var $other = null;
237     function __construct($user, $other)
238     {
239         parent::__construct($user);
240         $this->other = $other;
241     }
242 }
243
244 class StatsCommand extends Command
245 {
246     function handle($channel)
247     {
248         $profile = $this->user->getProfile();
249
250         $subs_count   = $profile->subscriptionCount();
251         $subbed_count = $profile->subscriberCount();
252         $notice_count = $profile->noticeCount();
253
254         $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
255                                    "Subscribers: %2\$s\n".
256                                    "Notices: %3\$s"),
257                                  $subs_count,
258                                  $subbed_count,
259                                  $notice_count));
260     }
261 }
262
263 class FavCommand extends Command
264 {
265     var $other = null;
266
267     function __construct($user, $other)
268     {
269         parent::__construct($user);
270         $this->other = $other;
271     }
272
273     function handle($channel)
274     {
275         $notice = $this->getNotice($this->other);
276         $fave = Fave::addNew($this->user, $notice);
277
278         if (!$fave) {
279             $channel->error($this->user, _('Could not create favorite.'));
280             return;
281         }
282
283         // @fixme favorite notification should be triggered
284         // at a lower level
285
286         $other = User::staticGet('id', $notice->profile_id);
287
288         if ($other && $other->id != $user->id) {
289             if ($other->email && $other->emailnotifyfav) {
290                 mail_notify_fave($other, $this->user, $notice);
291             }
292         }
293
294         $this->user->blowFavesCache();
295
296         $channel->output($this->user, _('Notice marked as fave.'));
297     }
298
299 }
300
301 class JoinCommand extends Command
302 {
303     var $other = null;
304
305     function __construct($user, $other)
306     {
307         parent::__construct($user);
308         $this->other = $other;
309     }
310
311     function handle($channel)
312     {
313         $group = $this->getGroup($this->other);
314         $cur   = $this->user;
315
316         if ($cur->isMember($group)) {
317             $channel->error($cur, _('You are already a member of that group'));
318             return;
319         }
320         if (Group_block::isBlocked($group, $cur->getProfile())) {
321           $channel->error($cur, _('You have been blocked from that group by the admin.'));
322             return;
323         }
324
325         try {
326             if (Event::handle('StartJoinGroup', array($group, $cur))) {
327                 Group_member::join($group->id, $cur->id);
328                 Event::handle('EndJoinGroup', array($group, $cur));
329             }
330         } catch (Exception $e) {
331             $channel->error($cur, sprintf(_('Could not join user %s to group %s'),
332                                           $cur->nickname, $group->nickname));
333             return;
334         }
335
336         $channel->output($cur, sprintf(_('%s joined group %s'),
337                                               $cur->nickname,
338                                               $group->nickname));
339     }
340
341 }
342 class DropCommand extends Command
343 {
344     var $other = null;
345
346     function __construct($user, $other)
347     {
348         parent::__construct($user);
349         $this->other = $other;
350     }
351
352     function handle($channel)
353     {
354         $group = $this->getGroup($this->other);
355         $cur   = $this->user;
356
357         if (!$group) {
358             $channel->error($cur, _('No such group.'));
359             return;
360         }
361
362         if (!$cur->isMember($group)) {
363             $channel->error($cur, _('You are not a member of that group.'));
364             return;
365         }
366
367         try {
368             if (Event::handle('StartLeaveGroup', array($group, $cur))) {
369                 Group_member::leave($group->id, $cur->id);
370                 Event::handle('EndLeaveGroup', array($group, $cur));
371             }
372         } catch (Exception $e) {
373             $channel->error($cur, sprintf(_('Could not remove user %s to group %s'),
374                                           $cur->nickname, $group->nickname));
375             return;
376         }
377
378         $channel->output($cur, sprintf(_('%s left group %s'),
379                                               $cur->nickname,
380                                               $group->nickname));
381     }
382
383 }
384
385 class WhoisCommand extends Command
386 {
387     var $other = null;
388     function __construct($user, $other)
389     {
390         parent::__construct($user);
391         $this->other = $other;
392     }
393
394     function handle($channel)
395     {
396         $recipient = $this->getProfile($this->other);
397
398         $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
399                          $recipient->profileurl);
400         if ($recipient->fullname) {
401             $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
402         }
403         if ($recipient->location) {
404             $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
405         }
406         if ($recipient->homepage) {
407             $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
408         }
409         if ($recipient->bio) {
410             $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
411         }
412         $channel->output($this->user, $whois);
413     }
414 }
415
416 class MessageCommand extends Command
417 {
418     var $other = null;
419     var $text = null;
420     function __construct($user, $other, $text)
421     {
422         parent::__construct($user);
423         $this->other = $other;
424         $this->text = $text;
425     }
426
427     function handle($channel)
428     {
429         try {
430             $other = $this->getUser($this->other);
431         } catch (CommandException $e) {
432             try {
433                 $profile = $this->getProfile($this->other);
434             } catch (CommandException $f) {
435                 throw $e;
436             }
437             throw new CommandException(sprintf(_('%s is a remote profile; you can only send direct messages to users on the same server.'), $this->other));
438         }
439
440         $len = mb_strlen($this->text);
441
442         if ($len == 0) {
443             $channel->error($this->user, _('No content!'));
444             return;
445         }
446
447         $this->text = common_shorten_links($this->text);
448
449         if (Message::contentTooLong($this->text)) {
450             $channel->error($this->user, sprintf(_('Message too long - maximum is %d characters, you sent %d'),
451                                                  Message::maxContent(), mb_strlen($this->text)));
452             return;
453         }
454
455         if (!$other) {
456             $channel->error($this->user, _('No such user.'));
457             return;
458         } else if (!$this->user->mutuallySubscribed($other)) {
459             $channel->error($this->user, _('You can\'t send a message to this user.'));
460             return;
461         } else if ($this->user->id == $other->id) {
462             $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.'));
463             return;
464         }
465         $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
466         if ($message) {
467             $message->notify();
468             $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
469         } else {
470             $channel->error($this->user, _('Error sending direct message.'));
471         }
472     }
473 }
474
475 class RepeatCommand extends Command
476 {
477     var $other = null;
478     function __construct($user, $other)
479     {
480         parent::__construct($user);
481         $this->other = $other;
482     }
483
484     function handle($channel)
485     {
486         $notice = $this->getNotice($this->other);
487
488         if($this->user->id == $notice->profile_id)
489         {
490             $channel->error($this->user, _('Cannot repeat your own notice'));
491             return;
492         }
493
494         if ($this->user->getProfile()->hasRepeated($notice->id)) {
495             $channel->error($this->user, _('Already repeated that notice'));
496             return;
497         }
498
499         $repeat = $notice->repeat($this->user->id, $channel->source);
500
501         if ($repeat) {
502
503             $channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname));
504         } else {
505             $channel->error($this->user, _('Error repeating notice.'));
506         }
507     }
508 }
509
510 class ReplyCommand extends Command
511 {
512     var $other = null;
513     var $text = null;
514     function __construct($user, $other, $text)
515     {
516         parent::__construct($user);
517         $this->other = $other;
518         $this->text = $text;
519     }
520
521     function handle($channel)
522     {
523         $notice = $this->getNotice($this->other);
524         $recipient = $notice->getProfile();
525
526         $len = mb_strlen($this->text);
527
528         if ($len == 0) {
529             $channel->error($this->user, _('No content!'));
530             return;
531         }
532
533         $this->text = common_shorten_links($this->text);
534
535         if (Notice::contentTooLong($this->text)) {
536             $channel->error($this->user, sprintf(_('Notice too long - maximum is %d characters, you sent %d'),
537                                                  Notice::maxContent(), mb_strlen($this->text)));
538             return;
539         }
540
541         $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(),
542                                   array('reply_to' => $notice->id));
543
544         if ($notice) {
545             $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname));
546         } else {
547             $channel->error($this->user, _('Error saving notice.'));
548         }
549
550     }
551 }
552
553 class GetCommand extends Command
554 {
555
556     var $other = null;
557
558     function __construct($user, $other)
559     {
560         parent::__construct($user);
561         $this->other = $other;
562     }
563
564     function handle($channel)
565     {
566         $target = $this->getProfile($this->other);
567
568         $notice = $target->getCurrentNotice();
569         if (!$notice) {
570             $channel->error($this->user, _('User has no last notice'));
571             return;
572         }
573         $notice_content = $notice->content;
574
575         $channel->output($this->user, $target->nickname . ": " . $notice_content);
576     }
577 }
578
579 class SubCommand extends Command
580 {
581
582     var $other = null;
583
584     function __construct($user, $other)
585     {
586         parent::__construct($user);
587         $this->other = $other;
588     }
589
590     function handle($channel)
591     {
592
593         if (!$this->other) {
594             $channel->error($this->user, _('Specify the name of the user to subscribe to'));
595             return;
596         }
597
598         $target = $this->getProfile($this->other);
599
600         $remote = Remote_profile::staticGet('id', $target->id);
601         if ($remote) {
602             throw new CommandException(_("Can't subscribe to OMB profiles by command."));
603         }
604
605         try {
606             Subscription::start($this->user->getProfile(),
607                                 $target);
608             $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
609         } catch (Exception $e) {
610             $channel->error($this->user, $e->getMessage());
611         }
612     }
613 }
614
615 class UnsubCommand extends Command
616 {
617
618     var $other = null;
619
620     function __construct($user, $other)
621     {
622         parent::__construct($user);
623         $this->other = $other;
624     }
625
626     function handle($channel)
627     {
628         if(!$this->other) {
629             $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
630             return;
631         }
632
633         $target = $this->getProfile($this->other);
634
635         try {
636             Subscription::cancel($this->user->getProfile(),
637                                  $target);
638             $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
639         } catch (Exception $e) {
640             $channel->error($this->user, $e->getMessage());
641         }
642     }
643 }
644
645 class OffCommand extends Command
646 {
647     var $other = null;
648     function __construct($user, $other=null)
649     {
650         parent::__construct($user);
651         $this->other = $other;
652     }
653     function handle($channel)
654     {
655         if ($other) {
656             $channel->error($this->user, _("Command not yet implemented."));
657         } else {
658             if ($channel->off($this->user)) {
659                 $channel->output($this->user, _('Notification off.'));
660             } else {
661                 $channel->error($this->user, _('Can\'t turn off notification.'));
662             }
663         }
664     }
665 }
666
667 class OnCommand extends Command
668 {
669     var $other = null;
670     function __construct($user, $other=null)
671     {
672         parent::__construct($user);
673         $this->other = $other;
674     }
675
676     function handle($channel)
677     {
678         if ($other) {
679             $channel->error($this->user, _("Command not yet implemented."));
680         } else {
681             if ($channel->on($this->user)) {
682                 $channel->output($this->user, _('Notification on.'));
683             } else {
684                 $channel->error($this->user, _('Can\'t turn on notification.'));
685             }
686         }
687     }
688 }
689
690 class LoginCommand extends Command
691 {
692     function handle($channel)
693     {
694         $disabled = common_config('logincommand','disabled');
695         $disabled = isset($disabled) && $disabled;
696         if($disabled) {
697             $channel->error($this->user, _('Login command is disabled'));
698             return;
699         }
700
701         try {
702             $login_token = Login_token::makeNew($this->user);
703         } catch (Exception $e) {
704             $channel->error($this->user, $e->getMessage());
705         }
706
707         $channel->output($this->user,
708             sprintf(_('This link is useable only once, and is good for only 2 minutes: %s'),
709                     common_local_url('otp',
710                         array('user_id' => $login_token->user_id, 'token' => $login_token->token))));
711     }
712 }
713
714 class LoseCommand extends Command
715 {
716
717     var $other = null;
718
719     function __construct($user, $other)
720     {
721         parent::__construct($user);
722         $this->other = $other;
723     }
724
725     function execute($channel)
726     {
727         if(!$this->other) {
728             $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
729             return;
730         }
731
732         $result=subs_unsubscribe_from($this->user, $this->other);
733
734         if ($result) {
735             $channel->output($this->user, sprintf(_('Unsubscribed  %s'), $this->other));
736         } else {
737             $channel->error($this->user, $result);
738         }
739     }
740 }
741
742 class SubscriptionsCommand extends Command
743 {
744     function handle($channel)
745     {
746         $profile = $this->user->getSubscriptions(0);
747         $nicknames=array();
748         while ($profile->fetch()) {
749             $nicknames[]=$profile->nickname;
750         }
751         if(count($nicknames)==0){
752             $out=_('You are not subscribed to anyone.');
753         }else{
754             $out = ngettext('You are subscribed to this person:',
755                 'You are subscribed to these people:',
756                 count($nicknames));
757             $out .= ' ';
758             $out .= implode(', ',$nicknames);
759         }
760         $channel->output($this->user,$out);
761     }
762 }
763
764 class SubscribersCommand extends Command
765 {
766     function handle($channel)
767     {
768         $profile = $this->user->getSubscribers();
769         $nicknames=array();
770         while ($profile->fetch()) {
771             $nicknames[]=$profile->nickname;
772         }
773         if(count($nicknames)==0){
774             $out=_('No one is subscribed to you.');
775         }else{
776             $out = ngettext('This person is subscribed to you:',
777                 'These people are subscribed to you:',
778                 count($nicknames));
779             $out .= ' ';
780             $out .= implode(', ',$nicknames);
781         }
782         $channel->output($this->user,$out);
783     }
784 }
785
786 class GroupsCommand extends Command
787 {
788     function handle($channel)
789     {
790         $group = $this->user->getGroups();
791         $groups=array();
792         while ($group->fetch()) {
793             $groups[]=$group->nickname;
794         }
795         if(count($groups)==0){
796             $out=_('You are not a member of any groups.');
797         }else{
798             $out = ngettext('You are a member of this group:',
799                 'You are a member of these groups:',
800                 count($nicknames));
801             $out.=implode(', ',$groups);
802         }
803         $channel->output($this->user,$out);
804     }
805 }
806
807 class HelpCommand extends Command
808 {
809     function handle($channel)
810     {
811         $channel->output($this->user,
812                          _("Commands:\n".
813                            "on - turn on notifications\n".
814                            "off - turn off notifications\n".
815                            "help - show this help\n".
816                            "follow <nickname> - subscribe to user\n".
817                            "groups - lists the groups you have joined\n".
818                            "subscriptions - list the people you follow\n".
819                            "subscribers - list the people that follow you\n".
820                            "leave <nickname> - unsubscribe from user\n".
821                            "d <nickname> <text> - direct message to user\n".
822                            "get <nickname> - get last notice from user\n".
823                            "whois <nickname> - get profile info on user\n".
824                            "lose <nickname> - force user to stop following you\n".
825                            "fav <nickname> - add user's last notice as a 'fave'\n".
826                            "fav #<notice_id> - add notice with the given id as a 'fave'\n".
827                            "repeat #<notice_id> - repeat a notice with a given id\n".
828                            "repeat <nickname> - repeat the last notice from user\n".
829                            "reply #<notice_id> - reply to notice with a given id\n".
830                            "reply <nickname> - reply to the last notice from user\n".
831                            "join <group> - join group\n".
832                            "login - Get a link to login to the web interface\n".
833                            "drop <group> - leave group\n".
834                            "stats - get your stats\n".
835                            "stop - same as 'off'\n".
836                            "quit - same as 'off'\n".
837                            "sub <nickname> - same as 'follow'\n".
838                            "unsub <nickname> - same as 'leave'\n".
839                            "last <nickname> - same as 'get'\n".
840                            "on <nickname> - not yet implemented.\n".
841                            "off <nickname> - not yet implemented.\n".
842                            "nudge <nickname> - remind a user to update.\n".
843                            "invite <phone number> - not yet implemented.\n".
844                            "track <word> - not yet implemented.\n".
845                            "untrack <word> - not yet implemented.\n".
846                            "track off - not yet implemented.\n".
847                            "untrack all - not yet implemented.\n".
848                            "tracks - not yet implemented.\n".
849                            "tracking - not yet implemented.\n"));
850     }
851 }