diff --git a/irc/channel.go b/irc/channel.go index eceb926c..4057d89e 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -1,9 +1,5 @@ package irc -import ( - "log" -) - type Channel struct { banList []UserMask flags ChannelModeSet @@ -31,26 +27,14 @@ func NewChannel(s *Server, name string) *Channel { return channel } -func (channel *Channel) Reply(reply Reply) { - if DEBUG_CHANNEL { - log.Printf("%s ← %s %s", channel, reply.Source(), reply) - } - - for client := range channel.members { - if (reply.Code() == ReplyCode(PRIVMSG)) && - (reply.Source() == client.Id()) { - continue - } - client.Reply(reply) - } -} - func (channel *Channel) IsEmpty() bool { return len(channel.members) == 0 } func (channel *Channel) Names(client *Client) { - client.Reply(NewNamesReply(channel)) + client.MultilineReply(channel.Nicks(), RPL_NAMREPLY, + "= %s :%s", channel.name) + client.RplEndOfNames(channel) } func (channel *Channel) ClientIsOperator(client *Client) bool { @@ -109,7 +93,7 @@ func (channel *Channel) ModeString() (str string) { func (channel *Channel) Join(client *Client, key string) { if (channel.key != "") && (channel.key != key) { - client.Reply(ErrBadChannelKey(channel)) + client.ErrBadChannelKey(channel) return } @@ -120,18 +104,23 @@ func (channel *Channel) Join(client *Client, key string) { channel.members[client][ChannelOperator] = true } - channel.Reply(RplJoin(client, channel)) + reply := RplJoin(client, channel) + for member := range channel.members { + member.replies <- reply + } channel.GetTopic(client) channel.Names(client) } func (channel *Channel) Part(client *Client, message string) { if !channel.members.Has(client) { - client.Reply(ErrNotOnChannel(channel)) + client.ErrNotOnChannel(channel) return } - channel.Reply(RplPart(client, channel, message)) + for member := range channel.members { + member.replies <- RplPart(member, channel, message) + } channel.Quit(client) if channel.IsEmpty() { @@ -141,7 +130,7 @@ func (channel *Channel) Part(client *Client, message string) { func (channel *Channel) GetTopic(client *Client) { if !channel.members.Has(client) { - client.Reply(ErrNotOnChannel(channel)) + client.ErrNotOnChannel(channel) return } @@ -151,35 +140,42 @@ func (channel *Channel) GetTopic(client *Client) { return } - client.Reply(RplTopic(channel)) + client.RplTopic(channel) } func (channel *Channel) SetTopic(client *Client, topic string) { if !channel.members.Has(client) { - client.Reply(ErrNotOnChannel(channel)) + client.ErrNotOnChannel(channel) return } if channel.flags[OpOnlyTopic] && !channel.members[client][ChannelOperator] { - client.Reply(ErrChanOPrivIsNeeded(channel)) + client.ErrChanOPrivIsNeeded(channel) return } channel.topic = topic - channel.Reply(RplTopicMsg(client, channel)) + for member := range channel.members { + member.replies <- RplTopicMsg(client, channel) + } } func (channel *Channel) PrivMsg(client *Client, message string) { if channel.flags[NoOutside] && !channel.members.Has(client) { - client.Reply(ErrCannotSendToChan(channel)) + client.ErrCannotSendToChan(channel) return } - channel.Reply(RplPrivMsg(client, channel, message)) + for member := range channel.members { + if member == client { + continue + } + member.replies <- RplPrivMsg(client, channel, message) + } } func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { if len(changes) == 0 { - client.Reply(RplChannelModeIs(channel)) + client.RplChannelModeIs(channel) return } @@ -191,13 +187,13 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { // TODO add/remove for _, banMask := range channel.banList { - client.Reply(RplBanList(channel, banMask)) + client.RplBanList(channel, banMask) } - client.Reply(RplEndOfBanList(channel)) + client.RplEndOfBanList(channel) case NoOutside, Private, Secret, OpOnlyTopic: if !channel.ClientIsOperator(client) { - client.Reply(ErrChanOPrivIsNeeded(channel)) + client.ErrChanOPrivIsNeeded(channel) continue } @@ -213,7 +209,7 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { case Key: if !channel.ClientIsOperator(client) { - client.Reply(ErrChanOPrivIsNeeded(channel)) + client.ErrChanOPrivIsNeeded(channel) continue } @@ -234,7 +230,7 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { case ChannelOperator, Voice: if !channel.ClientIsOperator(client) { - client.Reply(ErrChanOPrivIsNeeded(channel)) + client.ErrChanOPrivIsNeeded(channel) continue } @@ -267,16 +263,23 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { } if len(applied) > 0 { - channel.Reply(RplChannelMode(client, channel, applied)) + for member := range channel.members { + member.replies <- RplChannelMode(client, channel, applied) + } } } func (channel *Channel) Notice(client *Client, message string) { if channel.flags[NoOutside] && !channel.members.Has(client) { - client.Reply(ErrCannotSendToChan(channel)) + client.ErrCannotSendToChan(channel) return } - channel.Reply(RplNotice(client, channel, message)) + for member := range channel.members { + if member == client { + continue + } + member.replies <- RplNotice(client, channel, message) + } } func (channel *Channel) Quit(client *Client) { @@ -286,18 +289,20 @@ func (channel *Channel) Quit(client *Client) { func (channel *Channel) Kick(client *Client, target *Client, comment string) { if !client.flags[Operator] && !channel.members.Has(client) { - client.Reply(ErrNotOnChannel(channel)) + client.ErrNotOnChannel(channel) return } if !channel.ClientIsOperator(client) { - client.Reply(ErrChanOPrivIsNeeded(channel)) + client.ErrChanOPrivIsNeeded(channel) return } if !channel.members.Has(target) { - client.Reply(ErrUserNotInChannel(channel, target)) + client.ErrUserNotInChannel(channel, target) return } - channel.Reply(RplKick(channel, client, target, comment)) + for member := range channel.members { + member.replies <- RplKick(channel, client, target, comment) + } channel.Quit(target) } diff --git a/irc/client.go b/irc/client.go index 88ac9a6b..4a4692bb 100644 --- a/irc/client.go +++ b/irc/client.go @@ -28,7 +28,7 @@ type Client struct { phase Phase quitTimer *time.Timer realname string - replies chan Reply + replies chan string server *Server socket *Socket username string @@ -46,7 +46,7 @@ func NewClient(server *Server, conn net.Conn) *Client { phase: server.InitPhase(), server: server, socket: NewSocket(conn), - replies: make(chan Reply), + replies: make(chan string), } client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionTimeout) @@ -71,7 +71,7 @@ func (client *Client) readCommands() { switch err { case NotEnoughArgsError: parts := strings.SplitN(line, " ", 2) - client.Reply(ErrNeedMoreParams(client.server, parts[0])) + client.ErrNeedMoreParams(parts[0]) } continue } @@ -97,7 +97,7 @@ func (client *Client) connectionClosed() { func (client *Client) writeReplies() { for reply := range client.replies { - client.socket.Write(reply.Format(client)...) + client.socket.Write(reply) } client.socket.Close() client.doneWriting <- true @@ -144,7 +144,7 @@ func (client *Client) Touch() { } func (client *Client) Idle() { - client.Reply(RplPing(client.server, client)) + client.replies <- RplPing(client.server, client) if client.quitTimer == nil { client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout) @@ -188,16 +188,6 @@ func (client *Client) destroy() { } } -func (client *Client) Reply(reply Reply) { - if client.hasQuit { - if DEBUG_CLIENT { - log.Printf("%s dropping %s", client, reply) - } - return - } - client.replies <- reply -} - func (client *Client) IdleTime() time.Duration { return time.Since(client.atime) } @@ -276,7 +266,7 @@ func (client *Client) ChangeNickname(nickname string) { client.nick = nickname client.server.clients.Add(client) for friend := range client.Friends() { - friend.Reply(reply) + friend.replies <- reply } } @@ -285,7 +275,7 @@ func (client *Client) Quit(message string) { return } - client.Reply(RplError(client.server, client.Nick())) + client.replies <- RplError(client.server, "connection closed") client.hasQuit = true friends := client.Friends() @@ -295,7 +285,7 @@ func (client *Client) Quit(message string) { if len(friends) > 0 { reply := RplQuit(client, message) for friend := range friends { - friend.Reply(reply) + friend.replies <- reply } } } diff --git a/irc/commands.go b/irc/commands.go index 088f5efb..b882d436 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -70,10 +70,6 @@ func (command *BaseCommand) Source() Identifier { return command.Client() } -func (command *BaseCommand) Reply(reply Reply) { - command.client.Reply(reply) -} - func ParseCommand(line string) (cmd editableCommand, err error) { code, args := parseLine(line) constructor := parseCommandFuncs[code] diff --git a/irc/reply.go b/irc/reply.go index 6157d766..d113ad49 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -6,81 +6,29 @@ import ( "time" ) -type BaseReply struct { - code ReplyCode - id string - message string -} - -func (reply *BaseReply) Code() ReplyCode { - return reply.code -} - -func (reply *BaseReply) SetSource(source Identifier) { - reply.id = source.Id() -} - -func (reply *BaseReply) Source() string { - return reply.id -} - -type StringReply struct { - BaseReply -} - func NewStringReply(source Identifier, code StringCode, - format string, args ...interface{}) *StringReply { - reply := &StringReply{} - reply.code = code - reply.message = fmt.Sprintf(format, args...) - reply.SetSource(source) - return reply + format string, args ...interface{}) string { + header := fmt.Sprintf(":%s %s ", source, code) + message := fmt.Sprintf(format, args...) + return header + message + CRLF } -func (reply *StringReply) Format(client *Client) []string { - message := fmt.Sprintf(":%s %s %s%s", - reply.id, reply.code, reply.message, CRLF) - return []string{message} +func NewNumericReply(target *Client, code NumericCode, + format string, args ...interface{}) string { + header := fmt.Sprintf(":%s %s %s ", target.server.Id(), code, target.Nick()) + message := fmt.Sprintf(format, args...) + return header + message + CRLF } -func (reply *StringReply) String() string { - return fmt.Sprintf("Reply(source=%s, code=%s, message=%s)", - reply.id, reply.code, reply.message) -} - -type NumericReply struct { - BaseReply -} - -func NewNumericReply(source Identifier, code NumericCode, format string, - args ...interface{}) *NumericReply { - reply := &NumericReply{} - reply.code = code - reply.message = fmt.Sprintf(format, args...) - reply.SetSource(source) - return reply -} - -func (reply *NumericReply) Format(client *Client) []string { - message := fmt.Sprintf(":%s %s %s %s%s", - reply.id, reply.code, client.Nick(), reply.message, CRLF) - return []string{message} -} - -func (reply *NumericReply) String() string { - return fmt.Sprintf("Reply(source=%s, code=%d, message=%s)", - reply.id, reply.code, reply.message) +func (target *Client) NumericReply(code NumericCode, + format string, args ...interface{}) { + target.replies <- NewNumericReply(target, code, format, args...) } // // multiline replies // -type MultilineReply interface { - formatLine(*Client, []string) string - names() []string -} - func joinedLen(names []string) int { var l = len(names) - 1 // " " between names for _, name := range names { @@ -89,258 +37,175 @@ func joinedLen(names []string) int { return l } -func multilineFormat(reply MultilineReply, client *Client) []string { - lines := make([]string, 0) - baseLen := len(reply.formatLine(client, []string{})) +func (target *Client) MultilineReply(names []string, code NumericCode, format string, + args ...interface{}) { + baseLen := len(NewNumericReply(target, code, format)) tooLong := func(names []string) bool { return (baseLen + joinedLen(names)) > MAX_REPLY_LEN } + argsAndNames := func(names []string) []interface{} { + return append(args, strings.Join(names, " ")) + } from, to := 0, 1 - names := reply.names() for to < len(names) { if (from < (to - 1)) && tooLong(names[from:to]) { - lines = append(lines, reply.formatLine(client, names[from:to-1])) + target.NumericReply(code, format, argsAndNames(names[from:to-1])...) from, to = to-1, to } else { to += 1 } } if from < len(names) { - lines = append(lines, reply.formatLine(client, names[from:])) + target.NumericReply(code, format, argsAndNames(names[from:])...) } - return lines -} - -// names - -type NamesReply struct { - BaseReply - channel *Channel -} - -func NewNamesReply(channel *Channel) Reply { - reply := &NamesReply{ - channel: channel, - } - reply.SetSource(channel.server) - return reply -} - -func (reply *NamesReply) names() []string { - return reply.channel.Nicks() -} - -func (reply *NamesReply) formatLine(client *Client, names []string) string { - return RplNamReply(reply.channel, names).Format(client)[0] -} - -func (reply *NamesReply) Format(client *Client) []string { - lines := multilineFormat(reply, client) - lines = append(lines, RplEndOfNames(reply.channel).Format(client)...) - return lines -} - -func (reply *NamesReply) String() string { - return fmt.Sprintf("NamesReply(channel=%s, names=%s)", - reply.channel, reply.channel.Nicks()) -} - -// whois channels - -type WhoisChannelsReply struct { - BaseReply - client *Client -} - -func NewWhoisChannelsReply(client *Client) *WhoisChannelsReply { - reply := &WhoisChannelsReply{ - client: client, - } - reply.SetSource(client.server) - return reply -} - -func (reply *WhoisChannelsReply) names() []string { - chstrs := make([]string, len(reply.client.channels)) - index := 0 - for channel := range reply.client.channels { - switch { - case channel.members[reply.client][ChannelOperator]: - chstrs[index] = "@" + channel.name - - case channel.members[reply.client][Voice]: - chstrs[index] = "+" + channel.name - - default: - chstrs[index] = channel.name - } - index += 1 - } - return chstrs -} - -func (reply *WhoisChannelsReply) formatLine(client *Client, names []string) string { - return RplWhoisChannels(reply.client, names).Format(client)[0] -} - -func (reply *WhoisChannelsReply) Format(client *Client) []string { - return multilineFormat(reply, client) } // // messaging replies // -func RplPrivMsg(source Identifier, target Identifier, message string) Reply { +func RplPrivMsg(source Identifier, target Identifier, message string) string { return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message) } -func RplNotice(source Identifier, target Identifier, message string) Reply { +func RplNotice(source Identifier, target Identifier, message string) string { return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message) } -func RplNick(source Identifier, newNick string) Reply { +func RplNick(source Identifier, newNick string) string { return NewStringReply(source, NICK, newNick) } -func RplJoin(client *Client, channel *Channel) Reply { +func RplJoin(client *Client, channel *Channel) string { return NewStringReply(client, JOIN, channel.name) } -func RplPart(client *Client, channel *Channel, message string) Reply { +func RplPart(client *Client, channel *Channel, message string) string { return NewStringReply(client, PART, "%s :%s", channel, message) } -// TODO separate source and target -func RplMode(client *Client, changes ModeChanges) Reply { - return NewStringReply(client, MODE, "%s :%s", client.Nick(), changes) +func RplMode(client *Client, target *Client, changes ModeChanges) string { + return NewStringReply(client, MODE, "%s :%s", target.Nick(), changes) } func RplChannelMode(client *Client, channel *Channel, - changes ChannelModeChanges) Reply { + changes ChannelModeChanges) string { return NewStringReply(client, MODE, "%s %s", channel, changes) } -func RplTopicMsg(source Identifier, channel *Channel) Reply { +func RplTopicMsg(source Identifier, channel *Channel) string { return NewStringReply(source, TOPIC, "%s :%s", channel, channel.topic) } -func RplPing(server *Server, target Identifier) Reply { +func RplPing(server *Server, target Identifier) string { return NewStringReply(server, PING, target.Nick()) } -func RplPong(server *Server, client *Client) Reply { +func RplPong(server *Server, client *Client) string { return NewStringReply(server, PONG, client.Nick()) } -func RplQuit(client *Client, message string) Reply { +func RplQuit(client *Client, message string) string { return NewStringReply(client, QUIT, ":%s", message) } -func RplError(server *Server, message string) Reply { - return NewStringReply(server, ERROR, message) +func RplError(server *Server, message string) string { + return NewStringReply(server, ERROR, ":%s", message) } -func RplInviteMsg(channel *Channel, inviter *Client) Reply { +func RplInviteMsg(channel *Channel, inviter *Client) string { return NewStringReply(inviter, INVITE, channel.name) } -func RplKick(channel *Channel, client *Client, target *Client, comment string) Reply { +func RplKick(channel *Channel, client *Client, target *Client, comment string) string { return NewStringReply(client, KICK, "%s %s :%s", channel, target.Nick(), comment) } // numeric replies -func RplWelcome(source Identifier, client *Client) Reply { - return NewNumericReply(source, RPL_WELCOME, - ":Welcome to the Internet Relay Network %s", client.Id()) +func (target *Client) RplWelcome() { + target.NumericReply(RPL_WELCOME, + ":Welcome to the Internet Relay Network %s", target.Id()) } -func RplYourHost(server *Server) Reply { - return NewNumericReply(server, RPL_YOURHOST, - ":Your host is %s, running version %s", server.name, VERSION) +func (target *Client) RplYourHost() { + target.NumericReply(RPL_YOURHOST, + ":Your host is %s, running version %s", target.server.name, VERSION) } -func RplCreated(server *Server) Reply { - return NewNumericReply(server, RPL_CREATED, - ":This server was created %s", server.ctime.Format(time.RFC1123)) +func (target *Client) RplCreated() { + target.NumericReply(RPL_CREATED, + ":This server was created %s", target.server.ctime.Format(time.RFC1123)) } -func RplMyInfo(server *Server) Reply { - return NewNumericReply(server, RPL_MYINFO, - "%s %s aiOorsw abeIikmntpqrsl", server.name, VERSION) +func (target *Client) RplMyInfo() { + target.NumericReply(RPL_MYINFO, + "%s %s aiOorsw abeIikmntpqrsl", target.server.name, VERSION) } -func RplUModeIs(server *Server, client *Client) Reply { - return NewNumericReply(server, RPL_UMODEIS, client.ModeString()) +func (target *Client) RplUModeIs(client *Client) { + target.NumericReply(RPL_UMODEIS, client.ModeString()) } -func RplNoTopic(channel *Channel) Reply { - return NewNumericReply(channel.server, RPL_NOTOPIC, +func (target *Client) RplNoTopic(channel *Channel) { + target.NumericReply(RPL_NOTOPIC, "%s :No topic is set", channel.name) } -func RplTopic(channel *Channel) Reply { - return NewNumericReply(channel.server, RPL_TOPIC, +func (target *Client) RplTopic(channel *Channel) { + target.NumericReply(RPL_TOPIC, "%s :%s", channel.name, channel.topic) } // // NB: correction in errata -func RplInvitingMsg(channel *Channel, invitee *Client) Reply { - return NewNumericReply(channel.server, RPL_INVITING, +func (target *Client) RplInvitingMsg(channel *Channel, invitee *Client) { + target.NumericReply(RPL_INVITING, "%s %s", invitee.Nick(), channel.name) } -func RplNamReply(channel *Channel, names []string) *NumericReply { - return NewNumericReply(channel.server, RPL_NAMREPLY, "= %s :%s", - channel.name, strings.Join(names, " ")) -} - -func RplEndOfNames(channel *Channel) Reply { - return NewNumericReply(channel.server, RPL_ENDOFNAMES, +func (target *Client) RplEndOfNames(channel *Channel) { + target.NumericReply(RPL_ENDOFNAMES, "%s :End of NAMES list", channel.name) } // :You are now an IRC operator -func RplYoureOper(server *Server) Reply { - return NewNumericReply(server, RPL_YOUREOPER, ":You are now an IRC operator") +func (target *Client) RplYoureOper() { + target.NumericReply(RPL_YOUREOPER, + ":You are now an IRC operator") } -func RplWhoisUser(client *Client) Reply { - return NewNumericReply(client.server, RPL_WHOISUSER, "%s %s %s * :%s", - client.Nick(), client.username, client.hostname, client.realname) +func (target *Client) RplWhoisUser(client *Client) { + target.NumericReply(RPL_WHOISUSER, + "%s %s %s * :%s", client.Nick(), client.username, client.hostname, + client.realname) } -func RplWhoisOperator(client *Client) Reply { - return NewNumericReply(client.server, RPL_WHOISOPERATOR, +func (target *Client) RplWhoisOperator(client *Client) { + target.NumericReply(RPL_WHOISOPERATOR, "%s :is an IRC operator", client.Nick()) } -func RplWhoisIdle(client *Client) Reply { - return NewNumericReply(client.server, RPL_WHOISIDLE, +func (target *Client) RplWhoisIdle(client *Client) { + target.NumericReply(RPL_WHOISIDLE, "%s %d %d :seconds idle, signon time", client.Nick(), client.IdleSeconds(), client.SignonTime()) } -func RplWhoisChannels(client *Client, chstrs []string) Reply { - return NewNumericReply(client.server, RPL_WHOISCHANNELS, - "%s :%s", client.Nick(), strings.Join(chstrs, " ")) +func (target *Client) RplEndOfWhois() { + target.NumericReply(RPL_ENDOFWHOIS, + ":End of WHOIS list") } -func RplEndOfWhois(server *Server) Reply { - return NewNumericReply(server, RPL_ENDOFWHOIS, ":End of WHOIS list") -} - -func RplChannelModeIs(channel *Channel) Reply { - return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s", - channel, channel.ModeString()) +func (target *Client) RplChannelModeIs(channel *Channel) { + target.NumericReply(RPL_CHANNELMODEIS, + "%s %s", channel, channel.ModeString()) } // ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ] // : -func RplWhoReply(channel *Channel, client *Client) Reply { +func (target *Client) RplWhoReply(channel *Channel, client *Client) { channelName := "*" flags := "" @@ -362,175 +227,177 @@ func RplWhoReply(channel *Channel, client *Client) Reply { flags += "+" } } - return NewNumericReply(client.server, RPL_WHOREPLY, - "%s %s %s %s %s %s :%d %s", - channelName, client.username, client.hostname, client.server.name, - client.Nick(), flags, client.hops, client.realname) + target.NumericReply(RPL_WHOREPLY, + "%s %s %s %s %s %s :%d %s", channelName, client.username, client.hostname, + client.server.name, client.Nick(), flags, client.hops, client.realname) } // :End of WHO list -func RplEndOfWho(server *Server, name string) Reply { - return NewNumericReply(server, RPL_ENDOFWHO, "%s :End of WHO list", name) +func (target *Client) RplEndOfWho(name string) { + target.NumericReply(RPL_ENDOFWHO, + "%s :End of WHO list", name) } -func RplBanList(channel *Channel, ban UserMask) Reply { - return NewNumericReply(channel.server, RPL_BANLIST, "%s %s", channel.name, ban) +func (target *Client) RplBanList(channel *Channel, ban UserMask) { + target.NumericReply(RPL_BANLIST, + "%s %s", channel.name, ban) } -func RplEndOfBanList(channel *Channel) Reply { - return NewNumericReply(channel.server, RPL_ENDOFBANLIST, +func (target *Client) RplEndOfBanList(channel *Channel) { + target.NumericReply(RPL_ENDOFBANLIST, "%s :End of channel ban list", channel.name) } -func RplNowAway(server *Server) Reply { - return NewNumericReply(server, RPL_NOWAWAY, +func (target *Client) RplNowAway() { + target.NumericReply(RPL_NOWAWAY, ":You have been marked as being away") } -func RplUnAway(server *Server) Reply { - return NewNumericReply(server, RPL_UNAWAY, +func (target *Client) RplUnAway() { + target.NumericReply(RPL_UNAWAY, ":You are no longer marked as being away") } -func RplAway(server *Server, client *Client) Reply { - return NewNumericReply(server, RPL_AWAY, +func (target *Client) RplAway(client *Client) { + target.NumericReply(RPL_AWAY, "%s :%s", client.Nick(), client.awayMessage) } -func RplIsOn(server *Server, nicks []string) Reply { - return NewNumericReply(server, RPL_ISON, +func (target *Client) RplIsOn(nicks []string) { + target.NumericReply(RPL_ISON, ":%s", strings.Join(nicks, " ")) } -func RplMOTDStart(server *Server) Reply { - return NewNumericReply(server, RPL_MOTDSTART, - ":- %s Message of the day - ", server.name) +func (target *Client) RplMOTDStart() { + target.NumericReply(RPL_MOTDSTART, + ":- %s Message of the day - ", target.server.name) } -func RplMOTD(server *Server, line string) Reply { - return NewNumericReply(server, RPL_MOTD, +func (target *Client) RplMOTD(line string) { + target.NumericReply(RPL_MOTD, ":- %s", line) } -func RplMOTDEnd(server *Server) Reply { - return NewNumericReply(server, RPL_ENDOFMOTD, +func (target *Client) RplMOTDEnd() { + target.NumericReply(RPL_ENDOFMOTD, ":End of MOTD command") } -func RplList(channel *Channel) Reply { - return NewNumericReply(channel.server, RPL_LIST, "%s %d :%s", - channel, len(channel.members), channel.topic) +func (target *Client) RplList(channel *Channel) { + target.NumericReply(RPL_LIST, + "%s %d :%s", channel, len(channel.members), channel.topic) } -func RplListEnd(server *Server) Reply { - return NewNumericReply(server, RPL_LISTEND, ":End of LIST") +func (target *Client) RplListEnd(server *Server) { + target.NumericReply(RPL_LISTEND, + ":End of LIST") } // // errors (also numeric) // -func ErrAlreadyRegistered(source Identifier) Reply { - return NewNumericReply(source, ERR_ALREADYREGISTRED, +func (target *Client) ErrAlreadyRegistered() { + target.NumericReply(ERR_ALREADYREGISTRED, ":You may not reregister") } -func ErrNickNameInUse(source Identifier, nick string) Reply { - return NewNumericReply(source, ERR_NICKNAMEINUSE, +func (target *Client) ErrNickNameInUse(nick string) { + target.NumericReply(ERR_NICKNAMEINUSE, "%s :Nickname is already in use", nick) } -func ErrUnknownCommand(source Identifier, code StringCode) Reply { - return NewNumericReply(source, ERR_UNKNOWNCOMMAND, +func (target *Client) ErrUnknownCommand(code StringCode) { + target.NumericReply(ERR_UNKNOWNCOMMAND, "%s :Unknown command", code) } -func ErrUsersDontMatch(source Identifier) Reply { - return NewNumericReply(source, ERR_USERSDONTMATCH, +func (target *Client) ErrUsersDontMatch() { + target.NumericReply(ERR_USERSDONTMATCH, ":Cannot change mode for other users") } -func ErrNeedMoreParams(source Identifier, command string) Reply { - return NewNumericReply(source, ERR_NEEDMOREPARAMS, +func (target *Client) ErrNeedMoreParams(command string) { + target.NumericReply(ERR_NEEDMOREPARAMS, "%s :Not enough parameters", command) } -func ErrNoSuchChannel(server *Server, channel string) Reply { - return NewNumericReply(server, ERR_NOSUCHCHANNEL, +func (target *Client) ErrNoSuchChannel(channel string) { + target.NumericReply(ERR_NOSUCHCHANNEL, "%s :No such channel", channel) } -func ErrUserOnChannel(channel *Channel, member *Client) Reply { - return NewNumericReply(channel.server, ERR_USERONCHANNEL, +func (target *Client) ErrUserOnChannel(channel *Channel, member *Client) { + target.NumericReply(ERR_USERONCHANNEL, "%s %s :is already on channel", member.Nick(), channel.name) } -func ErrNotOnChannel(channel *Channel) Reply { - return NewNumericReply(channel.server, ERR_NOTONCHANNEL, +func (target *Client) ErrNotOnChannel(channel *Channel) { + target.NumericReply(ERR_NOTONCHANNEL, "%s :You're not on that channel", channel.name) } -func ErrInviteOnlyChannel(channel *Channel) Reply { - return NewNumericReply(channel.server, ERR_INVITEONLYCHAN, +func (target *Client) ErrInviteOnlyChannel(channel *Channel) { + target.NumericReply(ERR_INVITEONLYCHAN, "%s :Cannot join channel (+i)", channel.name) } -func ErrBadChannelKey(channel *Channel) Reply { - return NewNumericReply(channel.server, ERR_BADCHANNELKEY, +func (target *Client) ErrBadChannelKey(channel *Channel) { + target.NumericReply(ERR_BADCHANNELKEY, "%s :Cannot join channel (+k)", channel.name) } -func ErrNoSuchNick(source Identifier, nick string) Reply { - return NewNumericReply(source, ERR_NOSUCHNICK, +func (target *Client) ErrNoSuchNick(nick string) { + target.NumericReply(ERR_NOSUCHNICK, "%s :No such nick/channel", nick) } -func ErrPasswdMismatch(server *Server) Reply { - return NewNumericReply(server, ERR_PASSWDMISMATCH, ":Password incorrect") +func (target *Client) ErrPasswdMismatch() { + target.NumericReply(ERR_PASSWDMISMATCH, ":Password incorrect") } -func ErrNoChanModes(channel *Channel) Reply { - return NewNumericReply(channel.server, ERR_NOCHANMODES, +func (target *Client) ErrNoChanModes(channel *Channel) { + target.NumericReply(ERR_NOCHANMODES, "%s :Channel doesn't support modes", channel) } -func ErrNoPrivileges(server *Server) Reply { - return NewNumericReply(server, ERR_NOPRIVILEGES, ":Permission Denied") +func (target *Client) ErrNoPrivileges() { + target.NumericReply(ERR_NOPRIVILEGES, ":Permission Denied") } -func ErrRestricted(server *Server) Reply { - return NewNumericReply(server, ERR_RESTRICTED, ":Your connection is restricted!") +func (target *Client) ErrRestricted() { + target.NumericReply(ERR_RESTRICTED, ":Your connection is restricted!") } -func ErrNoSuchServer(server *Server, target string) Reply { - return NewNumericReply(server, ERR_NOSUCHSERVER, "%s :No such server", target) +func (target *Client) ErrNoSuchServer(server string) { + target.NumericReply(ERR_NOSUCHSERVER, "%s :No such server", server) } -func ErrUserNotInChannel(channel *Channel, client *Client) Reply { - return NewNumericReply(channel.server, ERR_USERNOTINCHANNEL, +func (target *Client) ErrUserNotInChannel(channel *Channel, client *Client) { + target.NumericReply(ERR_USERNOTINCHANNEL, "%s %s :They aren't on that channel", client.Nick(), channel) } -func ErrCannotSendToChan(channel *Channel) Reply { - return NewNumericReply(channel.server, ERR_CANNOTSENDTOCHAN, +func (target *Client) ErrCannotSendToChan(channel *Channel) { + target.NumericReply(ERR_CANNOTSENDTOCHAN, "%s :Cannot send to channel", channel) } // :You're not channel operator -func ErrChanOPrivIsNeeded(channel *Channel) Reply { - return NewNumericReply(channel.server, ERR_CHANOPRIVSNEEDED, +func (target *Client) ErrChanOPrivIsNeeded(channel *Channel) { + target.NumericReply(ERR_CHANOPRIVSNEEDED, "%s :You're not channel operator", channel) } -func ErrNoMOTD(server *Server) Reply { - return NewNumericReply(server, ERR_NOMOTD, ":MOTD File is missing") +func (target *Client) ErrNoMOTD() { + target.NumericReply(ERR_NOMOTD, ":MOTD File is missing") } -func ErrNoNicknameGiven(server *Server) Reply { - return NewNumericReply(server, ERR_NONICKNAMEGIVEN, ":No nickname given") +func (target *Client) ErrNoNicknameGiven() { + target.NumericReply(ERR_NONICKNAMEGIVEN, ":No nickname given") } -func ErrErroneusNickname(server *Server, nick string) Reply { - return NewNumericReply(server, ERR_ERRONEUSNICKNAME, +func (target *Client) ErrErroneusNickname(nick string) { + target.NumericReply(ERR_ERRONEUSNICKNAME, "%s :Erroneous nickname", nick) } diff --git a/irc/server.go b/irc/server.go index fbf997bf..9d33a0c9 100644 --- a/irc/server.go +++ b/irc/server.go @@ -86,7 +86,7 @@ func (server *Server) ReceiveCommands() { default: srvCmd, ok := cmd.(ServerCommand) if !ok { - client.Reply(ErrUnknownCommand(server, cmd.Code())) + client.ErrUnknownCommand(cmd.Code()) continue } switch srvCmd.(type) { @@ -192,28 +192,28 @@ func (s *Server) GenerateGuestNick() string { func (s *Server) tryRegister(c *Client) { if c.HasNick() && c.HasUsername() { c.Register() - c.Reply(RplWelcome(s, c)) - c.Reply(RplYourHost(s)) - c.Reply(RplCreated(s)) - c.Reply(RplMyInfo(s)) + c.RplWelcome() + c.RplYourHost() + c.RplCreated() + c.RplMyInfo() s.MOTD(c) } } func (server *Server) MOTD(client *Client) { if server.motdFile == "" { - client.Reply(ErrNoMOTD(server)) + client.ErrNoMOTD() return } file, err := os.Open(server.motdFile) if err != nil { - client.Reply(ErrNoMOTD(server)) + client.ErrNoMOTD() return } defer file.Close() - client.Reply(RplMOTDStart(server)) + client.RplMOTDStart() reader := bufio.NewReader(file) for { line, err := reader.ReadString('\n') @@ -224,17 +224,17 @@ func (server *Server) MOTD(client *Client) { if len(line) > 80 { for len(line) > 80 { - client.Reply(RplMOTD(server, line[0:80])) + client.RplMOTD(line[0:80]) line = line[80:] } if len(line) > 0 { - client.Reply(RplMOTD(server, line)) + client.RplMOTD(line) } } else { - client.Reply(RplMOTD(server, line)) + client.RplMOTD(line) } } - client.Reply(RplMOTDEnd(server)) + client.RplMOTDEnd() } func (s *Server) Id() string { @@ -266,7 +266,7 @@ func (m *PassCommand) HandleAuthServer(s *Server) { client := m.Client() if s.password != m.password { - client.Reply(ErrPasswdMismatch(s)) + client.ErrPasswdMismatch() client.socket.Close() return } @@ -286,17 +286,17 @@ func (m *NickCommand) HandleRegServer(s *Server) { client := m.Client() if m.nickname == "" { - client.Reply(ErrNoNicknameGiven(s)) + client.ErrNoNicknameGiven() return } if s.clients.Get(m.nickname) != nil { - client.Reply(ErrNickNameInUse(s, m.nickname)) + client.ErrNickNameInUse(m.nickname) return } if !IsNickname(m.nickname) { - client.Reply(ErrErroneusNickname(s, m.nickname)) + client.ErrErroneusNickname(m.nickname) return } @@ -315,7 +315,7 @@ func (msg *RFC2812UserCommand) HandleRegServer(server *Server) { for _, mode := range msg.Flags() { client.flags[mode] = true } - client.Reply(RplUModeIs(server, client)) + client.RplUModeIs(client) } msg.HandleRegServer2(server) } @@ -335,11 +335,11 @@ func (msg *QuitCommand) HandleRegServer(server *Server) { // func (m *PassCommand) HandleServer(s *Server) { - m.Client().Reply(ErrAlreadyRegistered(s)) + m.Client().ErrAlreadyRegistered() } func (m *PingCommand) HandleServer(s *Server) { - m.Client().Reply(RplPong(s, m.Client())) + m.Client().replies <- RplPong(s, m.Client()) } func (m *PongCommand) HandleServer(s *Server) { @@ -350,12 +350,12 @@ func (msg *NickCommand) HandleServer(server *Server) { client := msg.Client() if msg.nickname == "" { - client.Reply(ErrNoNicknameGiven(server)) + client.ErrNoNicknameGiven() return } if server.clients.Get(msg.nickname) != nil { - client.Reply(ErrNickNameInUse(server, msg.nickname)) + client.ErrNickNameInUse(msg.nickname) return } @@ -363,7 +363,7 @@ func (msg *NickCommand) HandleServer(server *Server) { } func (m *UserCommand) HandleServer(s *Server) { - m.Client().Reply(ErrAlreadyRegistered(s)) + m.Client().ErrAlreadyRegistered() } func (msg *QuitCommand) HandleServer(server *Server) { @@ -392,7 +392,7 @@ func (m *PartCommand) HandleServer(server *Server) { channel := server.channels[chname] if channel == nil { - m.Client().Reply(ErrNoSuchChannel(server, chname)) + m.Client().ErrNoSuchChannel(chname) continue } @@ -404,7 +404,7 @@ func (msg *TopicCommand) HandleServer(server *Server) { client := msg.Client() channel := server.channels[msg.channel] if channel == nil { - client.Reply(ErrNoSuchChannel(server, msg.channel)) + client.ErrNoSuchChannel(msg.channel) return } @@ -420,7 +420,7 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) { if IsChannel(msg.target) { channel := server.channels[msg.target] if channel == nil { - client.Reply(ErrNoSuchChannel(server, msg.target)) + client.ErrNoSuchChannel(msg.target) return } @@ -430,12 +430,12 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) { target := server.clients[msg.target] if target == nil { - client.Reply(ErrNoSuchNick(server, msg.target)) + client.ErrNoSuchNick(msg.target) return } - target.Reply(RplPrivMsg(client, target, msg.message)) + target.replies <- RplPrivMsg(client, target, msg.message) if target.flags[Away] { - client.Reply(RplAway(server, target)) + target.RplAway(client) } } @@ -444,12 +444,12 @@ func (m *ModeCommand) HandleServer(s *Server) { target := s.clients.Get(m.nickname) if target == nil { - client.Reply(ErrNoSuchNick(s, m.nickname)) + client.ErrNoSuchNick(m.nickname) return } if client != target && !client.flags[Operator] { - client.Reply(ErrUsersDontMatch(s)) + client.ErrUsersDontMatch() return } @@ -460,27 +460,47 @@ func (m *ModeCommand) HandleServer(s *Server) { case Invisible, ServerNotice, WallOps: switch change.op { case Add: - client.flags[change.mode] = true + target.flags[change.mode] = true changes = append(changes, change) case Remove: - delete(client.flags, change.mode) + delete(target.flags, change.mode) changes = append(changes, change) } case Operator, LocalOperator: if change.op == Remove { - delete(client.flags, change.mode) + delete(target.flags, change.mode) changes = append(changes, change) } } } + // Who should get these replies? if len(changes) > 0 { - client.Reply(RplMode(client, changes)) + client.replies <- RplMode(client, target, changes) } } +func (client *Client) WhoisChannelsNames() []string { + chstrs := make([]string, len(client.channels)) + index := 0 + for channel := range client.channels { + switch { + case channel.members[client][ChannelOperator]: + chstrs[index] = "@" + channel.name + + case channel.members[client][Voice]: + chstrs[index] = "+" + channel.name + + default: + chstrs[index] = channel.name + } + index += 1 + } + return chstrs +} + func (m *WhoisCommand) HandleServer(server *Server) { client := m.Client() @@ -490,16 +510,17 @@ func (m *WhoisCommand) HandleServer(server *Server) { // TODO implement wildcard matching mclient := server.clients.Get(mask) if mclient == nil { - client.Reply(ErrNoSuchNick(server, mask)) + client.ErrNoSuchNick(mask) continue } - client.Reply(RplWhoisUser(mclient)) + client.RplWhoisUser(mclient) if client.flags[Operator] { - client.Reply(RplWhoisOperator(mclient)) + client.RplWhoisOperator(mclient) } - client.Reply(RplWhoisIdle(mclient)) - client.Reply(NewWhoisChannelsReply(mclient)) - client.Reply(RplEndOfWhois(server)) + client.RplWhoisIdle(mclient) + client.MultilineReply(client.WhoisChannelsNames(), RPL_WHOISCHANNELS, + "%s :%s", client.Nick()) + client.RplEndOfWhois() } } @@ -507,7 +528,7 @@ func (msg *ChannelModeCommand) HandleServer(server *Server) { client := msg.Client() channel := server.channels[msg.channel] if channel == nil { - client.Reply(ErrNoSuchChannel(server, msg.channel)) + client.ErrNoSuchChannel(msg.channel) return } @@ -517,15 +538,15 @@ func (msg *ChannelModeCommand) HandleServer(server *Server) { func whoChannel(client *Client, channel *Channel) { for member := range channel.members { if !client.flags[Invisible] { - client.Reply(RplWhoReply(channel, member)) + client.RplWhoReply(channel, member) } } } func (msg *WhoCommand) HandleServer(server *Server) { client := msg.Client() - // TODO implement wildcard matching + // TODO implement wildcard matching mask := string(msg.mask) if mask == "" { for _, channel := range server.channels { @@ -539,25 +560,25 @@ func (msg *WhoCommand) HandleServer(server *Server) { } else { mclient := server.clients[mask] if mclient != nil && !mclient.flags[Invisible] { - client.Reply(RplWhoReply(nil, mclient)) + client.RplWhoReply(nil, mclient) } } - client.Reply(RplEndOfWho(server, mask)) + client.RplEndOfWho(mask) } func (msg *OperCommand) HandleServer(server *Server) { client := msg.Client() if server.operators[msg.name] != msg.password { - client.Reply(ErrPasswdMismatch(server)) + client.ErrPasswdMismatch() return } client.flags[Operator] = true - client.Reply(RplYoureOper(server)) - client.Reply(RplUModeIs(server, client)) + client.RplYoureOper() + client.RplUModeIs(client) } func (msg *AwayCommand) HandleServer(server *Server) { @@ -570,9 +591,9 @@ func (msg *AwayCommand) HandleServer(server *Server) { client.awayMessage = msg.text if client.flags[Away] { - client.Reply(RplNowAway(server)) + client.RplNowAway() } else { - client.Reply(RplUnAway(server)) + client.RplUnAway() } } @@ -586,7 +607,7 @@ func (msg *IsOnCommand) HandleServer(server *Server) { } } - client.Reply(RplIsOn(server, ison)) + client.RplIsOn(ison) } func (msg *MOTDCommand) HandleServer(server *Server) { @@ -598,7 +619,7 @@ func (msg *NoticeCommand) HandleServer(server *Server) { if IsChannel(msg.target) { channel := server.channels[msg.target] if channel == nil { - client.Reply(ErrNoSuchChannel(server, msg.target)) + client.ErrNoSuchChannel(msg.target) return } @@ -608,10 +629,10 @@ func (msg *NoticeCommand) HandleServer(server *Server) { target := server.clients.Get(msg.target) if target == nil { - client.Reply(ErrNoSuchNick(server, msg.target)) + client.ErrNoSuchNick(msg.target) return } - target.Reply(RplNotice(client, target, msg.message)) + target.replies <- RplNotice(client, target, msg.message) } func (msg *KickCommand) HandleServer(server *Server) { @@ -619,13 +640,13 @@ func (msg *KickCommand) HandleServer(server *Server) { for chname, nickname := range msg.kicks { channel := server.channels[chname] if channel == nil { - client.Reply(ErrNoSuchChannel(server, chname)) + client.ErrNoSuchChannel(chname) continue } target := server.clients[nickname] if target == nil { - client.Reply(ErrNoSuchNick(server, nickname)) + client.ErrNoSuchNick(nickname) continue } @@ -638,7 +659,7 @@ func (msg *ListCommand) HandleServer(server *Server) { // TODO target server if msg.target != "" { - client.Reply(ErrNoSuchServer(server, msg.target)) + client.ErrNoSuchServer(msg.target) return } @@ -648,20 +669,20 @@ func (msg *ListCommand) HandleServer(server *Server) { (channel.flags[Secret] || channel.flags[Private]) { continue } - client.Reply(RplList(channel)) + client.RplList(channel) } } else { for _, chname := range msg.channels { channel := server.channels[chname] if channel == nil || (!client.flags[Operator] && (channel.flags[Secret] || channel.flags[Private])) { - client.Reply(ErrNoSuchChannel(server, chname)) + client.ErrNoSuchChannel(chname) continue } - client.Reply(RplList(channel)) + client.RplList(channel) } } - client.Reply(RplListEnd(server)) + client.RplListEnd(server) } func (msg *NamesCommand) HandleServer(server *Server) { @@ -676,7 +697,7 @@ func (msg *NamesCommand) HandleServer(server *Server) { for _, chname := range msg.channels { channel := server.channels[chname] if channel == nil { - client.Reply(ErrNoSuchChannel(server, chname)) + client.ErrNoSuchChannel(chname) continue } channel.Names(client) diff --git a/irc/types.go b/irc/types.go index 8e756fd9..e8991f4d 100644 --- a/irc/types.go +++ b/irc/types.go @@ -170,20 +170,13 @@ type Identifier interface { } type Replier interface { - Reply(Reply) -} - -type Reply interface { - Code() ReplyCode - Format(*Client) []string - Source() string + Reply(...string) } type Command interface { Code() StringCode Client() *Client Source() Identifier - Reply(Reply) } type ServerCommand interface {