Op command support, and /op

This commit is contained in:
Andrey Petrov 2015-01-01 17:09:08 -08:00
parent 3b5f4faf76
commit 6874601c0b
4 changed files with 68 additions and 53 deletions

View File

@ -18,6 +18,7 @@ type Channel struct {
topic string topic string
history *History history *History
users *Set users *Set
ops *Set
broadcast chan Message broadcast chan Message
commands Commands commands Commands
closed bool closed bool
@ -32,10 +33,16 @@ func NewChannel() *Channel {
broadcast: broadcast, broadcast: broadcast,
history: NewHistory(historyLen), history: NewHistory(historyLen),
users: NewSet(), 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. // Close the channel and all the users it contains.
func (ch *Channel) Close() { func (ch *Channel) Close() {
ch.closeOnce.Do(func() { ch.closeOnce.Do(func() {

View File

@ -1,10 +1,10 @@
// FIXME: Would be sweet if we could piggyback on a cli parser or something.
package chat package chat
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"sync"
) )
// The error returned when an invalid command is issued. // The error returned when an invalid command is issued.
@ -29,58 +29,41 @@ type Command struct {
// If omitted, command is hidden from /help // If omitted, command is hidden from /help
Help string Help string
Handler func(*Channel, CommandMsg) error Handler func(*Channel, CommandMsg) error
// Command requires Op permissions
Op bool
} }
// Commands is a registry of available commands. // Commands is a registry of available commands.
type Commands struct { type Commands map[string]*Command
commands map[string]*Command
sync.RWMutex
}
// NewCommands returns a new Commands registry.
func NewCommands() *Commands {
return &Commands{
commands: map[string]*Command{},
}
}
// Add will register a command. If help string is empty, it will be hidden from // Add will register a command. If help string is empty, it will be hidden from
// Help(). // Help().
func (c *Commands) Add(cmd Command) error { func (c Commands) Add(cmd Command) error {
c.Lock()
defer c.Unlock()
if cmd.Prefix == "" { if cmd.Prefix == "" {
return ErrMissingPrefix return ErrMissingPrefix
} }
c.commands[cmd.Prefix] = &cmd c[cmd.Prefix] = &cmd
return nil return nil
} }
// Alias will add another command for the same handler, won't get added to help. // Alias will add another command for the same handler, won't get added to help.
func (c *Commands) Alias(command string, alias string) error { func (c Commands) Alias(command string, alias string) error {
c.Lock() cmd, ok := c[command]
defer c.Unlock()
cmd, ok := c.commands[command]
if !ok { if !ok {
return ErrInvalidCommand return ErrInvalidCommand
} }
c.commands[alias] = cmd c[alias] = cmd
return nil return nil
} }
// Run executes a command message. // 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 { if msg.From == nil {
return ErrNoOwner return ErrNoOwner
} }
c.RLock() cmd, ok := c[msg.Command()]
defer c.RUnlock()
cmd, ok := c.commands[msg.Command()]
if !ok { if !ok {
return ErrInvalidCommand return ErrInvalidCommand
} }
@ -89,24 +72,35 @@ func (c *Commands) Run(channel *Channel, msg CommandMsg) error {
} }
// Help will return collated help text as one string. // Help will return collated help text as one string.
func (c *Commands) Help() string { func (c Commands) Help(showOp bool) string {
c.RLock() // Filter by op
defer c.RUnlock() op := []*Command{}
normal := []*Command{}
// TODO: Could cache this... for _, cmd := range c {
help := NewCommandsHelp(c) if cmd.Op {
return help.String() 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() { func init() {
c := NewCommands() c := Commands{}
c.Add(Command{ c.Add(Command{
Prefix: "/help", Prefix: "/help",
Handler: func(channel *Channel, msg CommandMsg) error { 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 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
} }

View File

@ -17,12 +17,12 @@ type help struct {
} }
// NewCommandsHelp creates a help container from a commands container. // NewCommandsHelp creates a help container from a commands container.
func NewCommandsHelp(c *Commands) *help { func NewCommandsHelp(c []*Command) *help {
lookup := map[string]struct{}{} lookup := map[string]struct{}{}
h := help{ h := help{
items: []helpItem{}, items: []helpItem{},
} }
for _, cmd := range c.commands { for _, cmd := range c {
if cmd.Help == "" { if cmd.Help == "" {
// Skip hidden commands. // Skip hidden commands.
continue continue

View File

@ -16,7 +16,6 @@ var ErrUserClosed = errors.New("user closed")
type User struct { type User struct {
Config UserConfig Config UserConfig
name string name string
op bool
colorIdx int colorIdx int
joined time.Time joined time.Time
msg chan Message msg chan Message
@ -66,16 +65,6 @@ func (u *User) SetColorIdx(idx int) {
u.colorIdx = idx 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 // Block until user is closed
func (u *User) Wait() { func (u *User) Wait() {
<-u.done <-u.done