]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/command.php
Merge branch 'testing' 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             // TRANS: Message given requesting a profile for a non-existing user.
126             // TRANS: %s is the nickname of the user for which the profile could not be found.
127             throw new CommandException(sprintf(_('Could not find a user with nickname %s'), $arg));
128         }
129         return $profile;
130     }
131
132     /**
133      * Get a local user by name
134      * @return User
135      * @throws CommandException
136      */
137     function getUser($arg)
138     {
139         $user = null;
140         if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
141             $user = User::staticGet('nickname', $arg);
142         }
143         Event::handle('EndCommandGetUser', array($this, $arg, &$user));
144         if (!$user){
145             // TRANS: Message given getting a non-existing user.
146             // TRANS: %s is the nickname of the user that could not be found.
147             throw new CommandException(sprintf(_('Could not find a local user with nickname %s'),
148                                $arg));
149         }
150         return $user;
151     }
152
153     /**
154      * Get a local or remote group by name.
155      * @return User_group
156      * @throws CommandException
157      */
158     function getGroup($arg)
159     {
160         $group = null;
161         if (Event::handle('StartCommandGetGroup', array($this, $arg, &$group))) {
162             $group = User_group::getForNickname($arg, $this->user->getProfile());
163         }
164         Event::handle('EndCommandGetGroup', array($this, $arg, &$group));
165         if (!$group) {
166             throw new CommandException(_('No such group.'));
167         }
168         return $group;
169     }
170 }
171
172 class CommandException extends Exception
173 {
174 }
175
176 class UnimplementedCommand extends Command
177 {
178     function handle($channel)
179     {
180         $channel->error($this->user, _("Sorry, this command is not yet implemented."));
181     }
182 }
183
184 class TrackingCommand extends UnimplementedCommand
185 {
186 }
187
188 class TrackOffCommand extends UnimplementedCommand
189 {
190 }
191
192 class TrackCommand extends UnimplementedCommand
193 {
194     var $word = null;
195     function __construct($user, $word)
196     {
197         parent::__construct($user);
198         $this->word = $word;
199     }
200 }
201
202 class UntrackCommand extends UnimplementedCommand
203 {
204     var $word = null;
205     function __construct($user, $word)
206     {
207         parent::__construct($user);
208         $this->word = $word;
209     }
210 }
211
212 class NudgeCommand extends Command
213 {
214     var $other = null;
215     function __construct($user, $other)
216     {
217         parent::__construct($user);
218         $this->other = $other;
219     }
220
221     function handle($channel)
222     {
223         $recipient = $this->getUser($this->other);
224         if ($recipient->id == $this->user->id) {
225             throw new CommandException(_('It does not make a lot of sense to nudge yourself!'));
226         } else {
227             if ($recipient->email && $recipient->emailnotifynudge) {
228                 mail_notify_nudge($this->user, $recipient);
229             }
230             // XXX: notify by IM
231             // XXX: notify by SMS
232             // TRANS: Message given having nudged another user.
233             // TRANS: %s is the nickname of the user that was nudged.
234             $channel->output($this->user, sprintf(_('Nudge sent to %s'),
235                            $recipient->nickname));
236         }
237     }
238 }
239
240 class InviteCommand extends UnimplementedCommand
241 {
242     var $other = null;
243     function __construct($user, $other)
244     {
245         parent::__construct($user);
246         $this->other = $other;
247     }
248 }
249
250 class StatsCommand extends Command
251 {
252     function handle($channel)
253     {
254         $profile = $this->user->getProfile();
255
256         $subs_count   = $profile->subscriptionCount();
257         $subbed_count = $profile->subscriberCount();
258         $notice_count = $profile->noticeCount();
259
260         $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
261                                    "Subscribers: %2\$s\n".
262                                    "Notices: %3\$s"),
263                                  $subs_count,
264                                  $subbed_count,
265                                  $notice_count));
266     }
267 }
268
269 class FavCommand extends Command
270 {
271     var $other = null;
272
273     function __construct($user, $other)
274     {
275         parent::__construct($user);
276         $this->other = $other;
277     }
278
279     function handle($channel)
280     {
281         $notice = $this->getNotice($this->other);
282         $fave = Fave::addNew($this->user->getProfile(), $notice);
283
284         if (!$fave) {
285             $channel->error($this->user, _('Could not create favorite.'));
286             return;
287         }
288
289         // @fixme favorite notification should be triggered
290         // at a lower level
291
292         $other = User::staticGet('id', $notice->profile_id);
293
294         if ($other && $other->id != $user->id) {
295             if ($other->email && $other->emailnotifyfav) {
296                 mail_notify_fave($other, $this->user, $notice);
297             }
298         }
299
300         $this->user->blowFavesCache();
301
302         $channel->output($this->user, _('Notice marked as fave.'));
303     }
304
305 }
306
307 class JoinCommand extends Command
308 {
309     var $other = null;
310
311     function __construct($user, $other)
312     {
313         parent::__construct($user);
314         $this->other = $other;
315     }
316
317     function handle($channel)
318     {
319         $group = $this->getGroup($this->other);
320         $cur   = $this->user;
321
322         if ($cur->isMember($group)) {
323             $channel->error($cur, _('You are already a member of that group'));
324             return;
325         }
326         if (Group_block::isBlocked($group, $cur->getProfile())) {
327           $channel->error($cur, _('You have been blocked from that group by the admin.'));
328             return;
329         }
330
331         try {
332             if (Event::handle('StartJoinGroup', array($group, $cur))) {
333                 Group_member::join($group->id, $cur->id);
334                 Event::handle('EndJoinGroup', array($group, $cur));
335             }
336         } catch (Exception $e) {
337             // TRANS: Message given having failed to add a user to a group.
338             // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
339             $channel->error($cur, sprintf(_('Could not join user %1$s to group %2$s'),
340                                           $cur->nickname, $group->nickname));
341             return;
342         }
343
344         // TRANS: Message given having added a user to a group.
345         // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
346         $channel->output($cur, sprintf(_('%1$s joined group %2$s'),
347                                               $cur->nickname,
348                                               $group->nickname));
349     }
350
351 }
352 class DropCommand extends Command
353 {
354     var $other = null;
355
356     function __construct($user, $other)
357     {
358         parent::__construct($user);
359         $this->other = $other;
360     }
361
362     function handle($channel)
363     {
364         $group = $this->getGroup($this->other);
365         $cur   = $this->user;
366
367         if (!$group) {
368             $channel->error($cur, _('No such group.'));
369             return;
370         }
371
372         if (!$cur->isMember($group)) {
373             $channel->error($cur, _('You are not a member of that group.'));
374             return;
375         }
376
377         try {
378             if (Event::handle('StartLeaveGroup', array($group, $cur))) {
379                 Group_member::leave($group->id, $cur->id);
380                 Event::handle('EndLeaveGroup', array($group, $cur));
381             }
382         } catch (Exception $e) {
383             // TRANS: Message given having failed to remove a user from a group.
384             // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
385             $channel->error($cur, sprintf(_('Could not remove user %1$s from group %2$s'),
386                                           $cur->nickname, $group->nickname));
387             return;
388         }
389
390         // TRANS: Message given having removed a user from a group.
391         // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
392         $channel->output($cur, sprintf(_('%1$s left group %2$s'),
393                                               $cur->nickname,
394                                               $group->nickname));
395     }
396
397 }
398
399 class WhoisCommand extends Command
400 {
401     var $other = null;
402     function __construct($user, $other)
403     {
404         parent::__construct($user);
405         $this->other = $other;
406     }
407
408     function handle($channel)
409     {
410         $recipient = $this->getProfile($this->other);
411
412         // TRANS: Whois output.
413         // TRANS: %1$s nickname of the queried user, %2$s is their profile URL.
414         $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
415                          $recipient->profileurl);
416         if ($recipient->fullname) {
417             // TRANS: Whois output. %s is the full name of the queried user.
418             $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
419         }
420         if ($recipient->location) {
421             // TRANS: Whois output. %s is the location of the queried user.
422             $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
423         }
424         if ($recipient->homepage) {
425             // TRANS: Whois output. %s is the homepage of the queried user.
426             $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
427         }
428         if ($recipient->bio) {
429             // TRANS: Whois output. %s is the bio information of the queried user.
430             $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
431         }
432         $channel->output($this->user, $whois);
433     }
434 }
435
436 class MessageCommand extends Command
437 {
438     var $other = null;
439     var $text = null;
440     function __construct($user, $other, $text)
441     {
442         parent::__construct($user);
443         $this->other = $other;
444         $this->text = $text;
445     }
446
447     function handle($channel)
448     {
449         try {
450             $other = $this->getUser($this->other);
451         } catch (CommandException $e) {
452             try {
453                 $profile = $this->getProfile($this->other);
454             } catch (CommandException $f) {
455                 throw $e;
456             }
457             throw new CommandException(sprintf(_('%s is a remote profile; you can only send direct messages to users on the same server.'), $this->other));
458         }
459
460         $len = mb_strlen($this->text);
461
462         if ($len == 0) {
463             $channel->error($this->user, _('No content!'));
464             return;
465         }
466
467         $this->text = common_shorten_links($this->text);
468
469         if (Message::contentTooLong($this->text)) {
470             // TRANS: Message given if content is too long.
471             // TRANS: %1$d is the maximum number of characters, %2$d is the number of submitted characters.
472             $channel->error($this->user, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d'),
473                                                  Message::maxContent(), mb_strlen($this->text)));
474             return;
475         }
476
477         if (!$other) {
478             $channel->error($this->user, _('No such user.'));
479             return;
480         } else if (!$this->user->mutuallySubscribed($other)) {
481             $channel->error($this->user, _('You can\'t send a message to this user.'));
482             return;
483         } else if ($this->user->id == $other->id) {
484             $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.'));
485             return;
486         }
487         $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
488         if ($message) {
489             $message->notify();
490             // TRANS: Message given have sent a direct message to another user.
491             // TRANS: %s is the name of the other user.
492             $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
493         } else {
494             $channel->error($this->user, _('Error sending direct message.'));
495         }
496     }
497 }
498
499 class RepeatCommand extends Command
500 {
501     var $other = null;
502     function __construct($user, $other)
503     {
504         parent::__construct($user);
505         $this->other = $other;
506     }
507
508     function handle($channel)
509     {
510         $notice = $this->getNotice($this->other);
511
512         if($this->user->id == $notice->profile_id)
513         {
514             $channel->error($this->user, _('Cannot repeat your own notice'));
515             return;
516         }
517
518         if ($this->user->getProfile()->hasRepeated($notice->id)) {
519             $channel->error($this->user, _('Already repeated that notice'));
520             return;
521         }
522
523         $repeat = $notice->repeat($this->user->id, $channel->source);
524
525         if ($repeat) {
526
527             // TRANS: Message given having repeated a notice from another user.
528             // TRANS: %s is the name of the user for which the notice was repeated.
529             $channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname));
530         } else {
531             $channel->error($this->user, _('Error repeating notice.'));
532         }
533     }
534 }
535
536 class ReplyCommand extends Command
537 {
538     var $other = null;
539     var $text = null;
540     function __construct($user, $other, $text)
541     {
542         parent::__construct($user);
543         $this->other = $other;
544         $this->text = $text;
545     }
546
547     function handle($channel)
548     {
549         $notice = $this->getNotice($this->other);
550         $recipient = $notice->getProfile();
551
552         $len = mb_strlen($this->text);
553
554         if ($len == 0) {
555             $channel->error($this->user, _('No content!'));
556             return;
557         }
558
559         $this->text = common_shorten_links($this->text);
560
561         if (Notice::contentTooLong($this->text)) {
562             $channel->error($this->user, sprintf(_('Notice too long - maximum is %d characters, you sent %d'),
563                                                  Notice::maxContent(), mb_strlen($this->text)));
564             return;
565         }
566
567         $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(),
568                                   array('reply_to' => $notice->id));
569
570         if ($notice) {
571             $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname));
572         } else {
573             $channel->error($this->user, _('Error saving notice.'));
574         }
575
576     }
577 }
578
579 class GetCommand 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         $target = $this->getProfile($this->other);
593
594         $notice = $target->getCurrentNotice();
595         if (!$notice) {
596             $channel->error($this->user, _('User has no last notice'));
597             return;
598         }
599         $notice_content = $notice->content;
600
601         $channel->output($this->user, $target->nickname . ": " . $notice_content);
602     }
603 }
604
605 class SubCommand extends Command
606 {
607
608     var $other = null;
609
610     function __construct($user, $other)
611     {
612         parent::__construct($user);
613         $this->other = $other;
614     }
615
616     function handle($channel)
617     {
618
619         if (!$this->other) {
620             $channel->error($this->user, _('Specify the name of the user to subscribe to'));
621             return;
622         }
623
624         $target = $this->getProfile($this->other);
625
626         $remote = Remote_profile::staticGet('id', $target->id);
627         if ($remote) {
628             throw new CommandException(_("Can't subscribe to OMB profiles by command."));
629         }
630
631         try {
632             Subscription::start($this->user->getProfile(),
633                                 $target);
634             $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
635         } catch (Exception $e) {
636             $channel->error($this->user, $e->getMessage());
637         }
638     }
639 }
640
641 class UnsubCommand extends Command
642 {
643
644     var $other = null;
645
646     function __construct($user, $other)
647     {
648         parent::__construct($user);
649         $this->other = $other;
650     }
651
652     function handle($channel)
653     {
654         if(!$this->other) {
655             $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
656             return;
657         }
658
659         $target = $this->getProfile($this->other);
660
661         try {
662             Subscription::cancel($this->user->getProfile(),
663                                  $target);
664             $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
665         } catch (Exception $e) {
666             $channel->error($this->user, $e->getMessage());
667         }
668     }
669 }
670
671 class OffCommand extends Command
672 {
673     var $other = null;
674     function __construct($user, $other=null)
675     {
676         parent::__construct($user);
677         $this->other = $other;
678     }
679     function handle($channel)
680     {
681         if ($other) {
682             $channel->error($this->user, _("Command not yet implemented."));
683         } else {
684             if ($channel->off($this->user)) {
685                 $channel->output($this->user, _('Notification off.'));
686             } else {
687                 $channel->error($this->user, _('Can\'t turn off notification.'));
688             }
689         }
690     }
691 }
692
693 class OnCommand extends Command
694 {
695     var $other = null;
696     function __construct($user, $other=null)
697     {
698         parent::__construct($user);
699         $this->other = $other;
700     }
701
702     function handle($channel)
703     {
704         if ($other) {
705             $channel->error($this->user, _("Command not yet implemented."));
706         } else {
707             if ($channel->on($this->user)) {
708                 $channel->output($this->user, _('Notification on.'));
709             } else {
710                 $channel->error($this->user, _('Can\'t turn on notification.'));
711             }
712         }
713     }
714 }
715
716 class LoginCommand extends Command
717 {
718     function handle($channel)
719     {
720         $disabled = common_config('logincommand','disabled');
721         $disabled = isset($disabled) && $disabled;
722         if($disabled) {
723             $channel->error($this->user, _('Login command is disabled'));
724             return;
725         }
726
727         try {
728             $login_token = Login_token::makeNew($this->user);
729         } catch (Exception $e) {
730             $channel->error($this->user, $e->getMessage());
731         }
732
733         $channel->output($this->user,
734             sprintf(_('This link is useable only once, and is good for only 2 minutes: %s'),
735                     common_local_url('otp',
736                         array('user_id' => $login_token->user_id, 'token' => $login_token->token))));
737     }
738 }
739
740 class LoseCommand extends Command
741 {
742
743     var $other = null;
744
745     function __construct($user, $other)
746     {
747         parent::__construct($user);
748         $this->other = $other;
749     }
750
751     function execute($channel)
752     {
753         if(!$this->other) {
754             $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
755             return;
756         }
757
758         $result = Subscription::cancel($this->getProfile($this->other), $this->user->getProfile());
759
760         if ($result) {
761             $channel->output($this->user, sprintf(_('Unsubscribed  %s'), $this->other));
762         } else {
763             $channel->error($this->user, $result);
764         }
765     }
766 }
767
768 class SubscriptionsCommand extends Command
769 {
770     function handle($channel)
771     {
772         $profile = $this->user->getSubscriptions(0);
773         $nicknames=array();
774         while ($profile->fetch()) {
775             $nicknames[]=$profile->nickname;
776         }
777         if(count($nicknames)==0){
778             $out=_('You are not subscribed to anyone.');
779         }else{
780             $out = ngettext('You are subscribed to this person:',
781                 'You are subscribed to these people:',
782                 count($nicknames));
783             $out .= ' ';
784             $out .= implode(', ',$nicknames);
785         }
786         $channel->output($this->user,$out);
787     }
788 }
789
790 class SubscribersCommand extends Command
791 {
792     function handle($channel)
793     {
794         $profile = $this->user->getSubscribers();
795         $nicknames=array();
796         while ($profile->fetch()) {
797             $nicknames[]=$profile->nickname;
798         }
799         if(count($nicknames)==0){
800             $out=_('No one is subscribed to you.');
801         }else{
802             $out = ngettext('This person is subscribed to you:',
803                 'These people are subscribed to you:',
804                 count($nicknames));
805             $out .= ' ';
806             $out .= implode(', ',$nicknames);
807         }
808         $channel->output($this->user,$out);
809     }
810 }
811
812 class GroupsCommand extends Command
813 {
814     function handle($channel)
815     {
816         $group = $this->user->getGroups();
817         $groups=array();
818         while ($group->fetch()) {
819             $groups[]=$group->nickname;
820         }
821         if(count($groups)==0){
822             $out=_('You are not a member of any groups.');
823         }else{
824             $out = ngettext('You are a member of this group:',
825                 'You are a member of these groups:',
826                 count($nicknames));
827             $out.=implode(', ',$groups);
828         }
829         $channel->output($this->user,$out);
830     }
831 }
832
833 class HelpCommand extends Command
834 {
835     function handle($channel)
836     {
837         $channel->output($this->user,
838                          _("Commands:\n".
839                            "on - turn on notifications\n".
840                            "off - turn off notifications\n".
841                            "help - show this help\n".
842                            "follow <nickname> - subscribe to user\n".
843                            "groups - lists the groups you have joined\n".
844                            "subscriptions - list the people you follow\n".
845                            "subscribers - list the people that follow you\n".
846                            "leave <nickname> - unsubscribe from user\n".
847                            "d <nickname> <text> - direct message to user\n".
848                            "get <nickname> - get last notice from user\n".
849                            "whois <nickname> - get profile info on user\n".
850                            "lose <nickname> - force user to stop following you\n".
851                            "fav <nickname> - add user's last notice as a 'fave'\n".
852                            "fav #<notice_id> - add notice with the given id as a 'fave'\n".
853                            "repeat #<notice_id> - repeat a notice with a given id\n".
854                            "repeat <nickname> - repeat the last notice from user\n".
855                            "reply #<notice_id> - reply to notice with a given id\n".
856                            "reply <nickname> - reply to the last notice from user\n".
857                            "join <group> - join group\n".
858                            "login - Get a link to login to the web interface\n".
859                            "drop <group> - leave group\n".
860                            "stats - get your stats\n".
861                            "stop - same as 'off'\n".
862                            "quit - same as 'off'\n".
863                            "sub <nickname> - same as 'follow'\n".
864                            "unsub <nickname> - same as 'leave'\n".
865                            "last <nickname> - same as 'get'\n".
866                            "on <nickname> - not yet implemented.\n".
867                            "off <nickname> - not yet implemented.\n".
868                            "nudge <nickname> - remind a user to update.\n".
869                            "invite <phone number> - not yet implemented.\n".
870                            "track <word> - not yet implemented.\n".
871                            "untrack <word> - not yet implemented.\n".
872                            "track off - not yet implemented.\n".
873                            "untrack all - not yet implemented.\n".
874                            "tracks - not yet implemented.\n".
875                            "tracking - not yet implemented.\n"));
876     }
877 }