diff --git a/downstream.go b/downstream.go index 5de6f8a..b39464b 100644 --- a/downstream.go +++ b/downstream.go @@ -945,6 +945,44 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { Command: "WHO", Params: params, }) + case "WHOIS": + if len(msg.Params) == 0 { + return ircError{&irc.Message{ + Command: irc.ERR_NONICKNAMEGIVEN, + Params: []string{dc.nick, "No nickname given"}, + }} + } + + var target, mask string + if len(msg.Params) == 1 { + target = "" + mask = msg.Params[0] + } else { + target = msg.Params[0] + mask = msg.Params[1] + } + // TODO: support multiple WHOIS users + if i := strings.IndexByte(mask, ','); i >= 0 { + mask = mask[:i] + } + + // TODO: support WHOIS masks + uc, upstreamNick, err := dc.unmarshalEntity(mask) + if err != nil { + return err + } + + var params []string + if target != "" { + params = []string{target, upstreamNick} + } else { + params = []string{upstreamNick} + } + + uc.SendMessage(&irc.Message{ + Command: "WHOIS", + Params: params, + }) case "PRIVMSG": var targetsStr, text string if err := parseMessageParams(msg, &targetsStr, &text); err != nil { diff --git a/irc.go b/irc.go index 17e0011..8df3a2a 100644 --- a/irc.go +++ b/irc.go @@ -86,6 +86,13 @@ const ( const stdMembershipPrefixes = "~&@%+" +func (m membership) String() string { + if m == 0 { + return "" + } + return string(m) +} + func parseMembershipPrefix(s string) (prefix membership, nick string) { // TODO: any prefix from PREFIX RPL_ISUPPORT if strings.IndexByte(stdMembershipPrefixes, s[0]) >= 0 { diff --git a/upstream.go b/upstream.go index c0e3868..bef66e2 100644 --- a/upstream.go +++ b/upstream.go @@ -603,7 +603,101 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { dc.SendMessage(&irc.Message{ Prefix: dc.srv.prefix(), Command: irc.RPL_ENDOFWHO, - Params: []string{dc.nick, name, "End of /WHO list."}, + Params: []string{dc.nick, name, "End of WHO list"}, + }) + }) + case irc.RPL_WHOISUSER: + var nick, username, host, realname string + if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil { + return err + } + + uc.forEachDownstream(func(dc *downstreamConn) { + nick := dc.marshalNick(uc, nick) + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_WHOISUSER, + Params: []string{dc.nick, nick, username, host, "*", realname}, + }) + }) + case irc.RPL_WHOISSERVER: + var nick, server, serverInfo string + if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil { + return err + } + + uc.forEachDownstream(func(dc *downstreamConn) { + nick := dc.marshalNick(uc, nick) + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_WHOISSERVER, + Params: []string{dc.nick, nick, server, serverInfo}, + }) + }) + case irc.RPL_WHOISOPERATOR: + var nick string + if err := parseMessageParams(msg, nil, &nick); err != nil { + return err + } + + uc.forEachDownstream(func(dc *downstreamConn) { + nick := dc.marshalNick(uc, nick) + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_WHOISOPERATOR, + Params: []string{dc.nick, nick, "is an IRC operator"}, + }) + }) + case irc.RPL_WHOISIDLE: + var nick string + if err := parseMessageParams(msg, nil, &nick, nil); err != nil { + return err + } + + uc.forEachDownstream(func(dc *downstreamConn) { + nick := dc.marshalNick(uc, nick) + params := []string{dc.nick, nick} + params = append(params, msg.Params[2:]...) + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_WHOISIDLE, + Params: params, + }) + }) + case irc.RPL_WHOISCHANNELS: + var nick, channelList string + if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil { + return err + } + channels := strings.Split(channelList, " ") + + uc.forEachDownstream(func(dc *downstreamConn) { + nick := dc.marshalNick(uc, nick) + channelList := make([]string, len(channels)) + for i, channel := range channels { + prefix, channel := parseMembershipPrefix(channel) + channel = dc.marshalChannel(uc, channel) + channelList[i] = prefix.String() + channel + } + channels := strings.Join(channelList, " ") + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_WHOISCHANNELS, + Params: []string{dc.nick, nick, channels}, + }) + }) + case irc.RPL_ENDOFWHOIS: + var nick string + if err := parseMessageParams(msg, nil, &nick); err != nil { + return err + } + + uc.forEachDownstream(func(dc *downstreamConn) { + nick := dc.marshalNick(uc, nick) + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_ENDOFWHOIS, + Params: []string{dc.nick, nick, "End of WHOIS list"}, }) }) case "PRIVMSG":