4
4
"errors"
5
5
"fmt"
6
6
"io"
7
+ "strings"
7
8
"sync"
8
9
"time"
9
10
)
@@ -52,6 +53,49 @@ var clientFilters = map[string]func(*Client, *Message){
52
53
c .currentNick = m .Params [0 ]
53
54
}
54
55
},
56
+ "CAP" : func (c * Client , m * Message ) {
57
+ if c .remainingCapResponses <= 0 || len (m .Params ) <= 2 {
58
+ return
59
+ }
60
+
61
+ switch m .Params [1 ] {
62
+ case "LS" :
63
+ for _ , key := range strings .Split (m .Trailing (), " " ) {
64
+ cap := c .caps [key ]
65
+ cap .Available = true
66
+ c .caps [key ] = cap
67
+ }
68
+ c .remainingCapResponses --
69
+ case "ACK" :
70
+ for _ , key := range strings .Split (m .Trailing (), " " ) {
71
+ cap := c .caps [key ]
72
+ cap .Enabled = true
73
+ c .caps [key ] = cap
74
+ }
75
+ c .remainingCapResponses --
76
+ case "NAK" :
77
+ // If we got a NAK and this REQ was required, we need to bail
78
+ // with an error.
79
+ for _ , key := range strings .Split (m .Trailing (), " " ) {
80
+ if c .caps [key ].Required {
81
+ c .sendError (fmt .Errorf ("CAP %s requested but was rejected" , key ))
82
+ return
83
+ }
84
+ }
85
+ c .remainingCapResponses --
86
+ }
87
+
88
+ if c .remainingCapResponses <= 0 {
89
+ for key , cap := range c .caps {
90
+ if cap .Required && ! cap .Enabled {
91
+ c .sendError (fmt .Errorf ("CAP %s requested but not accepted" , key ))
92
+ return
93
+ }
94
+ }
95
+
96
+ c .Write ("CAP END" )
97
+ }
98
+ },
55
99
}
56
100
57
101
// ClientConfig is a structure used to configure a Client.
@@ -77,18 +121,34 @@ type ClientConfig struct {
77
121
Handler Handler
78
122
}
79
123
124
+ type cap struct {
125
+ // Requested means that this cap was requested by the user
126
+ Requested bool
127
+
128
+ // Required will be true if this cap is non-optional
129
+ Required bool
130
+
131
+ // Enabled means that this cap was accepted by the server
132
+ Enabled bool
133
+
134
+ // Available means that the server supports this cap
135
+ Available bool
136
+ }
137
+
80
138
// Client is a wrapper around Conn which is designed to make common operations
81
139
// much simpler.
82
140
type Client struct {
83
141
* Conn
84
142
config ClientConfig
85
143
86
144
// Internal state
87
- currentNick string
88
- limiter chan struct {}
89
- incomingPongChan chan string
90
- errChan chan error
91
- connected bool
145
+ currentNick string
146
+ limiter chan struct {}
147
+ incomingPongChan chan string
148
+ errChan chan error
149
+ caps map [string ]cap
150
+ remainingCapResponses int
151
+ connected bool
92
152
}
93
153
94
154
// NewClient creates a client given an io stream and a client config.
@@ -97,6 +157,7 @@ func NewClient(rw io.ReadWriter, config ClientConfig) *Client {
97
157
Conn : NewConn (rw ),
98
158
config : config ,
99
159
errChan : make (chan error , 1 ),
160
+ caps : make (map [string ]cap ),
100
161
}
101
162
102
163
// Replace the writer writeCallback with one of our own
@@ -114,6 +175,8 @@ func (c *Client) writeCallback(w *Writer, line string) error {
114
175
return err
115
176
}
116
177
178
+ // maybeStartLimiter will start a ticker which will limit how quickly messages
179
+ // can be written to the connection if the SendLimit is set in the config.
117
180
func (c * Client ) maybeStartLimiter (wg * sync.WaitGroup , exiting chan struct {}) {
118
181
if c .config .SendLimit == 0 {
119
182
return
@@ -147,6 +210,8 @@ func (c *Client) maybeStartLimiter(wg *sync.WaitGroup, exiting chan struct{}) {
147
210
}()
148
211
}
149
212
213
+ // maybeStartPingLoop will start a goroutine to send out PING messages at the
214
+ // PingFrequency in the config if the frequency is not 0.
150
215
func (c * Client ) maybeStartPingLoop (wg * sync.WaitGroup , exiting chan struct {}) {
151
216
if c .config .PingFrequency <= 0 {
152
217
return
@@ -177,6 +242,7 @@ func (c *Client) maybeStartPingLoop(wg *sync.WaitGroup, exiting chan struct{}) {
177
242
case data := <- c .incomingPongChan :
178
243
// Make sure the pong gets routed to the correct
179
244
// goroutine.
245
+
180
246
c := pingHandlers [data ]
181
247
delete (pingHandlers , data )
182
248
@@ -212,6 +278,51 @@ func (c *Client) handlePing(timestamp int64, pongChan chan struct{}, wg *sync.Wa
212
278
}
213
279
}
214
280
281
+ // maybeStartCapHandshake will run a CAP LS and all the relevant CAP REQ
282
+ // commands if there are any CAPs requested.
283
+ func (c * Client ) maybeStartCapHandshake () error {
284
+ if len (c .caps ) <= 0 {
285
+ return nil
286
+ }
287
+
288
+ c .Write ("CAP LS" )
289
+ c .remainingCapResponses = 1 // We count the CAP LS response as a normal response
290
+ for key , cap := range c .caps {
291
+ if cap .Requested {
292
+ c .Writef ("CAP REQ :%s" , key )
293
+ c .remainingCapResponses ++
294
+ }
295
+ }
296
+
297
+ return nil
298
+ }
299
+
300
+ // CapRequest allows you to request IRCv3 capabilities from the server during
301
+ // the handshake. The behavior is undefined if this is called before the
302
+ // handshake completes so it is recommended that this be called before Run. If
303
+ // the CAP is marked as required, the client will exit if that CAP could not be
304
+ // negotiated during the handshake.
305
+ func (c * Client ) CapRequest (capName string , required bool ) {
306
+ cap := c .caps [capName ]
307
+ cap .Requested = true
308
+ cap .Required = cap .Required || required
309
+ c .caps [capName ] = cap
310
+ }
311
+
312
+ // CapEnabled allows you to check if a CAP is enabled for this connection. Note
313
+ // that it will not be populated until after the CAP handshake is done, so it is
314
+ // recommended to wait to check this until after a message like 001.
315
+ func (c * Client ) CapEnabled (capName string ) bool {
316
+ return c .caps [capName ].Enabled
317
+ }
318
+
319
+ // CapAvailable allows you to check if a CAP is available on this server. Note
320
+ // that it will not be populated until after the CAP handshake is done, so it is
321
+ // recommended to wait to check this until after a message like 001.
322
+ func (c * Client ) CapAvailable (capName string ) bool {
323
+ return c .caps [capName ].Available
324
+ }
325
+
215
326
func (c * Client ) sendError (err error ) {
216
327
select {
217
328
case c .errChan <- err :
@@ -237,6 +348,10 @@ func (c *Client) Run() error {
237
348
c .Writef ("PASS :%s" , c .config .Pass )
238
349
}
239
350
351
+ c .maybeStartCapHandshake ()
352
+
353
+ // This feels wrong because it results in CAP LS, CAP REQ, NICK, USER, CAP
354
+ // END, but it works and lets us keep the code a bit simpler.
240
355
c .Writef ("NICK :%s" , c .config .Nick )
241
356
c .Writef ("USER %s 0.0.0.0 0.0.0.0 :%s" , c .config .User , c .config .Name )
242
357
0 commit comments