Browse Source

massive refactor and basic channel support: join, part, message

tags/v0.1.0
Jeremy Latt 11 years ago
parent
commit
24ad2172a8
10 changed files with 652 additions and 186 deletions
  1. 94
    0
      src/irc/channel.go
  2. 25
    18
      src/irc/client.go
  3. 155
    43
      src/irc/commands.go
  4. 29
    19
      src/irc/constants.go
  5. 0
    27
      src/irc/message.go
  6. 15
    2
      src/irc/net.go
  7. 82
    8
      src/irc/parse.go
  8. 148
    0
      src/irc/reply.go
  9. 0
    54
      src/irc/responses.go
  10. 104
    15
      src/irc/server.go

+ 94
- 0
src/irc/channel.go View File

@@ -0,0 +1,94 @@
1
+package irc
2
+
3
+type Channel struct {
4
+	name       string
5
+	key        string
6
+	topic      string
7
+	members    ClientSet
8
+	inviteOnly bool
9
+	invites    map[string]bool
10
+	server     *Server
11
+}
12
+
13
+type ChannelSet map[*Channel]bool
14
+
15
+func NewChannel(s *Server, name string) *Channel {
16
+	return &Channel{name: name, members: make(ClientSet), invites: make(map[string]bool), server: s}
17
+}
18
+
19
+func (ch *Channel) Send(reply Reply, fromClient *Client) {
20
+	for client := range ch.members {
21
+		if client != fromClient {
22
+			client.send <- reply
23
+		}
24
+	}
25
+}
26
+
27
+// channel functionality
28
+
29
+func (ch *Channel) Join(cl *Client, key string) {
30
+	if ch.key != key {
31
+		cl.send <- ErrInviteOnlyChannel(ch)
32
+		return
33
+	}
34
+
35
+	if ch.inviteOnly && !ch.invites[cl.nick] {
36
+		cl.send <- ErrBadChannelKey(ch)
37
+		return
38
+	}
39
+
40
+	ch.members[cl] = true
41
+	cl.channels[ch] = true
42
+
43
+	ch.Send(RplJoin(ch, cl), nil)
44
+	ch.GetTopic(cl)
45
+
46
+	for member := range ch.members {
47
+		cl.send <- RplNamReply(ch, member)
48
+	}
49
+	cl.send <- RplEndOfNames(ch.server)
50
+}
51
+
52
+func (ch *Channel) Part(cl *Client, message string) {
53
+	if !ch.members[cl] {
54
+		cl.send <- ErrNotOnChannel(ch)
55
+		return
56
+	}
57
+
58
+	delete(ch.members, cl)
59
+	delete(cl.channels, ch)
60
+
61
+	ch.Send(RplPart(ch, cl, message), nil)
62
+}
63
+
64
+func (ch *Channel) PrivMsg(cl *Client, message string) {
65
+	ch.Send(RplPrivMsgChannel(ch, cl, message), cl)
66
+}
67
+
68
+func (ch *Channel) GetTopic(cl *Client) {
69
+	if !ch.members[cl] {
70
+		cl.send <- ErrNotOnChannel(ch)
71
+		return
72
+	}
73
+
74
+	if ch.topic != "" {
75
+		cl.send <- RplTopic(ch)
76
+	} else {
77
+		cl.send <- RplNoTopic(ch)
78
+	}
79
+}
80
+
81
+func (ch *Channel) ChangeTopic(cl *Client, newTopic string) {
82
+	if !ch.members[cl] {
83
+		cl.send <- ErrNotOnChannel(ch)
84
+		return
85
+	}
86
+
87
+	ch.topic = newTopic
88
+
89
+	if ch.topic != "" {
90
+		ch.Send(RplTopic(ch), nil)
91
+	} else {
92
+		ch.Send(RplNoTopic(ch), nil)
93
+	}
94
+}

+ 25
- 18
src/irc/client.go View File

@@ -3,34 +3,46 @@ package irc
3 3
 import (
4 4
 	"fmt"
5 5
 	"net"
6
-	"strings"
7 6
 )
8 7
 
9 8
 type Client struct {
10 9
 	conn       net.Conn
11
-	send       chan<- string
10
+	hostname   string
11
+	send       chan<- Reply
12 12
 	recv       <-chan string
13 13
 	username   string
14 14
 	realname   string
15 15
 	nick       string
16 16
 	registered bool
17 17
 	invisible  bool
18
+	channels   ChannelSet
18 19
 }
19 20
 
21
+type ClientSet map[*Client]bool
22
+
20 23
 func NewClient(conn net.Conn) *Client {
21
-	client := new(Client)
22
-	client.conn = conn
23
-	client.send = StringWriteChan(conn)
24
-	client.recv = StringReadChan(conn)
24
+	client := &Client{conn: conn, recv: StringReadChan(conn), channels: make(ChannelSet), hostname: LookupHostname(conn.RemoteAddr())}
25
+	client.SetReplyToStringChan()
25 26
 	return client
26 27
 }
27 28
 
29
+func (c *Client) SetReplyToStringChan() {
30
+	send := make(chan Reply)
31
+	write := StringWriteChan(c.conn)
32
+	go func() {
33
+		for reply := range send {
34
+			write <- reply.String(c)
35
+		}
36
+	}()
37
+	c.send = send
38
+}
39
+
28 40
 // Adapt `chan string` to a `chan Message`.
29
-func (c *Client) Communicate(server chan<- *ClientMessage) {
41
+func (c *Client) Communicate(server *Server) {
30 42
 	for str := range c.recv {
31 43
 		m := ParseMessage(str)
32 44
 		if m != nil {
33
-			server <- &ClientMessage{c, m}
45
+			server.recv <- &ClientMessage{c, m}
34 46
 		}
35 47
 	}
36 48
 }
@@ -39,7 +51,7 @@ func (c *Client) Nick() string {
39 51
 	if c.nick != "" {
40 52
 		return c.nick
41 53
 	}
42
-	return "<guest>"
54
+	return "*"
43 55
 }
44 56
 
45 57
 func (c *Client) UModeString() string {
@@ -57,15 +69,10 @@ func (c *Client) HasUser() bool {
57 69
 	return c.username != ""
58 70
 }
59 71
 
60
-func (c *Client) Hostname() string {
61
-	addr := c.conn.RemoteAddr().String()
62
-	index := strings.LastIndex(addr, ":")
63
-	if index != -1 {
64
-		return addr[0:index]
65
-	}
66
-	return addr
72
+func (c *Client) UserHost() string {
73
+	return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.hostname)
67 74
 }
68 75
 
69
-func (c *Client) UserHost() string {
70
-	return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.Hostname())
76
+func (c *Client) Id() string {
77
+	return c.UserHost()
71 78
 }

+ 155
- 43
src/irc/commands.go View File

@@ -4,68 +4,180 @@ type Message interface {
4 4
 	Handle(s *Server, c *Client)
5 5
 }
6 6
 
7
+// unknown
8
+
9
+type UnknownMessage struct {
10
+	command string
11
+}
12
+
13
+func (m *UnknownMessage) Handle(s *Server, c *Client) {
14
+	c.send <- ErrUnknownCommand(s, m.command)
15
+}
16
+
17
+// PING
18
+
19
+type PingMessage struct {
20
+	server  string
21
+	server2 string
22
+}
23
+
24
+func (m *PingMessage) Handle(s *Server, c *Client) {
25
+	c.send <- RplPong(s)
26
+}
27
+
28
+// PONG
29
+
30
+type PongMessage struct {
31
+	server1 string
32
+	server2 string
33
+}
34
+
35
+func (m *PongMessage) Handle(s *Server, c *Client) {
36
+	// TODO update client atime
37
+}
38
+
39
+// NICK
40
+
41
+type NickMessage struct {
42
+	nickname string
43
+}
44
+
7 45
 func (m *NickMessage) Handle(s *Server, c *Client) {
8
-	if s.nicks[m.nickname] != nil {
9
-		c.send <- ErrNickNameInUse(m.nickname)
10
-		return
11
-	}
12
-	oldNick := c.nick
13
-	if c.nick != "" {
14
-		delete(s.nicks, c.nick)
15
-	}
16
-	c.nick = m.nickname
17
-	s.nicks[c.nick] = c
18
-	if c.registered {
19
-		c.send <- ReplyNick(oldNick, c)
20
-	} else {
21
-		tryRegister(s, c)
22
-	}
46
+	s.ChangeNick(c, m.nickname)
47
+}
48
+
49
+// USER
50
+
51
+type UserMessage struct {
52
+	user     string
53
+	mode     uint8
54
+	unused   string
55
+	realname string
23 56
 }
24 57
 
25 58
 func (m *UserMessage) Handle(s *Server, c *Client) {
26
-	if c.username != "" {
27
-		c.send <- ErrAlreadyRegistered(c.Nick())
28
-		return
29
-	}
30
-	c.username, c.realname = m.user, m.realname
31
-	tryRegister(s, c)
59
+	s.Register(c, m.user, m.realname)
32 60
 }
33 61
 
34
-func (m *QuitMessage) Handle(s *Server, c *Client) {
35
-	c.send <- MessageError()
36
-	c.conn.Close()
37
-	delete(s.nicks, c.nick)
62
+// QUIT
63
+
64
+type QuitMessage struct {
65
+	message string
38 66
 }
39 67
 
40
-func (m *UnknownMessage) Handle(s *Server, c *Client) {
41
-	c.send <- ErrUnknownCommand(c.Nick(), m.command)
68
+func (m *QuitMessage) Handle(s *Server, c *Client) {
69
+	s.Quit(c, m.message)
42 70
 }
43 71
 
44
-func (m *PingMessage) Handle(s *Server, c *Client) {
45
-	c.send <- MessagePong()
72
+// MODE
73
+
74
+type ModeMessage struct {
75
+	nickname string
76
+	modes    []string
46 77
 }
47 78
 
48 79
 func (m *ModeMessage) Handle(s *Server, c *Client) {
49 80
 	if m.nickname != c.nick {
50
-		c.send <- ErrUsersDontMatch(c.Nick())
81
+		c.send <- ErrUsersDontMatch(s)
51 82
 		return
52 83
 	}
53
-	for _, mode := range m.modes {
54
-		if mode == "+i" {
55
-			c.invisible = true
56
-		} else if mode == "-i" {
57
-			c.invisible = false
84
+	s.ChangeUserMode(c, m.modes)
85
+}
86
+
87
+// JOIN
88
+
89
+type JoinMessage struct {
90
+	channels []string
91
+	keys     []string
92
+	zero     bool
93
+}
94
+
95
+func (m *JoinMessage) Handle(s *Server, c *Client) {
96
+	if m.zero {
97
+		for channel := range c.channels {
98
+			channel.Part(c, "")
99
+		}
100
+	} else {
101
+		for i, name := range m.channels {
102
+			key := ""
103
+			if len(m.keys) > i {
104
+				key = m.keys[i]
105
+			}
106
+
107
+			s.GetOrMakeChannel(name).Join(c, key)
108
+		}
109
+	}
110
+}
111
+
112
+// PART
113
+
114
+type PartMessage struct {
115
+	channels []string
116
+	message  string
117
+}
118
+
119
+func (m *PartMessage) Handle(s *Server, c *Client) {
120
+	for _, chname := range m.channels {
121
+		channel := s.channels[chname]
122
+
123
+		if channel == nil {
124
+			c.send <- ErrNoSuchChannel(s, chname)
125
+			continue
58 126
 		}
127
+
128
+		channel.Part(c, m.message)
59 129
 	}
60
-	c.send <- ReplyUModeIs(c)
61 130
 }
62 131
 
63
-func tryRegister(s *Server, c *Client) {
64
-	if (!c.registered && c.HasNick() && c.HasUser()) {
65
-		c.registered = true
66
-		c.send <- ReplyWelcome(c)
67
-		c.send <- ReplyYourHost(c.Nick(), s.name)
68
-		c.send <- ReplyCreated(c.Nick(), s.ctime)
69
-		c.send <- ReplyMyInfo(c.Nick(), s.name)
132
+// PRIVMSG
133
+
134
+type PrivMsgMessage struct {
135
+	target  string
136
+	message string
137
+}
138
+
139
+func (m *PrivMsgMessage) TargetIsChannel() bool {
140
+	switch m.target[0] {
141
+	case '&', '#', '+', '!':
142
+		return true
143
+	}
144
+	return false
145
+}
146
+
147
+func (m *PrivMsgMessage) Handle(s *Server, c *Client) {
148
+	if m.TargetIsChannel() {
149
+		channel := s.channels[m.target]
150
+		if channel != nil {
151
+			channel.PrivMsg(c, m.message)
152
+		} else {
153
+			c.send <- ErrNoSuchNick(s, m.target)
154
+		}
155
+	} else {
156
+		client := s.nicks[m.target]
157
+		if client != nil {
158
+			client.send <- RplPrivMsg(client, m.message)
159
+		} else {
160
+			c.send <- ErrNoSuchNick(s, m.target)
161
+		}
162
+	}
163
+}
164
+
165
+// TOPIC
166
+
167
+type TopicMessage struct {
168
+	channel string
169
+	topic   string
170
+}
171
+
172
+func (m *TopicMessage) Handle(s *Server, c *Client) {
173
+	channel := s.channels[m.channel]
174
+	if channel == nil {
175
+		c.send <- ErrNoSuchChannel(s, m.channel)
176
+		return
177
+	}
178
+	if m.topic == "" {
179
+		channel.GetTopic(c)
180
+	} else {
181
+		channel.ChangeTopic(c, m.topic)
70 182
 	}
71 183
 }

+ 29
- 19
src/irc/constants.go View File

@@ -1,4 +1,6 @@
1
-// http://tools.ietf.org/html/rfc2812
1
+// channel management: http://tools.ietf.org/html/rfc2811
2
+// client protocol:    http://tools.ietf.org/html/rfc2812
3
+// server protocol:    http://tools.ietf.org/html/rfc2813
2 4
 package irc
3 5
 
4 6
 const (
@@ -6,22 +8,30 @@ const (
6 8
 )
7 9
 
8 10
 const (
9
-	RPL_WELCOME  = "001"
10
-	RPL_YOURHOST = "002"
11
-	RPL_CREATED  = "003"
12
-	RPL_MYINFO   = "004"
13
-	RPL_UMODEIS  = "221"
14
-	RPL_INFO     = "371"
15
-	RPL_NICK     = "NICK"
16
-)
17
-
18
-const (
19
-	ERR_NOSUCHNICK       = "401"
20
-	ERR_NOSUCHSERVER     = "402"
21
-	ERR_NOSUCHCHANNEL    = "403"
22
-	ERR_UNKNOWNCOMMAND   = "421"
23
-	ERR_NICKNAMEINUSE    = "433"
24
-	ERR_NEEDMOREPARAMS   = "461"
25
-	ERR_ALREADYREGISTRED = "462"
26
-	ERR_USERSDONTMATCH   = "502"
11
+	RPL_WELCOME           = "001"
12
+	RPL_YOURHOST          = "002"
13
+	RPL_CREATED           = "003"
14
+	RPL_MYINFO            = "004"
15
+	RPL_UMODEIS           = "221"
16
+	RPL_NOTOPIC           = "331"
17
+	RPL_TOPIC             = "332"
18
+	RPL_NAMREPLY          = "353"
19
+	RPL_ENDOFNAMES        = "366"
20
+	RPL_INFO              = "371"
21
+	ERR_NOSUCHNICK        = "401"
22
+	ERR_NOSUCHSERVER      = "402"
23
+	ERR_NOSUCHCHANNEL     = "403"
24
+	ERR_UNKNOWNCOMMAND    = "421"
25
+	ERR_NICKNAMEINUSE     = "433"
26
+	ERR_NOTONCHANNEL      = "442"
27
+	ERR_NEEDMOREPARAMS    = "461"
28
+	ERR_ALREADYREGISTRED  = "462"
29
+	ERR_INVITEONLYCHANNEL = "473"
30
+	ERR_BADCHANNELKEY     = "475"
31
+	ERR_USERSDONTMATCH    = "502"
32
+	RPL_JOIN              = "JOIN"
33
+	RPL_NICK              = "NICK"
34
+	RPL_PART              = "PART"
35
+	RPL_PONG              = "PONG"
36
+	RPL_PRIVMSG           = "PRIVMSG"
27 37
 )

+ 0
- 27
src/irc/message.go View File

@@ -1,27 +0,0 @@
1
-package irc
2
-
3
-type NickMessage struct {
4
-	nickname string
5
-}
6
-
7
-type UserMessage struct {
8
-	user string
9
-	mode uint8
10
-	unused string
11
-	realname string
12
-}
13
-
14
-type QuitMessage struct {
15
-	message string
16
-}
17
-
18
-type UnknownMessage struct {
19
-	command string
20
-}
21
-
22
-type PingMessage struct {}
23
-
24
-type ModeMessage struct {
25
-	nickname string
26
-	modes []string
27
-}

+ 15
- 2
src/irc/net.go View File

@@ -3,8 +3,8 @@ package irc
3 3
 import (
4 4
 	"bufio"
5 5
 	"log"
6
-	"strings"
7 6
 	"net"
7
+	"strings"
8 8
 )
9 9
 
10 10
 func readTrimmedLine(reader *bufio.Reader) (string, error) {
@@ -20,7 +20,7 @@ func StringReadChan(conn net.Conn) <-chan string {
20 20
 	go func() {
21 21
 		for {
22 22
 			line, err := readTrimmedLine(reader)
23
-			if (line != "") {
23
+			if line != "" {
24 24
 				ch <- line
25 25
 				log.Printf("%s -> %s", addr, line)
26 26
 			}
@@ -50,3 +50,16 @@ func StringWriteChan(conn net.Conn) chan<- string {
50 50
 
51 51
 	return ch
52 52
 }
53
+
54
+func LookupHostname(addr net.Addr) string {
55
+	addrStr := addr.String()
56
+	ipaddr, _, err := net.SplitHostPort(addrStr)
57
+	if err != nil {
58
+		return addrStr
59
+	}
60
+	names, err := net.LookupAddr(ipaddr)
61
+	if err != nil {
62
+		return ipaddr
63
+	}
64
+	return names[0]
65
+}

+ 82
- 8
src/irc/parse.go View File

@@ -7,12 +7,17 @@ import (
7 7
 	"strings"
8 8
 )
9 9
 
10
-var commands = map[string]func([]string) Message {
11
-	"MODE": NewModeMessage,
12
-	"NICK": NewNickMessage,
13
-	"PING": NewPingMessage,
14
-	"QUIT": NewQuitMessage,
15
-	"USER": NewUserMessage,
10
+var commands = map[string]func([]string) Message{
11
+	"JOIN":    NewJoinMessage,
12
+	"MODE":    NewModeMessage,
13
+	"NICK":    NewNickMessage,
14
+	"PART":    NewPartMessage,
15
+	"PING":    NewPingMessage,
16
+	"PONG":    NewPongMessage,
17
+	"PRIVMSG": NewPrivMsgMessage,
18
+	"QUIT":    NewQuitMessage,
19
+	"TOPIC":   NewTopicMessage,
20
+	"USER":    NewUserMessage,
16 21
 }
17 22
 
18 23
 func ParseMessage(line string) Message {
@@ -54,7 +59,6 @@ func parseLine(line string) (string, []string) {
54 59
 	return args[0], args[1:]
55 60
 }
56 61
 
57
-
58 62
 // []string => Message constructors
59 63
 
60 64
 func NewNickMessage(args []string) Message {
@@ -65,7 +69,25 @@ func NewNickMessage(args []string) Message {
65 69
 }
66 70
 
67 71
 func NewPingMessage(args []string) Message {
68
-	return &PingMessage{}
72
+	if len(args) < 1 {
73
+		return nil
74
+	}
75
+	message := &PingMessage{server: args[0]}
76
+	if len(args) > 1 {
77
+		message.server2 = args[1]
78
+	}
79
+	return message
80
+}
81
+
82
+func NewPongMessage(args []string) Message {
83
+	if len(args) < 1 {
84
+		return nil
85
+	}
86
+	message := &PongMessage{server1: args[0]}
87
+	if len(args) > 1 {
88
+		message.server2 = args[1]
89
+	}
90
+	return message
69 91
 }
70 92
 
71 93
 func NewQuitMessage(args []string) Message {
@@ -112,3 +134,55 @@ func NewModeMessage(args []string) Message {
112 134
 	}
113 135
 	return msg
114 136
 }
137
+
138
+func NewJoinMessage(args []string) Message {
139
+	msg := new(JoinMessage)
140
+
141
+	if len(args) > 0 {
142
+		if args[0] == "0" {
143
+			msg.zero = true
144
+		} else {
145
+			msg.channels = strings.Split(args[0], ",")
146
+		}
147
+
148
+		if len(args) > 1 {
149
+			msg.keys = strings.Split(args[1], ",")
150
+		}
151
+	}
152
+
153
+	return msg
154
+}
155
+
156
+func NewPartMessage(args []string) Message {
157
+	if len(args) < 1 {
158
+		return nil
159
+	}
160
+	msg := new(PartMessage)
161
+	msg.channels = strings.Split(args[0], ",")
162
+
163
+	if len(args) > 1 {
164
+		msg.message = args[1]
165
+	}
166
+
167
+	return msg
168
+}
169
+
170
+func NewPrivMsgMessage(args []string) Message {
171
+	if len(args) < 2 {
172
+		return nil
173
+	}
174
+
175
+	return &PrivMsgMessage{target: args[0], message: args[1]}
176
+}
177
+
178
+func NewTopicMessage(args []string) Message {
179
+	if len(args) < 1 {
180
+		return nil
181
+	}
182
+
183
+	message := &TopicMessage{channel: args[0]}
184
+	if len(args) > 1 {
185
+		message.topic = args[1]
186
+	}
187
+	return message
188
+}

+ 148
- 0
src/irc/reply.go View File

@@ -0,0 +1,148 @@
1
+package irc
2
+
3
+import (
4
+	"fmt"
5
+	"time"
6
+)
7
+
8
+type Identifier interface {
9
+	Id() string
10
+}
11
+
12
+type Reply interface {
13
+	String(client *Client) string
14
+}
15
+
16
+type BasicReply struct {
17
+	source  Identifier
18
+	code    string
19
+	message string
20
+}
21
+
22
+func (reply *BasicReply) String(client *Client) string {
23
+	prefix := fmt.Sprintf(":%s %s %s ", reply.source.Id(), reply.code, client.Nick())
24
+	return prefix + reply.message
25
+}
26
+
27
+type ChannelReply struct {
28
+	*BasicReply
29
+	channel *Channel
30
+}
31
+
32
+func (reply *ChannelReply) String(client *Client) string {
33
+	prefix := fmt.Sprintf(":%s %s %s ", reply.source.Id(), reply.code, reply.channel.name)
34
+	return prefix + reply.message
35
+}
36
+
37
+func NewReply(source Identifier, code string, message string) *BasicReply {
38
+	return &BasicReply{source, code, message}
39
+}
40
+
41
+// messaging
42
+
43
+func RplPrivMsg(source *Client, message string) Reply {
44
+	return NewReply(source, RPL_PRIVMSG, ":"+message)
45
+}
46
+
47
+func RplNick(client *Client, newNick string) Reply {
48
+	return NewReply(client, RPL_NICK, ":"+newNick)
49
+}
50
+
51
+func RplPrivMsgChannel(channel *Channel, source *Client, message string) Reply {
52
+	return &ChannelReply{NewReply(source, RPL_PRIVMSG, ":"+message), channel}
53
+}
54
+
55
+func RplJoin(channel *Channel, client *Client) Reply {
56
+	return &ChannelReply{NewReply(client, RPL_JOIN, channel.name), channel}
57
+}
58
+
59
+func RplPart(channel *Channel, client *Client, message string) Reply {
60
+	return &ChannelReply{NewReply(client, RPL_PART, ":"+message), channel}
61
+}
62
+
63
+// Server Info
64
+
65
+func RplWelcome(source Identifier, client *Client) Reply {
66
+	return NewReply(source, RPL_WELCOME, "Welcome to the Internet Relay Network "+client.Id())
67
+}
68
+
69
+func RplYourHost(server *Server, target *Client) Reply {
70
+	return NewReply(server, RPL_YOURHOST, fmt.Sprintf("Your host is %s, running version %s", server.hostname, VERSION))
71
+}
72
+
73
+func RplCreated(server *Server) Reply {
74
+	return NewReply(server, RPL_CREATED, "This server was created "+server.ctime.Format(time.RFC1123))
75
+}
76
+
77
+func RplMyInfo(server *Server) Reply {
78
+	return NewReply(server, RPL_MYINFO, server.name+" i ik")
79
+}
80
+
81
+func RplUModeIs(server *Server, client *Client) Reply {
82
+	return NewReply(server, RPL_UMODEIS, client.UModeString())
83
+}
84
+
85
+// channel operations
86
+
87
+func RplNoTopic(channel *Channel) Reply {
88
+	return &ChannelReply{NewReply(channel.server, RPL_NOTOPIC, channel.name+" :No topic is set"), channel}
89
+}
90
+
91
+func RplTopic(channel *Channel) Reply {
92
+	return &ChannelReply{NewReply(channel.server, RPL_TOPIC, fmt.Sprintf("%s :%s", channel.name, channel.topic)), channel}
93
+}
94
+
95
+func RplNamReply(channel *Channel, client *Client) Reply {
96
+	// TODO multiple names and splitting based on message size
97
+	return NewReply(channel.server, RPL_NAMREPLY, fmt.Sprintf("=%s :+%s", channel.name, client.Nick()))
98
+}
99
+
100
+func RplEndOfNames(source Identifier) Reply {
101
+	return NewReply(source, RPL_ENDOFNAMES, ":End of NAMES list")
102
+}
103
+
104
+func RplPong(server *Server) Reply {
105
+	return NewReply(server, RPL_PONG, "")
106
+}
107
+
108
+// errors
109
+
110
+func ErrAlreadyRegistered(source Identifier) Reply {
111
+	return NewReply(source, ERR_ALREADYREGISTRED, ":You may not reregister")
112
+}
113
+
114
+func ErrNickNameInUse(source Identifier, nick string) Reply {
115
+	return NewReply(source, ERR_NICKNAMEINUSE, nick+" :Nickname is already in use")
116
+}
117
+
118
+func ErrUnknownCommand(source Identifier, command string) Reply {
119
+	return NewReply(source, ERR_UNKNOWNCOMMAND, command+" :Unknown command")
120
+}
121
+
122
+func ErrUsersDontMatch(source Identifier) Reply {
123
+	return NewReply(source, ERR_USERSDONTMATCH, ":Cannot change mode for other users")
124
+}
125
+
126
+func ErrNeedMoreParams(source Identifier, command string) Reply {
127
+	return NewReply(source, ERR_NEEDMOREPARAMS, command+"%s :Not enough parameters")
128
+}
129
+
130
+func ErrNoSuchChannel(source Identifier, channel string) Reply {
131
+	return NewReply(source, ERR_NOSUCHCHANNEL, channel+" :No such channel")
132
+}
133
+
134
+func ErrNotOnChannel(channel *Channel) Reply {
135
+	return NewReply(channel.server, ERR_NOTONCHANNEL, channel.name+" :You're not on that channel")
136
+}
137
+
138
+func ErrInviteOnlyChannel(channel *Channel) Reply {
139
+	return NewReply(channel.server, ERR_INVITEONLYCHANNEL, channel.name+" :Cannot join channel (+i)")
140
+}
141
+
142
+func ErrBadChannelKey(channel *Channel) Reply {
143
+	return NewReply(channel.server, ERR_BADCHANNELKEY, channel.name+" :Cannot join channel (+k)")
144
+}
145
+
146
+func ErrNoSuchNick(source Identifier, nick string) Reply {
147
+	return NewReply(source, ERR_NOSUCHNICK, nick+" :No such nick/channel")
148
+}

+ 0
- 54
src/irc/responses.go View File

@@ -1,54 +0,0 @@
1
-package irc
2
-
3
-import (
4
-	"fmt"
5
-	"time"
6
-)
7
-
8
-func ReplyNick(oldNick string, c *Client) string {
9
-	return fmt.Sprintf(":%s!%s@%s %s :%s", oldNick, c.username, c.Hostname(), RPL_NICK, c.Nick())
10
-}
11
-
12
-func ReplyWelcome(c *Client) string {
13
-	return fmt.Sprintf("%s %s Welcome to the Internet Relay Network %s!%s@%s", RPL_WELCOME, c.Nick(), c.Nick(), c.username, c.Hostname())
14
-}
15
-
16
-func ReplyYourHost(nick string, server string) string {
17
-	return fmt.Sprintf("%s %s Your host is %s, running version %s", RPL_YOURHOST, nick, server, VERSION)
18
-}
19
-
20
-func ReplyCreated(nick string, ctime time.Time) string {
21
-	return fmt.Sprintf("%s %s This server was created %s", RPL_CREATED, nick, ctime.Format(time.RFC1123))
22
-}
23
-
24
-func ReplyMyInfo(nick string, servername string) string {
25
-	return fmt.Sprintf("%s %s %s %s i <channel modes>", RPL_MYINFO, nick, servername, VERSION)
26
-}
27
-
28
-func ReplyUModeIs(c *Client) string {
29
-	return fmt.Sprintf("%s %s %s", RPL_UMODEIS, c.Nick(), c.UModeString())
30
-}
31
-
32
-func ErrAlreadyRegistered(nick string) string {
33
-	return fmt.Sprintf("%s %s :You may not reregister", ERR_ALREADYREGISTRED, nick)
34
-}
35
-
36
-func ErrNickNameInUse(nick string) string {
37
-	return fmt.Sprintf("%s %s :Nickname is already in use", ERR_NICKNAMEINUSE, nick)
38
-}
39
-
40
-func ErrUnknownCommand(nick string, command string) string {
41
-	return fmt.Sprintf("%s %s %s :Unknown command", ERR_UNKNOWNCOMMAND, nick, command)
42
-}
43
-
44
-func ErrUsersDontMatch(nick string) string {
45
-	return fmt.Sprintf("%s %s :Cannot change mode for other users", ERR_USERSDONTMATCH, nick)
46
-}
47
-
48
-func MessagePong() string {
49
-	return "PONG"
50
-}
51
-
52
-func MessageError() string {
53
-	return "ERROR :Bye"
54
-}

+ 104
- 15
src/irc/server.go View File

@@ -7,24 +7,27 @@ import (
7 7
 )
8 8
 
9 9
 type Server struct {
10
-	ctime time.Time
11
-	name string
12
-	ch chan *ClientMessage
13
-	nicks map[string]*Client
10
+	hostname string
11
+	ctime    time.Time
12
+	name     string
13
+	recv     chan<- *ClientMessage
14
+	nicks    map[string]*Client
15
+	channels map[string]*Channel
14 16
 }
15 17
 
16 18
 type ClientMessage struct {
17
-	client *Client
19
+	client  *Client
18 20
 	message Message
19 21
 }
20 22
 
21 23
 func NewServer(name string) *Server {
22
-	server := new(Server)
23
-	server.ctime = time.Now()
24
-	server.name = name
25
-	server.ch = make(chan *ClientMessage)
26
-	server.nicks = make(map[string]*Client)
27
-	go server.Receive()
24
+	recv := make(chan *ClientMessage)
25
+	server := &Server{ctime: time.Now(), name: name, recv: recv, nicks: make(map[string]*Client), channels: make(map[string]*Channel)}
26
+	go func() {
27
+		for m := range recv {
28
+			m.message.Handle(server, m.client)
29
+		}
30
+	}()
28 31
 	return server
29 32
 }
30 33
 
@@ -34,18 +37,104 @@ func (s *Server) Listen(addr string) {
34 37
 		log.Fatal("Server.Listen: ", err)
35 38
 	}
36 39
 
40
+	s.hostname = LookupHostname(listener.Addr())
41
+	log.Print("Server.Listen: listening on ", addr)
42
+
37 43
 	for {
38 44
 		conn, err := listener.Accept()
39 45
 		if err != nil {
40 46
 			log.Print("Server.Listen: ", err)
41 47
 			continue
42 48
 		}
43
-		go NewClient(conn).Communicate(s.ch)
49
+		log.Print("Server.Listen: accepted ", conn.RemoteAddr())
50
+		go NewClient(conn).Communicate(s)
51
+	}
52
+}
53
+
54
+func (s *Server) GetOrMakeChannel(name string) *Channel {
55
+	channel := s.channels[name]
56
+
57
+	if channel == nil {
58
+		channel = NewChannel(s, name)
59
+		s.channels[name] = channel
60
+	}
61
+
62
+	return channel
63
+}
64
+
65
+// Send a message to clients of channels fromClient is a member.
66
+func (s *Server) SendToInterestedClients(fromClient *Client, reply Reply) {
67
+	clients := make(map[*Client]bool)
68
+	for channel := range fromClient.channels {
69
+		for client := range channel.members {
70
+			clients[client] = true
71
+		}
72
+	}
73
+
74
+	for client := range clients {
75
+		client.send <- reply
44 76
 	}
45 77
 }
46 78
 
47
-func (s *Server) Receive() {
48
-	for m := range s.ch {
49
-		m.message.Handle(s, m.client)
79
+// server functionality
80
+
81
+func (s *Server) ChangeNick(c *Client, newNick string) {
82
+	if s.nicks[newNick] != nil {
83
+		c.send <- ErrNickNameInUse(s, newNick)
84
+		return
50 85
 	}
86
+
87
+	s.SendToInterestedClients(c, RplNick(c, newNick))
88
+
89
+	if c.nick != "" {
90
+		delete(s.nicks, c.nick)
91
+	}
92
+	c.nick = newNick
93
+	s.nicks[c.nick] = c
94
+
95
+	s.TryRegister(c)
96
+}
97
+
98
+func (s *Server) Register(c *Client, user string, realName string) {
99
+	if c.username != "" {
100
+		c.send <- ErrAlreadyRegistered(s)
101
+		return
102
+	}
103
+
104
+	c.username, c.realname = user, realName
105
+	s.TryRegister(c)
106
+}
107
+
108
+func (s *Server) TryRegister(c *Client) {
109
+	if !c.registered && c.HasNick() && c.HasUser() {
110
+		c.registered = true
111
+		c.send <- RplWelcome(s, c)
112
+		c.send <- RplYourHost(s, c)
113
+		c.send <- RplCreated(s)
114
+		c.send <- RplMyInfo(s)
115
+	}
116
+}
117
+
118
+func (s *Server) Quit(c *Client, message string) {
119
+	for channel := range c.channels {
120
+		channel.Part(c, message)
121
+	}
122
+	delete(s.nicks, c.nick)
123
+
124
+	c.conn.Close()
125
+}
126
+
127
+func (s *Server) ChangeUserMode(c *Client, modes []string) {
128
+	for _, mode := range modes {
129
+		if mode == "+i" {
130
+			c.invisible = true
131
+		} else if mode == "-i" {
132
+			c.invisible = false
133
+		}
134
+	}
135
+	c.send <- RplUModeIs(s, c)
136
+}
137
+
138
+func (s *Server) Id() string {
139
+	return s.hostname
51 140
 }

Loading…
Cancel
Save