diff --git a/irc/channel.go b/irc/channel.go index bcf56cd4..5072c40b 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -84,6 +84,14 @@ func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) { } } +func IsPrivMsg(reply Reply) bool { + strReply, ok := reply.(*StringReply) + if !ok { + return false + } + return strReply.code == "PRIVMSG" +} + func (channel *Channel) receiveReplies(replies <-chan Reply) { for reply := range replies { if channel.destroyed { @@ -98,9 +106,10 @@ func (channel *Channel) receiveReplies(replies <-chan Reply) { } channel.mutex.Lock() for client := range channel.members { - if reply.Source() != Identifier(client) { - client.Reply(reply) + if IsPrivMsg(reply) && (reply.Source() == Identifier(client)) { + continue } + client.Reply(reply) } channel.mutex.Unlock() } @@ -187,9 +196,7 @@ func (channel *Channel) Join(client *Client) { channel.mutex.Unlock() client.channels.Add(channel) - reply := RplJoin(client, channel) - client.Reply(reply) - channel.Reply(reply) + channel.Reply(RplJoin(client, channel)) channel.GetTopic(client) channel.GetUsers(client) } @@ -216,9 +223,7 @@ func (m *PartCommand) HandleChannel(channel *Channel) { return } - reply := RplPart(client, channel, m.Message()) - client.Reply(reply) - channel.Reply(reply) + channel.Reply(RplPart(client, channel, m.Message())) channel.members.Remove(client) client.channels.Remove(channel) @@ -245,9 +250,7 @@ func (m *TopicCommand) HandleChannel(channel *Channel) { channel.topic = m.topic channel.GetTopic(client) - reply := RplTopicMsg(client, channel) - client.Reply(reply) - channel.Reply(reply) + channel.Reply(RplTopicMsg(client, channel)) return } @@ -267,10 +270,18 @@ func (m *PrivMsgCommand) HandleChannel(channel *Channel) { func (msg *ChannelModeCommand) HandleChannel(channel *Channel) { client := msg.Client() - for _, modeOp := range msg.modeOps { - switch modeOp.mode { + if len(msg.changes) == 0 { + client.Reply(RplChannelModeIs(channel)) + return + } + + changes := make(ChannelModeChanges, 0) + + for _, change := range msg.changes { + switch change.mode { case BanMask: // TODO add/remove + for _, banMask := range channel.banList { client.Reply(RplBanList(channel, banMask)) } @@ -282,12 +293,14 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) { continue } - switch modeOp.op { + switch change.op { case Add: - channel.flags[modeOp.mode] = true + channel.flags[change.mode] = true + changes = append(changes, change) case Remove: - delete(channel.flags, modeOp.mode) + delete(channel.flags, change.mode) + changes = append(changes, change) } case Key: @@ -296,34 +309,33 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) { continue } - switch modeOp.op { + switch change.op { case Add: - if modeOp.arg == "" { + if change.arg == "" { // TODO err reply continue } - channel.key = modeOp.arg + channel.key = change.arg + changes = append(changes, change) case Remove: channel.key = "" + changes = append(changes, change) } - } - mmode := ChannelMemberMode(modeOp.mode) - switch mmode { case ChannelOperator, Voice: if !channel.ClientIsOperator(client) { client.Reply(ErrChanOPrivIsNeeded(channel)) continue } - if modeOp.arg == "" { + if change.arg == "" { // TODO err reply continue } - target := channel.server.clients[modeOp.arg] + target := channel.server.clients[change.arg] if target == nil { // TODO err reply continue @@ -334,16 +346,21 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) { continue } - switch modeOp.op { + switch change.op { case Add: - channel.members[target][mmode] = true + channel.members[target][change.mode] = true + changes = append(changes, change) + case Remove: - channel.members[target][mmode] = false + channel.members[target][change.mode] = false + changes = append(changes, change) } } } - client.Reply(RplChannelModeIs(channel)) + if len(changes) > 0 { + channel.Reply(RplChannelMode(client, channel, changes)) + } } func (m *NoticeCommand) HandleChannel(channel *Channel) { diff --git a/irc/commands.go b/irc/commands.go index 546bbdba..480ed04f 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -402,6 +402,26 @@ func (change *ModeChange) String() string { return fmt.Sprintf("%s%s", change.op, change.mode) } +type ModeChanges []ModeChange + +func (changes ModeChanges) String() string { + if len(changes) == 0 { + return "" + } + + op := changes[0].op + str := changes[0].op.String() + for _, change := range changes { + if change.op == op { + str += change.mode.String() + } else { + op = change.op + str += " " + change.op.String() + } + } + return str +} + type ModeCommand struct { BaseCommand nickname string @@ -436,27 +456,47 @@ func (cmd *ModeCommand) String() string { return fmt.Sprintf("MODE(nickname=%s, changes=%s)", cmd.nickname, cmd.changes) } -type ChannelModeOp struct { +type ChannelModeChange struct { mode ChannelMode op ModeOp arg string } -func (op *ChannelModeOp) String() string { +func (op *ChannelModeChange) String() string { return fmt.Sprintf("{%s %s %s}", op.op, op.mode, op.arg) } +type ChannelModeChanges []ChannelModeChange + +func (changes ChannelModeChanges) String() string { + if len(changes) == 0 { + return "" + } + + str := "+" + if changes[0].op == Remove { + str = "-" + } + for _, change := range changes { + str += change.mode.String() + } + for _, change := range changes { + str += " " + change.arg + } + return str +} + type ChannelModeCommand struct { BaseCommand channel string - modeOps []ChannelModeOp + changes ChannelModeChanges } // MODE *( ( "-" / "+" ) * * ) func NewChannelModeCommand(args []string) (editableCommand, error) { cmd := &ChannelModeCommand{ channel: args[0], - modeOps: make([]ChannelModeOp, 0), + changes: make(ChannelModeChanges, 0), } args = args[1:] @@ -472,18 +512,18 @@ func NewChannelModeCommand(args []string) (editableCommand, error) { skipArgs := 1 for _, mode := range modeArg { - modeOp := ChannelModeOp{ + change := ChannelModeChange{ mode: ChannelMode(mode), op: op, } - switch modeOp.mode { + switch change.mode { case Key, BanMask, ExceptionMask, InviteMask, UserLimit: if len(args) > skipArgs { - modeOp.arg = args[skipArgs] + change.arg = args[skipArgs] skipArgs += 1 } } - cmd.modeOps = append(cmd.modeOps, modeOp) + cmd.changes = append(cmd.changes, change) } args = args[skipArgs:] } @@ -492,7 +532,7 @@ func NewChannelModeCommand(args []string) (editableCommand, error) { } func (msg *ChannelModeCommand) String() string { - return fmt.Sprintf("MODE(channel=%s, modeOps=%s)", msg.channel, msg.modeOps) + return fmt.Sprintf("MODE(channel=%s, changes=%s)", msg.channel, msg.changes) } func NewModeCommand(args []string) (editableCommand, error) { diff --git a/irc/constants.go b/irc/constants.go index 4704baaf..0ef8bd87 100644 --- a/irc/constants.go +++ b/irc/constants.go @@ -173,24 +173,23 @@ const ( ServerNotice UserMode = 's' WallOps UserMode = 'w' - Anonymous ChannelMode = 'a' // flag - BanMask ChannelMode = 'b' // arg - ExceptionMask ChannelMode = 'e' // arg - InviteMask ChannelMode = 'I' // arg - InviteOnly ChannelMode = 'i' // flag - Key ChannelMode = 'k' // flag arg - Moderated ChannelMode = 'm' // flag - NoOutside ChannelMode = 'n' // flag - OpOnlyTopic ChannelMode = 't' // flag - Private ChannelMode = 'p' // flag - Quiet ChannelMode = 'q' // flag - ReOp ChannelMode = 'r' // flag - Secret ChannelMode = 's' // flag - UserLimit ChannelMode = 'l' // flag arg - - ChannelCreator ChannelMemberMode = 'O' // flag - ChannelOperator ChannelMemberMode = 'o' // arg - Voice ChannelMemberMode = 'v' // arg + Anonymous ChannelMode = 'a' // flag + BanMask ChannelMode = 'b' // arg + ChannelCreator ChannelMode = 'O' // flag + ChannelOperator ChannelMode = 'o' // arg + ExceptionMask ChannelMode = 'e' // arg + InviteMask ChannelMode = 'I' // arg + InviteOnly ChannelMode = 'i' // flag + Key ChannelMode = 'k' // flag arg + Moderated ChannelMode = 'm' // flag + NoOutside ChannelMode = 'n' // flag + OpOnlyTopic ChannelMode = 't' // flag + Private ChannelMode = 'p' // flag + Quiet ChannelMode = 'q' // flag + ReOp ChannelMode = 'r' // flag + Secret ChannelMode = 's' // flag + UserLimit ChannelMode = 'l' // flag arg + Voice ChannelMode = 'v' // arg ) const ( diff --git a/irc/reply.go b/irc/reply.go index 7099a819..54d2be27 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -144,11 +144,20 @@ func RplJoin(client *Client, channel *Channel) Reply { } func RplPart(client *Client, channel *Channel, message string) Reply { - return NewStringReply(client, "PART", "%s :%s", channel.name, message) + return NewStringReply(client, "PART", "%s :%s", channel, message) +} + +func RplMode(client *Client, changes ModeChanges) Reply { + return NewStringReply(client, "MODE", "%s :%s", client.Nick(), changes) +} + +func RplChannelMode(client *Client, channel *Channel, + changes ChannelModeChanges) Reply { + return NewStringReply(client, "MODE", "%s %s", channel, changes) } func RplTopicMsg(source Identifier, channel *Channel) Reply { - return NewStringReply(source, "TOPIC", "%s :%s", channel.name, channel.topic) + return NewStringReply(source, "TOPIC", "%s :%s", channel, channel.topic) } func RplPing(server *Server, target Identifier) Reply { @@ -207,9 +216,11 @@ func RplTopic(channel *Channel) Reply { "%s :%s", channel.name, channel.topic) } +// +// NB: correction in errata func RplInvitingMsg(channel *Channel, invitee *Client) Reply { return NewNumericReply(channel.server, RPL_INVITING, - "%s %s", channel.name, invitee.Nick()) + "%s %s", invitee.Nick(), channel.name) } func RplNamReply(channel *Channel, names []string) *NumericReply { @@ -238,7 +249,7 @@ func RplEndOfWhois(server *Server) Reply { func RplChannelModeIs(channel *Channel) Reply { return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s", - channel.name, channel.ModeString()) + channel, channel.ModeString()) } // ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ] diff --git a/irc/server.go b/irc/server.go index 1f5491c0..6648e129 100644 --- a/irc/server.go +++ b/irc/server.go @@ -270,7 +270,6 @@ func (m *NickCommand) HandleRegServer(s *Server) { client.nick = m.nickname s.clients.Add(client) - client.Reply(RplNick(client, m.nickname)) s.tryRegister(client) } @@ -414,22 +413,31 @@ func (m *PrivMsgCommand) HandleServer(s *Server) { func (m *ModeCommand) HandleServer(s *Server) { client := m.Client() target := s.clients[m.nickname] - if client == target { - for _, change := range m.changes { - if change.mode == Invisible { - switch change.op { - case Add: - client.invisible = true - case Remove: - client.invisible = false - } - } - } - client.Reply(RplUModeIs(s, client)) + // TODO other auth + if client != target { + client.Reply(ErrUsersDontMatch(s)) return } - client.Reply(ErrUsersDontMatch(s)) + changes := make(ModeChanges, 0) + + for _, change := range m.changes { + if change.mode == Invisible { + switch change.op { + case Add: + client.invisible = true + changes = append(changes, change) + + case Remove: + client.invisible = false + changes = append(changes, change) + } + } + } + + if len(changes) > 0 { + client.Reply(RplMode(client, changes)) + } } func (m *WhoisCommand) HandleServer(server *Server) { diff --git a/irc/types.go b/irc/types.go index 487bccab..554f5093 100644 --- a/irc/types.go +++ b/irc/types.go @@ -15,6 +15,10 @@ type Mask string // add, remove, list modes type ModeOp rune +func (op ModeOp) String() string { + return string(op) +} + // user mode flags type UserMode rune @@ -37,9 +41,6 @@ func (mode ChannelMode) String() string { return fmt.Sprintf("%c", mode) } -// user-channel mode flags -type ChannelMemberMode rune - type ChannelNameMap map[string]*Channel func (channels ChannelNameMap) Add(channel *Channel) error { @@ -84,19 +85,19 @@ func (clients ClientNameMap) Remove(client *Client) error { return nil } -type ChannelMemberModeSet map[ChannelMemberMode]bool +type ChannelModeSet map[ChannelMode]bool -type ClientSet map[*Client]ChannelMemberModeSet +type ClientSet map[*Client]ChannelModeSet func (clients ClientSet) Add(client *Client) { - clients[client] = make(ChannelMemberModeSet) + clients[client] = make(ChannelModeSet) } func (clients ClientSet) Remove(client *Client) { delete(clients, client) } -func (clients ClientSet) HasMode(client *Client, mode ChannelMemberMode) bool { +func (clients ClientSet) HasMode(client *Client, mode ChannelMode) bool { modes, ok := clients[client] if !ok { return false