// Copyright (c) Liam Stanley <me@liamstanley.io>. 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} } cmd.c.Send(&Event{Command: NICK, Params: []string{name}}) return nil } // 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 for i := 0; i < len(channels); i++ { if !IsValidChannel(channels[i]) { return &ErrInvalidTarget{Target: channels[i]} } if len(buffer+","+channels[i]) > max { cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}}) buffer = "" continue } if len(buffer) == 0 { buffer = channels[i] } else { buffer += "," + channels[i] } if i == len(channels)-1 { cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}}) return nil } } 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} } cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}}) return nil } // Part leaves an IRC channel. func (cmd *Commands) Part(channel, message string) error { if !IsValidChannel(channel) { return &ErrInvalidTarget{Target: channel} } cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}}) return nil } // 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} } cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message}) return nil } // 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} } cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message}) return nil } // 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...)) } // ErrInvalidSource is returned when a method needs to know the origin of an // event, however Event.Source is unknown (e.g. sent by the user, not the // server.) var ErrInvalidSource = errors.New("event has nil or invalid source address") // Reply sends a reply to channel or user, based on where the supplied event // originated from. See also ReplyTo(). func (cmd *Commands) Reply(event Event, message string) error { if event.Source == nil { return ErrInvalidSource } if len(event.Params) > 0 && IsValidChannel(event.Params[0]) { return cmd.Message(event.Params[0], message) } return cmd.Message(event.Source.Name, message) } // Replyf sends a reply to channel or user with a format string, based on // where the supplied event originated from. See also ReplyTof(). func (cmd *Commands) Replyf(event Event, format string, a ...interface{}) error { return cmd.Reply(event, fmt.Sprintf(format, a...)) } // ReplyTo sends a reply to a channel or user, based on where the supplied // event originated from. ReplyTo(), when originating from a channel will // default to replying with "<user>, <message>". See also Reply(). func (cmd *Commands) ReplyTo(event Event, message string) error { if event.Source == nil { return ErrInvalidSource } if len(event.Params) > 0 && IsValidChannel(event.Params[0]) { return cmd.Message(event.Params[0], event.Source.Name+", "+message) } return cmd.Message(event.Source.Name, message) } // ReplyTof sends a reply to a channel or user with a format string, based // on where the supplied event originated from. ReplyTo(), when originating // from a channel will default to replying with "<user>, <message>". See // also Replyf(). func (cmd *Commands) ReplyTof(event Event, format string, a ...interface{}) error { return cmd.ReplyTo(event, 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} } cmd.c.Send(&Event{ Command: PRIVMSG, Params: []string{target}, Trailing: fmt.Sprintf("\001ACTION %s\001", message), }) return nil } // 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} } cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message}) return nil } // 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) } cmd.c.Send(e) return nil } // 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) { 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} } cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}}) return nil } // 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} } cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}}) return nil } // Ping sends a PING query to the server, with a specific identifier that // the server should respond with. func (cmd *Commands) Ping(id string) { cmd.c.write(&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) { cmd.c.write(&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) { 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 != "" { cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason}) return nil } cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}}) return nil } // 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} } cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}}) return nil } // 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) { if reason == "" { 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() { 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 { cmd.c.Send(&Event{Command: LIST}) return nil } // 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 for i := 0; i < len(channels); i++ { if !IsValidChannel(channels[i]) { return &ErrInvalidTarget{Target: channels[i]} } if len(buffer+","+channels[i]) > max { cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}}) buffer = "" continue } if len(buffer) == 0 { buffer = channels[i] } else { buffer += "," + channels[i] } if i == len(channels)-1 { cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}}) return nil } } 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} } cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}}) return nil }