source: code/trunk/upstream.go@ 20

Last change on this file since 20 was 20, checked in by contact, 5 years ago

Split IRC helpers to separate file

File size: 5.0 KB
Line 
1package jounce
2
3import (
4 "crypto/tls"
5 "fmt"
6 "io"
7 "net"
8 "strconv"
9 "strings"
10 "time"
11
12 "gopkg.in/irc.v3"
13)
14
15type upstreamChannel struct {
16 Name string
17 Topic string
18 TopicWho string
19 TopicTime time.Time
20 Status channelStatus
21 Members map[string]membership
22}
23
24type upstreamConn struct {
25 upstream *Upstream
26 net net.Conn
27 irc *irc.Conn
28 srv *Server
29
30 serverName string
31 availableUserModes string
32 availableChannelModes string
33 channelModesWithParam string
34
35 registered bool
36 modes modeSet
37 channels map[string]*upstreamChannel
38}
39
40func (c *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
41 ch, ok := c.channels[name]
42 if !ok {
43 return nil, fmt.Errorf("unknown channel %q", name)
44 }
45 return ch, nil
46}
47
48func (c *upstreamConn) handleMessage(msg *irc.Message) error {
49 switch msg.Command {
50 case "PING":
51 // TODO: handle params
52 return c.irc.WriteMessage(&irc.Message{
53 Command: "PONG",
54 Params: []string{c.srv.Hostname},
55 })
56 case "MODE":
57 if len(msg.Params) < 2 {
58 return newNeedMoreParamsError(msg.Command)
59 }
60 if nick := msg.Params[0]; nick != c.upstream.Nick {
61 return fmt.Errorf("received MODE message for unknow nick %q", nick)
62 }
63 return c.modes.Apply(msg.Params[1])
64 case "NOTICE":
65 c.srv.Logger.Printf("%q: %v", c.upstream.Addr, msg)
66 case irc.RPL_WELCOME:
67 c.registered = true
68 c.srv.Logger.Printf("Connection to %q registered", c.upstream.Addr)
69
70 for _, ch := range c.upstream.Channels {
71 err := c.irc.WriteMessage(&irc.Message{
72 Command: "JOIN",
73 Params: []string{ch},
74 })
75 if err != nil {
76 return err
77 }
78 }
79 case irc.RPL_MYINFO:
80 if len(msg.Params) < 5 {
81 return newNeedMoreParamsError(msg.Command)
82 }
83 c.serverName = msg.Params[1]
84 c.availableUserModes = msg.Params[3]
85 c.availableChannelModes = msg.Params[4]
86 if len(msg.Params) > 5 {
87 c.channelModesWithParam = msg.Params[5]
88 }
89 case "JOIN":
90 if len(msg.Params) < 1 {
91 return newNeedMoreParamsError(msg.Command)
92 }
93 for _, ch := range strings.Split(msg.Params[0], ",") {
94 c.srv.Logger.Printf("Joined channel %q", ch)
95 c.channels[ch] = &upstreamChannel{
96 Name: ch,
97 Members: make(map[string]membership),
98 }
99 }
100 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
101 if len(msg.Params) < 3 {
102 return newNeedMoreParamsError(msg.Command)
103 }
104 ch, err := c.getChannel(msg.Params[1])
105 if err != nil {
106 return err
107 }
108 if msg.Command == irc.RPL_TOPIC {
109 ch.Topic = msg.Params[2]
110 } else {
111 ch.Topic = ""
112 }
113 case "TOPIC":
114 if len(msg.Params) < 1 {
115 return newNeedMoreParamsError(msg.Command)
116 }
117 ch, err := c.getChannel(msg.Params[0])
118 if err != nil {
119 return err
120 }
121 if len(msg.Params) > 1 {
122 ch.Topic = msg.Params[1]
123 } else {
124 ch.Topic = ""
125 }
126 case rpl_topicwhotime:
127 if len(msg.Params) < 4 {
128 return newNeedMoreParamsError(msg.Command)
129 }
130 ch, err := c.getChannel(msg.Params[1])
131 if err != nil {
132 return err
133 }
134 ch.TopicWho = msg.Params[2]
135 sec, err := strconv.ParseInt(msg.Params[3], 10, 64)
136 if err != nil {
137 return fmt.Errorf("failed to parse topic time: %v", err)
138 }
139 ch.TopicTime = time.Unix(sec, 0)
140 case irc.RPL_NAMREPLY:
141 if len(msg.Params) < 4 {
142 return newNeedMoreParamsError(msg.Command)
143 }
144 ch, err := c.getChannel(msg.Params[2])
145 if err != nil {
146 return err
147 }
148
149 status, err := parseChannelStatus(msg.Params[1])
150 if err != nil {
151 return err
152 }
153 ch.Status = status
154
155 for _, s := range strings.Split(msg.Params[3], " ") {
156 membership, nick := parseMembershipPrefix(s)
157 ch.Members[nick] = membership
158 }
159 case irc.RPL_ENDOFNAMES:
160 // TODO
161 case irc.RPL_YOURHOST, irc.RPL_CREATED:
162 // Ignore
163 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
164 // Ignore
165 case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
166 // Ignore
167 case rpl_localusers, rpl_globalusers:
168 // Ignore
169 case irc.RPL_STATSVLINE, irc.RPL_STATSPING, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
170 // Ignore
171 default:
172 c.srv.Logger.Printf("Unhandled upstream message: %v", msg)
173 }
174 return nil
175}
176
177func connect(s *Server, upstream *Upstream) error {
178 s.Logger.Printf("Connecting to %v", upstream.Addr)
179
180 netConn, err := tls.Dial("tcp", upstream.Addr, nil)
181 if err != nil {
182 return fmt.Errorf("failed to dial %q: %v", upstream.Addr, err)
183 }
184
185 c := upstreamConn{
186 upstream: upstream,
187 net: netConn,
188 irc: irc.NewConn(netConn),
189 srv: s,
190 channels: make(map[string]*upstreamChannel),
191 }
192 defer netConn.Close()
193
194 err = c.irc.WriteMessage(&irc.Message{
195 Command: "NICK",
196 Params: []string{upstream.Nick},
197 })
198 if err != nil {
199 return err
200 }
201
202 err = c.irc.WriteMessage(&irc.Message{
203 Command: "USER",
204 Params: []string{upstream.Username, "0", "*", upstream.Realname},
205 })
206 if err != nil {
207 return err
208 }
209
210 for {
211 msg, err := c.irc.ReadMessage()
212 if err == io.EOF {
213 break
214 } else if err != nil {
215 return fmt.Errorf("failed to read IRC command: %v", err)
216 }
217
218 if err := c.handleMessage(msg); err != nil {
219 c.srv.Logger.Printf("Failed to handle message %q from %q: %v", msg, upstream.Addr, err)
220 }
221 }
222
223 return netConn.Close()
224}
Note: See TracBrowser for help on using the repository browser.