Merge pull request #25 from edmund-huber/theater_mode

WIP: adding theater-mode, fixes #15
This commit is contained in:
Jeremy Latt 2014-03-20 11:13:40 -07:00
commit 6403e79a5b
10 changed files with 212 additions and 31 deletions

@ -9,3 +9,6 @@ password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRP
[operator "root"]
password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor'
[theater "#ghostbusters"]
password = "JDJhJDA0JG0yY1h4cTRFUHhkcjIzN2p1M2Nvb2VEYjAzSHh4eTB3YkZ0VFRLV1ZPVXdqeFBSRUtmRlBT" ; 'venkman'

@ -6,14 +6,15 @@ import (
)
type Channel struct {
flags ChannelModeSet
lists map[ChannelMode]*UserMaskSet
key Text
members MemberSet
name Name
server *Server
topic Text
userLimit uint64
flags ChannelModeSet
lists map[ChannelMode]*UserMaskSet
key Text
members MemberSet
name Name
server *Server
topic Text
userLimit uint64
theaterUser *Client
}
// NewChannel creates a new channel from a `Server` and a `name`
@ -405,9 +406,11 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
return channel.applyModeMember(client, change.mode, change.op,
NewName(change.arg))
case Theater:
client.ErrConfiguredMode(change.mode)
default:
client.ErrUnknownMode(change.mode, channel)
return false
}
return false
}

@ -13,27 +13,28 @@ const (
)
type Client struct {
atime time.Time
authorized bool
awayMessage Text
capabilities CapabilitySet
capState CapState
channels ChannelSet
commands chan Command
ctime time.Time
flags map[UserMode]bool
hasQuit bool
hops uint
hostname Name
idleTimer *time.Timer
loginTimer *time.Timer
nick Name
quitTimer *time.Timer
realname Text
registered bool
server *Server
socket *Socket
username Name
atime time.Time
authorized bool
awayMessage Text
capabilities CapabilitySet
capState CapState
channels ChannelSet
commands chan Command
ctime time.Time
flags map[UserMode]bool
hasQuit bool
hops uint
hostname Name
idleTimer *time.Timer
loginTimer *time.Timer
nick Name
quitTimer *time.Timer
realname Text
registered bool
server *Server
socket *Socket
username Name
theaterChannels []*Channel
}
func NewClient(server *Server, conn net.Conn) *Client {
@ -259,6 +260,10 @@ func (client *Client) Quit(message Text) {
return
}
for _, channel := range client.theaterChannels {
delete(channel.flags, Theater)
}
client.Reply(RplError("connection closed"))
client.hasQuit = true
client.server.whoWas.Append(client)

@ -49,6 +49,7 @@ var (
PRIVMSG: NewPrivMsgCommand,
PROXY: NewProxyCommand,
QUIT: NewQuitCommand,
THEATER: NewTheaterCommand, // nonstandard
TIME: NewTimeCommand,
TOPIC: NewTopicCommand,
USER: NewUserCommand,
@ -947,6 +948,31 @@ func NewInviteCommand(args []string) (Command, error) {
}, nil
}
func NewTheaterCommand(args []string) (Command, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
} else if upperSubCmd := strings.ToUpper(args[0]); upperSubCmd == "IDENTIFY" && len(args) == 3 {
return &TheaterIdentifyCommand{
channel: NewName(args[1]),
PassCommand: PassCommand{password: []byte(args[2])},
}, nil
} else if upperSubCmd == "PRIVMSG" && len(args) == 4 {
return &TheaterPrivMsgCommand{
channel: NewName(args[1]),
asNick: NewName(args[2]),
message: NewText(args[3]),
}, nil
} else if upperSubCmd == "ACTION" && len(args) == 4 {
return &TheaterActionCommand{
channel: NewName(args[1]),
asNick: NewName(args[2]),
action: NewText(args[3]),
}, nil
} else {
return nil, ErrParseCommand
}
}
type TimeCommand struct {
BaseCommand
target Name

@ -29,6 +29,8 @@ type Config struct {
}
Operator map[string]*PassConfig
Theater map[string]*PassConfig
}
func (conf *Config) Operators() map[Name][]byte {
@ -39,6 +41,18 @@ func (conf *Config) Operators() map[Name][]byte {
return operators
}
func (conf *Config) Theaters() map[Name][]byte {
theaters := make(map[Name][]byte)
for s, theaterConf := range conf.Theater {
name := NewName(s)
if !name.IsChannel() {
log.Fatal("config uses a non-channel for a theater!")
}
theaters[name] = theaterConf.PasswordBytes()
}
return theaters
}
func LoadConfig(filename string) (config *Config, err error) {
config = &Config{}
err = gcfg.ReadFileInto(config, filename)

@ -30,6 +30,7 @@ const (
PRIVMSG StringCode = "PRIVMSG"
PROXY StringCode = "PROXY"
QUIT StringCode = "QUIT"
THEATER StringCode = "THEATER" // nonstandard
TIME StringCode = "TIME"
TOPIC StringCode = "TOPIC"
USER StringCode = "USER"

@ -83,6 +83,7 @@ const (
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated
Theater ChannelMode = 'T' // flag arg, nonstandard
UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg
)
@ -90,7 +91,7 @@ const (
var (
SupportedChannelModes = ChannelModes{
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
OpOnlyTopic, Persistent, Private, UserLimit,
OpOnlyTopic, Persistent, Private, Theater, UserLimit,
}
)

@ -548,6 +548,11 @@ func (target *Client) ErrUnknownMode(mode ChannelMode, channel *Channel) {
"%s :is unknown mode char to me for %s", mode, channel)
}
func (target *Client) ErrConfiguredMode(mode ChannelMode) {
target.NumericReply(ERR_UNKNOWNMODE,
"%s :can only change this mode in daemon configuration", mode)
}
func (target *Client) ErrChannelIsFull(channel *Channel) {
target.NumericReply(ERR_CHANNELISFULL,
"%s :Cannot join channel (+l)", channel)

@ -40,6 +40,7 @@ type Server struct {
password []byte
signals chan os.Signal
whoWas *WhoWasList
theaters map[Name][]byte
}
var (
@ -61,6 +62,7 @@ func NewServer(config *Config) *Server {
operators: config.Operators(),
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
whoWas: NewWhoWasList(100),
theaters: config.Theaters(),
}
if config.Server.Password != "" {

121
irc/theater.go Normal file

@ -0,0 +1,121 @@
package irc
import (
"fmt"
)
type TheaterClient Name
func (c TheaterClient) Id() Name {
return Name(c)
}
func (c TheaterClient) Nick() Name {
return Name(c)
}
type TheaterSubCommand string
type theaterSubCommand interface {
String() string
}
type TheaterIdentifyCommand struct {
PassCommand
channel Name
}
func (m *TheaterIdentifyCommand) LoadPassword(s *Server) {
m.hash = s.theaters[m.channel]
}
func (cmd *TheaterIdentifyCommand) String() string {
return fmt.Sprintf("THEATER_IDENTIFY(channel=%s)", cmd.channel)
}
func (m *TheaterIdentifyCommand) HandleServer(s *Server) {
client := m.Client()
if !m.channel.IsChannel() {
client.ErrNoSuchChannel(m.channel)
return
}
channel := s.channels.Get(m.channel)
if channel == nil {
client.ErrNoSuchChannel(m.channel)
return
}
if (m.hash == nil) || (m.err != nil) {
client.ErrPasswdMismatch()
return
}
if channel.theaterUser == nil {
client.theaterChannels = append(client.theaterChannels, channel)
channel.flags[Theater] = true
channel.theaterUser = client
}
}
type TheaterPrivMsgCommand struct {
BaseCommand
channel Name
asNick Name
message Text
}
func (cmd *TheaterPrivMsgCommand) String() string {
return fmt.Sprintf("THEATER_PRIVMSG(channel=%s, asNick=%s, message=%s)", cmd.channel, cmd.asNick, cmd.message)
}
func (m *TheaterPrivMsgCommand) HandleServer(s *Server) {
client := m.Client()
if !m.channel.IsChannel() {
client.ErrNoSuchChannel(m.channel)
return
}
channel := s.channels.Get(m.channel)
if channel == nil {
client.ErrNoSuchChannel(m.channel)
return
}
if channel.theaterUser == client {
for member := range channel.members {
member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, m.message))
}
}
}
type TheaterActionCommand struct {
BaseCommand
channel Name
asNick Name
action Text
}
func (cmd *TheaterActionCommand) String() string {
return fmt.Sprintf("THEATER_ACTION(channel=%s, asNick=%s, action=%s)", cmd.channel, cmd.asNick, cmd.action)
}
func (m *TheaterActionCommand) HandleServer(s *Server) {
client := m.Client()
if m.channel.IsChannel() {
client.ErrNoSuchChannel(m.channel)
return
}
channel := s.channels.Get(m.channel)
if channel == nil {
client.ErrNoSuchChannel(m.channel)
return
}
if channel.theaterUser == client {
for member := range channel.members {
member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, NewText(fmt.Sprintf("\001ACTION %s\001", m.action))))
}
}
}