Pull IRC client out

This commit is contained in:
Ken-Håvard Lieng 2015-06-06 00:34:13 +02:00
parent 78b6a0859b
commit adcf12e1fa
11 changed files with 567 additions and 538 deletions

129
irc/client.go Normal file
View File

@ -0,0 +1,129 @@
package irc
import (
"bufio"
"crypto/tls"
"net"
"strings"
"sync"
)
type Client struct {
Server string
Host string
TLS bool
TLSConfig *tls.Config
Password string
Username string
Realname string
Messages chan *Message
nick string
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
}
func NewClient(nick, username string) *Client {
return &Client{
nick: nick,
Username: username,
Realname: nick,
Messages: make(chan *Message, 32),
out: make(chan string, 32),
quit: make(chan struct{}),
reconnect: make(chan struct{}),
}
}
func (c *Client) GetNick() string {
c.lock.Lock()
defer c.lock.Unlock()
return c.nick
}
func (c *Client) Connected() bool {
c.lock.Lock()
defer c.lock.Unlock()
return c.connected
}
func (c *Client) Pass(password string) {
c.write("PASS " + password)
}
func (c *Client) Nick(nick string) {
c.Write("NICK " + nick)
c.lock.Lock()
c.nick = nick
c.lock.Unlock()
}
func (c *Client) User(username, realname string) {
c.writef("USER %s 0 * :%s", username, realname)
}
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, ","))
}
func (c *Client) Topic(channel string) {
c.Write("TOPIC " + channel)
}
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)
}
func (c *Client) Whois(nick string) {
c.Write("WHOIS " + nick)
}
func (c *Client) Away(message string) {
c.Write("AWAY :" + message)
}

177
irc/conn.go Normal file
View File

@ -0,0 +1,177 @@
package irc
import (
"bufio"
"crypto/tls"
"fmt"
"net"
"strings"
"sync"
"time"
)
func (c *Client) Connect(address string) {
if idx := strings.Index(address, ":"); idx < 0 {
c.Host = address
if c.TLS {
address += ":6697"
} else {
address += ":6667"
}
} else {
c.Host = address[:idx]
}
c.Server = address
c.dialer = &net.Dialer{Timeout: 10 * time.Second}
go c.run()
}
func (c *Client) Write(data string) {
c.out <- data + "\r\n"
}
func (c *Client) Writef(format string, a ...interface{}) {
c.out <- fmt.Sprintf(format+"\r\n", a...)
}
func (c *Client) write(data string) {
c.conn.Write([]byte(data + "\r\n"))
}
func (c *Client) writef(format string, a ...interface{}) {
fmt.Fprintf(c.conn, format+"\r\n", a...)
}
func (c *Client) connect() error {
if c.TLS {
if c.TLSConfig == nil {
c.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
if conn, err := tls.DialWithDialer(c.dialer, "tcp", c.Server, c.TLSConfig); err != nil {
return err
} else {
c.conn = conn
}
} else {
if conn, err := c.dialer.Dial("tcp", c.Server); err != nil {
return err
} else {
c.conn = conn
}
}
c.lock.Lock()
c.connected = true
c.lock.Unlock()
c.reader = bufio.NewReader(c.conn)
if c.Password != "" {
c.Pass(c.Password)
}
c.write("NICK " + c.nick)
c.User(c.Username, c.Realname)
c.ready.Add(1)
go c.send()
go c.recv()
return nil
}
func (c *Client) tryConnect() {
// TODO: backoff
for {
select {
case <-c.quit:
return
default:
}
err := c.connect()
if err == nil {
return
}
}
}
func (c *Client) run() {
c.tryConnect()
for {
select {
case <-c.quit:
c.close()
c.lock.Lock()
c.connected = false
c.lock.Unlock()
return
case <-c.reconnect:
c.reconnect = make(chan struct{})
c.once = sync.Once{}
c.tryConnect()
}
}
}
func (c *Client) send() {
c.ready.Wait()
for {
select {
case <-c.quit:
return
case <-c.reconnect:
return
case msg := <-c.out:
_, err := c.conn.Write([]byte(msg))
if err != nil {
return
}
}
}
}
func (c *Client) recv() {
defer c.conn.Close()
for {
line, err := c.reader.ReadString('\n')
if err != nil {
c.lock.Lock()
c.connected = false
c.lock.Unlock()
c.once.Do(c.ready.Done)
close(c.reconnect)
return
}
select {
case <-c.quit:
return
default:
}
msg := parseMessage(line)
c.Messages <- msg
switch msg.Command {
case Ping:
go c.write("PONG :" + msg.Trailing)
case ReplyWelcome:
c.once.Do(c.ready.Done)
}
}
}
func (c *Client) close() {
if c.Connected() {
c.once.Do(c.ready.Done)
}
close(c.out)
close(c.Messages)
}

35
irc/const.go Normal file
View File

@ -0,0 +1,35 @@
package irc
const (
Ping = "PING"
Nick = "NICK"
Join = "JOIN"
Part = "PART"
Mode = "MODE"
Privmsg = "PRIVMSG"
Notice = "NOTICE"
Topic = "TOPIC"
Quit = "QUIT"
ReplyWelcome = "001"
ReplyYourHost = "002"
ReplyCreated = "003"
ReplyLUserClient = "251"
ReplyLUserOp = "252"
ReplyLUserUnknown = "253"
ReplyLUserChannels = "254"
ReplyLUserMe = "255"
ReplyAway = "301"
ReplyWhoisUser = "311"
ReplyWhoisServer = "312"
ReplyWhoisOperator = "313"
ReplyWhoisIdle = "317"
ReplyEndOfWhois = "318"
ReplyWhoisChannels = "319"
ReplyTopic = "332"
ReplyNamReply = "353"
ReplyEndOfNames = "366"
ReplyMotd = "372"
ReplyMotdStart = "375"
ReplyEndOfMotd = "376"
)

48
irc/message.go Normal file
View File

@ -0,0 +1,48 @@
package irc
import (
"strings"
)
type Message struct {
Prefix string
Nick string
Command string
Params []string
Trailing string
}
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]
if i := strings.Index(msg.Prefix, "!"); i > 0 {
msg.Nick = msg.Prefix[:i]
} else {
msg.Nick = msg.Prefix
}
}
if i := strings.Index(line, " :"); i > 0 {
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:]
}
if msg.Trailing != "" {
msg.Params = append(msg.Params, msg.Trailing)
}
return &msg
}

62
server/conn.go Normal file
View File

@ -0,0 +1,62 @@
package server
import (
"time"
"github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket"
)
type conn struct {
conn *websocket.Conn
in chan WSRequest
out chan []byte
}
func newConn(ws *websocket.Conn) *conn {
return &conn{
conn: ws,
in: make(chan WSRequest, 32),
out: make(chan []byte, 32),
}
}
func (c *conn) send() {
var err error
ping := time.Tick(20 * time.Second)
for {
select {
case msg, ok := <-c.out:
if !ok {
return
}
err = c.conn.WriteMessage(websocket.TextMessage, msg)
case <-ping:
err = c.conn.WriteJSON(WSResponse{Type: "ping"})
}
if err != nil {
return
}
}
}
func (c *conn) recv() {
var req WSRequest
for {
err := c.conn.ReadJSON(&req)
if err != nil {
close(c.in)
return
}
c.in <- req
}
}
func (c *conn) close() {
close(c.out)
}

View File

@ -1,379 +0,0 @@
package server
import (
"bufio"
"crypto/tls"
"fmt"
"net"
"strings"
"sync"
"time"
)
const (
PING = "PING"
NICK = "NICK"
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"
RPL_AWAY = "301"
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
Nick string
Command string
Params []string
Trailing string
}
type IRC struct {
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
Messages chan *Message
Server string
Host string
TLS bool
TLSConfig *tls.Config
Password string
Username string
Realname string
}
func NewIRC(nick, username string) *IRC {
return &IRC{
nick: nick,
Username: username,
Realname: nick,
Messages: make(chan *Message, 32),
out: make(chan string, 32),
quit: make(chan struct{}),
reconnect: make(chan struct{}),
}
}
func (i *IRC) Connect(address string) {
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
i.dialer = &net.Dialer{Timeout: 10 * time.Second}
go i.run()
}
func (i *IRC) Connected() bool {
i.lock.Lock()
defer i.lock.Unlock()
return i.connected
}
func (i *IRC) Pass(password string) {
i.write("PASS " + password)
}
func (i *IRC) Nick(nick string) {
i.Write("NICK " + nick)
i.lock.Lock()
i.nick = nick
i.lock.Unlock()
}
func (i *IRC) User(username, realname string) {
i.writef("USER %s 0 * :%s", username, realname)
}
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() {
if i.Connected() {
i.write("QUIT")
}
close(i.quit)
}()
}
func (i *IRC) Join(channels ...string) {
i.Write("JOIN " + strings.Join(channels, ","))
}
func (i *IRC) Part(channels ...string) {
i.Write("PART " + strings.Join(channels, ","))
}
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, ","))
}
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)
}
func (i *IRC) Away(message string) {
i.Write("AWAY :" + message)
}
func (i *IRC) GetNick() string {
i.lock.Lock()
defer i.lock.Unlock()
return i.nick
}
func (i *IRC) Write(data string) {
i.out <- data + "\r\n"
}
func (i *IRC) Writef(format string, a ...interface{}) {
i.out <- fmt.Sprintf(format+"\r\n", a...)
}
func (i *IRC) write(data string) {
i.conn.Write([]byte(data + "\r\n"))
}
func (i *IRC) writef(format string, a ...interface{}) {
fmt.Fprintf(i.conn, format+"\r\n", a...)
}
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
}
func (i *IRC) send() {
i.ready.Wait()
for {
select {
case <-i.quit:
return
case <-i.reconnect:
return
case msg := <-i.out:
_, err := i.conn.Write([]byte(msg))
if err != nil {
return
}
}
}
}
func (i *IRC) recv() {
defer i.conn.Close()
for {
line, err := i.reader.ReadString('\n')
if err != nil {
i.lock.Lock()
i.connected = false
i.lock.Unlock()
i.once.Do(i.ready.Done)
close(i.reconnect)
return
}
select {
case <-i.quit:
return
default:
}
msg := parseMessage(line)
i.Messages <- msg
switch msg.Command {
case PING:
go i.write("PONG :" + msg.Trailing)
case RPL_WELCOME:
i.once.Do(i.ready.Done)
}
}
}
func (i *IRC) close() {
if i.Connected() {
i.once.Do(i.ready.Done)
}
close(i.out)
close(i.Messages)
}
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]
if i := strings.Index(msg.Prefix, "!"); i > 0 {
msg.Nick = msg.Prefix[:i]
} else {
msg.Nick = msg.Prefix
}
}
if i := strings.Index(line, " :"); i > 0 {
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:]
}
if msg.Trailing != "" {
msg.Params = append(msg.Params, msg.Trailing)
}
return &msg
}

View File

@ -4,86 +4,87 @@ import (
"log" "log"
"strings" "strings"
"github.com/khlieng/name_pending/irc"
"github.com/khlieng/name_pending/storage" "github.com/khlieng/name_pending/storage"
) )
func handleMessages(irc *IRC, session *Session) { func handleIRC(client *irc.Client, session *Session) {
var whois WhoisReply var whois WhoisReply
userBuffers := make(map[string][]string) userBuffers := make(map[string][]string)
var motd MOTD var motd MOTD
for { for {
msg, ok := <-irc.Messages msg, ok := <-client.Messages
if !ok { if !ok {
session.deleteIRC(irc.Host) session.deleteIRC(client.Host)
return return
} }
switch msg.Command { switch msg.Command {
case NICK: case irc.Nick:
session.sendJSON("nick", Nick{ session.sendJSON("nick", Nick{
Server: irc.Host, Server: client.Host,
Old: msg.Nick, Old: msg.Nick,
New: msg.Trailing, New: msg.Trailing,
}) })
channelStore.RenameUser(msg.Nick, msg.Trailing, irc.Host) channelStore.RenameUser(msg.Nick, msg.Trailing, client.Host)
case JOIN: case irc.Join:
session.sendJSON("join", Join{ session.sendJSON("join", Join{
Server: irc.Host, Server: client.Host,
User: msg.Nick, User: msg.Nick,
Channels: msg.Params, Channels: msg.Params,
}) })
channelStore.AddUser(msg.Nick, irc.Host, msg.Params[0]) channelStore.AddUser(msg.Nick, client.Host, msg.Params[0])
if msg.Nick == irc.GetNick() { if msg.Nick == client.GetNick() {
session.user.AddChannel(storage.Channel{ session.user.AddChannel(storage.Channel{
Server: irc.Host, Server: client.Host,
Name: msg.Params[0], Name: msg.Params[0],
}) })
} }
case PART: case irc.Part:
session.sendJSON("part", Part{ session.sendJSON("part", Part{
Join: Join{ Join: Join{
Server: irc.Host, Server: client.Host,
User: msg.Nick, User: msg.Nick,
Channels: msg.Params, Channels: msg.Params,
}, },
Reason: msg.Trailing, Reason: msg.Trailing,
}) })
channelStore.RemoveUser(msg.Nick, irc.Host, msg.Params[0]) channelStore.RemoveUser(msg.Nick, client.Host, msg.Params[0])
if msg.Nick == irc.GetNick() { if msg.Nick == client.GetNick() {
session.user.RemoveChannel(irc.Host, msg.Params[0]) session.user.RemoveChannel(client.Host, msg.Params[0])
} }
case MODE: case irc.Mode:
target := msg.Params[0] target := msg.Params[0]
if len(msg.Params) > 2 && isChannel(target) { if len(msg.Params) > 2 && isChannel(target) {
mode := parseMode(msg.Params[1]) mode := parseMode(msg.Params[1])
mode.Server = irc.Host mode.Server = client.Host
mode.Channel = target mode.Channel = target
mode.User = msg.Params[2] mode.User = msg.Params[2]
session.sendJSON("mode", mode) session.sendJSON("mode", mode)
channelStore.SetMode(irc.Host, target, msg.Params[2], mode.Add, mode.Remove) channelStore.SetMode(client.Host, target, msg.Params[2], mode.Add, mode.Remove)
} }
case PRIVMSG, NOTICE: case irc.Privmsg, irc.Notice:
if msg.Params[0] == irc.GetNick() { if msg.Params[0] == client.GetNick() {
session.sendJSON("pm", Chat{ session.sendJSON("pm", Chat{
Server: irc.Host, Server: client.Host,
From: msg.Nick, From: msg.Nick,
Message: msg.Trailing, Message: msg.Trailing,
}) })
} else { } else {
session.sendJSON("message", Chat{ session.sendJSON("message", Chat{
Server: irc.Host, Server: client.Host,
From: msg.Nick, From: msg.Nick,
To: msg.Params[0], To: msg.Params[0],
Message: msg.Trailing, Message: msg.Trailing,
@ -91,90 +92,90 @@ func handleMessages(irc *IRC, session *Session) {
} }
if msg.Params[0] != "*" { if msg.Params[0] != "*" {
session.user.LogMessage(irc.Host, msg.Nick, msg.Params[0], msg.Trailing) session.user.LogMessage(client.Host, msg.Nick, msg.Params[0], msg.Trailing)
} }
case QUIT: case irc.Quit:
session.sendJSON("quit", Quit{ session.sendJSON("quit", Quit{
Server: irc.Host, Server: client.Host,
User: msg.Nick, User: msg.Nick,
Reason: msg.Trailing, Reason: msg.Trailing,
}) })
channelStore.RemoveUserAll(msg.Nick, irc.Host) channelStore.RemoveUserAll(msg.Nick, client.Host)
case RPL_WELCOME, case irc.ReplyWelcome,
RPL_YOURHOST, irc.ReplyYourHost,
RPL_CREATED, irc.ReplyCreated,
RPL_LUSERCLIENT, irc.ReplyLUserClient,
RPL_LUSEROP, irc.ReplyLUserOp,
RPL_LUSERUNKNOWN, irc.ReplyLUserUnknown,
RPL_LUSERCHANNELS, irc.ReplyLUserChannels,
RPL_LUSERME: irc.ReplyLUserMe:
session.sendJSON("pm", Chat{ session.sendJSON("pm", Chat{
Server: irc.Host, Server: client.Host,
From: msg.Nick, From: msg.Nick,
Message: strings.Join(msg.Params[1:], " "), Message: strings.Join(msg.Params[1:], " "),
}) })
case RPL_WHOISUSER: case irc.ReplyWhoisUser:
whois.Nick = msg.Params[1] whois.Nick = msg.Params[1]
whois.Username = msg.Params[2] whois.Username = msg.Params[2]
whois.Host = msg.Params[3] whois.Host = msg.Params[3]
whois.Realname = msg.Params[5] whois.Realname = msg.Params[5]
case RPL_WHOISSERVER: case irc.ReplyWhoisServer:
whois.Server = msg.Params[2] whois.Server = msg.Params[2]
case RPL_WHOISCHANNELS: case irc.ReplyWhoisChannels:
whois.Channels = append(whois.Channels, strings.Split(strings.TrimRight(msg.Trailing, " "), " ")...) whois.Channels = append(whois.Channels, strings.Split(strings.TrimRight(msg.Trailing, " "), " ")...)
case RPL_ENDOFWHOIS: case irc.ReplyEndOfWhois:
session.sendJSON("whois", whois) session.sendJSON("whois", whois)
whois = WhoisReply{} whois = WhoisReply{}
case RPL_TOPIC: case irc.ReplyTopic:
session.sendJSON("topic", Topic{ session.sendJSON("topic", Topic{
Server: irc.Host, Server: client.Host,
Channel: msg.Params[1], Channel: msg.Params[1],
Topic: msg.Trailing, Topic: msg.Trailing,
}) })
channelStore.SetTopic(msg.Trailing, irc.Host, msg.Params[1]) channelStore.SetTopic(msg.Trailing, client.Host, msg.Params[1])
case RPL_NAMREPLY: case irc.ReplyNamReply:
users := strings.Split(msg.Trailing, " ") users := strings.Split(msg.Trailing, " ")
userBuffer := userBuffers[msg.Params[2]] userBuffer := userBuffers[msg.Params[2]]
userBuffers[msg.Params[2]] = append(userBuffer, users...) userBuffers[msg.Params[2]] = append(userBuffer, users...)
case RPL_ENDOFNAMES: case irc.ReplyEndOfNames:
channel := msg.Params[1] channel := msg.Params[1]
users := userBuffers[channel] users := userBuffers[channel]
session.sendJSON("users", Userlist{ session.sendJSON("users", Userlist{
Server: irc.Host, Server: client.Host,
Channel: channel, Channel: channel,
Users: users, Users: users,
}) })
channelStore.SetUsers(users, irc.Host, channel) channelStore.SetUsers(users, client.Host, channel)
delete(userBuffers, channel) delete(userBuffers, channel)
case RPL_MOTDSTART: case irc.ReplyMotdStart:
motd.Server = irc.Host motd.Server = client.Host
motd.Title = msg.Trailing motd.Title = msg.Trailing
case RPL_MOTD: case irc.ReplyMotd:
motd.Content = append(motd.Content, msg.Trailing) motd.Content = append(motd.Content, msg.Trailing)
case RPL_ENDOFMOTD: case irc.ReplyEndOfMotd:
session.sendJSON("motd", motd) session.sendJSON("motd", motd)
motd = MOTD{} motd = MOTD{}
default: default:
printMessage(msg, irc) printMessage(msg, client)
} }
} }
} }
@ -202,6 +203,6 @@ func isChannel(s string) bool {
return strings.IndexAny(s, "&#+!") == 0 return strings.IndexAny(s, "&#+!") == 0
} }
func printMessage(msg *Message, irc *IRC) { func printMessage(msg *irc.Message, i *irc.Client) {
log.Println(irc.GetNick()+":", msg.Prefix, msg.Command, msg.Params, msg.Trailing) log.Println(i.GetNick()+":", msg.Prefix, msg.Command, msg.Params, msg.Trailing)
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket" "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket"
"github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/julienschmidt/httprouter" "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
"github.com/khlieng/name_pending/irc"
"github.com/khlieng/name_pending/storage" "github.com/khlieng/name_pending/storage"
) )
@ -63,16 +64,16 @@ func reconnect() {
channels := user.GetChannels() channels := user.GetChannels()
for _, server := range user.GetServers() { for _, server := range user.GetServers() {
irc := NewIRC(server.Nick, server.Username) i := irc.NewClient(server.Nick, server.Username)
irc.TLS = server.TLS i.TLS = server.TLS
irc.Password = server.Password i.Password = server.Password
irc.Realname = server.Realname i.Realname = server.Realname
go func(server storage.Server) { go func(server storage.Server) {
irc.Connect(server.Address) i.Connect(server.Address)
session.setIRC(irc.Host, irc) session.setIRC(i.Host, i)
go handleMessages(irc, session) go handleIRC(i, session)
var joining []string var joining []string
for _, channel := range channels { for _, channel := range channels {
@ -80,7 +81,7 @@ func reconnect() {
joining = append(joining, channel.Name) joining = append(joining, channel.Name)
} }
} }
irc.Join(joining...) i.Join(joining...)
}(server) }(server)
} }
} }

View File

@ -4,14 +4,15 @@ import (
"encoding/json" "encoding/json"
"sync" "sync"
"github.com/khlieng/name_pending/irc"
"github.com/khlieng/name_pending/storage" "github.com/khlieng/name_pending/storage"
) )
type Session struct { type Session struct {
irc map[string]*IRC irc map[string]*irc.Client
ircLock sync.Mutex ircLock sync.Mutex
ws map[string]*WebSocket ws map[string]*conn
wsLock sync.Mutex wsLock sync.Mutex
out chan []byte out chan []byte
@ -20,23 +21,23 @@ type Session struct {
func NewSession() *Session { func NewSession() *Session {
return &Session{ return &Session{
irc: make(map[string]*IRC), irc: make(map[string]*irc.Client),
ws: make(map[string]*WebSocket), ws: make(map[string]*conn),
out: make(chan []byte, 32), out: make(chan []byte, 32),
} }
} }
func (s *Session) getIRC(server string) (*IRC, bool) { func (s *Session) getIRC(server string) (*irc.Client, bool) {
s.ircLock.Lock() s.ircLock.Lock()
irc, ok := s.irc[server] i, ok := s.irc[server]
s.ircLock.Unlock() s.ircLock.Unlock()
return irc, ok return i, ok
} }
func (s *Session) setIRC(server string, irc *IRC) { func (s *Session) setIRC(server string, i *irc.Client) {
s.ircLock.Lock() s.ircLock.Lock()
s.irc[server] = irc s.irc[server] = i
s.ircLock.Unlock() s.ircLock.Unlock()
} }
@ -54,7 +55,7 @@ func (s *Session) numIRC() int {
return n return n
} }
func (s *Session) setWS(addr string, w *WebSocket) { func (s *Session) setWS(addr string, w *conn) {
s.wsLock.Lock() s.wsLock.Lock()
s.ws[addr] = w s.ws[addr] = w
s.wsLock.Unlock() s.wsLock.Unlock()
@ -85,7 +86,7 @@ func (s *Session) write() {
for res := range s.out { for res := range s.out {
s.wsLock.Lock() s.wsLock.Lock()
for _, ws := range s.ws { for _, ws := range s.ws {
ws.Out <- res ws.out <- res
} }
s.wsLock.Unlock() s.wsLock.Unlock()
} }

View File

@ -1,47 +0,0 @@
package server
import (
"time"
"github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket"
)
type WebSocket struct {
conn *websocket.Conn
Out chan []byte
}
func NewWebSocket(ws *websocket.Conn) *WebSocket {
return &WebSocket{
conn: ws,
Out: make(chan []byte, 32),
}
}
func (w *WebSocket) write() {
var err error
ping := time.Tick(20 * time.Second)
for {
select {
case msg, ok := <-w.Out:
if !ok {
return
}
err = w.conn.WriteMessage(websocket.TextMessage, msg)
case <-ping:
err = w.conn.WriteJSON(WSResponse{Type: "ping"})
}
if err != nil {
return
}
}
}
func (w *WebSocket) close() {
close(w.Out)
}

View File

@ -7,31 +7,32 @@ import (
"github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket" "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket"
"github.com/khlieng/name_pending/irc"
"github.com/khlieng/name_pending/storage" "github.com/khlieng/name_pending/storage"
) )
func handleWS(ws *websocket.Conn) { func handleWS(conn *websocket.Conn) {
defer ws.Close() defer conn.Close()
var session *Session var session *Session
var UUID string var UUID string
var req WSRequest
addr := ws.RemoteAddr().String() addr := conn.RemoteAddr().String()
w := NewWebSocket(ws)
go w.write() ws := newConn(conn)
defer ws.close()
go ws.send()
go ws.recv()
log.Println(addr, "connected") log.Println(addr, "connected")
for { for {
err := ws.ReadJSON(&req) req, ok := <-ws.in
if err != nil { if !ok {
if session != nil { if session != nil {
session.deleteWS(addr) session.deleteWS(addr)
} }
w.close()
log.Println(addr, "disconnected") log.Println(addr, "disconnected")
return return
} }
@ -77,7 +78,7 @@ func handleWS(ws *websocket.Conn) {
go session.write() go session.write()
} }
session.setWS(addr, w) session.setWS(addr, ws)
case "connect": case "connect":
var data Connect var data Connect
@ -87,24 +88,24 @@ func handleWS(ws *websocket.Conn) {
if _, ok := session.getIRC(data.Server); !ok { if _, ok := session.getIRC(data.Server); !ok {
log.Println(addr, "connecting to", data.Server) log.Println(addr, "connecting to", data.Server)
irc := NewIRC(data.Nick, data.Username) i := irc.NewClient(data.Nick, data.Username)
irc.TLS = data.TLS i.TLS = data.TLS
irc.Password = data.Password i.Password = data.Password
irc.Realname = data.Realname i.Realname = data.Realname
if idx := strings.Index(data.Server, ":"); idx < 0 { if idx := strings.Index(data.Server, ":"); idx < 0 {
session.setIRC(data.Server, irc) session.setIRC(data.Server, i)
} else { } else {
session.setIRC(data.Server[:idx], irc) session.setIRC(data.Server[:idx], i)
} }
go func() { go func() {
irc.Connect(data.Server) i.Connect(data.Server)
go handleMessages(irc, session) go handleIRC(i, session)
session.user.AddServer(storage.Server{ session.user.AddServer(storage.Server{
Name: data.Name, Name: data.Name,
Address: irc.Host, Address: i.Host,
TLS: data.TLS, TLS: data.TLS,
Password: data.Password, Password: data.Password,
Nick: data.Nick, Nick: data.Nick,
@ -121,8 +122,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Join(data.Channels...) i.Join(data.Channels...)
} }
case "part": case "part":
@ -130,8 +131,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Part(data.Channels...) i.Part(data.Channels...)
} }
case "quit": case "quit":
@ -139,10 +140,10 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Quit() i.Quit()
session.deleteIRC(data.Server) session.deleteIRC(data.Server)
channelStore.RemoveUserAll(irc.GetNick(), data.Server) channelStore.RemoveUserAll(i.GetNick(), data.Server)
session.user.RemoveServer(data.Server) session.user.RemoveServer(data.Server)
} }
@ -151,8 +152,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Privmsg(data.To, data.Message) i.Privmsg(data.To, data.Message)
} }
case "nick": case "nick":
@ -160,8 +161,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Nick(data.New) i.Nick(data.New)
session.user.SetNick(data.New, data.Server) session.user.SetNick(data.New, data.Server)
} }
@ -170,8 +171,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Invite(data.User, data.Channel) i.Invite(data.User, data.Channel)
} }
case "kick": case "kick":
@ -179,8 +180,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Kick(data.Channel, data.User) i.Kick(data.Channel, data.User)
} }
case "whois": case "whois":
@ -188,8 +189,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Whois(data.User) i.Whois(data.User)
} }
case "away": case "away":
@ -197,8 +198,8 @@ func handleWS(ws *websocket.Conn) {
json.Unmarshal(req.Request, &data) json.Unmarshal(req.Request, &data)
if irc, ok := session.getIRC(data.Server); ok { if i, ok := session.getIRC(data.Server); ok {
irc.Away(data.Message) i.Away(data.Message)
} }
case "search": case "search":