diff --git a/build.sh b/build.sh index 725dde50..f6bb868f 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,4 @@ #!/bin/bash export GOPATH="$PWD" go get "code.google.com/p/go.crypto/bcrypt" -go install -v ergonomadic genpasswd +go install ergonomadic genpasswd diff --git a/src/irc/channel.go b/src/irc/channel.go index cb691b6d..c33f112a 100644 --- a/src/irc/channel.go +++ b/src/irc/channel.go @@ -1,9 +1,13 @@ package irc +import ( + "log" +) + type Channel struct { server *Server - replies chan<- Reply commands chan<- ChannelCommand + replies chan<- Reply name string key string topic string @@ -27,28 +31,6 @@ type ChannelCommand interface { HandleChannel(channel *Channel) } -type JoinChannelCommand struct { - *JoinCommand - key string -} - -type PartChannelCommand struct { - Command - message string -} - -type GetTopicChannelCommand struct { - *TopicCommand -} - -type SetTopicChannelCommand struct { - *TopicCommand -} - -type PrivMsgChannelCommand struct { - *PrivMsgCommand -} - // NewChannel creates a new channel from a `Server` and a `name` string, which // must be unique on the server. func NewChannel(s *Server, name string) *Channel { @@ -69,14 +51,17 @@ func NewChannel(s *Server, name string) *Channel { // Forward `Reply`s to all `User`s of the `Channel`. func (ch *Channel) receiveReplies(replies <-chan Reply) { for reply := range replies { - for client := range ch.members { - client.replies <- reply + for user := range ch.members { + if user != reply.Source() { + user.replies <- reply + } } } } func (ch *Channel) receiveCommands(commands <-chan ChannelCommand) { for command := range commands { + log.Printf("%s %T %+v", ch.Id(), command, command) command.HandleChannel(ch) } } @@ -95,29 +80,46 @@ func (ch *Channel) IsEmpty() bool { return len(ch.members) == 0 } +func (channel *Channel) GetTopic(replier Replier) { + if channel.topic == "" { + replier.Replies() <- RplNoTopic(channel) + return + } + + replier.Replies() <- RplTopic(channel) +} + +func (channel *Channel) Id() string { + return channel.name +} + +func (channel *Channel) PublicId() string { + return channel.name +} + // // commands // -func (m *JoinChannelCommand) HandleChannel(channel *Channel) { +func (m *JoinCommand) HandleChannel(channel *Channel) { client := m.Client() user := client.user - if channel.key != m.key { + if channel.key != m.channels[channel.name] { client.user.replies <- ErrBadChannelKey(channel) return } - channel.members.Add(client.user) - client.user.channels.Add(channel) + channel.members.Add(user) + user.channels.Add(channel) channel.replies <- RplJoin(channel, user) channel.GetTopic(user) - client.user.replies <- RplNamReply(channel) - client.user.replies <- RplEndOfNames(channel.server) + user.replies <- RplNamReply(channel) + user.replies <- RplEndOfNames(channel.server) } -func (m *PartChannelCommand) HandleChannel(channel *Channel) { +func (m *PartCommand) HandleChannel(channel *Channel) { user := m.Client().user if !channel.members[user] { @@ -140,25 +142,7 @@ func (m *PartChannelCommand) HandleChannel(channel *Channel) { } } -func (channel *Channel) GetTopic(user *User) { - if !channel.members[user] { - user.replies <- ErrNotOnChannel(channel) - return - } - - if channel.topic == "" { - user.replies <- RplNoTopic(channel) - return - } - - user.replies <- RplTopic(channel) -} - -func (m *GetTopicChannelCommand) HandleChannel(channel *Channel) { - channel.GetTopic(m.Client().user) -} - -func (m *SetTopicChannelCommand) HandleChannel(channel *Channel) { +func (m *TopicCommand) HandleChannel(channel *Channel) { user := m.Client().user if !channel.members[user] { @@ -166,6 +150,11 @@ func (m *SetTopicChannelCommand) HandleChannel(channel *Channel) { return } + if m.topic == "" { + channel.GetTopic(user) + return + } + channel.topic = m.topic if channel.topic == "" { @@ -176,6 +165,6 @@ func (m *SetTopicChannelCommand) HandleChannel(channel *Channel) { channel.replies <- RplTopic(channel) } -func (m *PrivMsgChannelCommand) HandleChannel(channel *Channel) { +func (m *PrivMsgCommand) HandleChannel(channel *Channel) { channel.replies <- RplPrivMsgChannel(channel, m.Client().user, m.message) } diff --git a/src/irc/client.go b/src/irc/client.go index 37ee72cb..bcaa34b9 100644 --- a/src/irc/client.go +++ b/src/irc/client.go @@ -7,9 +7,13 @@ import ( "time" ) +type Replier interface { + Identifier + Replies() chan<- Reply +} + type Client struct { conn net.Conn - replies chan<- Reply username string realname string hostname string @@ -20,6 +24,12 @@ type Client struct { server *Server atime time.Time user *User + replies chan<- Reply +} + +type ClientMessage interface { + Client() *Client + SetClient(*Client) } type ClientSet map[*Client]bool @@ -36,10 +46,7 @@ func NewClient(server *Server, conn net.Conn) *Client { replies: replies, } - // Connect the conn to the server. go client.readConn(read) - - // Connect the reply channel to the conn. go client.writeConn(write, replies) return client @@ -51,7 +58,7 @@ func (c *Client) readConn(recv <-chan string) { m, err := ParseCommand(str) if err != nil { - // TODO handle error + c.replies <- ErrNeedMoreParams(c.server, str) continue } @@ -68,6 +75,14 @@ func (c *Client) writeConn(write chan<- string, replies <-chan Reply) { } } +func (c *Client) Replies() chan<- Reply { + return c.replies +} + +func (c *Client) Server() *Server { + return c.server +} + func (c *Client) Nick() string { if c.user != nil { return c.user.nick diff --git a/src/irc/commands.go b/src/irc/commands.go index 164ff34f..8fbf1b1f 100644 --- a/src/irc/commands.go +++ b/src/irc/commands.go @@ -6,12 +6,6 @@ import ( "strings" ) -type Command interface { - Client() *Client - SetClient(*Client) - Handle(*Server) -} - var ( NotEnoughArgsError = errors.New("not enough arguments") ) @@ -28,7 +22,59 @@ func (base *BaseCommand) SetClient(c *Client) { base.client = c } -// unknown [args...] +var ( + ErrParseCommand = errors.New("failed to parse message") + parseCommandFuncs = map[string]func([]string) (Command, error){ + "JOIN": NewJoinCommand, + "MODE": NewModeCommand, + "NICK": NewNickCommand, + "PART": NewPartCommand, + "PASS": NewPassCommand, + "PING": NewPingCommand, + "PONG": NewPongCommand, + "PRIVMSG": NewPrivMsgCommand, + "QUIT": NewQuitCommand, + "TOPIC": NewTopicCommand, + "USER": NewUserMsgCommand, + } +) + +func ParseCommand(line string) (Command, error) { + command, args := parseLine(line) + constructor := parseCommandFuncs[command] + if constructor == nil { + return NewUnknownCommand(command, args), nil + } + return constructor(args) +} + +func parseArg(line string) (arg string, rest string) { + if line == "" { + return + } + + if strings.HasPrefix(line, ":") { + arg = line[1:] + } else { + parts := strings.SplitN(line, " ", 2) + arg = parts[0] + if len(parts) > 1 { + rest = parts[1] + } + } + return +} + +func parseLine(line string) (command string, args []string) { + args = make([]string, 0) + for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) { + args = append(args, arg) + } + command, args = strings.ToUpper(args[0]), args[1:] + return +} + +// [args...] type UnknownCommand struct { *BaseCommand @@ -36,7 +82,7 @@ type UnknownCommand struct { args []string } -func NewUnknownCommand(command string, args []string) Command { +func NewUnknownCommand(command string, args []string) *UnknownCommand { return &UnknownCommand{ BaseCommand: &BaseCommand{}, command: command, @@ -44,10 +90,6 @@ func NewUnknownCommand(command string, args []string) Command { } } -func (m *UnknownCommand) Handle(s *Server) { - m.Client().replies <- ErrUnknownCommand(s, m.command) -} - // PING [ ] type PingCommand struct { @@ -128,7 +170,7 @@ func NewNickCommand(args []string) (Command, error) { // USER -type UserCommand struct { +type UserMsgCommand struct { *BaseCommand user string mode uint8 @@ -136,11 +178,11 @@ type UserCommand struct { realname string } -func NewUserCommand(args []string) (Command, error) { +func NewUserMsgCommand(args []string) (Command, error) { if len(args) != 4 { return nil, NotEnoughArgsError } - msg := &UserCommand{ + msg := &UserMsgCommand{ BaseCommand: &BaseCommand{}, user: args[0], unused: args[2], @@ -174,26 +216,36 @@ func NewQuitCommand(args []string) (Command, error) { type JoinCommand struct { *BaseCommand - channels []string - keys []string + channels map[string]string zero bool } func NewJoinCommand(args []string) (Command, error) { msg := &JoinCommand{ BaseCommand: &BaseCommand{}, + channels: make(map[string]string), } - if len(args) > 0 { - if args[0] == "0" { - msg.zero = true - } else { - msg.channels = strings.Split(args[0], ",") - } - if len(args) > 1 { - msg.keys = strings.Split(args[1], ",") + if len(args) == 0 { + return nil, NotEnoughArgsError + } + + if args[0] == "0" { + msg.zero = true + return msg, nil + } + + channels := strings.Split(args[0], ",") + keys := make([]string, len(channels)) + if len(args) > 1 { + for i, key := range strings.Split(args[1], ",") { + keys[i] = key } } + for i, channel := range channels { + msg.channels[channel] = keys[i] + } + return msg, nil } @@ -268,40 +320,25 @@ func NewTopicCommand(args []string) (Command, error) { return msg, nil } -// LOGIN - -type LoginCommand struct { +type ModeCommand struct { *BaseCommand - nick string - password string + nickname string + modes string } -func NewLoginCommand(args []string) (Command, error) { - if len(args) < 2 { +func NewModeCommand(args []string) (Command, error) { + if len(args) == 0 { return nil, NotEnoughArgsError } - return &LoginCommand{ + + cmd := &ModeCommand{ BaseCommand: &BaseCommand{}, - nick: args[0], - password: args[1], - }, nil -} - -// RESERVE - -type ReserveCommand struct { - *BaseCommand - nick string - password string -} - -func NewReserveCommand(args []string) (Command, error) { - if len(args) < 2 { - return nil, NotEnoughArgsError + nickname: args[0], } - return &ReserveCommand{ - BaseCommand: &BaseCommand{}, - nick: args[0], - password: args[1], - }, nil + + if len(args) > 1 { + cmd.modes = args[1] + } + + return cmd, nil } diff --git a/src/irc/nickserv.go b/src/irc/nickserv.go new file mode 100644 index 00000000..ce5d44b7 --- /dev/null +++ b/src/irc/nickserv.go @@ -0,0 +1,135 @@ +package irc + +import ( + "log" +) + +type NickServCommand interface { + ClientMessage + HandleNickServ(*NickServ) +} + +type NickServ struct { + *Service +} + +func NewNickServ(s *Server) *NickServ { + ns := &NickServ{} + ns.Service = NewService(s, "NickServ", func(m *PrivMsgCommand) { + m.HandleNickServ(ns) + }) + return ns +} + +var ( + parseNickServCommandFuncs = map[string]func([]string) (NickServCommand, error){ + "REGISTER": NewRegisterCommand, + "IDENTIFY": NewIdentifyCommand, + } +) + +// +// commands +// + +func (m *PrivMsgCommand) HandleNickServ(ns *NickServ) { + command, args := parseLine(m.message) + constructor := parseNickServCommandFuncs[command] + if constructor == nil { + ns.Reply(m.Client(), "Unknown command.") + return + } + + cmd, err := constructor(args) + if err != nil { + ns.Reply(m.Client(), "Not enough parameters.") + return + } + + cmd.SetClient(m.Client()) + log.Printf("%s %T %+v", ns.Id(), cmd, cmd) + cmd.HandleNickServ(ns) +} + +// +// sub-commands +// + +type RegisterCommand struct { + *BaseCommand + password string + email string +} + +func NewRegisterCommand(args []string) (NickServCommand, error) { + if len(args) == 0 { + return nil, NotEnoughArgsError + } + + cmd := &RegisterCommand{ + BaseCommand: &BaseCommand{}, + password: args[0], + } + if len(args) > 1 { + cmd.email = args[1] + } + return cmd, nil +} + +func (m *RegisterCommand) HandleNickServ(ns *NickServ) { + client := m.Client() + + if client.user != nil { + ns.Reply(client, "You are already registered.") + return + } + + if ns.server.users[client.nick] != nil { + ns.Reply(client, "That nick is already registered.") + return + } + + user := NewUser(client.nick, m.password, ns.server) + ns.server.users[client.nick] = user + ns.Reply(client, "You have registered.") + + if !user.Login(client, client.nick, m.password) { + ns.Reply(client, "Login failed.") + } + ns.Reply(client, "Logged in.") +} + +type IdentifyCommand struct { + *BaseCommand + password string +} + +func NewIdentifyCommand(args []string) (NickServCommand, error) { + if len(args) == 0 { + return nil, NotEnoughArgsError + } + + return &IdentifyCommand{ + BaseCommand: &BaseCommand{}, + password: args[0], + }, nil +} + +func (m *IdentifyCommand) HandleNickServ(ns *NickServ) { + client := m.Client() + if client.user != nil { + ns.Reply(client, "That nick is already registered.") + return + } + + user := ns.server.users[client.nick] + if user == nil { + ns.Reply(client, "No such nick.") + return + } + + if !user.Login(client, client.nick, m.password) { + ns.Reply(client, "Login failed.") + } + ns.Reply(client, "Logged in.") +} diff --git a/src/irc/parse.go b/src/irc/parse.go deleted file mode 100644 index b3f8f365..00000000 --- a/src/irc/parse.go +++ /dev/null @@ -1,60 +0,0 @@ -package irc - -import ( - "errors" - "strings" -) - -type ParseFunc func([]string) (Command, error) - -var ( - ErrParseCommand = errors.New("failed to parse message") - parseCommandFuncs = map[string]ParseFunc{ - "JOIN": NewJoinCommand, - "LOGIN": NewLoginCommand, - "NICK": NewNickCommand, - "PART": NewPartCommand, - "PASS": NewPassCommand, - "PING": NewPingCommand, - "PONG": NewPongCommand, - "PRIVMSG": NewPrivMsgCommand, - "QUIT": NewQuitCommand, - "TOPIC": NewTopicCommand, - "USER": NewUserCommand, - } -) - -func ParseCommand(line string) (Command, error) { - command, args := parseLine(line) - constructor := parseCommandFuncs[command] - if constructor == nil { - return NewUnknownCommand(command, args), nil - } - return constructor(args) -} - -func parseArg(line string) (arg string, rest string) { - if line == "" { - return - } - - if strings.HasPrefix(line, ":") { - arg = line[1:] - } else { - parts := strings.SplitN(line, " ", 2) - arg = parts[0] - if len(parts) > 1 { - rest = parts[1] - } - } - return -} - -func parseLine(line string) (command string, args []string) { - args = make([]string, 0) - for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) { - args = append(args, arg) - } - command, args = args[0], args[1:] - return -} diff --git a/src/irc/reply.go b/src/irc/reply.go index 5a2e9f4e..14c05666 100644 --- a/src/irc/reply.go +++ b/src/irc/reply.go @@ -9,10 +9,12 @@ import ( type Identifier interface { Id() string PublicId() string + Nick() string } type Reply interface { String(client *Client) string + Source() Identifier } type BasicReply struct { @@ -21,7 +23,8 @@ type BasicReply struct { message string } -func NewBasicReply(source Identifier, code string, format string, args ...interface{}) *BasicReply { +func NewBasicReply(source Identifier, code string, + format string, args ...interface{}) *BasicReply { message := fmt.Sprintf(format, args...) fullMessage := fmt.Sprintf(":%s %s %s\r\n", source.Id(), code, message) return &BasicReply{source, code, fullMessage} @@ -31,11 +34,16 @@ func (reply *BasicReply) String(client *Client) string { return reply.message } +func (reply *BasicReply) Source() Identifier { + return reply.source +} + type NumericReply struct { *BasicReply } -func NewNumericReply(source Identifier, code string, format string, args ...interface{}) *NumericReply { +func NewNumericReply(source Identifier, code string, + format string, args ...interface{}) *NumericReply { return &NumericReply{&BasicReply{source, code, fmt.Sprintf(format, args...)}} } @@ -47,7 +55,7 @@ func (reply *NumericReply) String(client *Client) string { // messaging replies func RplPrivMsg(source Identifier, target Identifier, message string) Reply { - return NewBasicReply(source, RPL_PRIVMSG, "%s :%s", target, message) + return NewBasicReply(source, RPL_PRIVMSG, "%s :%s", target.Nick(), message) } func RplNick(client *Client, newNick string) Reply { diff --git a/src/irc/server.go b/src/irc/server.go index adb44a4b..ba7a51e5 100644 --- a/src/irc/server.go +++ b/src/irc/server.go @@ -10,15 +10,22 @@ import ( type ClientNameMap map[string]*Client type ChannelNameMap map[string]*Channel type UserNameMap map[string]*User +type ServiceNameMap map[string]*Service + +type Command interface { + ClientMessage + Handle(*Server) +} type Server struct { hostname string ctime time.Time name string - commands chan<- Command password []byte users UserNameMap channels ChannelNameMap + services ServiceNameMap + commands chan<- Command } func NewServer(name string) *Server { @@ -29,13 +36,16 @@ func NewServer(name string) *Server { commands: commands, users: make(UserNameMap), channels: make(ChannelNameMap), + services: make(ServiceNameMap), } go server.receiveCommands(commands) + NewNickServ(server) return server } func (server *Server) receiveCommands(commands <-chan Command) { for command := range commands { + log.Printf("%s %T %+v", server.Id(), command, command) command.Client().atime = time.Now() command.Handle(server) } @@ -75,10 +85,10 @@ func (s *Server) GetOrMakeChannel(name string) *Channel { // Send a message to clients of channels fromClient is a member. func (s *Server) InterestedUsers(fromUser *User) UserSet { users := make(UserSet) - users[fromUser] = true + users.Add(fromUser) for channel := range fromUser.channels { for user := range channel.members { - users[user] = true + users.Add(user) } } @@ -97,11 +107,6 @@ func (s *Server) tryRegister(c *Client) { } } -func (s *Server) ChangeUserMode(c *Client, modes []string) { - // Don't allow any mode changes. - c.replies <- RplUModeIs(s, c) -} - func (s *Server) Id() string { return s.hostname } @@ -110,6 +115,10 @@ func (s *Server) PublicId() string { return s.Id() } +func (s *Server) Nick() string { + return s.name +} + func (s *Server) DeleteChannel(channel *Channel) { delete(s.channels, channel.name) } @@ -118,6 +127,10 @@ func (s *Server) DeleteChannel(channel *Channel) { // commands // +func (m *UnknownCommand) Handle(s *Server) { + m.Client().replies <- ErrUnknownCommand(s, m.command) +} + func (m *PingCommand) Handle(s *Server) { m.Client().replies <- RplPong(s) } @@ -149,7 +162,7 @@ func (m *NickCommand) Handle(s *Server) { c.user.replies <- ErrNoPrivileges(s) } -func (m *UserCommand) Handle(s *Server) { +func (m *UserMsgCommand) Handle(s *Server) { c := m.Client() if c.username != "" { c.replies <- ErrAlreadyRegistered(s) @@ -162,19 +175,25 @@ func (m *UserCommand) Handle(s *Server) { func (m *QuitCommand) Handle(s *Server) { c := m.Client() - reply := RplQuit(c, m.message) - for user := range s.InterestedUsers(c.user) { - user.replies <- reply + + user := c.user + if user != nil { + reply := RplQuit(c, m.message) + for user := range s.InterestedUsers(c.user) { + user.replies <- reply + } } c.conn.Close() - user := c.user - user.LogoutClient(c) + if user == nil { + return + } + user.LogoutClient(c) if !user.HasClients() { - cmd := &PartChannelCommand{ - Command: m, + cmd := &PartCommand{ + BaseCommand: &BaseCommand{c}, } - for channel := range c.user.channels { + for channel := range user.channels { channel.commands <- cmd } } @@ -182,27 +201,39 @@ func (m *QuitCommand) Handle(s *Server) { func (m *JoinCommand) Handle(s *Server) { c := m.Client() + + if c.user == nil { + for name := range m.channels { + c.replies <- ErrNoSuchChannel(s, name) + } + return + } + if m.zero { - cmd := &PartChannelCommand{ - Command: m, + cmd := &PartCommand{ + BaseCommand: &BaseCommand{c}, } for channel := range c.user.channels { channel.commands <- cmd } - } else { - for i, name := range m.channels { - key := "" - if len(m.keys) > i { - key = m.keys[i] - } + return + } - s.GetOrMakeChannel(name).commands <- &JoinChannelCommand{m, key} - } + for name := range m.channels { + s.GetOrMakeChannel(name).commands <- m } } func (m *PartCommand) Handle(s *Server) { user := m.Client().user + + if user == nil { + for _, chname := range m.channels { + m.Client().replies <- ErrNoSuchChannel(s, chname) + } + return + } + for _, chname := range m.channels { channel := s.channels[chname] @@ -211,82 +242,60 @@ func (m *PartCommand) Handle(s *Server) { continue } - channel.commands <- &PartChannelCommand{m, m.message} + channel.commands <- m } } func (m *TopicCommand) Handle(s *Server) { user := m.Client().user + + if user == nil { + m.Client().replies <- ErrNoSuchChannel(s, m.channel) + return + } + channel := s.channels[m.channel] if channel == nil { user.replies <- ErrNoSuchChannel(s, m.channel) return } - if m.topic == "" { - channel.commands <- &GetTopicChannelCommand{m} - return - } - - channel.commands <- &SetTopicChannelCommand{m} + channel.commands <- m } func (m *PrivMsgCommand) Handle(s *Server) { + service := s.services[m.target] + if service != nil { + service.commands <- m + return + } + user := m.Client().user + if user == nil { + m.Client().replies <- ErrNoSuchNick(s, m.target) + return + } if m.TargetIsChannel() { channel := s.channels[m.target] if channel == nil { - user.replies <- ErrNoSuchNick(s, m.target) + user.replies <- ErrNoSuchChannel(s, m.target) return } - channel.commands <- &PrivMsgChannelCommand{m} + channel.commands <- m return } target := s.users[m.target] - if target != nil { - target.replies <- ErrNoSuchNick(s, m.target) + if target == nil { + user.replies <- ErrNoSuchNick(s, m.target) return } - target.replies <- RplPrivMsg(user, target, m.message) + target.commands <- m } -func (m *LoginCommand) Handle(s *Server) { - client := m.Client() - if client.user != nil { - client.replies <- ErrAlreadyRegistered(s) - return - } - - user := s.users[m.nick] - if user == nil { - client.replies <- ErrNoSuchNick(s, m.nick) - return - } - - if !user.Login(client, m.nick, m.password) { - client.replies <- ErrRestricted(s) - return - } - - client.replies <- RplNick(client, m.nick) - // TODO join channels -} - -func (m *ReserveCommand) Handle(s *Server) { - client := m.Client() - if client.user != nil { - client.replies <- ErrAlreadyRegistered(s) - return - } - - if s.users[m.nick] != nil { - client.replies <- ErrNickNameInUse(s, m.nick) - return - } - - s.users[m.nick] = NewUser(m.nick, m.password, s) +func (m *ModeCommand) Handle(s *Server) { + m.Client().replies <- RplUModeIs(s, m.Client()) } diff --git a/src/irc/service.go b/src/irc/service.go new file mode 100644 index 00000000..4f3842ce --- /dev/null +++ b/src/irc/service.go @@ -0,0 +1,64 @@ +package irc + +import ( + "fmt" + "log" +) + +type ServiceCommand interface { + Command + HandleService(*Service) +} + +type PrivMsgCommandFunc func(*PrivMsgCommand) + +type Service struct { + server *Server + name string + commands chan<- ServiceCommand + Handle PrivMsgCommandFunc +} + +func NewService(s *Server, name string, Handle PrivMsgCommandFunc) *Service { + commands := make(chan ServiceCommand) + service := &Service{ + server: s, + name: name, + commands: commands, + Handle: Handle, + } + go service.receiveCommands(commands) + s.services[name] = service + return service +} + +func (service *Service) receiveCommands(commands <-chan ServiceCommand) { + for command := range commands { + log.Printf("%s %T %+V", service.Id(), command, command) + command.HandleService(service) + } +} + +func (service *Service) Id() string { + return fmt.Sprintf("%s!%s@%s", service.name, service.name, service.server.name) +} + +func (service *Service) PublicId() string { + return service.Id() +} + +func (service *Service) Nick() string { + return service.name +} + +func (service *Service) Reply(client *Client, message string) { + client.replies <- RplPrivMsg(service, client, message) +} + +// +// commands +// + +func (m *PrivMsgCommand) HandleService(s *Service) { + s.Handle(m) +} diff --git a/src/irc/user.go b/src/irc/user.go index b29f0fbf..1884a151 100644 --- a/src/irc/user.go +++ b/src/irc/user.go @@ -3,16 +3,22 @@ package irc import ( "code.google.com/p/go.crypto/bcrypt" "fmt" + "log" ) +type UserCommand interface { + Command + HandleUser(*User) +} + type User struct { nick string hash []byte server *Server - replies chan<- Reply - commands <-chan Command clients ClientSet channels ChannelSet + commands chan<- UserCommand + replies chan<- Reply } type UserSet map[*User]bool @@ -30,21 +36,32 @@ func NewUser(nick string, password string, server *Server) *User { if err != nil { panic("bcrypt failed; cannot generate password hash") } + commands := make(chan UserCommand) replies := make(chan Reply) user := &User{ - nick: nick, - hash: hash, - server: server, - clients: make(ClientSet), - replies: replies, + nick: nick, + hash: hash, + server: server, + clients: make(ClientSet), + channels: make(ChannelSet), + replies: replies, } + go user.receiveCommands(commands) go user.receiveReplies(replies) return user } +func (user *User) receiveCommands(commands <-chan UserCommand) { + for command := range commands { + log.Printf("%s %T %+v", user.Id(), command, command) + command.HandleUser(user) + } +} + // Distribute replies to clients. func (user *User) receiveReplies(replies <-chan Reply) { for reply := range replies { + log.Printf("%s %T %+v", user.Id(), reply, reply) for client := range user.clients { client.replies <- reply } @@ -82,8 +99,11 @@ func (user *User) Login(c *Client, nick string, password string) bool { user.clients[c] = true c.user = user - c.replies <- RplNick(c, user.nick) - // TODO join channels + for channel := range user.channels { + channel.GetTopic(c) + c.replies <- RplNamReply(channel) + c.replies <- RplEndOfNames(channel.server) + } return true } @@ -98,3 +118,15 @@ func (user *User) LogoutClient(c *Client) bool { func (user *User) HasClients() bool { return len(user.clients) > 0 } + +func (user *User) Replies() chan<- Reply { + return user.replies +} + +// +// commands +// + +func (m *PrivMsgCommand) HandleUser(user *User) { + user.replies <- RplPrivMsg(m.Client(), user, m.message) +}