diff --git a/irc/channel.go b/irc/channel.go index ec309ede..80c8360d 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -145,13 +145,7 @@ func (channel *Channel) Part(client *Client, message string) { } channel.Reply(RplPart(client, channel, message)) - - channel.members.Remove(client) - client.channels.Remove(channel) - - for member := range channel.members { - member.RemoveFriend(client) - } + channel.Quit(client) if channel.IsEmpty() { channel.server.channels.Remove(channel) @@ -305,4 +299,23 @@ func (channel *Channel) Quit(client *Client) { } channel.members.Remove(client) + client.channels.Remove(channel) +} + +func (channel *Channel) Kick(client *Client, target *Client, comment string) { + if !channel.members.Has(client) { + client.Reply(ErrNotOnChannel(channel)) + return + } + if !channel.ClientIsOperator(client) { + client.Reply(ErrChanOPrivIsNeeded(channel)) + return + } + if !channel.members.Has(target) { + client.Reply(ErrUserNotInChannel(channel, target)) + return + } + + channel.Reply(RplKick(channel, client, target, comment)) + channel.Quit(target) } diff --git a/irc/client.go b/irc/client.go index 29e9e152..ca1b0818 100644 --- a/irc/client.go +++ b/irc/client.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "strings" "time" ) @@ -57,9 +58,8 @@ func (client *Client) readCommands() { if err != nil { switch err { case NotEnoughArgsError: - client.Reply(ErrNeedMoreParams(client.server, line)) - default: - client.Reply(ErrUnknownCommand(client.server, line)) + parts := strings.SplitN(line, " ", 2) + client.Reply(ErrNeedMoreParams(client.server, parts[0])) } continue } diff --git a/irc/commands.go b/irc/commands.go index 8d45dfd5..e546de88 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -9,7 +9,7 @@ import ( type editableCommand interface { Command - SetName(string) + SetCode(StringCode) SetClient(*Client) } @@ -18,33 +18,34 @@ type parseCommandFunc func([]string) (editableCommand, error) var ( NotEnoughArgsError = errors.New("not enough arguments") ErrParseCommand = errors.New("failed to parse message") - parseCommandFuncs = map[string]parseCommandFunc{ - "AWAY": NewAwayCommand, - "CAP": NewCapCommand, - "ISON": NewIsOnCommand, - "JOIN": NewJoinCommand, - "MODE": NewModeCommand, - "MOTD": NewMOTDCommand, - "NICK": NewNickCommand, - "NOTICE": NewNoticeCommand, - "OPER": NewOperCommand, - "PART": NewPartCommand, - "PASS": NewPassCommand, - "PING": NewPingCommand, - "PONG": NewPongCommand, - "PRIVMSG": NewPrivMsgCommand, - "PROXY": NewProxyCommand, - "QUIT": NewQuitCommand, - "TOPIC": NewTopicCommand, - "USER": NewUserMsgCommand, - "WHO": NewWhoCommand, - "WHOIS": NewWhoisCommand, + parseCommandFuncs = map[StringCode]parseCommandFunc{ + AWAY: NewAwayCommand, + CAP: NewCapCommand, + ISON: NewIsOnCommand, + JOIN: NewJoinCommand, + KICK: NewKickCommand, + MODE: NewModeCommand, + MOTD: NewMOTDCommand, + NICK: NewNickCommand, + NOTICE: NewNoticeCommand, + OPER: NewOperCommand, + PART: NewPartCommand, + PASS: NewPassCommand, + PING: NewPingCommand, + PONG: NewPongCommand, + PRIVMSG: NewPrivMsgCommand, + PROXY: NewProxyCommand, + QUIT: NewQuitCommand, + TOPIC: NewTopicCommand, + USER: NewUserMsgCommand, + WHO: NewWhoCommand, + WHOIS: NewWhoisCommand, } ) type BaseCommand struct { client *Client - name string + code StringCode } func (command *BaseCommand) Client() *Client { @@ -55,12 +56,12 @@ func (command *BaseCommand) SetClient(c *Client) { command.client = c } -func (command *BaseCommand) Name() string { - return command.name +func (command *BaseCommand) Code() StringCode { + return command.code } -func (command *BaseCommand) SetName(name string) { - command.name = name +func (command *BaseCommand) SetCode(code StringCode) { + command.code = code } func (command *BaseCommand) Source() Identifier { @@ -72,15 +73,15 @@ func (command *BaseCommand) Reply(reply Reply) { } func ParseCommand(line string) (cmd editableCommand, err error) { - command, args := parseLine(line) - constructor := parseCommandFuncs[command] + code, args := parseLine(line) + constructor := parseCommandFuncs[code] if constructor == nil { - cmd = NewUnknownCommand(command, args) + cmd = NewUnknownCommand(args) } else { cmd, err = constructor(args) } if cmd != nil { - cmd.SetName(command) + cmd.SetCode(code) } return } @@ -102,13 +103,13 @@ func parseArg(line string) (arg string, rest string) { return } -func parseLine(line string) (command string, args []string) { +func parseLine(line string) (command StringCode, args []string) { args = make([]string, 0) for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) { args = append(args, arg) } if len(args) > 0 { - command, args = strings.ToUpper(args[0]), args[1:] + command, args = StringCode(strings.ToUpper(args[0])), args[1:] } return } @@ -121,10 +122,10 @@ type UnknownCommand struct { } func (cmd *UnknownCommand) String() string { - return fmt.Sprintf("UNKNOWN(command=%s, args=%s)", cmd.Name(), cmd.args) + return fmt.Sprintf("UNKNOWN(command=%s, args=%s)", cmd.Code(), cmd.args) } -func NewUnknownCommand(command string, args []string) *UnknownCommand { +func NewUnknownCommand(args []string) *UnknownCommand { return &UnknownCommand{ args: args, } @@ -739,3 +740,41 @@ func NewNoticeCommand(args []string) (editableCommand, error) { message: args[1], }, nil } + +type KickCommand struct { + BaseCommand + kicks map[string]string + comment string +} + +func (msg *KickCommand) Comment() string { + if msg.comment == "" { + return msg.Source().Nick() + } + return msg.comment +} + +func NewKickCommand(args []string) (editableCommand, error) { + if len(args) < 2 { + return nil, NotEnoughArgsError + } + channels := strings.Split(args[0], ",") + users := strings.Split(args[1], ",") + if (len(channels) != len(users)) && (len(users) != 1) { + return nil, NotEnoughArgsError + } + cmd := &KickCommand{ + kicks: make(map[string]string), + } + for index, channel := range channels { + if len(users) == 1 { + cmd.kicks[channel] = users[0] + } else { + cmd.kicks[channel] = users[index] + } + } + if len(args) > 2 { + cmd.comment = args[2] + } + return cmd, nil +} diff --git a/irc/constants.go b/irc/constants.go index 710c3398..2bd63fcc 100644 --- a/irc/constants.go +++ b/irc/constants.go @@ -24,18 +24,29 @@ const ( QUIT_TIMEOUT = time.Minute // how long after idle before a client is kicked // string codes - PRIVMSG StringCode = "PRIVMSG" - NOTICE StringCode = "NOTICE" - NICK StringCode = "NICK" - JOIN StringCode = "JOIN" - PART StringCode = "PART" - MODE StringCode = "MODE" - TOPIC StringCode = "TOPIC" - PING StringCode = "PING" - PONG StringCode = "PONG" - QUIT StringCode = "QUIT" + AWAY StringCode = "AWAY" + CAP StringCode = "CAP" ERROR StringCode = "ERROR" INVITE StringCode = "INVITE" + ISON StringCode = "ISON" + JOIN StringCode = "JOIN" + KICK StringCode = "KICK" + MODE StringCode = "MODE" + MOTD StringCode = "MOTD" + NICK StringCode = "NICK" + NOTICE StringCode = "NOTICE" + OPER StringCode = "OPER" + PART StringCode = "PART" + PASS StringCode = "PASS" + PING StringCode = "PING" + PONG StringCode = "PONG" + PRIVMSG StringCode = "PRIVMSG" + PROXY StringCode = "PROXY" + QUIT StringCode = "QUIT" + TOPIC StringCode = "TOPIC" + USER StringCode = "USER" + WHO StringCode = "WHO" + WHOIS StringCode = "WHOIS" // numeric codes RPL_WELCOME NumericCode = 1 diff --git a/irc/reply.go b/irc/reply.go index a0cee495..384d2070 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -183,6 +183,11 @@ func RplInviteMsg(channel *Channel, inviter *Client) Reply { return NewStringReply(inviter, INVITE, channel.name) } +func RplKick(channel *Channel, client *Client, target *Client, comment string) Reply { + return NewStringReply(client, KICK, "%s %s :%s", + channel, target.Nick(), comment) +} + // numeric replies func RplWelcome(source Identifier, client *Client) Reply { @@ -330,9 +335,9 @@ func ErrNickNameInUse(source Identifier, nick string) Reply { "%s :Nickname is already in use", nick) } -func ErrUnknownCommand(source Identifier, command string) Reply { +func ErrUnknownCommand(source Identifier, code StringCode) Reply { return NewNumericReply(source, ERR_UNKNOWNCOMMAND, - "%s :Unknown command", command) + "%s :Unknown command", code) } func ErrUsersDontMatch(source Identifier) Reply { @@ -381,7 +386,7 @@ func ErrPasswdMismatch(server *Server) Reply { func ErrNoChanModes(channel *Channel) Reply { return NewNumericReply(channel.server, ERR_NOCHANMODES, - "%s :Channel doesn't support modes", channel.name) + "%s :Channel doesn't support modes", channel) } func ErrNoPrivileges(server *Server) Reply { @@ -396,20 +401,20 @@ func ErrNoSuchServer(server *Server, target string) Reply { return NewNumericReply(server, ERR_NOSUCHSERVER, "%s :No such server", target) } -func ErrUserNotInChannel(server *Server, nick string, channel *Channel) Reply { - return NewNumericReply(server, ERR_USERNOTINCHANNEL, - "%s %s :They aren't on that channel", nick, channel.name) +func ErrUserNotInChannel(channel *Channel, client *Client) Reply { + return NewNumericReply(channel.server, ERR_USERNOTINCHANNEL, + "%s %s :They aren't on that channel", client.Nick(), channel) } func ErrCannotSendToChan(channel *Channel) Reply { return NewNumericReply(channel.server, ERR_CANNOTSENDTOCHAN, - "%s :Cannot send to channel", channel.name) + "%s :Cannot send to channel", channel) } // :You're not channel operator func ErrChanOPrivIsNeeded(channel *Channel) Reply { return NewNumericReply(channel.server, ERR_CHANOPRIVSNEEDED, - "%s :You're not channel operator", channel.name) + "%s :You're not channel operator", channel) } func ErrNoMOTD(server *Server) Reply { diff --git a/irc/server.go b/irc/server.go index df755c9a..d8d21e99 100644 --- a/irc/server.go +++ b/irc/server.go @@ -80,7 +80,7 @@ func (server *Server) ReceiveCommands() { default: serverCommand, ok := command.(ServerCommand) if !ok { - client.Reply(ErrUnknownCommand(server, command.Name())) + client.Reply(ErrUnknownCommand(server, command.Code())) continue } client.Touch() @@ -542,3 +542,22 @@ func (msg *NoticeCommand) HandleServer(server *Server) { } target.Reply(RplNotice(client, target, msg.message)) } + +func (msg *KickCommand) HandleServer(server *Server) { + client := msg.Client() + for chname, nickname := range msg.kicks { + channel := server.channels[chname] + if channel == nil { + client.Reply(ErrNoSuchChannel(server, chname)) + continue + } + + target := server.clients[nickname] + if target == nil { + client.Reply(ErrNoSuchNick(server, nickname)) + continue + } + + channel.Kick(client, target, msg.Comment()) + } +} diff --git a/irc/types.go b/irc/types.go index b3896d8b..beae3873 100644 --- a/irc/types.go +++ b/irc/types.go @@ -171,7 +171,7 @@ type Reply interface { } type Command interface { - Name() string + Code() StringCode Client() *Client Source() Identifier Reply(Reply)