diff --git a/src/irc/client.go b/src/irc/client.go index 75b2e729..52221a3d 100644 --- a/src/irc/client.go +++ b/src/irc/client.go @@ -5,18 +5,19 @@ import ( ) type Client struct { - addr net.Addr + conn net.Conn send chan<- string recv <-chan string username string realname string nick string registered bool + invisible bool } func NewClient(conn net.Conn) *Client { client := new(Client) - client.addr = conn.RemoteAddr() + client.conn = conn client.send = StringWriteChan(conn) client.recv = StringReadChan(conn) return client @@ -38,3 +39,10 @@ func (c *Client) Nick() string { } return "" } + +func (c *Client) UModeString() string { + if c.invisible { + return "+i" + } + return "" +} diff --git a/src/irc/commands.go b/src/irc/commands.go index 00d95884..6afd061b 100644 --- a/src/irc/commands.go +++ b/src/irc/commands.go @@ -1,5 +1,9 @@ package irc +type Message interface { + Handle(s *Server, c *Client) +} + func (m *NickMessage) Handle(s *Server, c *Client) { if s.nicks[m.nickname] != nil { c.send <- ErrNickNameInUse(m.nickname) @@ -24,6 +28,7 @@ func (m *UserMessage) Handle(s *Server, c *Client) { func (m *QuitMessage) Handle(s *Server, c *Client) { c.send <- MessageError() + c.conn.Close() delete(s.nicks, c.nick) } @@ -35,6 +40,21 @@ func (m *PingMessage) Handle(s *Server, c *Client) { c.send <- MessagePong() } +func (m *ModeMessage) Handle(s *Server, c *Client) { + if m.nickname != c.nick { + c.send <- ErrUsersDontMatch(c.Nick()) + return + } + for _, mode := range m.modes { + if mode == "+i" { + c.invisible = true + } else if mode == "-i" { + c.invisible = false + } + } + c.send <- ReplyUModeIs(c) +} + func tryRegister(s *Server, c *Client) { if (!c.registered && c.nick != "" && c.username != "") { c.registered = true diff --git a/src/irc/constants.go b/src/irc/constants.go index 9c3a637e..b7796e66 100644 --- a/src/irc/constants.go +++ b/src/irc/constants.go @@ -10,6 +10,7 @@ const ( RPL_YOURHOST = "002" RPL_CREATED = "003" RPL_MYINFO = "004" + RPL_UMODEIS = "221" RPL_NONE = "300" ) diff --git a/src/irc/message.go b/src/irc/message.go index 37282ffb..c3a7e944 100644 --- a/src/irc/message.go +++ b/src/irc/message.go @@ -1,9 +1,5 @@ package irc -type Message interface { - Handle(s *Server, c *Client) -} - type NickMessage struct { nickname string } @@ -15,7 +11,6 @@ type UserMessage struct { realname string } - type QuitMessage struct { message string } @@ -25,3 +20,8 @@ type UnknownMessage struct { } type PingMessage struct {} + +type ModeMessage struct { + nickname string + modes []string +} diff --git a/src/irc/net.go b/src/irc/net.go index 0bfc1bdd..ca70c06e 100644 --- a/src/irc/net.go +++ b/src/irc/net.go @@ -16,12 +16,13 @@ func readTrimmedLine(reader *bufio.Reader) (string, error) { func StringReadChan(conn net.Conn) <-chan string { ch := make(chan string) reader := bufio.NewReader(conn) + addr := conn.RemoteAddr() go func() { for { line, err := readTrimmedLine(reader) if (line != "") { ch <- line - log.Printf("%s -> %s", conn.RemoteAddr(), line) + log.Printf("%s -> %s", addr, line) } if err != nil { break @@ -35,13 +36,14 @@ func StringReadChan(conn net.Conn) <-chan string { func StringWriteChan(conn net.Conn) chan<- string { ch := make(chan string) writer := bufio.NewWriter(conn) + addr := conn.RemoteAddr() go func() { for str := range ch { if _, err := writer.WriteString(str + "\r\n"); err != nil { break } writer.Flush() - log.Printf("%s <- %s", conn.RemoteAddr(), str) + log.Printf("%s <- %s", addr, str) } close(ch) }() diff --git a/src/irc/parse.go b/src/irc/parse.go index 1ceea9b0..3fcda8e4 100644 --- a/src/irc/parse.go +++ b/src/irc/parse.go @@ -1,10 +1,33 @@ package irc import ( + "fmt" + "regexp" "strconv" "strings" ) +var commands = map[string]func([]string) Message { + "MODE": NewModeMessage, + "NICK": NewNickMessage, + "PING": NewPingMessage, + "QUIT": NewQuitMessage, + "USER": NewUserMessage, +} + +func ParseMessage(line string) Message { + command, args := parseLine(line) + constructor, ok := commands[command] + var msg Message + if ok { + msg = constructor(args) + } + if msg == nil { + msg = &UnknownMessage{command} + } + return msg +} + func parseArg(line string) (string, string) { if line == "" { return "", "" @@ -31,25 +54,6 @@ func parseLine(line string) (string, []string) { return args[0], args[1:] } -var commands = map[string]func([]string) Message { - "NICK": NewNickMessage, - "PING": NewPingMessage, - "QUIT": NewQuitMessage, - "USER": NewUserMessage, -} - -func ParseMessage(line string) Message { - command, args := parseLine(line) - constructor, ok := commands[command] - var msg Message - if ok { - msg = constructor(args) - } - if msg == nil { - msg = &UnknownMessage{command} - } - return msg -} // []string => Message constructors @@ -86,3 +90,25 @@ func NewUserMessage(args []string) Message { msg.realname = args[3] return msg } + +var MODE_RE = regexp.MustCompile("^[-+][a-zA-Z]+$") + +func NewModeMessage(args []string) Message { + if len(args) < 1 { + return nil + } + msg := new(ModeMessage) + msg.nickname = args[0] + for _, arg := range args[1:] { + if !MODE_RE.MatchString(arg) { + // TODO invalid args + return nil + } + prefix := arg[0] + for _, c := range arg[1:] { + mode := fmt.Sprintf("%c%c", prefix, c) + msg.modes = append(msg.modes, mode) + } + } + return msg +} diff --git a/src/irc/responses.go b/src/irc/responses.go index c9710692..b444c213 100644 --- a/src/irc/responses.go +++ b/src/irc/responses.go @@ -17,7 +17,11 @@ func ReplyCreated(nick string, created string) string { } func ReplyMyInfo(nick string, servername string) string { - return fmt.Sprintf("%s %s %s %s ", RPL_MYINFO, nick, servername, VERSION) + return fmt.Sprintf("%s %s %s %s i ", RPL_MYINFO, nick, servername, VERSION) +} + +func ReplyUModeIs(c *Client) string { + return fmt.Sprintf("%s %s %s", RPL_UMODEIS, c.Nick(), c.UModeString()) } func ErrAlreadyRegistered(nick string) string { @@ -32,6 +36,10 @@ func ErrUnknownCommand(nick string, command string) string { return fmt.Sprintf("%s %s %s :Unknown command", ERR_UNKNOWNCOMMAND, nick, command) } +func ErrUsersDontMatch(nick string) string { + return fmt.Sprintf("%s %s :Cannot change mode for other users", ERR_USERSDONTMATCH, nick) +} + func MessagePong() string { return "PONG" }