2015-05-01 20:59:46 +00:00
|
|
|
package server
|
2015-01-17 01:37:21 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"strings"
|
2015-01-29 23:38:51 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2015-01-17 01:37:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
PING = "PING"
|
2015-01-31 22:35:38 +00:00
|
|
|
NICK = "NICK"
|
2015-01-17 01:37:21 +00:00
|
|
|
JOIN = "JOIN"
|
|
|
|
PART = "PART"
|
|
|
|
MODE = "MODE"
|
|
|
|
PRIVMSG = "PRIVMSG"
|
|
|
|
NOTICE = "NOTICE"
|
|
|
|
TOPIC = "TOPIC"
|
|
|
|
QUIT = "QUIT"
|
|
|
|
|
|
|
|
RPL_WELCOME = "001"
|
|
|
|
RPL_YOURHOST = "002"
|
|
|
|
RPL_CREATED = "003"
|
|
|
|
RPL_LUSERCLIENT = "251"
|
|
|
|
RPL_LUSEROP = "252"
|
|
|
|
RPL_LUSERUNKNOWN = "253"
|
|
|
|
RPL_LUSERCHANNELS = "254"
|
|
|
|
RPL_LUSERME = "255"
|
|
|
|
|
2015-02-21 12:06:05 +00:00
|
|
|
RPL_AWAY = "301"
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
RPL_WHOISUSER = "311"
|
|
|
|
RPL_WHOISSERVER = "312"
|
|
|
|
RPL_WHOISOPERATOR = "313"
|
|
|
|
RPL_WHOISIDLE = "317"
|
|
|
|
RPL_ENDOFWHOIS = "318"
|
|
|
|
RPL_WHOISCHANNELS = "319"
|
|
|
|
|
|
|
|
RPL_TOPIC = "332"
|
|
|
|
|
|
|
|
RPL_NAMREPLY = "353"
|
|
|
|
RPL_ENDOFNAMES = "366"
|
|
|
|
|
|
|
|
RPL_MOTD = "372"
|
|
|
|
RPL_MOTDSTART = "375"
|
|
|
|
RPL_ENDOFMOTD = "376"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Message struct {
|
|
|
|
Prefix string
|
2015-05-31 23:48:07 +00:00
|
|
|
Nick string
|
2015-01-17 01:37:21 +00:00
|
|
|
Command string
|
|
|
|
Params []string
|
|
|
|
Trailing string
|
|
|
|
}
|
|
|
|
|
|
|
|
type IRC struct {
|
2015-06-01 03:44:30 +00:00
|
|
|
conn net.Conn
|
|
|
|
connected bool
|
|
|
|
dialer *net.Dialer
|
|
|
|
reader *bufio.Reader
|
|
|
|
out chan string
|
|
|
|
|
|
|
|
quit chan struct{}
|
|
|
|
reconnect chan struct{}
|
|
|
|
ready sync.WaitGroup
|
|
|
|
once sync.Once
|
|
|
|
lock sync.Mutex
|
|
|
|
|
|
|
|
nick string
|
2015-01-17 01:37:21 +00:00
|
|
|
|
|
|
|
Messages chan *Message
|
|
|
|
Server string
|
|
|
|
Host string
|
|
|
|
TLS bool
|
|
|
|
TLSConfig *tls.Config
|
2015-02-07 02:10:58 +00:00
|
|
|
Password string
|
2015-01-17 01:37:21 +00:00
|
|
|
Username string
|
|
|
|
Realname string
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIRC(nick, username string) *IRC {
|
|
|
|
return &IRC{
|
2015-06-01 03:44:30 +00:00
|
|
|
nick: nick,
|
|
|
|
Username: username,
|
|
|
|
Realname: nick,
|
|
|
|
Messages: make(chan *Message, 32),
|
|
|
|
out: make(chan string, 32),
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
reconnect: make(chan struct{}),
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-01 03:44:30 +00:00
|
|
|
func (i *IRC) Connect(address string) {
|
2015-01-17 01:37:21 +00:00
|
|
|
if idx := strings.Index(address, ":"); idx < 0 {
|
|
|
|
i.Host = address
|
|
|
|
|
|
|
|
if i.TLS {
|
|
|
|
address += ":6697"
|
|
|
|
} else {
|
|
|
|
address += ":6667"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
i.Host = address[:idx]
|
|
|
|
}
|
|
|
|
i.Server = address
|
2015-06-01 03:44:30 +00:00
|
|
|
i.dialer = &net.Dialer{Timeout: 10 * time.Second}
|
2015-01-17 01:37:21 +00:00
|
|
|
|
2015-06-01 03:44:30 +00:00
|
|
|
go i.run()
|
|
|
|
}
|
2015-01-17 01:37:21 +00:00
|
|
|
|
2015-06-01 03:44:30 +00:00
|
|
|
func (i *IRC) Connected() bool {
|
|
|
|
i.lock.Lock()
|
|
|
|
defer i.lock.Unlock()
|
2015-01-29 23:38:51 +00:00
|
|
|
|
2015-06-01 03:44:30 +00:00
|
|
|
return i.connected
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Pass(password string) {
|
2015-01-29 23:38:51 +00:00
|
|
|
i.write("PASS " + password)
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Nick(nick string) {
|
2015-06-01 03:44:30 +00:00
|
|
|
i.Write("NICK " + nick)
|
2015-02-06 23:16:51 +00:00
|
|
|
|
2015-06-01 03:44:30 +00:00
|
|
|
i.lock.Lock()
|
2015-02-06 23:16:51 +00:00
|
|
|
i.nick = nick
|
2015-06-01 03:44:30 +00:00
|
|
|
i.lock.Unlock()
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) User(username, realname string) {
|
2015-01-29 23:38:51 +00:00
|
|
|
i.writef("USER %s 0 * :%s", username, realname)
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
2015-02-07 02:10:58 +00:00
|
|
|
func (i *IRC) Oper(name, password string) {
|
|
|
|
i.Write("OPER " + name + " " + password)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Mode(target, modes, params string) {
|
|
|
|
i.Write(strings.TrimRight("MODE "+target+" "+modes+" "+params, " "))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Quit() {
|
|
|
|
go func() {
|
2015-06-01 03:44:30 +00:00
|
|
|
if i.Connected() {
|
|
|
|
i.write("QUIT")
|
|
|
|
}
|
|
|
|
close(i.quit)
|
2015-02-07 02:10:58 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
func (i *IRC) Join(channels ...string) {
|
|
|
|
i.Write("JOIN " + strings.Join(channels, ","))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Part(channels ...string) {
|
|
|
|
i.Write("PART " + strings.Join(channels, ","))
|
|
|
|
}
|
|
|
|
|
2015-02-07 02:10:58 +00:00
|
|
|
func (i *IRC) Topic(channel string) {
|
|
|
|
i.Write("TOPIC " + channel)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Invite(nick, channel string) {
|
|
|
|
i.Write("INVITE " + nick + " " + channel)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Kick(channel string, users ...string) {
|
|
|
|
i.Write("KICK " + channel + " " + strings.Join(users, ","))
|
|
|
|
}
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
func (i *IRC) Privmsg(target, msg string) {
|
|
|
|
i.Writef("PRIVMSG %s :%s", target, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Notice(target, msg string) {
|
|
|
|
i.Writef("NOTICE %s :%s", target, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Whois(nick string) {
|
|
|
|
i.Write("WHOIS " + nick)
|
|
|
|
}
|
|
|
|
|
2015-02-21 12:06:05 +00:00
|
|
|
func (i *IRC) Away(message string) {
|
|
|
|
i.Write("AWAY :" + message)
|
|
|
|
}
|
|
|
|
|
2015-02-06 23:16:51 +00:00
|
|
|
func (i *IRC) GetNick() string {
|
2015-06-01 03:44:30 +00:00
|
|
|
i.lock.Lock()
|
|
|
|
defer i.lock.Unlock()
|
2015-02-06 23:16:51 +00:00
|
|
|
|
|
|
|
return i.nick
|
|
|
|
}
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
func (i *IRC) Write(data string) {
|
2015-01-29 23:38:51 +00:00
|
|
|
i.out <- data + "\r\n"
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) Writef(format string, a ...interface{}) {
|
2015-01-29 23:38:51 +00:00
|
|
|
i.out <- fmt.Sprintf(format+"\r\n", a...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) write(data string) {
|
2015-02-02 00:54:26 +00:00
|
|
|
i.conn.Write([]byte(data + "\r\n"))
|
2015-01-29 23:38:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) writef(format string, a ...interface{}) {
|
2015-01-17 01:37:21 +00:00
|
|
|
fmt.Fprintf(i.conn, format+"\r\n", a...)
|
|
|
|
}
|
|
|
|
|
2015-06-01 03:44:30 +00:00
|
|
|
func (i *IRC) run() {
|
|
|
|
i.tryConnect()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-i.quit:
|
|
|
|
i.close()
|
|
|
|
i.lock.Lock()
|
|
|
|
i.connected = false
|
|
|
|
i.lock.Unlock()
|
|
|
|
return
|
|
|
|
|
|
|
|
case <-i.reconnect:
|
|
|
|
i.reconnect = make(chan struct{})
|
|
|
|
i.once = sync.Once{}
|
|
|
|
i.tryConnect()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) tryConnect() {
|
|
|
|
// TODO: backoff
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-i.quit:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
err := i.connect()
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IRC) connect() error {
|
|
|
|
if i.TLS {
|
|
|
|
if i.TLSConfig == nil {
|
|
|
|
i.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
if conn, err := tls.DialWithDialer(i.dialer, "tcp", i.Server, i.TLSConfig); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
i.conn = conn
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if conn, err := i.dialer.Dial("tcp", i.Server); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
i.conn = conn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
i.lock.Lock()
|
|
|
|
i.connected = true
|
|
|
|
i.lock.Unlock()
|
|
|
|
|
|
|
|
i.reader = bufio.NewReader(i.conn)
|
|
|
|
|
|
|
|
if i.Password != "" {
|
|
|
|
i.Pass(i.Password)
|
|
|
|
}
|
|
|
|
i.write("NICK " + i.nick)
|
|
|
|
i.User(i.Username, i.Realname)
|
|
|
|
|
|
|
|
i.ready.Add(1)
|
|
|
|
go i.send()
|
|
|
|
go i.recv()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-01-29 23:38:51 +00:00
|
|
|
func (i *IRC) send() {
|
|
|
|
i.ready.Wait()
|
2015-04-29 21:54:44 +00:00
|
|
|
for {
|
2015-06-01 03:44:30 +00:00
|
|
|
select {
|
|
|
|
case <-i.quit:
|
2015-05-16 00:58:26 +00:00
|
|
|
return
|
2015-06-01 03:44:30 +00:00
|
|
|
|
|
|
|
case <-i.reconnect:
|
|
|
|
return
|
|
|
|
|
|
|
|
case msg := <-i.out:
|
|
|
|
_, err := i.conn.Write([]byte(msg))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2015-05-16 00:58:26 +00:00
|
|
|
}
|
2015-01-29 23:38:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
func (i *IRC) recv() {
|
2015-06-01 03:44:30 +00:00
|
|
|
defer i.conn.Close()
|
2015-01-17 01:37:21 +00:00
|
|
|
for {
|
|
|
|
line, err := i.reader.ReadString('\n')
|
|
|
|
if err != nil {
|
2015-06-01 03:44:30 +00:00
|
|
|
i.lock.Lock()
|
|
|
|
i.connected = false
|
|
|
|
i.lock.Unlock()
|
|
|
|
i.once.Do(i.ready.Done)
|
|
|
|
close(i.reconnect)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-i.quit:
|
2015-01-17 01:37:21 +00:00
|
|
|
return
|
2015-06-01 03:44:30 +00:00
|
|
|
default:
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
msg := parseMessage(line)
|
2015-05-16 01:56:58 +00:00
|
|
|
i.Messages <- msg
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
switch msg.Command {
|
|
|
|
case PING:
|
2015-05-16 01:56:58 +00:00
|
|
|
go i.write("PONG :" + msg.Trailing)
|
2015-01-29 23:38:51 +00:00
|
|
|
|
|
|
|
case RPL_WELCOME:
|
2015-05-16 01:56:58 +00:00
|
|
|
i.once.Do(i.ready.Done)
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-16 00:58:26 +00:00
|
|
|
func (i *IRC) close() {
|
2015-06-01 03:44:30 +00:00
|
|
|
if i.Connected() {
|
|
|
|
i.once.Do(i.ready.Done)
|
|
|
|
}
|
2015-05-16 00:58:26 +00:00
|
|
|
close(i.out)
|
2015-05-16 01:56:58 +00:00
|
|
|
close(i.Messages)
|
2015-05-16 00:58:26 +00:00
|
|
|
}
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
func parseMessage(line string) *Message {
|
|
|
|
line = strings.Trim(line, "\r\n")
|
|
|
|
msg := Message{}
|
|
|
|
cmdStart := 0
|
|
|
|
cmdEnd := len(line)
|
|
|
|
|
|
|
|
if strings.HasPrefix(line, ":") {
|
|
|
|
cmdStart = strings.Index(line, " ") + 1
|
|
|
|
msg.Prefix = line[1 : cmdStart-1]
|
2015-06-01 03:44:30 +00:00
|
|
|
|
|
|
|
if i := strings.Index(msg.Prefix, "!"); i > 0 {
|
|
|
|
msg.Nick = msg.Prefix[:i]
|
|
|
|
} else {
|
|
|
|
msg.Nick = msg.Prefix
|
|
|
|
}
|
2015-01-17 01:37:21 +00:00
|
|
|
}
|
|
|
|
|
2015-02-03 22:33:23 +00:00
|
|
|
if i := strings.Index(line, " :"); i > 0 {
|
2015-01-17 01:37:21 +00:00
|
|
|
cmdEnd = i
|
|
|
|
msg.Trailing = line[i+2:]
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := strings.Split(line[cmdStart:cmdEnd], " ")
|
|
|
|
msg.Command = cmd[0]
|
|
|
|
if len(cmd) > 1 {
|
|
|
|
msg.Params = cmd[1:]
|
|
|
|
}
|
|
|
|
|
2015-02-03 22:33:23 +00:00
|
|
|
if msg.Trailing != "" {
|
|
|
|
msg.Params = append(msg.Params, msg.Trailing)
|
|
|
|
}
|
|
|
|
|
2015-01-17 01:37:21 +00:00
|
|
|
return &msg
|
|
|
|
}
|