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 {
190 if cfg.Theme != nil {
191 theme = cfg.Theme.ID()
193 body := fmt.Sprintf("Current theme: %s", theme)
194 room.Send(message.NewSystemMsg(body, user))
199 for _, t := range message.Themes {
203 body := fmt.Sprintf("Set theme: %s", id)
204 room.Send(message.NewSystemMsg(body, user))
208 return errors.New("theme not found")
214 Help: "Silence room announcements.",
215 Handler: func(room *Room, msg message.CommandMsg) error {
218 cfg.Quiet = !cfg.Quiet
223 body = "Quiet mode is toggled ON"
225 body = "Quiet mode is toggled OFF"
227 room.Send(message.NewSystemMsg(body, u))
235 Handler: func(room *Room, msg message.CommandMsg) error {
239 me = "slaps themselves around a bit with a large trout."
241 me = fmt.Sprintf("slaps %s around a bit with a large trout.", strings.Join(args, " "))
244 room.Send(message.NewEmoteMsg(me, msg.From()))
251 PrefixHelp: "[USER]",
252 Help: "Hide messages from USER, /unignore USER to stop hiding.",
253 Handler: func(room *Room, msg message.CommandMsg) error {
254 id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/ignore"))
256 // Print ignored names, if any.
258 msg.From().Ignored.Each(func(_ string, item set.Item) error {
259 names = append(names, item.Key())
265 systemMsg = "0 users ignored."
267 systemMsg = fmt.Sprintf("%d ignored: %s", len(names), strings.Join(names, ", "))
270 room.Send(message.NewSystemMsg(systemMsg, msg.From()))
274 if id == msg.From().ID() {
275 return errors.New("cannot ignore self")
277 target, ok := room.MemberByID(id)
279 return fmt.Errorf("user not found: %s", id)
282 err := msg.From().Ignored.Add(set.Itemize(id, target))
283 if err == set.ErrCollision {
284 return fmt.Errorf("user already ignored: %s", id)
285 } else if err != nil {
289 room.Send(message.NewSystemMsg(fmt.Sprintf("Ignoring: %s", target.Name()), msg.From()))
297 Handler: func(room *Room, msg message.CommandMsg) error {
298 id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/unignore"))
300 return errors.New("must specify user")
303 if err := msg.From().Ignored.Remove(id); err != nil {
307 room.Send(message.NewSystemMsg(fmt.Sprintf("No longer ignoring: %s", id), msg.From()))