From d7d6263e3582b0e1d62073b1ac174b447a3f07e1 Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Sun, 9 Dec 2012 13:47:02 -0800 Subject: [PATCH] rudimentary (broken) invite support --- src/irc/channel.go | 17 +++++ src/irc/commands.go | 46 ++++++++++++++ src/irc/constants.go | 144 ++++++++++++++++++++++++++++++++++++++----- src/irc/parse.go | 15 +---- src/irc/reply.go | 68 ++++++++++++++------ 5 files changed, 244 insertions(+), 46 deletions(-) diff --git a/src/irc/channel.go b/src/irc/channel.go index 2b6c3657..52520a3f 100644 --- a/src/irc/channel.go +++ b/src/irc/channel.go @@ -119,3 +119,20 @@ func (ch *Channel) ChangeTopic(cl *Client, newTopic string) { ch.Send(RplNoTopic(ch), nil) } } + +func (ch *Channel) Invite(inviter *Client, invitee *Client) { + if !ch.members[inviter] { + inviter.send <- ErrNotOnChannel(ch) + return + } + + if ch.members[invitee] { + inviter.send <- ErrUserOnChannel(ch, invitee) + return + } + + ch.invites[invitee.nick] = true + + invitee.send <- RplInviteMsg(ch, inviter) + inviter.send <- RplInvitingMsg(ch, invitee) +} diff --git a/src/irc/commands.go b/src/irc/commands.go index de6f1195..0dbfe244 100644 --- a/src/irc/commands.go +++ b/src/irc/commands.go @@ -15,6 +15,19 @@ type Message interface { var ( ErrNotEnoughArgs = errors.New("not enough arguments") ErrUModeUnknownFlag = errors.New("unknown umode flag") + parseCommandFuncs = map[string]ParseFunc{ + "INVITE": NewInviteMessage, + "JOIN": NewJoinMessage, + "MODE": NewModeMessage, + "NICK": NewNickMessage, + "PART": NewPartMessage, + "PING": NewPingMessage, + "PONG": NewPongMessage, + "PRIVMSG": NewPrivMsgMessage, + "QUIT": NewQuitMessage, + "TOPIC": NewTopicMessage, + "USER": NewUserMessage, + } ) // unknown @@ -319,3 +332,36 @@ func (m *TopicMessage) Handle(s *Server, c *Client) { channel.ChangeTopic(c, m.topic) } } + +// INVITE + +type InviteMessage struct { + nickname string + channel string +} + +func NewInviteMessage(args []string) (Message, error) { + if len(args) < 2 { + return nil, ErrNotEnoughArgs + } + return &InviteMessage{ + nickname: args[0], + channel: args[1], + }, nil +} + +func (m *InviteMessage) Handle(s *Server, c *Client) { + channel := s.channels[m.channel] + if channel == nil { + c.send <- ErrNoSuchNick(s, m.channel) + return + } + + invitee := s.nicks[m.nickname] + if invitee == nil { + c.send <- ErrNoSuchNick(s, m.nickname) + return + } + + channel.Invite(c, invitee) +} diff --git a/src/irc/constants.go b/src/irc/constants.go index 4826d036..efd2029f 100644 --- a/src/irc/constants.go +++ b/src/irc/constants.go @@ -5,31 +5,145 @@ const ( ) const ( - // # numeric codes - // ## reply codes - RPL_WELCOME = "001" - RPL_YOURHOST = "002" - RPL_CREATED = "003" - RPL_MYINFO = "004" - RPL_UMODEIS = "221" - RPL_NOTOPIC = "331" - RPL_TOPIC = "332" - RPL_NAMREPLY = "353" - RPL_ENDOFNAMES = "366" - RPL_INFO = "371" - // ## error codes + // numeric codes + RPL_WELCOME = "001" + RPL_YOURHOST = "002" + RPL_CREATED = "003" + RPL_MYINFO = "004" + RPL_BOUNCE = "005" + RPL_TRACELINK = "200" + RPL_TRACECONNECTING = "201" + RPL_TRACEHANDSHAKE = "202" + RPL_TRACEUNKNOWN = "203" + RPL_TRACEOPERATOR = "204" + RPL_TRACEUSER = "205" + RPL_TRACESERVER = "206" + RPL_TRACESERVICE = "207" + RPL_TRACENEWTYPE = "208" + RPL_TRACECLASS = "209" + RPL_TRACERECONNECT = "210" + RPL_STATSLINKINFO = "211" + RPL_STATSCOMMANDS = "212" + RPL_ENDOFSTATS = "219" + RPL_UMODEIS = "221" + RPL_SERVLIST = "234" + RPL_SERVLISTEND = "235" + RPL_STATSUPTIME = "242" + RPL_STATSOLINE = "243" + RPL_LUSERCLIENT = "251" + RPL_LUSEROP = "252" + RPL_LUSERUNKNOWN = "253" + RPL_LUSERCHANNELS = "254" + RPL_LUSERME = "255" + RPL_ADMINME = "256" + RPL_ADMINLOC1 = "257" + RPL_ADMINLOC2 = "258" + RPL_ADMINEMAIL = "259" + RPL_TRACELOG = "261" + RPL_TRACEEND = "262" + RPL_TRYAGAIN = "263" + RPL_AWAY = "301" + RPL_USERHOST = "302" + RPL_ISON = "303" + RPL_UNAWAY = "305" + RPL_NOWAWAY = "306" + RPL_WHOISUSER = "311" + RPL_WHOISSERVER = "312" + RPL_WHOISOPERATOR = "313" + RPL_WHOWASUSER = "314" + RPL_ENDOFWHO = "315" + RPL_WHOISIDLE = "317" + RPL_ENDOFWHOIS = "318" + RPL_WHOISCHANNELS = "319" + RPL_LIST = "322" + RPL_LISTEND = "323" + RPL_CHANNELMODEIS = "324" + RPL_UNIQOPIS = "325" + RPL_NOTOPIC = "331" + RPL_TOPIC = "332" + RPL_INVITING = "341" + RPL_SUMMONING = "342" + RPL_INVITELIST = "346" + RPL_ENDOFINVITELIST = "347" + RPL_EXCEPTLIST = "348" + RPL_ENDOFEXCEPTLIST = "349" + RPL_VERSION = "351" + RPL_WHOREPLY = "352" + RPL_NAMREPLY = "353" + RPL_LINKS = "364" + RPL_ENDOFLINKS = "365" + RPL_ENDOFNAMES = "366" + RPL_BANLIST = "367" + RPL_ENDOFBANLIST = "368" + RPL_ENDOFWHOWAS = "369" + RPL_INFO = "371" + RPL_MOTD = "372" + RPL_ENDOFINFO = "374" + RPL_MOTDSTART = "375" + RPL_ENDOFMOTD = "376" + RPL_YOUREOPER = "381" + RPL_REHASHING = "382" + RPL_YOURESERVICE = "383" + RPL_TIME = "391" + RPL_USERSSTART = "392" + RPL_USERS = "393" + RPL_ENDOFUSERS = "394" + RPL_NOUSERS = "395" ERR_NOSUCHNICK = "401" ERR_NOSUCHSERVER = "402" ERR_NOSUCHCHANNEL = "403" + ERR_CANNOTSENDTOCHAN = "404" + ERR_TOOMANYCHANNELS = "405" + ERR_WASNOSUCHNICK = "406" + ERR_TOOMANYTARGETS = "407" + ERR_NOSUCHSERVICE = "408" + ERR_NOORIGIN = "409" + ERR_NORECIPIENT = "411" + ERR_NOTEXTTOSEND = "412" + ERR_NOTOPLEVEL = "413" + ERR_WILDTOPLEVEL = "414" + ERR_BADMASK = "415" ERR_UNKNOWNCOMMAND = "421" + ERR_NOMOTD = "422" + ERR_NOADMININFO = "423" + ERR_FILEERROR = "424" + ERR_NONICKNAMEGIVEN = "431" + ERR_ERRONEUSNICKNAME = "432" ERR_NICKNAMEINUSE = "433" + ERR_NICKCOLLISION = "436" + ERR_UNAVAILRESOURCE = "437" + ERR_USERNOTINCHANNEL = "441" ERR_NOTONCHANNEL = "442" + ERR_USERONCHANNEL = "443" + ERR_NOLOGIN = "444" + ERR_SUMMONDISABLED = "445" + ERR_USERSDISABLED = "446" + ERR_NOTREGISTERED = "451" ERR_NEEDMOREPARAMS = "461" ERR_ALREADYREGISTRED = "462" - ERR_INVITEONLYCHANNEL = "473" + ERR_NOPERMFORHOST = "463" + ERR_PASSWDMISMATCH = "464" + ERR_YOUREBANNEDCREEP = "465" + ERR_YOUWILLBEBANNED = "466" + ERR_KEYSET = "467" + ERR_CHANNELISFULL = "471" + ERR_UNKNOWNMODE = "472" + ERR_INVITEONLYCHAN = "473" + ERR_BANNEDFROMCHAN = "474" ERR_BADCHANNELKEY = "475" + ERR_BADCHANMASK = "476" + ERR_NOCHANMODES = "477" + ERR_BANLISTFULL = "478" + ERR_NOPRIVILEGES = "481" + ERR_CHANOPRIVSNEEDED = "482" + ERR_CANTKILLSERVER = "483" + ERR_RESTRICTED = "484" + ERR_UNIQOPPRIVSNEEDED = "485" + ERR_NOOPERHOST = "491" + ERR_UMODEUNKNOWNFLAG = "501" ERR_USERSDONTMATCH = "502" - // # message codes + // message codes + RPL_INVITE = "INVITE" RPL_JOIN = "JOIN" RPL_NICK = "NICK" RPL_PART = "PART" diff --git a/src/irc/parse.go b/src/irc/parse.go index 4a302e09..15520bfa 100644 --- a/src/irc/parse.go +++ b/src/irc/parse.go @@ -5,18 +5,7 @@ import ( "strings" ) -var commands = map[string]func([]string) (Message, error){ - "JOIN": NewJoinMessage, - "MODE": NewModeMessage, - "NICK": NewNickMessage, - "PART": NewPartMessage, - "PING": NewPingMessage, - "PONG": NewPongMessage, - "PRIVMSG": NewPrivMsgMessage, - "QUIT": NewQuitMessage, - "TOPIC": NewTopicMessage, - "USER": NewUserMessage, -} +type ParseFunc func([]string) (Message, error) var ( ErrParseMessage = errors.New("failed to parse message") @@ -24,7 +13,7 @@ var ( func ParseMessage(line string) (msg Message, err error) { command, args := parseLine(line) - constructor, ok := commands[command] + constructor, ok := parseCommandFuncs[command] if !ok { msg = &UnknownMessage{command} return diff --git a/src/irc/reply.go b/src/irc/reply.go index 5a553ae3..1ba77e07 100644 --- a/src/irc/reply.go +++ b/src/irc/reply.go @@ -49,6 +49,15 @@ func RplNick(client *Client, newNick string) Reply { return NewReply(client, RPL_NICK, ":"+newNick) } +func RplInviteMsg(channel *Channel, inviter *Client) Reply { + return NewReply(inviter, RPL_INVITE, channel.name) +} + +func RplInvitingMsg(channel *Channel, invitee *Client) Reply { + return NewReply(channel.server, RPL_INVITING, + fmt.Sprintf("%s %s", channel.name, invitee.Nick())) +} + func RplPrivMsgChannel(channel *Channel, source *Client, message string) Reply { return &ChannelReply{NewReply(source, RPL_PRIVMSG, ":"+message), channel} } @@ -64,19 +73,23 @@ func RplPart(channel *Channel, client *Client, message string) Reply { // Server Info func RplWelcome(source Identifier, client *Client) Reply { - return NewReply(source, RPL_WELCOME, "Welcome to the Internet Relay Network "+client.Id()) + return NewReply(source, RPL_WELCOME, + "Welcome to the Internet Relay Network "+client.Id()) } func RplYourHost(server *Server, target *Client) Reply { - return NewReply(server, RPL_YOURHOST, fmt.Sprintf("Your host is %s, running version %s", server.hostname, VERSION)) + return NewReply(server, RPL_YOURHOST, + fmt.Sprintf("Your host is %s, running version %s", server.hostname, VERSION)) } func RplCreated(server *Server) Reply { - return NewReply(server, RPL_CREATED, "This server was created "+server.ctime.Format(time.RFC1123)) + return NewReply(server, RPL_CREATED, + "This server was created "+server.ctime.Format(time.RFC1123)) } func RplMyInfo(server *Server) Reply { - return NewReply(server, RPL_MYINFO, fmt.Sprintf("%s %s i ik", server.name, VERSION)) + return NewReply(server, RPL_MYINFO, + fmt.Sprintf("%s %s i ik", server.name, VERSION)) } func RplUModeIs(server *Server, client *Client) Reply { @@ -86,20 +99,24 @@ func RplUModeIs(server *Server, client *Client) Reply { // channel operations func RplNoTopic(channel *Channel) Reply { - return &ChannelReply{NewReply(channel.server, RPL_NOTOPIC, channel.name+" :No topic is set"), channel} + return &ChannelReply{NewReply(channel.server, RPL_NOTOPIC, + channel.name+" :No topic is set"), channel} } func RplTopic(channel *Channel) Reply { - return &ChannelReply{NewReply(channel.server, RPL_TOPIC, fmt.Sprintf("%s :%s", channel.name, channel.topic)), channel} + return &ChannelReply{NewReply(channel.server, RPL_TOPIC, + fmt.Sprintf("%s :%s", channel.name, channel.topic)), channel} } func RplNamReply(channel *Channel) Reply { // TODO multiple names and splitting based on message size - return NewReply(channel.server, RPL_NAMREPLY, fmt.Sprintf("= %s :%s", channel.name, strings.Join(channel.Nicks(), " "))) + return NewReply(channel.server, RPL_NAMREPLY, + fmt.Sprintf("= %s :%s", channel.name, strings.Join(channel.Nicks(), " "))) } func RplEndOfNames(source Identifier) Reply { - return NewReply(source, RPL_ENDOFNAMES, ":End of NAMES list") + return NewReply(source, RPL_ENDOFNAMES, + ":End of NAMES list") } func RplPong(server *Server) Reply { @@ -109,41 +126,56 @@ func RplPong(server *Server) Reply { // errors func ErrAlreadyRegistered(source Identifier) Reply { - return NewReply(source, ERR_ALREADYREGISTRED, ":You may not reregister") + return NewReply(source, ERR_ALREADYREGISTRED, + ":You may not reregister") } func ErrNickNameInUse(source Identifier, nick string) Reply { - return NewReply(source, ERR_NICKNAMEINUSE, nick+" :Nickname is already in use") + return NewReply(source, ERR_NICKNAMEINUSE, + nick+" :Nickname is already in use") } func ErrUnknownCommand(source Identifier, command string) Reply { - return NewReply(source, ERR_UNKNOWNCOMMAND, command+" :Unknown command") + return NewReply(source, ERR_UNKNOWNCOMMAND, + command+" :Unknown command") } func ErrUsersDontMatch(source Identifier) Reply { - return NewReply(source, ERR_USERSDONTMATCH, ":Cannot change mode for other users") + return NewReply(source, ERR_USERSDONTMATCH, + ":Cannot change mode for other users") } func ErrNeedMoreParams(source Identifier, command string) Reply { - return NewReply(source, ERR_NEEDMOREPARAMS, command+"%s :Not enough parameters") + return NewReply(source, ERR_NEEDMOREPARAMS, + command+"%s :Not enough parameters") } func ErrNoSuchChannel(source Identifier, channel string) Reply { - return NewReply(source, ERR_NOSUCHCHANNEL, channel+" :No such channel") + return NewReply(source, ERR_NOSUCHCHANNEL, + channel+" :No such channel") +} + +func ErrUserOnChannel(channel *Channel, member *Client) Reply { + return NewReply(channel.server, ERR_USERONCHANNEL, + fmt.Sprintf("%s %s :is already on channel", member.nick, channel.name)) } func ErrNotOnChannel(channel *Channel) Reply { - return NewReply(channel.server, ERR_NOTONCHANNEL, channel.name+" :You're not on that channel") + return NewReply(channel.server, ERR_NOTONCHANNEL, + channel.name+" :You're not on that channel") } func ErrInviteOnlyChannel(channel *Channel) Reply { - return NewReply(channel.server, ERR_INVITEONLYCHANNEL, channel.name+" :Cannot join channel (+i)") + return NewReply(channel.server, ERR_INVITEONLYCHAN, + channel.name+" :Cannot join channel (+i)") } func ErrBadChannelKey(channel *Channel) Reply { - return NewReply(channel.server, ERR_BADCHANNELKEY, channel.name+" :Cannot join channel (+k)") + return NewReply(channel.server, ERR_BADCHANNELKEY, + channel.name+" :Cannot join channel (+k)") } func ErrNoSuchNick(source Identifier, nick string) Reply { - return NewReply(source, ERR_NOSUCHNICK, nick+" :No such nick/channel") + return NewReply(source, ERR_NOSUCHNICK, + nick+" :No such nick/channel") }