2015-06-05 22:34:13 +00:00
|
|
|
package irc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"crypto/tls"
|
|
|
|
"net"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2020-05-04 23:35:05 +00:00
|
|
|
"time"
|
2015-12-11 16:06:29 +00:00
|
|
|
|
2016-03-01 00:51:26 +00:00
|
|
|
"github.com/jpillora/backoff"
|
2015-06-05 22:34:13 +00:00
|
|
|
)
|
|
|
|
|
2020-05-23 07:42:20 +00:00
|
|
|
type Config struct {
|
|
|
|
Host string
|
|
|
|
Port string
|
|
|
|
TLS bool
|
|
|
|
TLSConfig *tls.Config
|
|
|
|
Nick string
|
|
|
|
Password string
|
|
|
|
Username string
|
|
|
|
Realname string
|
|
|
|
SASL SASL
|
2020-05-20 02:19:40 +00:00
|
|
|
// Version is the reply to VERSION and FINGER CTCP messages
|
|
|
|
Version string
|
|
|
|
// Source is the reply to SOURCE CTCP messages
|
|
|
|
Source string
|
|
|
|
|
2020-05-23 07:42:20 +00:00
|
|
|
HandleNickInUse func(string) string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Client struct {
|
2020-05-24 09:09:59 +00:00
|
|
|
Config *Config
|
2020-05-23 07:42:20 +00:00
|
|
|
|
2016-01-13 17:53:54 +00:00
|
|
|
Messages chan *Message
|
2017-07-02 01:31:00 +00:00
|
|
|
ConnectionChanged chan ConnectionState
|
2019-01-27 07:53:07 +00:00
|
|
|
Features *Features
|
|
|
|
nick string
|
|
|
|
channels []string
|
2015-06-05 22:34:13 +00:00
|
|
|
|
2020-05-23 06:05:37 +00:00
|
|
|
wantedCapabilities []string
|
|
|
|
requestedCapabilities map[string][]string
|
|
|
|
enabledCapabilities map[string][]string
|
|
|
|
|
2018-06-17 20:53:22 +00:00
|
|
|
conn net.Conn
|
|
|
|
connected bool
|
|
|
|
registered bool
|
|
|
|
dialer *net.Dialer
|
2019-01-11 03:53:50 +00:00
|
|
|
recvBuf []byte
|
|
|
|
scan *bufio.Scanner
|
2018-06-17 20:53:22 +00:00
|
|
|
backoff *backoff.Backoff
|
|
|
|
out chan string
|
2015-06-05 22:34:13 +00:00
|
|
|
|
|
|
|
quit chan struct{}
|
|
|
|
reconnect chan struct{}
|
2016-01-12 23:12:51 +00:00
|
|
|
sendRecv sync.WaitGroup
|
2015-06-05 22:34:13 +00:00
|
|
|
lock sync.Mutex
|
|
|
|
}
|
|
|
|
|
2020-05-24 09:09:59 +00:00
|
|
|
func NewClient(config *Config) *Client {
|
2020-05-23 07:42:20 +00:00
|
|
|
if config.Port == "" {
|
|
|
|
if config.TLS {
|
|
|
|
config.Port = "6697"
|
|
|
|
} else {
|
|
|
|
config.Port = "6667"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Username == "" {
|
|
|
|
config.Username = config.Nick
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Realname == "" {
|
|
|
|
config.Realname = config.Nick
|
|
|
|
}
|
|
|
|
|
2020-05-24 09:09:59 +00:00
|
|
|
wantedCapabilities := append([]string{}, clientWantedCaps...)
|
|
|
|
|
|
|
|
if config.SASL != nil {
|
|
|
|
wantedCapabilities = append(wantedCapabilities, "sasl")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Client{
|
2020-05-23 07:42:20 +00:00
|
|
|
Config: config,
|
|
|
|
nick: config.Nick,
|
2020-05-23 06:05:37 +00:00
|
|
|
Features: NewFeatures(),
|
|
|
|
Messages: make(chan *Message, 32),
|
2020-05-24 09:09:59 +00:00
|
|
|
wantedCapabilities: wantedCapabilities,
|
2020-05-23 07:42:20 +00:00
|
|
|
requestedCapabilities: map[string][]string{},
|
|
|
|
enabledCapabilities: map[string][]string{},
|
2020-05-23 06:05:37 +00:00
|
|
|
ConnectionChanged: make(chan ConnectionState, 4),
|
|
|
|
out: make(chan string, 32),
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
reconnect: make(chan struct{}),
|
|
|
|
dialer: &net.Dialer{Timeout: 10 * time.Second},
|
|
|
|
recvBuf: make([]byte, 0, 4096),
|
2016-01-13 00:00:57 +00:00
|
|
|
backoff: &backoff.Backoff{
|
2020-05-04 23:35:05 +00:00
|
|
|
Min: 500 * time.Millisecond,
|
|
|
|
Max: 30 * time.Second,
|
2016-01-13 00:00:57 +00:00
|
|
|
Jitter: true,
|
|
|
|
},
|
2015-06-05 22:34:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) GetNick() string {
|
|
|
|
c.lock.Lock()
|
2018-04-29 00:13:22 +00:00
|
|
|
nick := c.nick
|
|
|
|
c.lock.Unlock()
|
|
|
|
return nick
|
2015-06-05 22:34:13 +00:00
|
|
|
}
|
|
|
|
|
2020-05-21 03:24:26 +00:00
|
|
|
func (c *Client) Is(nick string) bool {
|
|
|
|
return c.EqualFold(nick, c.GetNick())
|
|
|
|
}
|
|
|
|
|
2017-04-11 04:04:59 +00:00
|
|
|
func (c *Client) setNick(nick string) {
|
|
|
|
c.lock.Lock()
|
|
|
|
c.nick = nick
|
|
|
|
c.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
2015-06-05 22:34:13 +00:00
|
|
|
func (c *Client) Connected() bool {
|
|
|
|
c.lock.Lock()
|
2018-04-29 00:13:22 +00:00
|
|
|
connected := c.connected
|
|
|
|
c.lock.Unlock()
|
|
|
|
return connected
|
2015-06-05 22:34:13 +00:00
|
|
|
}
|
|
|
|
|
2018-06-17 20:53:22 +00:00
|
|
|
func (c *Client) Registered() bool {
|
|
|
|
c.lock.Lock()
|
|
|
|
reg := c.registered
|
|
|
|
c.lock.Unlock()
|
|
|
|
return reg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) setRegistered(reg bool) {
|
|
|
|
c.lock.Lock()
|
|
|
|
c.registered = reg
|
|
|
|
c.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
2020-05-23 07:42:20 +00:00
|
|
|
func (c *Client) Host() string {
|
|
|
|
return c.Config.Host
|
|
|
|
}
|
|
|
|
|
2015-06-05 22:34:13 +00:00
|
|
|
func (c *Client) Nick(nick string) {
|
|
|
|
c.Write("NICK " + nick)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Oper(name, password string) {
|
|
|
|
c.Write("OPER " + name + " " + password)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Mode(target, modes, params string) {
|
|
|
|
c.Write(strings.TrimRight("MODE "+target+" "+modes+" "+params, " "))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Quit() {
|
|
|
|
go func() {
|
|
|
|
if c.Connected() {
|
|
|
|
c.write("QUIT")
|
|
|
|
}
|
|
|
|
close(c.quit)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Join(channels ...string) {
|
|
|
|
c.Write("JOIN " + strings.Join(channels, ","))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Part(channels ...string) {
|
|
|
|
c.Write("PART " + strings.Join(channels, ","))
|
2020-05-01 03:40:49 +00:00
|
|
|
c.removeChannels(channels...)
|
2015-06-05 22:34:13 +00:00
|
|
|
}
|
|
|
|
|
2017-05-28 05:20:43 +00:00
|
|
|
func (c *Client) Topic(channel string, topic ...string) {
|
|
|
|
msg := "TOPIC " + channel
|
|
|
|
if len(topic) > 0 {
|
|
|
|
msg += " :" + topic[0]
|
|
|
|
}
|
|
|
|
c.Write(msg)
|
2015-06-05 22:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Invite(nick, channel string) {
|
|
|
|
c.Write("INVITE " + nick + " " + channel)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Kick(channel string, users ...string) {
|
|
|
|
c.Write("KICK " + channel + " " + strings.Join(users, ","))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Privmsg(target, msg string) {
|
|
|
|
c.Writef("PRIVMSG %s :%s", target, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Notice(target, msg string) {
|
|
|
|
c.Writef("NOTICE %s :%s", target, msg)
|
|
|
|
}
|
|
|
|
|
2020-05-20 02:19:40 +00:00
|
|
|
func (c *Client) ReplyCTCP(target, command, params string) {
|
|
|
|
c.Notice(target, EncodeCTCP(&CTCP{
|
|
|
|
Command: command,
|
|
|
|
Params: params,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2015-06-05 22:34:13 +00:00
|
|
|
func (c *Client) Whois(nick string) {
|
|
|
|
c.Write("WHOIS " + nick)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Away(message string) {
|
|
|
|
c.Write("AWAY :" + message)
|
|
|
|
}
|
2015-06-11 02:57:52 +00:00
|
|
|
|
2019-01-11 01:46:46 +00:00
|
|
|
func (c *Client) List() {
|
|
|
|
c.Write("LIST")
|
|
|
|
}
|
|
|
|
|
2015-06-11 02:57:52 +00:00
|
|
|
func (c *Client) writePass(password string) {
|
|
|
|
c.write("PASS " + password)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) writeNick(nick string) {
|
|
|
|
c.write("NICK " + nick)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) writeUser(username, realname string) {
|
|
|
|
c.writef("USER %s 0 * :%s", username, realname)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) register() {
|
2020-05-23 06:05:37 +00:00
|
|
|
c.writeCAP()
|
2020-05-23 07:42:20 +00:00
|
|
|
if c.Config.Password != "" {
|
|
|
|
c.writePass(c.Config.Password)
|
2015-06-11 02:57:52 +00:00
|
|
|
}
|
2020-05-23 07:42:20 +00:00
|
|
|
c.writeNick(c.Config.Nick)
|
|
|
|
c.writeUser(c.Config.Username, c.Config.Realname)
|
2015-06-11 02:57:52 +00:00
|
|
|
}
|
2016-02-03 20:12:32 +00:00
|
|
|
|
|
|
|
func (c *Client) addChannel(channel string) {
|
|
|
|
c.lock.Lock()
|
|
|
|
c.channels = append(c.channels, channel)
|
|
|
|
c.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
2020-05-01 03:40:49 +00:00
|
|
|
func (c *Client) removeChannels(channels ...string) {
|
|
|
|
c.lock.Lock()
|
|
|
|
for _, removeCh := range channels {
|
|
|
|
for i, ch := range c.channels {
|
|
|
|
if c.EqualFold(removeCh, ch) {
|
|
|
|
c.channels = append(c.channels[:i], c.channels[i+1:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
2016-02-03 20:12:32 +00:00
|
|
|
func (c *Client) flushChannels() {
|
|
|
|
c.lock.Lock()
|
|
|
|
if len(c.channels) > 0 {
|
|
|
|
c.Join(c.channels...)
|
|
|
|
c.channels = []string{}
|
|
|
|
}
|
|
|
|
c.lock.Unlock()
|
|
|
|
}
|