From 6874601c0b4d583eaae38ba1aa38b9687c1f37e9 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Thu, 1 Jan 2015 17:09:08 -0800 Subject: [PATCH] Op command support, and /op --- chat/channel.go | 9 ++++- chat/command.go | 97 +++++++++++++++++++++++++++++-------------------- chat/help.go | 4 +- chat/user.go | 11 ------ 4 files changed, 68 insertions(+), 53 deletions(-) diff --git a/chat/channel.go b/chat/channel.go index 646fe0c..3bdca40 100644 --- a/chat/channel.go +++ b/chat/channel.go @@ -18,6 +18,7 @@ type Channel struct { topic string history *History users *Set + ops *Set broadcast chan Message commands Commands closed bool @@ -32,10 +33,16 @@ func NewChannel() *Channel { broadcast: broadcast, history: NewHistory(historyLen), users: NewSet(), - commands: *defaultCmdHandlers, + ops: NewSet(), + commands: *defaultCommands, } } +// SetCommands sets the channel's command handlers. +func (ch *Channel) SetCommands(commands Commands) { + ch.commands = commands +} + // Close the channel and all the users it contains. func (ch *Channel) Close() { ch.closeOnce.Do(func() { diff --git a/chat/command.go b/chat/command.go index 9047b4d..50d50e5 100644 --- a/chat/command.go +++ b/chat/command.go @@ -1,10 +1,10 @@ +// FIXME: Would be sweet if we could piggyback on a cli parser or something. package chat import ( "errors" "fmt" "strings" - "sync" ) // The error returned when an invalid command is issued. @@ -29,58 +29,41 @@ type Command struct { // If omitted, command is hidden from /help Help string Handler func(*Channel, CommandMsg) error + // Command requires Op permissions + Op bool } // Commands is a registry of available commands. -type Commands struct { - commands map[string]*Command - sync.RWMutex -} - -// NewCommands returns a new Commands registry. -func NewCommands() *Commands { - return &Commands{ - commands: map[string]*Command{}, - } -} +type Commands map[string]*Command // Add will register a command. If help string is empty, it will be hidden from // Help(). -func (c *Commands) Add(cmd Command) error { - c.Lock() - defer c.Unlock() - +func (c Commands) Add(cmd Command) error { if cmd.Prefix == "" { return ErrMissingPrefix } - c.commands[cmd.Prefix] = &cmd + c[cmd.Prefix] = &cmd return nil } // Alias will add another command for the same handler, won't get added to help. -func (c *Commands) Alias(command string, alias string) error { - c.Lock() - defer c.Unlock() - - cmd, ok := c.commands[command] +func (c Commands) Alias(command string, alias string) error { + cmd, ok := c[command] if !ok { return ErrInvalidCommand } - c.commands[alias] = cmd + c[alias] = cmd return nil } // Run executes a command message. -func (c *Commands) Run(channel *Channel, msg CommandMsg) error { +func (c Commands) Run(channel *Channel, msg CommandMsg) error { if msg.From == nil { return ErrNoOwner } - c.RLock() - defer c.RUnlock() - - cmd, ok := c.commands[msg.Command()] + cmd, ok := c[msg.Command()] if !ok { return ErrInvalidCommand } @@ -89,24 +72,35 @@ func (c *Commands) Run(channel *Channel, msg CommandMsg) error { } // Help will return collated help text as one string. -func (c *Commands) Help() string { - c.RLock() - defer c.RUnlock() - - // TODO: Could cache this... - help := NewCommandsHelp(c) - return help.String() +func (c Commands) Help(showOp bool) string { + // Filter by op + op := []*Command{} + normal := []*Command{} + for _, cmd := range c { + if cmd.Op { + op = append(op, cmd) + } else { + normal = append(normal, cmd) + } + } + help := "Available commands:" + Newline + NewCommandsHelp(normal).String() + if showOp { + help += Newline + "Operator commands:" + Newline + NewCommandsHelp(op).String() + } + return help } -var defaultCmdHandlers *Commands +var defaultCommands *Commands func init() { - c := NewCommands() + c := Commands{} c.Add(Command{ Prefix: "/help", Handler: func(channel *Channel, msg CommandMsg) error { - channel.Send(NewSystemMsg("Available commands:"+Newline+c.Help(), msg.From())) + user := msg.From() + op := channel.ops.In(user) + channel.Send(NewSystemMsg(channel.commands.Help(op), user)) return nil }, }) @@ -198,5 +192,30 @@ func init() { }, }) - defaultCmdHandlers = c + c.Add(Command{ + Prefix: "/op", + PrefixHelp: "USER", + Help: "Mark user as admin.", + Handler: func(channel *Channel, msg CommandMsg) error { + if !channel.ops.In(msg.From()) { + return errors.New("must be op") + } + + args := msg.Args() + if len(args) != 1 { + return errors.New("must specify user") + } + + // TODO: Add support for fingerprint-based op'ing. + user, err := channel.users.Get(Id(args[0])) + if err != nil { + return errors.New("user not found") + } + + channel.ops.Add(user) + return nil + }, + }) + + defaultCommands = &c } diff --git a/chat/help.go b/chat/help.go index c6e6856..9e9cd42 100644 --- a/chat/help.go +++ b/chat/help.go @@ -17,12 +17,12 @@ type help struct { } // NewCommandsHelp creates a help container from a commands container. -func NewCommandsHelp(c *Commands) *help { +func NewCommandsHelp(c []*Command) *help { lookup := map[string]struct{}{} h := help{ items: []helpItem{}, } - for _, cmd := range c.commands { + for _, cmd := range c { if cmd.Help == "" { // Skip hidden commands. continue diff --git a/chat/user.go b/chat/user.go index 4f113d5..542f0bc 100644 --- a/chat/user.go +++ b/chat/user.go @@ -16,7 +16,6 @@ var ErrUserClosed = errors.New("user closed") type User struct { Config UserConfig name string - op bool colorIdx int joined time.Time msg chan Message @@ -66,16 +65,6 @@ func (u *User) SetColorIdx(idx int) { u.colorIdx = idx } -// Return whether user is an admin -func (u *User) Op() bool { - return u.op -} - -// Set whether user is an admin -func (u *User) SetOp(op bool) { - u.op = op -} - // Block until user is closed func (u *User) Wait() { <-u.done