From 06648393a17fc08bc2feb814115758d36fa54405 Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Sat, 8 Feb 2014 17:10:04 -0800 Subject: [PATCH] implement join/quit and channel messages properly --- README.md | 1 + irc/channel.go | 11 +++--- irc/client.go | 58 +++++++++++++++++--------------- irc/constants.go | 3 +- irc/net.go | 9 +++-- irc/reply.go | 15 ++++----- irc/server.go | 87 ++++++++++++++++++++++++++---------------------- 7 files changed, 100 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 03ba3a16..28d5a542 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ I wanted to learn Go. ## Helpful Documentation +- [RFC 1459: Internet Relay Chat Protocol](http://tools.ietf.org/html/rfc1459) - [RFC 2811: IRC Channel Management](http://tools.ietf.org/html/rfc2811) - [RFC 2812: IRC Client Protocol](http://tools.ietf.org/html/rfc2812) - [RFC 2813: IRC Server Protocol](http://tools.ietf.org/html/rfc2813) diff --git a/irc/channel.go b/irc/channel.go index 36ee3831..fa958b30 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -55,7 +55,10 @@ func (channel *Channel) receiveReplies(replies <-chan Reply) { log.Printf("%s ← %s : %s", channel, reply.Source(), reply) } for client := range channel.members { - client.replies <- reply + var dest Identifier = client + if reply.Source() != dest { + client.replies <- reply + } } } } @@ -99,10 +102,6 @@ func (channel *Channel) Nick() string { return channel.name } -func (channel *Channel) PublicId() string { - return channel.name -} - func (channel *Channel) String() string { return channel.Id() } @@ -176,5 +175,5 @@ func (m *TopicCommand) HandleChannel(channel *Channel) { } func (m *PrivMsgCommand) HandleChannel(channel *Channel) { - channel.replies <- RplPrivMsgChannel(channel, m.Client(), m.message) + channel.replies <- RplPrivMsg(m.Client(), channel, m.message) } diff --git a/irc/client.go b/irc/client.go index ee898c87..78b5452c 100644 --- a/irc/client.go +++ b/irc/client.go @@ -71,20 +71,33 @@ func (c *Client) writeConn(write chan<- string, replies <-chan Reply) { } } +func (client *Client) Destroy() *Client { + client.conn.Close() + return client +} + func (c *Client) Replies() chan<- Reply { return c.replies } -func (c *Client) Server() *Server { - return c.server +func (client *Client) HasNick() bool { + return client.nick != "" } -func (c *Client) Nick() string { - if c.HasNick() { - return c.nick +func (client *Client) HasUsername() bool { + return client.username != "" +} + +func (fromClient *Client) InterestedClients() ClientSet { + clients := make(ClientSet) + clients[fromClient] = true + for channel := range fromClient.channels { + for client := range channel.members { + clients[client] = true + } } - return "guest" + return clients } func (c *Client) UModeString() string { @@ -94,23 +107,20 @@ func (c *Client) UModeString() string { return "" } -func (c *Client) HasNick() bool { - return c.nick != "" -} - -func (c *Client) HasUsername() bool { - return c.username != "" -} - -func (c *Client) Username() string { - if c.HasUsername() { - return c.username - } - return "guest" -} - func (c *Client) UserHost() string { - return fmt.Sprintf("%s!%s@%s", c.Nick(), c.Username(), c.hostname) + nick := c.nick + if nick == "" { + nick = "*" + } + username := c.username + if username == "" { + username = "*" + } + return fmt.Sprintf("%s!%s@%s", nick, username, c.hostname) +} + +func (c *Client) Nick() string { + return c.nick } func (c *Client) Id() string { @@ -120,7 +130,3 @@ func (c *Client) Id() string { func (c *Client) String() string { return c.UserHost() } - -func (c *Client) PublicId() string { - return fmt.Sprintf("%s!%s@%s", c.Nick(), c.Nick(), c.server.Id()) -} diff --git a/irc/constants.go b/irc/constants.go index f8e94b19..b78ea889 100644 --- a/irc/constants.go +++ b/irc/constants.go @@ -8,7 +8,7 @@ var ( ) const ( - VERSION = "irc-1" + VERSION = "ergonomadic-1" ) const ( @@ -150,6 +150,7 @@ const ( ERR_UMODEUNKNOWNFLAG = 501 ERR_USERSDONTMATCH = 502 // message codes + RPL_ERROR = "ERROR" RPL_INVITE = "INVITE" RPL_JOIN = "JOIN" RPL_NICK = "NICK" diff --git a/irc/net.go b/irc/net.go index 1f5ce209..5f30538f 100644 --- a/irc/net.go +++ b/irc/net.go @@ -20,9 +20,12 @@ func StringReadChan(conn net.Conn) <-chan string { ch := make(chan string) reader := bufio.NewReader(conn) go func() { + defer conn.Close() + defer close(ch) for { line, err := readTrimmedLine(reader) if err != nil { + log.Print("net: ", err) break } if DEBUG_NET { @@ -30,7 +33,6 @@ func StringReadChan(conn net.Conn) <-chan string { } ch <- line } - close(ch) }() return ch } @@ -39,18 +41,19 @@ func StringWriteChan(conn net.Conn) chan<- string { ch := make(chan string) writer := bufio.NewWriter(conn) go func() { + defer conn.Close() + defer close(ch) for str := range ch { if DEBUG_NET { log.Printf("%s ← %s : %s", conn.RemoteAddr(), conn.LocalAddr(), str) } if _, err := writer.WriteString(str + "\r\n"); err != nil { + log.Print("net: ", err) break } writer.Flush() } - close(ch) }() - return ch } diff --git a/irc/reply.go b/irc/reply.go index 463c2ae8..6a7a8d74 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -8,7 +8,6 @@ import ( type Identifier interface { Id() string - PublicId() string Nick() string } @@ -146,10 +145,6 @@ func RplNick(source Identifier, newNick string) Reply { return NewStringReply(source, RPL_NICK, newNick) } -func RplPrivMsgChannel(channel *Channel, source Identifier, message string) Reply { - return NewStringReply(source, RPL_PRIVMSG, "%s :%s", channel.name, message) -} - func RplJoin(channel *Channel, client *Client) Reply { return NewStringReply(client, RPL_JOIN, channel.name) } @@ -158,14 +153,18 @@ func RplPart(channel *Channel, client *Client, message string) Reply { return NewStringReply(client, RPL_PART, "%s :%s", channel.name, message) } -func RplPong(server *Server) Reply { - return NewStringReply(server, RPL_PONG, server.Id()) +func RplPong(server *Server, client *Client) Reply { + return NewStringReply(server, RPL_PONG, client.Nick()) } func RplQuit(client *Client, message string) Reply { return NewStringReply(client, RPL_QUIT, ":%s", message) } +func RplError(server *Server, target Identifier) Reply { + return NewStringReply(server, RPL_ERROR, target.Nick()) +} + func RplInviteMsg(channel *Channel, inviter *Client) Reply { return NewStringReply(inviter, RPL_INVITE, channel.name) } @@ -189,7 +188,7 @@ func RplCreated(server *Server) Reply { func RplMyInfo(server *Server) Reply { return NewNumericReply(server, RPL_MYINFO, - "%s %s a kn", server.name, VERSION) + "%s %s aiwroOs kn", server.name, VERSION) } func RplUModeIs(server *Server, client *Client) Reply { diff --git a/irc/server.go b/irc/server.go index 379d4df6..e807f3b4 100644 --- a/irc/server.go +++ b/irc/server.go @@ -1,6 +1,9 @@ package irc import ( + "crypto/rand" + "encoding/binary" + "fmt" "log" "net" "time" @@ -49,18 +52,16 @@ func (s *Server) Listen(addr string) { } s.hostname = LookupHostname(listener.Addr()) - if DEBUG_SERVER { - log.Print("Server.Listen: listening on ", addr) - } + log.Print("Server.Listen: listening on ", addr) for { conn, err := listener.Accept() if err != nil { - log.Print("Server.Listen: ", err) + log.Print("Server.Accept: ", err) continue } if DEBUG_SERVER { - log.Print("Server.Listen: accepted ", conn.RemoteAddr()) + log.Print("Server.Accept: ", conn.RemoteAddr()) } NewClient(s, conn) } @@ -77,17 +78,22 @@ func (s *Server) GetOrMakeChannel(name string) *Channel { return channel } -// Send a message to clients of channels fromClient is a member. -func (s *Server) interestedClients(fromClient *Client) ClientSet { - clients := make(ClientSet) - clients[fromClient] = true - for channel := range fromClient.channels { - for client := range channel.members { - clients[client] = true +func (s *Server) GenerateGuestNick() string { + bytes := make([]byte, 8) + for { + _, err := rand.Read(bytes) + if err != nil { + panic(err) + } + randInt, n := binary.Uvarint(bytes) + if n <= 0 { + continue // TODO handle error + } + nick := fmt.Sprintf("guest%d", randInt) + if s.clients[nick] == nil { + return nick } } - - return clients } // server functionality @@ -112,15 +118,11 @@ func (s *Server) Id() string { } func (s *Server) String() string { - return s.Id() -} - -func (s *Server) PublicId() string { - return s.Id() + return s.name } func (s *Server) Nick() string { - return s.name + return s.Id() } func (s *Server) DeleteChannel(channel *Channel) { @@ -136,7 +138,7 @@ func (m *UnknownCommand) HandleServer(s *Server) { } func (m *PingCommand) HandleServer(s *Server) { - m.Client().Replies() <- RplPong(s) + m.Client().Replies() <- RplPong(s, m.Client()) } func (m *PongCommand) HandleServer(s *Server) { @@ -163,13 +165,11 @@ func (m *NickCommand) HandleServer(s *Server) { } reply := RplNick(c, m.nickname) - for iclient := range s.interestedClients(c) { + for iclient := range c.InterestedClients() { iclient.replies <- reply } - if c.HasNick() { - delete(s.clients, c.nick) - } + delete(s.clients, c.nick) s.clients[m.nickname] = c c.nick = m.nickname @@ -190,18 +190,19 @@ func (m *UserMsgCommand) HandleServer(s *Server) { func (m *QuitCommand) HandleServer(s *Server) { c := m.Client() - reply := RplQuit(c, m.message) - for client := range s.interestedClients(c) { - client.replies <- reply - } - cmd := &PartCommand{ - BaseCommand: BaseCommand{c}, - } - for channel := range c.channels { - channel.commands <- cmd - } - c.conn.Close() delete(s.clients, c.nick) + for channel := range c.channels { + delete(channel.members, c) + } + + c.replies <- RplError(s, c) + c.Destroy() + + reply := RplQuit(c, m.message) + for client := range c.InterestedClients() { + client.replies <- reply + + } } func (m *JoinCommand) HandleServer(s *Server) { @@ -266,10 +267,16 @@ func (m *PrivMsgCommand) HandleServer(s *Server) { } func (m *ModeCommand) HandleServer(s *Server) { - for _, change := range m.changes { - if change.mode == Invisible { - m.Client().invisible = change.add + client := m.Client() + if client.Nick() == m.nickname { + for _, change := range m.changes { + if change.mode == Invisible { + client.invisible = change.add + } } + client.replies <- RplUModeIs(s, client) + return } - m.Client().replies <- RplUModeIs(s, m.Client()) + + client.replies <- ErrUsersDontMatch(client) }