3 // FIXME: Would be sweet if we could piggyback on a cli parser or something.
10 "github.com/shazow/ssh-chat/chat/message"
11 "github.com/shazow/ssh-chat/set"
14 // The error returned when an invalid command is issued.
15 var ErrInvalidCommand = errors.New("invalid command")
17 // The error returned when a command is given without an owner.
18 var ErrNoOwner = errors.New("command without owner")
20 // The error returned when a command is performed without the necessary number
22 var ErrMissingArg = errors.New("missing argument")
24 // The error returned when a command is added without a prefix.
25 var ErrMissingPrefix = errors.New("command missing prefix")
27 // Command is a definition of a handler for a command.
29 // The command's key, such as /foo
31 // Extra help regarding arguments
33 // If omitted, command is hidden from /help
35 Handler func(*Room, message.CommandMsg) error
36 // Command requires Op permissions
40 // Commands is a registry of available commands.
41 type Commands map[string]*Command
43 // Add will register a command. If help string is empty, it will be hidden from
45 func (c Commands) Add(cmd Command) error {
47 return ErrMissingPrefix
54 // Alias will add another command for the same handler, won't get added to help.
55 func (c Commands) Alias(command string, alias string) error {
58 return ErrInvalidCommand
64 // Run executes a command message.
65 func (c Commands) Run(room *Room, msg message.CommandMsg) error {
66 if msg.From() == nil {
70 cmd, ok := c[msg.Command()]
72 return ErrInvalidCommand
75 return cmd.Handler(room, msg)
78 // Help will return collated help text as one string.
79 func (c Commands) Help(showOp bool) string {
82 normal := []*Command{}
83 for _, cmd := range c {
87 normal = append(normal, cmd)
90 help := "Available commands:" + message.Newline + NewCommandsHelp(normal).String()
92 help += message.Newline + "-> Operator commands:" + message.Newline + NewCommandsHelp(op).String()
97 var defaultCommands *Commands
100 defaultCommands = &Commands{}
101 InitCommands(defaultCommands)
104 // InitCommands injects default commands into a Commands registry.
105 func InitCommands(c *Commands) {
108 Handler: func(room *Room, msg message.CommandMsg) error {
109 op := room.IsOp(msg.From())
110 room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
117 Handler: func(room *Room, msg message.CommandMsg) error {
118 me := strings.TrimLeft(msg.Body(), "/me")
120 me = "is at a loss for words."
125 room.Send(message.NewEmoteMsg(me, msg.From()))
132 Help: "Exit the chat.",
133 Handler: func(room *Room, msg message.CommandMsg) error {
138 c.Alias("/exit", "/quit")
143 Help: "Rename yourself.",
144 Handler: func(room *Room, msg message.CommandMsg) error {
151 member, ok := room.MemberByID(u.ID())
153 return errors.New("failed to find member")
157 member.SetID(SanitizeName(args[0]))
158 err := room.Rename(oldID, member)
169 Help: "List users who are connected.",
170 Handler: func(room *Room, msg message.CommandMsg) error {
172 names := room.NamesPrefix("")
173 body := fmt.Sprintf("%d connected: %s", len(names), strings.Join(names, ", "))
174 room.Send(message.NewSystemMsg(body, msg.From()))
178 c.Alias("/names", "/list")
182 PrefixHelp: "[colors|...]",
183 Help: "Set your color theme. (More themes: solarized, mono, hacker)",
184 Handler: func(room *Room, msg message.CommandMsg) error {
189 if user.Config.Theme != nil {
190 theme = user.Config.Theme.ID()
192 body := fmt.Sprintf("Current theme: %s", theme)
193 room.Send(message.NewSystemMsg(body, user))
198 for _, t := range message.Themes {
200 user.Config.Theme = &t
201 body := fmt.Sprintf("Set theme: %s", id)
202 room.Send(message.NewSystemMsg(body, user))
206 return errors.New("theme not found")
212 Help: "Silence room announcements.",
213 Handler: func(room *Room, msg message.CommandMsg) error {
219 body = "Quiet mode is toggled ON"
221 body = "Quiet mode is toggled OFF"
223 room.Send(message.NewSystemMsg(body, u))
231 Handler: func(room *Room, msg message.CommandMsg) error {
235 me = "slaps themselves around a bit with a large trout."
237 me = fmt.Sprintf("slaps %s around a bit with a large trout.", strings.Join(args, " "))
240 room.Send(message.NewEmoteMsg(me, msg.From()))
247 PrefixHelp: "[USER]",
248 Help: "Hide messages from USER, /unignore USER to stop hiding.",
249 Handler: func(room *Room, msg message.CommandMsg) error {
250 id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/ignore"))
252 // Print ignored names, if any.
254 msg.From().Ignored.Each(func(_ string, item set.Item) error {
255 names = append(names, item.Key())
261 systemMsg = "0 users ignored."
263 systemMsg = fmt.Sprintf("%d ignored: %s", len(names), strings.Join(names, ", "))
266 room.Send(message.NewSystemMsg(systemMsg, msg.From()))
270 if id == msg.From().ID() {
271 return errors.New("cannot ignore self")
273 target, ok := room.MemberByID(id)
275 return fmt.Errorf("user not found: %s", id)
278 err := msg.From().Ignored.Add(set.Itemize(id, target))
279 if err == set.ErrCollision {
280 return fmt.Errorf("user already ignored: %s", id)
281 } else if err != nil {
285 room.Send(message.NewSystemMsg(fmt.Sprintf("Ignoring: %s", target.Name()), msg.From()))
293 Handler: func(room *Room, msg message.CommandMsg) error {
294 id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/unignore"))
296 return errors.New("must specify user")
299 if err := msg.From().Ignored.Remove(id); err != nil {
303 room.Send(message.NewSystemMsg(fmt.Sprintf("No longer ignoring: %s", id), msg.From()))