From 27efadda57c50e78b16befd570140cb8b543d6a6 Mon Sep 17 00:00:00 2001 From: Liam Stanley Date: Mon, 13 Feb 2017 07:52:29 -0500 Subject: [PATCH] implement commands --- builtin.go | 15 +-- client.go | 334 +----------------------------------------------- client_test.go | 8 +- commands.go | 340 +++++++++++++++++++++++++++++++++++++++++++++++++ ctcp.go | 14 +- event.go | 5 +- state.go | 5 + 7 files changed, 368 insertions(+), 353 deletions(-) create mode 100644 commands.go diff --git a/builtin.go b/builtin.go index 413f2b6..147d393 100644 --- a/builtin.go +++ b/builtin.go @@ -92,12 +92,12 @@ func handleConnect(c *Client, e Event) { // nickCollisionHandler helps prevent the client from having conflicting // nicknames with another bot, user, etc. func nickCollisionHandler(c *Client, e Event) { - c.Nick(c.GetNick() + "_") + c.Commands.Nick(c.GetNick() + "_") } // handlePING helps respond to ping requests from the server. func handlePING(c *Client, e Event) { - c.Pong(e.Trailing) + c.Commands.Pong(e.Trailing) } // handleJOIN ensures that the state has updated users and channels. @@ -247,14 +247,13 @@ func handleKICK(c *Client, e Event) { // handleNICK ensures that users are renamed in state, or the client name is // up to date. func handleNICK(c *Client, e Event) { - if len(e.Params) != 1 { - // Something erronous was sent to us. - return - } - c.state.mu.Lock() // renameUser updates the LastActive time automatically. - c.state.renameUser(e.Source.Name, e.Params[0]) + if len(e.Params) == 1 { + c.state.renameUser(e.Source.Name, e.Params[0]) + } else if len(e.Trailing) > 0 { + c.state.renameUser(e.Source.Name, e.Trailing) + } c.state.mu.Unlock() } diff --git a/client.go b/client.go index 5abf420..eeedcaf 100644 --- a/client.go +++ b/client.go @@ -34,7 +34,8 @@ type Client struct { // Handlers is a handler which manages internal and external handlers. Handlers *Caller // CTCP is a handler which manages internal and external CTCP handlers. - CTCP *CTCP + CTCP *CTCP + Commands *Commands // conn is a net.Conn reference to the IRC server. conn *ircConn @@ -176,6 +177,8 @@ func New(config Config) *Client { initTime: time.Now(), } + c.Commands = &Commands{c: c} + if c.Config.Debugger == nil { c.debug = log.New(ioutil.Discard, "", 0) } else { @@ -566,20 +569,6 @@ func (c *Client) GetNick() (nick string) { return nick } -// Nick changes the client nickname. -func (c *Client) Nick(name string) error { - if !IsValidNick(name) { - return &ErrInvalidTarget{Target: name} - } - - c.state.mu.Lock() - c.state.nick = name - err := c.Send(&Event{Command: NICK, Params: []string{name}}) - c.state.mu.Unlock() - - return err -} - // Channels returns the active list of channels that the client is in. // Panics if tracking is disabled. func (c *Client) Channels() []string { @@ -614,321 +603,6 @@ func (c *Client) IsInChannel(channel string) bool { return inChannel } -// Join attempts to enter a list of IRC channels, at bulk if possible to -// prevent sending extensive JOIN commands. -func (c *Client) Join(channels ...string) error { - // We can join multiple channels at once, however we need to ensure that - // we are not exceeding the line length. (see maxLength) - max := maxLength - len(JOIN) - 1 - - var buffer string - var err error - - for i := 0; i < len(channels); i++ { - if !IsValidChannel(channels[i]) { - return &ErrInvalidTarget{Target: channels[i]} - } - - if len(buffer+","+channels[i]) > max { - err = c.Send(&Event{Command: JOIN, Params: []string{buffer}}) - if err != nil { - return err - } - buffer = "" - continue - } - - if len(buffer) == 0 { - buffer = channels[i] - } else { - buffer += "," + channels[i] - } - - if i == len(channels)-1 { - return c.Send(&Event{Command: JOIN, Params: []string{buffer}}) - } - } - - return nil -} - -// JoinKey attempts to enter an IRC channel with a password. -func (c *Client) JoinKey(channel, password string) error { - if !IsValidChannel(channel) { - return &ErrInvalidTarget{Target: channel} - } - - return c.Send(&Event{Command: JOIN, Params: []string{channel, password}}) -} - -// Part leaves an IRC channel. -func (c *Client) Part(channel, message string) error { - if !IsValidChannel(channel) { - return &ErrInvalidTarget{Target: channel} - } - - return c.Send(&Event{Command: JOIN, Params: []string{channel}}) -} - -// PartMessage leaves an IRC channel with a specified leave message. -func (c *Client) PartMessage(channel, message string) error { - if !IsValidChannel(channel) { - return &ErrInvalidTarget{Target: channel} - } - - return c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message}) -} - -// SendCTCP sends a CTCP request to target. Note that this method uses -// PRIVMSG specifically. -func (c *Client) SendCTCP(target, ctcpType, message string) error { - out := encodeCTCPRaw(ctcpType, message) - if out == "" { - return errors.New("invalid CTCP") - } - - return c.Message(target, out) -} - -// SendCTCPf sends a CTCP request to target using a specific format. Note that -// this method uses PRIVMSG specifically. -func (c *Client) SendCTCPf(target, ctcpType, format string, a ...interface{}) error { - return c.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...)) -} - -// SendCTCPReplyf sends a CTCP response to target using a specific format. -// Note that this method uses NOTICE specifically. -func (c *Client) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) error { - return c.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...)) -} - -// SendCTCPReply sends a CTCP response to target. Note that this method uses -// NOTICE specifically. -func (c *Client) SendCTCPReply(target, ctcpType, message string) error { - out := encodeCTCPRaw(ctcpType, message) - if out == "" { - return errors.New("invalid CTCP") - } - - return c.Notice(target, out) -} - -// Message sends a PRIVMSG to target (either channel, service, or user). -func (c *Client) Message(target, message string) error { - if !IsValidNick(target) && !IsValidChannel(target) { - return &ErrInvalidTarget{Target: target} - } - - return c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message}) -} - -// Messagef sends a formated PRIVMSG to target (either channel, service, or -// user). -func (c *Client) Messagef(target, format string, a ...interface{}) error { - return c.Message(target, fmt.Sprintf(format, a...)) -} - -// Action sends a PRIVMSG ACTION (/me) to target (either channel, service, -// or user). -func (c *Client) Action(target, message string) error { - if !IsValidNick(target) && !IsValidChannel(target) { - return &ErrInvalidTarget{Target: target} - } - - return c.Send(&Event{ - Command: PRIVMSG, - Params: []string{target}, - Trailing: fmt.Sprintf("\001ACTION %s\001", message), - }) -} - -// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel, -// service, or user). -func (c *Client) Actionf(target, format string, a ...interface{}) error { - return c.Action(target, fmt.Sprintf(format, a...)) -} - -// Notice sends a NOTICE to target (either channel, service, or user). -func (c *Client) Notice(target, message string) error { - if !IsValidNick(target) && !IsValidChannel(target) { - return &ErrInvalidTarget{Target: target} - } - - return c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message}) -} - -// Noticef sends a formated NOTICE to target (either channel, service, or -// user). -func (c *Client) Noticef(target, format string, a ...interface{}) error { - return c.Notice(target, fmt.Sprintf(format, a...)) -} - -// SendRaw sends a raw string back to the server, without carriage returns -// or newlines. -func (c *Client) SendRaw(raw string) error { - e := ParseEvent(raw) - if e == nil { - return errors.New("invalid event: " + raw) - } - - return c.Send(e) -} - -// SendRawf sends a formated string back to the server, without carriage -// returns or newlines. -func (c *Client) SendRawf(format string, a ...interface{}) error { - return c.SendRaw(fmt.Sprintf(format, a...)) -} - -// Topic sets the topic of channel to message. Does not verify the length -// of the topic. -func (c *Client) Topic(channel, message string) error { - return c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message}) -} - -// Who sends a WHO query to the server, which will attempt WHOX by default. -// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This -// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with -// girc's builtin tracking functionality. -func (c *Client) Who(target string) error { - if !IsValidNick(target) && !IsValidChannel(target) && !IsValidUser(target) { - return &ErrInvalidTarget{Target: target} - } - - return c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}}) -} - -// Whois sends a WHOIS query to the server, targeted at a specific user. -// as WHOIS is a bit slower, you may want to use WHO for brief user info. -func (c *Client) Whois(nick string) error { - if !IsValidNick(nick) { - return &ErrInvalidTarget{Target: nick} - } - - return c.Send(&Event{Command: WHOIS, Params: []string{nick}}) -} - -// Ping sends a PING query to the server, with a specific identifier that -// the server should respond with. -func (c *Client) Ping(id string) error { - return c.Send(&Event{Command: PING, Params: []string{id}}) -} - -// Pong sends a PONG query to the server, with an identifier which was -// received from a previous PING query received by the client. -func (c *Client) Pong(id string) error { - return c.Send(&Event{Command: PONG, Params: []string{id}}) -} - -// Oper sends a OPER authentication query to the server, with a username -// and password. -func (c *Client) Oper(user, pass string) error { - return c.Send(&Event{Command: OPER, Params: []string{user, pass}, Sensitive: true}) -} - -// Kick sends a KICK query to the server, attempting to kick nick from -// channel, with reason. If reason is blank, one will not be sent to the -// server. -func (c *Client) Kick(channel, nick, reason string) error { - if !IsValidChannel(channel) { - return &ErrInvalidTarget{Target: channel} - } - - if !IsValidNick(nick) { - return &ErrInvalidTarget{Target: nick} - } - - if reason != "" { - return c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason}) - } - - return c.Send(&Event{Command: KICK, Params: []string{channel, nick}}) -} - -// Invite sends a INVITE query to the server, to invite nick to channel. -func (c *Client) Invite(channel, nick string) error { - if !IsValidChannel(channel) { - return &ErrInvalidTarget{Target: channel} - } - - if !IsValidNick(nick) { - return &ErrInvalidTarget{Target: nick} - } - - return c.Send(&Event{Command: INVITE, Params: []string{nick, channel}}) -} - -// Away sends a AWAY query to the server, suggesting that the client is no -// longer active. If reason is blank, Client.Back() is called. Also see -// Client.Back(). -func (c *Client) Away(reason string) error { - if reason == "" { - return c.Back() - } - - return c.Send(&Event{Command: AWAY, Params: []string{reason}}) -} - -// Back sends a AWAY query to the server, however the query is blank, -// suggesting that the client is active once again. Also see Client.Away(). -func (c *Client) Back() error { - return c.Send(&Event{Command: AWAY}) -} - -// List sends a LIST query to the server, which will list channels and topics. -// Supports multiple channels at once, in hopes it will reduce extensive -// LIST queries to the server. Supply no channels to run a list against the -// entire server (warning, that may mean LOTS of channels!) -func (c *Client) List(channels ...string) error { - if len(channels) == 0 { - return c.Send(&Event{Command: LIST}) - } - - // We can LIST multiple channels at once, however we need to ensure that - // we are not exceeding the line length. (see maxLength) - max := maxLength - len(JOIN) - 1 - - var buffer string - var err error - - for i := 0; i < len(channels); i++ { - if !IsValidChannel(channels[i]) { - return &ErrInvalidTarget{Target: channels[i]} - } - - if len(buffer+","+channels[i]) > max { - err = c.Send(&Event{Command: LIST, Params: []string{buffer}}) - if err != nil { - return err - } - buffer = "" - continue - } - - if len(buffer) == 0 { - buffer = channels[i] - } else { - buffer += "," + channels[i] - } - - if i == len(channels)-1 { - return c.Send(&Event{Command: LIST, Params: []string{buffer}}) - } - } - - return nil -} - -// Whowas sends a WHOWAS query to the server. amount is the amount of results -// you want back. -func (c *Client) Whowas(nick string, amount int) error { - if !IsValidNick(nick) { - return &ErrInvalidTarget{Target: nick} - } - - return c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}}) -} - // GetServerOption retrieves a server capability setting that was retrieved // during client connection. This is also known as ISUPPORT (or RPL_PROTOCTL). // Will panic if used when tracking has been disabled. Examples of usage: diff --git a/client_test.go b/client_test.go index 41937ea..9b7d98e 100644 --- a/client_test.go +++ b/client_test.go @@ -48,12 +48,12 @@ func Example_simple() { }) client.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) { - c.Join("#dev") + c.Commands.Join("#dev") }) client.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) { if strings.Contains(e.Trailing, "hello") { - c.Message(e.Params[0], "hello world!") + c.Commands.Message(e.Params[0], "hello world!") } }) @@ -88,12 +88,12 @@ func Example_commands() { }) client.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) { - c.Join("#channel", "#other-channel") + c.Commands.Join("#channel", "#other-channel") }) client.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) { if strings.HasPrefix(e.Trailing, "!hello") { - c.Message(e.Params[0], "hello world!") + c.Commands.Message(e.Params[0], "hello world!") return } diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..651e80d --- /dev/null +++ b/commands.go @@ -0,0 +1,340 @@ +// Copyright (c) Liam Stanley . All rights reserved. Use +// of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +package girc + +import ( + "errors" + "fmt" +) + +// Commands holds a large list of useful methods to interact with the server, +// and wrappers for common events. +type Commands struct { + c *Client +} + +// Nick changes the client nickname. +func (cmd *Commands) Nick(name string) error { + if !IsValidNick(name) { + return &ErrInvalidTarget{Target: name} + } + + return cmd.c.Send(&Event{Command: NICK, Params: []string{name}}) +} + +// Join attempts to enter a list of IRC channels, at bulk if possible to +// prevent sending extensive JOIN commands. +func (cmd *Commands) Join(channels ...string) error { + // We can join multiple channels at once, however we need to ensure that + // we are not exceeding the line length. (see maxLength) + max := maxLength - len(JOIN) - 1 + + var buffer string + var err error + + for i := 0; i < len(channels); i++ { + if !IsValidChannel(channels[i]) { + return &ErrInvalidTarget{Target: channels[i]} + } + + if len(buffer+","+channels[i]) > max { + err = cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}}) + if err != nil { + return err + } + buffer = "" + continue + } + + if len(buffer) == 0 { + buffer = channels[i] + } else { + buffer += "," + channels[i] + } + + if i == len(channels)-1 { + return cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}}) + } + } + + return nil +} + +// JoinKey attempts to enter an IRC channel with a password. +func (cmd *Commands) JoinKey(channel, password string) error { + if !IsValidChannel(channel) { + return &ErrInvalidTarget{Target: channel} + } + + return cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}}) +} + +// Part leaves an IRC channel. +func (cmd *Commands) Part(channel, message string) error { + if !IsValidChannel(channel) { + return &ErrInvalidTarget{Target: channel} + } + + return cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}}) +} + +// PartMessage leaves an IRC channel with a specified leave message. +func (cmd *Commands) PartMessage(channel, message string) error { + if !IsValidChannel(channel) { + return &ErrInvalidTarget{Target: channel} + } + + return cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message}) +} + +// SendCTCP sends a CTCP request to target. Note that this method uses +// PRIVMSG specifically. +func (cmd *Commands) SendCTCP(target, ctcpType, message string) error { + out := encodeCTCPRaw(ctcpType, message) + if out == "" { + return errors.New("invalid CTCP") + } + + return cmd.Message(target, out) +} + +// SendCTCPf sends a CTCP request to target using a specific format. Note that +// this method uses PRIVMSG specifically. +func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) error { + return cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...)) +} + +// SendCTCPReplyf sends a CTCP response to target using a specific format. +// Note that this method uses NOTICE specifically. +func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) error { + return cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...)) +} + +// SendCTCPReply sends a CTCP response to target. Note that this method uses +// NOTICE specifically. +func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) error { + out := encodeCTCPRaw(ctcpType, message) + if out == "" { + return errors.New("invalid CTCP") + } + + return cmd.Notice(target, out) +} + +// Message sends a PRIVMSG to target (either channel, service, or user). +func (cmd *Commands) Message(target, message string) error { + if !IsValidNick(target) && !IsValidChannel(target) { + return &ErrInvalidTarget{Target: target} + } + + return cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message}) +} + +// Messagef sends a formated PRIVMSG to target (either channel, service, or +// user). +func (cmd *Commands) Messagef(target, format string, a ...interface{}) error { + return cmd.Message(target, fmt.Sprintf(format, a...)) +} + +// Action sends a PRIVMSG ACTION (/me) to target (either channel, service, +// or user). +func (cmd *Commands) Action(target, message string) error { + if !IsValidNick(target) && !IsValidChannel(target) { + return &ErrInvalidTarget{Target: target} + } + + return cmd.c.Send(&Event{ + Command: PRIVMSG, + Params: []string{target}, + Trailing: fmt.Sprintf("\001ACTION %s\001", message), + }) +} + +// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel, +// service, or user). +func (cmd *Commands) Actionf(target, format string, a ...interface{}) error { + return cmd.Action(target, fmt.Sprintf(format, a...)) +} + +// Notice sends a NOTICE to target (either channel, service, or user). +func (cmd *Commands) Notice(target, message string) error { + if !IsValidNick(target) && !IsValidChannel(target) { + return &ErrInvalidTarget{Target: target} + } + + return cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message}) +} + +// Noticef sends a formated NOTICE to target (either channel, service, or +// user). +func (cmd *Commands) Noticef(target, format string, a ...interface{}) error { + return cmd.Notice(target, fmt.Sprintf(format, a...)) +} + +// SendRaw sends a raw string back to the server, without carriage returns +// or newlines. +func (cmd *Commands) SendRaw(raw string) error { + e := ParseEvent(raw) + if e == nil { + return errors.New("invalid event: " + raw) + } + + return cmd.c.Send(e) +} + +// SendRawf sends a formated string back to the server, without carriage +// returns or newlines. +func (cmd *Commands) SendRawf(format string, a ...interface{}) error { + return cmd.SendRaw(fmt.Sprintf(format, a...)) +} + +// Topic sets the topic of channel to message. Does not verify the length +// of the topic. +func (cmd *Commands) Topic(channel, message string) error { + return cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message}) +} + +// Who sends a WHO query to the server, which will attempt WHOX by default. +// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This +// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with +// girc's builtin tracking functionality. +func (cmd *Commands) Who(target string) error { + if !IsValidNick(target) && !IsValidChannel(target) && !IsValidUser(target) { + return &ErrInvalidTarget{Target: target} + } + + return cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}}) +} + +// Whois sends a WHOIS query to the server, targeted at a specific user. +// as WHOIS is a bit slower, you may want to use WHO for brief user info. +func (cmd *Commands) Whois(nick string) error { + if !IsValidNick(nick) { + return &ErrInvalidTarget{Target: nick} + } + + return cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}}) +} + +// Ping sends a PING query to the server, with a specific identifier that +// the server should respond with. +func (cmd *Commands) Ping(id string) error { + return cmd.c.Send(&Event{Command: PING, Params: []string{id}}) +} + +// Pong sends a PONG query to the server, with an identifier which was +// received from a previous PING query received by the client. +func (cmd *Commands) Pong(id string) error { + return cmd.c.Send(&Event{Command: PONG, Params: []string{id}}) +} + +// Oper sends a OPER authentication query to the server, with a username +// and password. +func (cmd *Commands) Oper(user, pass string) error { + return cmd.c.Send(&Event{Command: OPER, Params: []string{user, pass}, Sensitive: true}) +} + +// Kick sends a KICK query to the server, attempting to kick nick from +// channel, with reason. If reason is blank, one will not be sent to the +// server. +func (cmd *Commands) Kick(channel, nick, reason string) error { + if !IsValidChannel(channel) { + return &ErrInvalidTarget{Target: channel} + } + + if !IsValidNick(nick) { + return &ErrInvalidTarget{Target: nick} + } + + if reason != "" { + return cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason}) + } + + return cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}}) +} + +// Invite sends a INVITE query to the server, to invite nick to channel. +func (cmd *Commands) Invite(channel, nick string) error { + if !IsValidChannel(channel) { + return &ErrInvalidTarget{Target: channel} + } + + if !IsValidNick(nick) { + return &ErrInvalidTarget{Target: nick} + } + + return cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}}) +} + +// Away sends a AWAY query to the server, suggesting that the client is no +// longer active. If reason is blank, Client.Back() is called. Also see +// Client.Back(). +func (cmd *Commands) Away(reason string) error { + if reason == "" { + return cmd.Back() + } + + return cmd.c.Send(&Event{Command: AWAY, Params: []string{reason}}) +} + +// Back sends a AWAY query to the server, however the query is blank, +// suggesting that the client is active once again. Also see Client.Away(). +func (cmd *Commands) Back() error { + return cmd.c.Send(&Event{Command: AWAY}) +} + +// List sends a LIST query to the server, which will list channels and topics. +// Supports multiple channels at once, in hopes it will reduce extensive +// LIST queries to the server. Supply no channels to run a list against the +// entire server (warning, that may mean LOTS of channels!) +func (cmd *Commands) List(channels ...string) error { + if len(channels) == 0 { + return cmd.c.Send(&Event{Command: LIST}) + } + + // We can LIST multiple channels at once, however we need to ensure that + // we are not exceeding the line length. (see maxLength) + max := maxLength - len(JOIN) - 1 + + var buffer string + var err error + + for i := 0; i < len(channels); i++ { + if !IsValidChannel(channels[i]) { + return &ErrInvalidTarget{Target: channels[i]} + } + + if len(buffer+","+channels[i]) > max { + err = cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}}) + if err != nil { + return err + } + buffer = "" + continue + } + + if len(buffer) == 0 { + buffer = channels[i] + } else { + buffer += "," + channels[i] + } + + if i == len(channels)-1 { + return cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}}) + } + } + + return nil +} + +// Whowas sends a WHOWAS query to the server. amount is the amount of results +// you want back. +func (cmd *Commands) Whowas(nick string, amount int) error { + if !IsValidNick(nick) { + return &ErrInvalidTarget{Target: nick} + } + + return cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}}) +} diff --git a/ctcp.go b/ctcp.go index 259c6d7..1043afe 100644 --- a/ctcp.go +++ b/ctcp.go @@ -146,7 +146,7 @@ func (c *CTCP) call(client *Client, event *CTCPEvent) { if _, ok := c.handlers[event.Command]; !ok { // Send a ERRMSG reply, if we know who sent it. if event.Source != nil && IsValidNick(event.Source.Name) { - client.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query") + client.Commands.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query") } return } @@ -237,7 +237,7 @@ func handleCTCPPing(client *Client, ctcp CTCPEvent) { if ctcp.Reply { return } - client.SendCTCPReply(ctcp.Source.Name, CTCP_PING, ctcp.Text) + client.Commands.SendCTCPReply(ctcp.Source.Name, CTCP_PING, ctcp.Text) } // handleCTCPPong replies with a pong. @@ -245,7 +245,7 @@ func handleCTCPPong(client *Client, ctcp CTCPEvent) { if ctcp.Reply { return } - client.SendCTCPReply(ctcp.Source.Name, CTCP_PONG, "") + client.Commands.SendCTCPReply(ctcp.Source.Name, CTCP_PONG, "") } // handleCTCPVersion replies with the name of the client, Go version, as well @@ -253,11 +253,11 @@ func handleCTCPPong(client *Client, ctcp CTCPEvent) { // arm, etc). func handleCTCPVersion(client *Client, ctcp CTCPEvent) { if client.Config.Version != "" { - client.SendCTCPReply(ctcp.Source.Name, CTCP_VERSION, client.Config.Version) + client.Commands.SendCTCPReply(ctcp.Source.Name, CTCP_VERSION, client.Config.Version) return } - client.SendCTCPReplyf( + client.Commands.SendCTCPReplyf( ctcp.Source.Name, CTCP_VERSION, "girc (github.com/lrstanley/girc) using %s (%s, %s)", runtime.Version(), runtime.GOOS, runtime.GOARCH, @@ -266,11 +266,11 @@ func handleCTCPVersion(client *Client, ctcp CTCPEvent) { // handleCTCPSource replies with the public git location of this library. func handleCTCPSource(client *Client, ctcp CTCPEvent) { - client.SendCTCPReply(ctcp.Source.Name, CTCP_SOURCE, "https://github.com/lrstanley/girc") + client.Commands.SendCTCPReply(ctcp.Source.Name, CTCP_SOURCE, "https://github.com/lrstanley/girc") } // handleCTCPTime replies with a RFC 1123 (Z) formatted version of Go's // local time. func handleCTCPTime(client *Client, ctcp CTCPEvent) { - client.SendCTCPReply(ctcp.Source.Name, CTCP_TIME, ":"+time.Now().Format(time.RFC1123Z)) + client.Commands.SendCTCPReply(ctcp.Source.Name, CTCP_TIME, ":"+time.Now().Format(time.RFC1123Z)) } diff --git a/event.go b/event.go index 4c38263..a4186a7 100644 --- a/event.go +++ b/event.go @@ -95,10 +95,7 @@ func ParseEvent(raw string) (e *Event) { j++ // Find prefix for trailer. - i = bytes.Index([]byte(raw[j:]), []byte{eventSpace, messagePrefix}) - if i != -1 { - i++ - } + i = strings.IndexByte(raw[j:], messagePrefix) if i < 0 || raw[j+i-1] != eventSpace { // No trailing argument. diff --git a/state.go b/state.go index 550b6e8..94c7db9 100644 --- a/state.go +++ b/state.go @@ -267,6 +267,11 @@ func (s *state) renameUser(from, to string) { return } + // Update our nickname. + if from == s.nick { + s.nick = to + } + for k := range s.channels { // Check to see if they're in this channel. if _, ok := s.channels[k].users[from]; !ok {