diff --git a/channel.go b/channel.go index 19e4ae9..f1f2fbf 100644 --- a/channel.go +++ b/channel.go @@ -9,12 +9,12 @@ import ( // Channel represents an IRC channel or room type Channel struct { - Name string - Modes ChannelModeSet + Name string + *ChannelModeSet Topic string Key string - members map[string]ChannelModeSet + members map[string]*ChannelModeSet membersMutex sync.RWMutex Server *Server @@ -23,17 +23,17 @@ type Channel struct { // NewChannel creates and returns a new Channel func NewChannel(s *Server, creator *Client) *Channel { c := &Channel{} - c.members = map[string]ChannelModeSet{} + c.members = map[string]*ChannelModeSet{} c.Server = s - c.Modes = NewChannelModeSet() + c.ChannelModeSet = NewChannelModeSet() return c } // Join handles a client joining the channel and notifies other channel members func (c *Channel) Join(client *Client, key string) { - _, ok := c.members[client.Nickname] - if ok { // client is already in this channel + + if c.HasMember(client) { // client is already in this channel return } if len(c.Key) != 0 { //if key is required, verify that client provided matching key @@ -46,13 +46,14 @@ func (c *Channel) Join(client *Client, key string) { return } } - operator := len(c.members) == 0 + creator := c.GetMemberCount() == 0 c.AddMember(client) client.AddChannel(c) - if operator { // Client is creating channel + if creator { // Client is creating channel // Creator should be a channel operator - Maybe check if it is a "safe" channel + c.AddMemberMode(client, ChannelModeOperator) } @@ -85,6 +86,9 @@ func (c *Channel) Join(client *Client, key string) { mClient, _ := client.Server.GetClientByNick(member) if mClient != nil { + if c.MemberHasMode(mClient, ChannelModeOperator) { + memberStr += "@" + } memberStr += mClient.Nickname + " " mClient.Encode(&m) @@ -104,8 +108,7 @@ func (c *Channel) Join(client *Client, key string) { // Part handles when a client leaves a channel func (c *Channel) Part(client *Client, message string) { - _, ok := c.members[client.Nickname] - if !ok { // client is not in this channel + if !c.HasMember(client) { // client is not in this channel m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"} client.Encode(&m) return @@ -125,8 +128,7 @@ func (c *Channel) Part(client *Client, message string) { // Quit is when a client quits the server - at channel level, similar to part func (c *Channel) Quit(client *Client, message string) { - _, ok := c.members[client.Nickname] - if !ok { // client is not in this channel + if !c.HasMember(client) { // client is not in this channel //m := irc.Message{Prefix: &irc.Prefix{Name: client.Server.config.Name}, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"} //client.Encode(&m) return @@ -201,6 +203,21 @@ func (c *Channel) RemoveMember(client *Client) { } } +// HasMember returns if a client is an existing member of this channel +func (c *Channel) HasMember(client *Client) bool { + c.membersMutex.RLock() + defer c.membersMutex.RUnlock() + _, found := c.members[client.Nickname] + return found +} + +// GetMemberCount returns how many users are currently on the channel +func (c *Channel) GetMemberCount() int { + c.membersMutex.RLock() + defer c.membersMutex.RUnlock() + return len(c.members) +} + // AddMemberMode adds a mode for the member of the channel func (c *Channel) AddMemberMode(client *Client, mode ChannelMode) { c.membersMutex.Lock() @@ -224,6 +241,23 @@ func (c *Channel) RemoveMemberMode(client *Client, mode ChannelMode) { } +func (c *Channel) GetMemberModes(client *Client) *ChannelModeSet { + c.membersMutex.RLock() + defer c.membersMutex.RUnlock() + return c.members[client.Nickname] +} + +// MemberHasMode returns whether the given client has the requested mode +func (c *Channel) MemberHasMode(client *Client, mode ChannelMode) bool { + c.membersMutex.RLock() + defer c.membersMutex.RUnlock() + member, ok := c.members[client.Nickname] + if !ok { //client is not a member in channel + return false + } + return member.HasMode(mode) +} + func (c *Channel) delete() { if len(c.members) != 0 { return @@ -234,7 +268,7 @@ func (c *Channel) delete() { var channelStarters = map[uint8]interface{}{'&': nil, '#': nil, '+': nil, '!': nil} -// validName checks if it meets parameters found in rfc2812 1.3 +// validName checks if it meets parameters found in RFC 2812 Section 1.3 func (c *Channel) validName() bool { _, ok := channelStarters[c.Name[0]] if !ok { @@ -253,3 +287,48 @@ func (c *Channel) validName() bool { return true } + +// TopicCommand handles querying or modifying the channels topic +func (c *Channel) TopicCommand(client *Client, topic string) { + + if !c.HasMember(client) { // Client isn't on this channel + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"} + client.Encode(&m) + return + } + + //Client is trying to get topic + if len(topic) == 0 { //Get channels topic + if len(c.Topic) == 0 { // No topic is not set + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_NOTOPIC, Params: []string{c.Name}, Trailing: "No topic is set"} + client.Encode(&m) + return + } + + // Return Channel topic + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_TOPIC, Params: []string{c.Name}, Trailing: c.Topic} + client.Encode(&m) + return + + } + + // Client is trying to set topic + // If client is the operator, he can set the topic always + isOp := c.MemberHasMode(client, ChannelModeOperator) + tMode := c.HasMode(ChannelModeTopic) + + if isOp || !tMode { // Has permissions - operator or channel does not have +t mode + c.Topic = topic + //Notify channel members of new topic + m := irc.Message{Prefix: client.Prefix, Command: irc.TOPIC, Params: []string{c.Name}, Trailing: c.Topic} + c.SendMessage(&m) + return + } + + // Don't have permissions to set topic + + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_CHANOPRIVSNEEDED, Params: []string{c.Name}, Trailing: "You're not channel operator"} + client.Encode(&m) + return + +} diff --git a/client.go b/client.go index b874460..522dc4c 100644 --- a/client.go +++ b/client.go @@ -33,6 +33,8 @@ type Client struct { channels map[string]*Channel channelMutex sync.Mutex + + *UserModeSet } func (s *Server) newClient(ircConn *irc.Conn, conn net.Conn) *Client { @@ -40,6 +42,7 @@ func (s *Server) newClient(ircConn *irc.Conn, conn net.Conn) *Client { client.authorized = len(s.Config.Password) == 0 client.idleTimer = time.AfterFunc(time.Minute, client.idle) client.channels = map[string]*Channel{} + client.UserModeSet = NewUserModeSet() return client } @@ -53,13 +56,13 @@ func (c *Client) Close() error { // Ping sends an IRC PING command to a client func (c *Client) Ping() { - m := irc.Message{Command: irc.PING, Params: []string{"JuddBot"}, Trailing: "JuddBot"} + m := irc.Message{Command: irc.PING, Trailing: c.Server.Config.Name} c.Encode(&m) } // Pong sends an IRC PONG command to the client func (c *Client) Pong() { - m := irc.Message{Command: irc.PONG, Params: []string{"JuddBot"}, Trailing: "JuddBot"} + m := irc.Message{Command: irc.PONG, Trailing: c.Server.Config.Name} c.Encode(&m) } @@ -101,7 +104,7 @@ func (c *Client) idle() { func (c *Client) quit() { // Have client leave/part each channel for _, channel := range c.GetChannels() { - channel.Quit(c, "") + channel.Quit(c, "Disconnected") } c.Quit() } @@ -120,7 +123,7 @@ func (c *Client) Quit() { func (c *Client) Welcome() { // Have all client info now - c.Prefix = &irc.Prefix{Name: c.Nickname, User: c.Username, Host: c.Host} + c.Prefix = &irc.Prefix{Name: c.Nickname, User: c.Name, Host: c.Host} m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WELCOME, Params: []string{c.Nickname, c.Server.Config.Welcome}} diff --git a/commands.go b/commands.go index bd3c68b..53399c9 100644 --- a/commands.go +++ b/commands.go @@ -21,26 +21,30 @@ func (f CommandHandlerFunc) ServeIRC(message *irc.Message, client *Client) { } // PingHandler is a CommandHandler to respond to IRC PING commands from a client -// Implemented according to RFC 1459 4.6.2 and RFC 2812 3.7.2 +// Implemented according to RFC 1459 Section 4.6.2 and RFC 2812 Section 3.7.2 func PingHandler(message *irc.Message, client *Client) { client.Pong() } // PongHandler is a CommandHandler to respond to IRC PONG commands from a client -// Implemented according to RFC 1459 4.6.3 and RFC 2812 3.7.3 +// Implemented according to RFC 1459 Section 4.6.3 and RFC 2812 Section 3.7.3 func PongHandler(message *irc.Message, client *Client) { //client.Ping() } // QuitHandler is a CommandHandler to respond to IRC QUIT commands from a client -// Implemented according to RFC 1459 4.1.6 and RFC 2812 3.1.7 +// Implemented according to RFC 1459 Section 4.1.6 and RFC 2812 Section 3.1.7 func QuitHandler(message *irc.Message, client *Client) { var leavingMessage string if len(message.Params) != 0 { leavingMessage = message.Params[0] + } else if len(message.Trailing) != 0 { + leavingMessage = message.Trailing + } else { + leavingMessage = client.Nickname } for _, channel := range client.GetChannels() { channel.Quit(client, leavingMessage) @@ -53,7 +57,7 @@ func QuitHandler(message *irc.Message, client *Client) { } // NickHandler is a CommandHandler to respond to IRC NICK commands from a client -// Implemented according to RFC 1459 4.1.2 and RFC 2812 3.1.2 +// Implemented according to RFC 1459 Section 4.1.2 and RFC 2812 Section 3.1.2 func NickHandler(message *irc.Message, client *Client) { var m irc.Message @@ -97,7 +101,7 @@ func NickHandler(message *irc.Message, client *Client) { } // UserHandler is a CommandHandler to respond to IRC USER commands from a client -// Implemented according to RFC 1459 4.1.3 and RFC 2812 3.1.3 +// Implemented according to RFC 1459 Section 4.1.3 and RFC 2812 Section 3.1.3 func UserHandler(message *irc.Message, client *Client) { var m irc.Message serverName := client.Server.Config.Name @@ -136,7 +140,7 @@ func UserHandler(message *irc.Message, client *Client) { } // JoinHandler is a CommandHandler to respond to IRC JOIN commands from a client -// Implemented according to RFC 1459 4.2.1 and RFC 2812 3.2.1 +// Implemented according to RFC 1459 Section 4.2.1 and RFC 2812 Section 3.2.1 func JoinHandler(message *irc.Message, client *Client) { channelNames := message.Params[0] if channelNames == "0" { // Leave all channels @@ -177,7 +181,7 @@ func JoinHandler(message *irc.Message, client *Client) { } // PartHandler is a CommandHandler to respond to IRC PART commands from a client -// Implemented according to RFC 1459 4.2.2 and RFC 2812 3.2.2 +// Implemented according to RFC 1459 Section 4.2.2 and RFC 2812 Section 3.2.2 func PartHandler(message *irc.Message, client *Client) { if len(message.Params) == 0 { m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"} @@ -202,7 +206,7 @@ func PartHandler(message *irc.Message, client *Client) { } // PrivMsgHandler is a CommandHandler to respond to IRC PRIVMSG commands from a client -// Implemented according to RFC 1459 4.4.1 and RFC 2812 3.3.1 +// Implemented according to RFC 1459 Section 4.4.1 and RFC 2812 Section 3.3.1 func PrivMsgHandler(message *irc.Message, client *Client) { if len(message.Params) == 0 { m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NORECIPIENT, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"} @@ -232,6 +236,12 @@ func PrivMsgHandler(message *irc.Message, client *Client) { if ok { m := irc.Message{Prefix: client.Prefix, Command: irc.PRIVMSG, Params: []string{cl.Nickname}, Trailing: message.Trailing} cl.Encode(&m) + + if cl.HasMode(UserModeAway) { + m := irc.Message{Prefix: cl.Server.Prefix, Command: irc.RPL_AWAY, Params: []string{client.Nickname, cl.Nickname}, Trailing: cl.AwayMessage} + client.Encode(&m) + } + return } @@ -241,7 +251,7 @@ func PrivMsgHandler(message *irc.Message, client *Client) { } // NoticeHandler is a CommandHandler to respond to IRC NOTICE commands from a client -// Implemented according to RFC 1459 4.4.2 and RFC 2812 3.3.2 +// Implemented according to RFC 1459 Section 4.4.2 and RFC 2812 Section 3.3.2 func NoticeHandler(message *irc.Message, client *Client) { if len(message.Params) == 0 { m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NORECIPIENT, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"} @@ -280,7 +290,7 @@ func NoticeHandler(message *irc.Message, client *Client) { } // WhoHandler is a CommandHandler to respond to IRC WHO commands from a client -// Implemented according to RFC 1459 4.5.1 and RFC 2812 3.6.1 +// Implemented according to RFC 1459 Section 4.5.1 and RFC 2812 Section 3.6.1 func WhoHandler(message *irc.Message, client *Client) { if len(message.Params) == 0 { //return listing of all users @@ -292,13 +302,188 @@ func WhoHandler(message *irc.Message, client *Client) { cl, found := client.Server.GetClientByNick(clientName) if found { - msg := fmt.Sprintf("%s %s %s %s %s %s %s%s :%d %s", client.Nickname, ch.Name, cl.Name, cl.Host, client.Server.Config.Name, cl.Nickname, "H", "", 0, cl.RealName) + msg := whoLine(cl, ch, client.Nickname) m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)} client.Encode(&m) } } - m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFWHO, Params: []string{client.Nickname, ch.Name}, Trailing: "End of WHO list"} - client.Encode(&m) + } else { + // Not a channel, maybe a user + cl, ok := client.Server.GetClientByNick(message.Params[0]) + if ok { + msg := whoLine(cl, nil, client.Nickname) + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)} + client.Encode(&m) + } } + + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFWHO, Params: []string{client.Nickname, message.Params[0]}, Trailing: "End of WHO list"} + client.Encode(&m) + +} + +func whoLine(client *Client, channel *Channel, recipientClient string) string { + channelName := "*" + + here := "H" + if client.UserModeSet.HasMode(UserModeAway) { + here = "G" + } + opStatus := "" + if client.HasMode(UserModeOperator) || client.HasMode(UserModeLocalOperator) { + opStatus += "*" + } + if channel != nil { + channelName = channel.Name + if channel.MemberHasMode(client, ChannelModeOperator) { + opStatus += "@" + } + } + + hopCount := 0 //For now only local clients allowed - no federation + + return fmt.Sprintf("%s %s %s %s %s %s %s%s :%d %s", recipientClient, channelName, client.Name, client.Host, client.Server.Config.Name, client.Nickname, here, opStatus, hopCount, client.RealName) + +} + +// TopicHandler is a CommandHandler to respond to IRC TOPIC commands from a client +// Implemented according to RFC 1459 Section 4.2.4 and RFC 2812 Section 3.2.4 +func TopicHandler(message *irc.Message, client *Client) { + if len(message.Params) == 0 { + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"} + client.Encode(&m) + return + } + + channelName := message.Params[0] + channel, ok := client.Server.GetChannel(channelName) + if !ok { + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Params: []string{channelName}, Trailing: "No such channel"} + client.Encode(&m) + return + } + + m := "" + if len(message.Params) > 1 { + m = message.Params[1] + } else { + m = message.Trailing + } + channel.TopicCommand(client, m) + +} + +// AwayHandler is a CommandHandler to respond to IRC AWAY commands from a client +// Implemented according to RFC 1459 Section 5.1 and RFC 2812 Section 4.1 +func AwayHandler(message *irc.Message, client *Client) { + if len(message.Params) == 0 && len(message.Trailing) == 0 { + client.AwayMessage = "" + client.RemoveMode(UserModeAway) + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UNAWAY, Trailing: "You are no longer marked as being away"} + client.Encode(&m) + return + } + if len(message.Trailing) > 0 { + client.AwayMessage = message.Trailing + } else { + client.AwayMessage = strings.Join(message.Params, " ") + } + client.AddMode(UserModeAway) + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_NOWAWAY, Trailing: "You have been marked as being away"} + + client.Encode(&m) + return + +} + +// ModeHandler is a CommandHandler to respond to IRC MODE commands from a client +// Implemented according to RFC 1459 Section 4.2.3 and RFC 2812 Section 3.1.5 and RFC 2811 +func ModeHandler(message *irc.Message, client *Client) { + if len(message.Params) == 0 { + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"} + client.Encode(&m) + return + } + + id := message.Params[0] + _, ok := client.Server.GetChannel(id) + if ok { + ChannelModeHandler(message, client) + return + } + UserModeHandler(message, client) + +} + +// UserModeHandler is a specialized CommandHandler to respond to global or user IRC MODE commands from a client +// Implemented according to RFC 1459 Section 4.2.3.2 and RFC 2812 Section 3.1.5 +func UserModeHandler(message *irc.Message, client *Client) { + if len(message.Params) == 0 { + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"} + client.Encode(&m) + return + } + + username := message.Params[0] + if username != client.Nickname { + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_USERSDONTMATCH, Trailing: "Cannot change mode for other users"} + client.Encode(&m) + return + } + + if len(message.Params) == 1 { // just nickname is provided + // return current settings for this user + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{client.UserModeSet.String()}} + client.Encode(&m) + return + } + + for _, modeFlags := range message.Params[1:] { + modifier := ModeModifier(modeFlags[0]) + switch modifier { + case ModeModifierAdd: + case ModeModifierRemove: + default: + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Trailing: "Unknown MODE flag"} + client.Encode(&m) + return + } + + for _, modeFlag := range modeFlags[1:] { + mode := UserMode(modeFlag) + _, ok := UserModes[mode] + if !ok || mode == UserModeAway { // Away flag should only be set with AWAY command + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Trailing: "Unknown MODE flag"} + client.Encode(&m) + return + } + if modifier == ModeModifierAdd { + switch mode { + case UserModeOperator, UserModeLocalOperator: // Can't make oneself an operator + default: + client.AddMode(mode) + } + + } else if modifier == ModeModifierRemove { + switch mode { + case UserModeRestricted: // Can't remove oneself from being restricted + default: + client.RemoveMode(mode) + } + + } + } + } + + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{client.Nickname, client.UserModeSet.String()}} + client.Encode(&m) + return + +} + +// ChannelModeHandler is a specialized CommandHandler to respond to channel IRC MODE commands from a client +// Implemented according to RFC 1459 Section 4.2.3.1 and RFC 2811 +func ChannelModeHandler(message *irc.Message, client *Client) { + } diff --git a/modes.go b/modes.go index eec6667..7a832f7 100644 --- a/modes.go +++ b/modes.go @@ -2,7 +2,7 @@ package irc import "sync" -// UserMode - RFC 1459 4.2.3.2 and RFC 2812 3.1.5 +// UserMode - RFC 1459 Section 4.2.3.2 and RFC 2812 Section 3.1.5 type UserMode rune const ( @@ -15,6 +15,7 @@ const ( UserModeServerNotice UserMode = 's' //obsolete ) +// UserModes contains the supported User UserMode types var UserModes = map[UserMode]interface{}{ UserModeAway: nil, UserModeInvisible: nil, @@ -25,17 +26,20 @@ var UserModes = map[UserMode]interface{}{ UserModeServerNotice: nil, } +// UserModeSet provides means for storing and checking UserModes type UserModeSet struct { userModes map[UserMode]interface{} - mutex sync.Mutex + mutex sync.RWMutex } -func NewUserModeSet() UserModeSet { +// NewUserModeSet creates and returns a new UserModeSet +func NewUserModeSet() *UserModeSet { u := UserModeSet{} u.userModes = map[UserMode]interface{}{} - return u + return &u } +// AddMode adds a mode to the UserModeSet func (u *UserModeSet) AddMode(mode UserMode) { u.mutex.Lock() defer u.mutex.Unlock() @@ -43,6 +47,7 @@ func (u *UserModeSet) AddMode(mode UserMode) { } +// RemoveMode removes a mode from the UserModeSet func (u *UserModeSet) RemoveMode(mode UserMode) { u.mutex.Lock() defer u.mutex.Unlock() @@ -50,16 +55,28 @@ func (u *UserModeSet) RemoveMode(mode UserMode) { } +// HasMode detects if a UserMode is contained in the UserModeSet +func (u *UserModeSet) HasMode(mode UserMode) bool { + u.mutex.RLock() + defer u.mutex.RUnlock() + _, found := u.userModes[mode] + return found +} + +// String formats the UserModeString to be returned for MODE queries func (u *UserModeSet) String() string { s := "" - for m, _ := range u.userModes { + if len(u.userModes) > 0 { + s = "+" + } + for m := range u.userModes { s += string(m) } return s } -// ModeModifier - RFC 1459 4.2.3 and RFC 2812 3.1.5 +// ModeModifier - RFC 1459 Section 4.2.3 and RFC 2812 Section 3.1.5 type ModeModifier rune const ( @@ -67,7 +84,7 @@ const ( ModeModifierRemove ModeModifier = '-' ) -// ChannelMode - RFC 1459 4.2.3.1 and RFC 2812 3.2.3 and RFC 2811 4 +// ChannelMode - RFC 1459 Section 4.2.3.1 and RFC 2812 Section 3.2.3 and RFC 2811 Section 4 type ChannelMode rune const ( @@ -93,27 +110,75 @@ const ( ChannelModeInvitationMask ChannelMode = 'I' ) +// ChannelModes contains the supported channel modes +// Currently supported modes are just those in RFC 1459 +var ChannelModes = map[ChannelMode]interface{}{ + //ChannelModeCreator: nil, + ChannelModeOperator: nil, + ChannelModeVoice: nil, + //ChannelModeAnonymous: nil, + ChannelModeInviteOnly: nil, + ChannelModeModerated: nil, + ChannelModeNoOutsideMessages: nil, + //ChannelModeQuiet: nil, + ChannelModePrivate: nil, + ChannelModeSecret: nil, + //ChannelModeReOp: nil, + ChannelModeTopic: nil, + ChannelModeKey: nil, + ChannelModeLimit: nil, + ChannelModeBan: nil, + //ChannelModeExceptionMask: nil, + //ChannelModeInvitationMask: nil, +} + +// ChannelModeSet represents a set of active ChannelModes type ChannelModeSet struct { - Modes map[ChannelMode]interface{} - mutex sync.Mutex + modes map[ChannelMode]interface{} + mutex sync.RWMutex } -func NewChannelModeSet() ChannelModeSet { +// NewChannelModeSet creates and returns a new ChannelModeSet +func NewChannelModeSet() *ChannelModeSet { c := ChannelModeSet{} - c.Modes = map[ChannelMode]interface{}{} - return c + c.modes = map[ChannelMode]interface{}{} + return &c } +// AddMode adds a ChannelMode as active func (c *ChannelModeSet) AddMode(mode ChannelMode) { c.mutex.Lock() defer c.mutex.Unlock() - c.Modes[mode] = nil + c.modes[mode] = nil } +// RemoveMode removes the given mode from the active set func (c *ChannelModeSet) RemoveMode(mode ChannelMode) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.Modes, mode) + delete(c.modes, mode) } + +// HasMode determines if the ChannelModeSet contains the given ChannelMode +func (c *ChannelModeSet) HasMode(mode ChannelMode) bool { + c.mutex.RLock() + defer c.mutex.RUnlock() + _, found := c.modes[mode] + return found + +} + +// String returns the ChannelModeSet formatted for the MODE queries +func (c *ChannelModeSet) String() string { + s := "" + if len(c.modes) > 0 { + s = "+" + } + for m := range c.modes { + s += string(m) + } + + return s +}