3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 07:59:31 +01:00

cleanup and reorganization

This commit is contained in:
Jeremy Latt 2012-12-09 12:51:50 -08:00
parent 2dba5f4c47
commit 55f7c89468
8 changed files with 254 additions and 196 deletions

View File

@ -11,3 +11,9 @@ I wanted to learn Go.
### What's with the name? ### What's with the name?
"Ergonomadic" is an anagram of "Go IRC Daemon". "Ergonomadic" is an anagram of "Go IRC Daemon".
### Helpful Documentation
- [IRC Channel Management](http://tools.ietf.org/html/rfc2811)
- [IRC Client Protocol](http://tools.ietf.org/html/rfc2812)
- [IRC Server Protocol](http://tools.ietf.org/html/rfc2813)

View File

@ -1,5 +1,9 @@
package irc package irc
import (
"sort"
)
type Channel struct { type Channel struct {
name string name string
key string key string
@ -12,10 +16,19 @@ type Channel struct {
type ChannelSet map[*Channel]bool type ChannelSet map[*Channel]bool
// NewChannel creates a new channel from a `Server` and a `name` string, which
// must be unique on the server.
func NewChannel(s *Server, name string) *Channel { func NewChannel(s *Server, name string) *Channel {
return &Channel{name: name, members: make(ClientSet), invites: make(map[string]bool), server: s} return &Channel{
name: name,
members: make(ClientSet),
invites: make(map[string]bool),
server: s,
}
} }
// Send a `Reply` to all `Client`s of the `Channel`. Skip `fromClient`, if it is
// provided.
func (ch *Channel) Send(reply Reply, fromClient *Client) { func (ch *Channel) Send(reply Reply, fromClient *Client) {
for client := range ch.members { for client := range ch.members {
if client != fromClient { if client != fromClient {
@ -24,7 +37,20 @@ func (ch *Channel) Send(reply Reply, fromClient *Client) {
} }
} }
func (ch *Channel) Nicks() []string {
nicks := make([]string, len(ch.members))
i := 0
for member := range ch.members {
nicks[i] = member.Nick()
i++
}
sort.Strings(nicks)
return nicks
}
//
// channel functionality // channel functionality
//
func (ch *Channel) Join(cl *Client, key string) { func (ch *Channel) Join(cl *Client, key string) {
if ch.key != key { if ch.key != key {
@ -42,10 +68,7 @@ func (ch *Channel) Join(cl *Client, key string) {
ch.Send(RplJoin(ch, cl), nil) ch.Send(RplJoin(ch, cl), nil)
ch.GetTopic(cl) ch.GetTopic(cl)
cl.send <- RplNamReply(ch)
for member := range ch.members {
cl.send <- RplNamReply(ch, member)
}
cl.send <- RplEndOfNames(ch.server) cl.send <- RplEndOfNames(ch.server)
} }

View File

@ -16,12 +16,19 @@ type Client struct {
registered bool registered bool
invisible bool invisible bool
channels ChannelSet channels ChannelSet
server *Server
} }
type ClientSet map[*Client]bool type ClientSet map[*Client]bool
func NewClient(conn net.Conn) *Client { func NewClient(server *Server, conn net.Conn) *Client {
client := &Client{conn: conn, recv: StringReadChan(conn), channels: make(ChannelSet), hostname: LookupHostname(conn.RemoteAddr())} client := &Client{
channels: make(ChannelSet),
conn: conn,
hostname: LookupHostname(conn.RemoteAddr()),
recv: StringReadChan(conn),
server: server,
}
client.SetReplyToStringChan() client.SetReplyToStringChan()
return client return client
} }
@ -38,12 +45,14 @@ func (c *Client) SetReplyToStringChan() {
} }
// Adapt `chan string` to a `chan Message`. // Adapt `chan string` to a `chan Message`.
func (c *Client) Communicate(server *Server) { func (c *Client) Communicate() {
for str := range c.recv { for str := range c.recv {
m := ParseMessage(str) m, err := ParseMessage(str)
if m != nil { if err != nil {
server.recv <- &ClientMessage{c, m} // TODO handle error
return
} }
c.server.recv <- &ClientMessage{c, m}
} }
} }

View File

@ -1,15 +1,30 @@
package irc package irc
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
type Message interface { type Message interface {
Handle(s *Server, c *Client) Handle(s *Server, c *Client)
} }
var (
ErrNotEnoughArgs = errors.New("not enough arguments")
ErrUModeUnknownFlag = errors.New("unknown umode flag")
)
// unknown // unknown
type UnknownMessage struct { type UnknownMessage struct {
command string command string
} }
// NB: no constructor, created on demand in parser for invalid messages.
func (m *UnknownMessage) Handle(s *Server, c *Client) { func (m *UnknownMessage) Handle(s *Server, c *Client) {
c.send <- ErrUnknownCommand(s, m.command) c.send <- ErrUnknownCommand(s, m.command)
} }
@ -21,6 +36,17 @@ type PingMessage struct {
server2 string server2 string
} }
func NewPingMessage(args []string) (Message, error) {
if len(args) < 1 {
return nil, ErrNotEnoughArgs
}
msg := &PingMessage{server: args[0]}
if len(args) > 1 {
msg.server2 = args[1]
}
return msg, nil
}
func (m *PingMessage) Handle(s *Server, c *Client) { func (m *PingMessage) Handle(s *Server, c *Client) {
c.send <- RplPong(s) c.send <- RplPong(s)
} }
@ -32,8 +58,19 @@ type PongMessage struct {
server2 string server2 string
} }
func NewPongMessage(args []string) (Message, error) {
if len(args) < 1 {
return nil, ErrNotEnoughArgs
}
message := &PongMessage{server1: args[0]}
if len(args) > 1 {
message.server2 = args[1]
}
return message, nil
}
func (m *PongMessage) Handle(s *Server, c *Client) { func (m *PongMessage) Handle(s *Server, c *Client) {
// TODO update client atime // no-op
} }
// NICK // NICK
@ -42,6 +79,13 @@ type NickMessage struct {
nickname string nickname string
} }
func NewNickMessage(args []string) (Message, error) {
if len(args) != 1 {
return nil, ErrNotEnoughArgs
}
return &NickMessage{args[0]}, nil
}
func (m *NickMessage) Handle(s *Server, c *Client) { func (m *NickMessage) Handle(s *Server, c *Client) {
s.ChangeNick(c, m.nickname) s.ChangeNick(c, m.nickname)
} }
@ -55,8 +99,24 @@ type UserMessage struct {
realname string realname string
} }
func NewUserMessage(args []string) (Message, error) {
if len(args) != 4 {
return nil, ErrNotEnoughArgs
}
msg := &UserMessage{
user: args[0],
unused: args[2],
realname: args[3],
}
mode, err := strconv.ParseUint(args[1], 10, 8)
if err == nil {
msg.mode = uint8(mode)
}
return msg, nil
}
func (m *UserMessage) Handle(s *Server, c *Client) { func (m *UserMessage) Handle(s *Server, c *Client) {
s.Register(c, m.user, m.realname) s.UserLogin(c, m.user, m.realname)
} }
// QUIT // QUIT
@ -65,6 +125,14 @@ type QuitMessage struct {
message string message string
} }
func NewQuitMessage(args []string) (Message, error) {
msg := &QuitMessage{}
if len(args) > 0 {
msg.message = args[0]
}
return msg, nil
}
func (m *QuitMessage) Handle(s *Server, c *Client) { func (m *QuitMessage) Handle(s *Server, c *Client) {
s.Quit(c, m.message) s.Quit(c, m.message)
} }
@ -76,6 +144,28 @@ type ModeMessage struct {
modes []string modes []string
} }
var MODE_RE = regexp.MustCompile("^[-+][a-zA-Z]+$")
func NewModeMessage(args []string) (Message, error) {
if len(args) < 1 {
return nil, ErrNotEnoughArgs
}
msg := &ModeMessage{
nickname: args[0],
}
for _, arg := range args[1:] {
if !MODE_RE.MatchString(arg) {
return nil, ErrUModeUnknownFlag
}
prefix := arg[0]
for _, c := range arg[1:] {
mode := fmt.Sprintf("%c%c", prefix, c)
msg.modes = append(msg.modes, mode)
}
}
return msg, nil
}
func (m *ModeMessage) Handle(s *Server, c *Client) { func (m *ModeMessage) Handle(s *Server, c *Client) {
if m.nickname != c.nick { if m.nickname != c.nick {
c.send <- ErrUsersDontMatch(s) c.send <- ErrUsersDontMatch(s)
@ -92,6 +182,22 @@ type JoinMessage struct {
zero bool zero bool
} }
func NewJoinMessage(args []string) (Message, error) {
msg := &JoinMessage{}
if len(args) > 0 {
if args[0] == "0" {
msg.zero = true
} else {
msg.channels = strings.Split(args[0], ",")
}
if len(args) > 1 {
msg.keys = strings.Split(args[1], ",")
}
}
return msg, nil
}
func (m *JoinMessage) Handle(s *Server, c *Client) { func (m *JoinMessage) Handle(s *Server, c *Client) {
if m.zero { if m.zero {
for channel := range c.channels { for channel := range c.channels {
@ -116,6 +222,17 @@ type PartMessage struct {
message string message string
} }
func NewPartMessage(args []string) (Message, error) {
if len(args) < 1 {
return nil, ErrNotEnoughArgs
}
msg := &PartMessage{channels: strings.Split(args[0], ",")}
if len(args) > 1 {
msg.message = args[1]
}
return msg, nil
}
func (m *PartMessage) Handle(s *Server, c *Client) { func (m *PartMessage) Handle(s *Server, c *Client) {
for _, chname := range m.channels { for _, chname := range m.channels {
channel := s.channels[chname] channel := s.channels[chname]
@ -136,6 +253,16 @@ type PrivMsgMessage struct {
message string message string
} }
func NewPrivMsgMessage(args []string) (Message, error) {
if len(args) < 2 {
return nil, ErrNotEnoughArgs
}
return &PrivMsgMessage{
target: args[0],
message: args[1],
}, nil
}
func (m *PrivMsgMessage) TargetIsChannel() bool { func (m *PrivMsgMessage) TargetIsChannel() bool {
switch m.target[0] { switch m.target[0] {
case '&', '#', '+', '!': case '&', '#', '+', '!':
@ -169,6 +296,17 @@ type TopicMessage struct {
topic string topic string
} }
func NewTopicMessage(args []string) (Message, error) {
if len(args) < 1 {
return nil, ErrNotEnoughArgs
}
msg := &TopicMessage{channel: args[0]}
if len(args) > 1 {
msg.topic = args[1]
}
return msg, nil
}
func (m *TopicMessage) Handle(s *Server, c *Client) { func (m *TopicMessage) Handle(s *Server, c *Client) {
channel := s.channels[m.channel] channel := s.channels[m.channel]
if channel == nil { if channel == nil {

View File

@ -1,6 +1,3 @@
// channel management: http://tools.ietf.org/html/rfc2811
// client protocol: http://tools.ietf.org/html/rfc2812
// server protocol: http://tools.ietf.org/html/rfc2813
package irc package irc
const ( const (
@ -8,6 +5,8 @@ const (
) )
const ( const (
// # numeric codes
// ## reply codes
RPL_WELCOME = "001" RPL_WELCOME = "001"
RPL_YOURHOST = "002" RPL_YOURHOST = "002"
RPL_CREATED = "003" RPL_CREATED = "003"
@ -18,6 +17,7 @@ const (
RPL_NAMREPLY = "353" RPL_NAMREPLY = "353"
RPL_ENDOFNAMES = "366" RPL_ENDOFNAMES = "366"
RPL_INFO = "371" RPL_INFO = "371"
// ## error codes
ERR_NOSUCHNICK = "401" ERR_NOSUCHNICK = "401"
ERR_NOSUCHSERVER = "402" ERR_NOSUCHSERVER = "402"
ERR_NOSUCHCHANNEL = "403" ERR_NOSUCHCHANNEL = "403"
@ -29,6 +29,7 @@ const (
ERR_INVITEONLYCHANNEL = "473" ERR_INVITEONLYCHANNEL = "473"
ERR_BADCHANNELKEY = "475" ERR_BADCHANNELKEY = "475"
ERR_USERSDONTMATCH = "502" ERR_USERSDONTMATCH = "502"
// # message codes
RPL_JOIN = "JOIN" RPL_JOIN = "JOIN"
RPL_NICK = "NICK" RPL_NICK = "NICK"
RPL_PART = "PART" RPL_PART = "PART"

View File

@ -1,13 +1,11 @@
package irc package irc
import ( import (
"fmt" "errors"
"regexp"
"strconv"
"strings" "strings"
) )
var commands = map[string]func([]string) Message{ var commands = map[string]func([]string) (Message, error){
"JOIN": NewJoinMessage, "JOIN": NewJoinMessage,
"MODE": NewModeMessage, "MODE": NewModeMessage,
"NICK": NewNickMessage, "NICK": NewNickMessage,
@ -20,169 +18,43 @@ var commands = map[string]func([]string) Message{
"USER": NewUserMessage, "USER": NewUserMessage,
} }
func ParseMessage(line string) Message { var (
ErrParseMessage = errors.New("failed to parse message")
)
func ParseMessage(line string) (msg Message, err error) {
command, args := parseLine(line) command, args := parseLine(line)
constructor, ok := commands[command] constructor, ok := commands[command]
var msg Message if !ok {
if ok {
msg = constructor(args)
}
if msg == nil {
msg = &UnknownMessage{command} msg = &UnknownMessage{command}
return
} }
return msg msg, err = constructor(args)
return
} }
func parseArg(line string) (string, string) { func parseArg(line string) (arg string, rest string) {
if line == "" { if line == "" {
return "", "" return
} }
if strings.HasPrefix(line, ":") { if strings.HasPrefix(line, ":") {
return line[1:], "" arg = line[1:]
} } else {
parts := strings.SplitN(line, " ", 2) parts := strings.SplitN(line, " ", 2)
arg := parts[0] arg = parts[0]
rest := ""
if len(parts) > 1 { if len(parts) > 1 {
rest = parts[1] rest = parts[1]
} }
return arg, rest }
return
} }
func parseLine(line string) (string, []string) { func parseLine(line string) (command string, args []string) {
args := make([]string, 0) args = make([]string, 0)
for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) { for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) {
args = append(args, arg) args = append(args, arg)
} }
return args[0], args[1:] command, args = args[0], args[1:]
} return
// []string => Message constructors
func NewNickMessage(args []string) Message {
if len(args) != 1 {
return nil
}
return &NickMessage{args[0]}
}
func NewPingMessage(args []string) Message {
if len(args) < 1 {
return nil
}
message := &PingMessage{server: args[0]}
if len(args) > 1 {
message.server2 = args[1]
}
return message
}
func NewPongMessage(args []string) Message {
if len(args) < 1 {
return nil
}
message := &PongMessage{server1: args[0]}
if len(args) > 1 {
message.server2 = args[1]
}
return message
}
func NewQuitMessage(args []string) Message {
msg := QuitMessage{}
if len(args) > 0 {
msg.message = args[0]
}
return &msg
}
func NewUserMessage(args []string) Message {
if len(args) != 4 {
return nil
}
msg := new(UserMessage)
msg.user = args[0]
mode, err := strconv.ParseUint(args[1], 10, 8)
if err == nil {
msg.mode = uint8(mode)
}
msg.unused = args[2]
msg.realname = args[3]
return msg
}
var MODE_RE = regexp.MustCompile("^[-+][a-zA-Z]+$")
func NewModeMessage(args []string) Message {
if len(args) < 1 {
return nil
}
msg := new(ModeMessage)
msg.nickname = args[0]
for _, arg := range args[1:] {
if !MODE_RE.MatchString(arg) {
// TODO invalid args
return nil
}
prefix := arg[0]
for _, c := range arg[1:] {
mode := fmt.Sprintf("%c%c", prefix, c)
msg.modes = append(msg.modes, mode)
}
}
return msg
}
func NewJoinMessage(args []string) Message {
msg := new(JoinMessage)
if len(args) > 0 {
if args[0] == "0" {
msg.zero = true
} else {
msg.channels = strings.Split(args[0], ",")
}
if len(args) > 1 {
msg.keys = strings.Split(args[1], ",")
}
}
return msg
}
func NewPartMessage(args []string) Message {
if len(args) < 1 {
return nil
}
msg := new(PartMessage)
msg.channels = strings.Split(args[0], ",")
if len(args) > 1 {
msg.message = args[1]
}
return msg
}
func NewPrivMsgMessage(args []string) Message {
if len(args) < 2 {
return nil
}
return &PrivMsgMessage{target: args[0], message: args[1]}
}
func NewTopicMessage(args []string) Message {
if len(args) < 1 {
return nil
}
message := &TopicMessage{channel: args[0]}
if len(args) > 1 {
message.topic = args[1]
}
return message
} }

View File

@ -2,6 +2,7 @@ package irc
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
) )
@ -75,7 +76,7 @@ func RplCreated(server *Server) Reply {
} }
func RplMyInfo(server *Server) Reply { func RplMyInfo(server *Server) Reply {
return NewReply(server, RPL_MYINFO, server.name+" i ik") return NewReply(server, RPL_MYINFO, fmt.Sprintf("%s %s i ik", server.name, VERSION))
} }
func RplUModeIs(server *Server, client *Client) Reply { func RplUModeIs(server *Server, client *Client) Reply {
@ -92,9 +93,9 @@ func RplTopic(channel *Channel) Reply {
return &ChannelReply{NewReply(channel.server, RPL_TOPIC, fmt.Sprintf("%s :%s", channel.name, channel.topic)), channel} return &ChannelReply{NewReply(channel.server, RPL_TOPIC, fmt.Sprintf("%s :%s", channel.name, channel.topic)), channel}
} }
func RplNamReply(channel *Channel, client *Client) Reply { func RplNamReply(channel *Channel) Reply {
// TODO multiple names and splitting based on message size // TODO multiple names and splitting based on message size
return NewReply(channel.server, RPL_NAMREPLY, fmt.Sprintf("=%s :+%s", channel.name, client.Nick())) return NewReply(channel.server, RPL_NAMREPLY, fmt.Sprintf("= %s :%s", channel.name, strings.Join(channel.Nicks(), " ")))
} }
func RplEndOfNames(source Identifier) Reply { func RplEndOfNames(source Identifier) Reply {

View File

@ -22,7 +22,13 @@ type ClientMessage struct {
func NewServer(name string) *Server { func NewServer(name string) *Server {
recv := make(chan *ClientMessage) recv := make(chan *ClientMessage)
server := &Server{ctime: time.Now(), name: name, recv: recv, nicks: make(map[string]*Client), channels: make(map[string]*Channel)} server := &Server{
ctime: time.Now(),
name: name,
recv: recv,
nicks: make(map[string]*Client),
channels: make(map[string]*Channel),
}
go func() { go func() {
for m := range recv { for m := range recv {
m.message.Handle(server, m.client) m.message.Handle(server, m.client)
@ -47,7 +53,7 @@ func (s *Server) Listen(addr string) {
continue continue
} }
log.Print("Server.Listen: accepted ", conn.RemoteAddr()) log.Print("Server.Listen: accepted ", conn.RemoteAddr())
go NewClient(conn).Communicate(s) go NewClient(s, conn).Communicate()
} }
} }
@ -65,6 +71,7 @@ func (s *Server) GetOrMakeChannel(name string) *Channel {
// Send a message to clients of channels fromClient is a member. // Send a message to clients of channels fromClient is a member.
func (s *Server) SendToInterestedClients(fromClient *Client, reply Reply) { func (s *Server) SendToInterestedClients(fromClient *Client, reply Reply) {
clients := make(map[*Client]bool) clients := make(map[*Client]bool)
clients[fromClient] = true
for channel := range fromClient.channels { for channel := range fromClient.channels {
for client := range channel.members { for client := range channel.members {
clients[client] = true clients[client] = true
@ -84,28 +91,29 @@ func (s *Server) ChangeNick(c *Client, newNick string) {
return return
} }
s.SendToInterestedClients(c, RplNick(c, newNick))
if c.nick != "" { if c.nick != "" {
delete(s.nicks, c.nick) delete(s.nicks, c.nick)
} }
c.nick = newNick
s.nicks[c.nick] = c s.nicks[c.nick] = c
s.TryRegister(c) s.SendToInterestedClients(c, RplNick(c, newNick))
c.nick = newNick
s.tryRegister(c)
} }
func (s *Server) Register(c *Client, user string, realName string) { func (s *Server) UserLogin(c *Client, user string, realName string) {
if c.username != "" { if c.username != "" {
c.send <- ErrAlreadyRegistered(s) c.send <- ErrAlreadyRegistered(s)
return return
} }
c.username, c.realname = user, realName c.username, c.realname = user, realName
s.TryRegister(c) s.tryRegister(c)
} }
func (s *Server) TryRegister(c *Client) { func (s *Server) tryRegister(c *Client) {
if !c.registered && c.HasNick() && c.HasUser() { if !c.registered && c.HasNick() && c.HasUser() {
c.registered = true c.registered = true
c.send <- RplWelcome(s, c) c.send <- RplWelcome(s, c)