@@ -9,257 +9,118 @@ import (
9
9
"net"
10
10
"strconv"
11
11
"strings"
12
- "sync"
13
12
"time"
14
-
15
- tomb "gopkg.in/tomb.v2"
16
13
)
17
14
18
15
type Client struct {
16
+ // Logger for messages. By defaut this will be a NilLogger
19
17
Logger Logger
20
18
21
- handler Handler
22
- connected bool
19
+ // Internal things
23
20
currentNick string
24
- nick string
25
- user string
26
- name string
27
- password string
28
- lock * sync.Mutex
29
21
conn io.ReadWriteCloser
30
- t * tomb.Tomb
31
- write chan string
22
+ in * bufio.Reader
32
23
}
33
24
34
- func NewClient (handler Handler , nick string , user string , name string , pass string ) * Client {
35
- // Create the client
36
- c := & Client {
37
- nil ,
38
- handler ,
39
- false ,
40
- nick ,
41
- nick ,
42
- user ,
43
- name ,
44
- pass ,
45
- & sync.Mutex {},
46
- nil ,
47
- & tomb.Tomb {},
48
- make (chan string ),
25
+ func Dial (addr string , nick , user , name , pass string ) (* Client , error ) {
26
+ conn , err := net .Dial ("tcp" , addr )
27
+ if err != nil {
28
+ return nil , err
49
29
}
50
30
51
- return c
52
- }
53
-
54
- func (c * Client ) CurrentNick () string {
55
- return c .currentNick
31
+ return NewClient (conn , nick , user , name , pass ), nil
56
32
}
57
33
58
- func (c * Client ) Dial (host string ) error {
59
- c .lock .Lock ()
60
- defer c .lock .Unlock ()
61
-
62
- if c .connected {
63
- return errors .New ("Already connected" )
64
- }
65
-
66
- var err error
67
- c .conn , err = net .Dial ("tcp" , host )
34
+ func DialTLS (addr string , c * tls.Config , nick , user , name , pass string ) (* Client , error ) {
35
+ conn , err := tls .Dial ("tcp" , addr , c )
68
36
if err != nil {
69
- return err
37
+ return nil , err
70
38
}
71
39
72
- c .connected = true
73
-
74
- return c .start ()
40
+ return NewClient (conn , nick , user , name , pass ), nil
75
41
}
76
42
77
- func (c * Client ) DialTLS (host string , conf * tls.Config ) error {
78
- c .lock .Lock ()
79
- defer c .lock .Unlock ()
80
-
81
- if c .connected {
82
- return errors .New ("Already connected" )
43
+ func NewClient (rwc io.ReadWriteCloser , nick , user , name , pass string ) * Client {
44
+ // Create the client
45
+ c := & Client {
46
+ & NilLogger {},
47
+ nick ,
48
+ rwc ,
49
+ bufio .NewReader (rwc ),
83
50
}
84
51
85
- var err error
86
- c .conn , err = tls .Dial ("tcp" , host , conf )
87
- if err != nil {
88
- return err
52
+ // Send the info we need to
53
+ if len (pass ) > 0 {
54
+ c .Writef ("PASS %s" , pass )
89
55
}
90
56
91
- c .connected = true
57
+ c .Writef ("NICK %s" , nick )
58
+ c .Writef ("USER %s 0.0.0.0 0.0.0.0 :%s" , user , name )
92
59
93
- return c .start ()
60
+ return c
61
+ }
62
+
63
+ func (c * Client ) CurrentNick () string {
64
+ return c .currentNick
94
65
}
95
66
96
67
func (c * Client ) Write (line string ) {
97
- // Try to write it to the writer. Fall back to waiting until the bot dies.
98
- select {
99
- case c .write <- line :
100
- case <- c .t .Dying ():
68
+ if c .Logger != nil {
69
+ c .Logger .Debug ("-->" , line )
101
70
}
71
+ c .conn .Write ([]byte (line ))
72
+ c .conn .Write ([]byte ("\r \n " ))
102
73
}
103
74
104
75
func (c * Client ) Writef (format string , args ... interface {}) {
105
76
c .Write (fmt .Sprintf (format , args ... ))
106
77
}
107
78
108
- func (c * Client ) start () error {
109
- // Start up the tomb with all our loops.
110
- //
111
- // Note that it's only safe to call tomb.Go from inside
112
- // other functions that have been started the same way,
113
- // so we make a quick closure to take care of that.
114
- c .t .Go (func () error {
115
- // Ping Loop
116
- c .t .Go (c .pingLoop )
117
-
118
- // Read Loop
119
- c .t .Go (c .readLoop )
120
-
121
- // Write Loop
122
- c .t .Go (c .writeLoop )
123
-
124
- // Cleanup Loop
125
- c .t .Go (c .cleanupLoop )
126
-
127
- // Actually connect
128
- if len (c .password ) > 0 {
129
- c .Writef ("PASS %s" , c .password )
130
- }
131
-
132
- c .Writef ("NICK %s" , c .nick )
133
- c .Writef ("USER %s 0.0.0.0 0.0.0.0 :%s" , c .user , c .name )
79
+ func (c * Client ) ReadEvent () (* Event , error ) {
80
+ line , err := c .in .ReadString ('\n' )
81
+ if err != nil {
82
+ return nil , err
83
+ }
134
84
135
- return nil
136
- })
85
+ if c .Logger != nil {
86
+ c .Logger .Debug ("<--" , strings .TrimRight (line , "\r \n " ))
87
+ }
137
88
138
- // This will wait until all goroutines in the Tomb die
139
- return c .t .Wait ()
140
- }
89
+ // Parse the event from our line
90
+ e := ParseEvent (line )
141
91
142
- func (c * Client ) pingLoop () error {
143
- // Tick every 2 minutes
144
- t := time .NewTicker (2 * time .Minute )
145
- defer t .Stop ()
146
- for {
147
- select {
148
- case <- t .C :
149
- c .Writef ("PING :%d" , time .Now ().UnixNano ())
150
- case <- c .t .Dying ():
151
- return nil
152
- }
153
- }
154
- }
92
+ // Now that we have the event parsed, do some preprocessing on it
93
+ lastArg := e .Trailing ()
155
94
156
- func (c * Client ) readLoop () error {
157
- in := bufio .NewReader (c .conn )
158
- for {
159
- // If we're dying exit out
160
- select {
161
- case <- c .t .Dying ():
162
- return nil
163
- default :
164
- }
95
+ // Clean up CTCP stuff so everyone
96
+ // doesn't have to parse it manually
97
+ if e .Command == "PRIVMSG" && len (lastArg ) > 0 && lastArg [0 ] == '\x01' {
98
+ e .Command = "CTCP"
165
99
166
- line , err := in .ReadString ('\n' )
167
- if err != nil {
168
- return err
100
+ if i := strings .LastIndex (lastArg , "\x01 " ); i > - 1 {
101
+ e .Args [len (e .Args )- 1 ] = lastArg [1 :i ]
169
102
}
103
+ } else if e .Command == "PING" {
104
+ c .Writef ("PONG :%s" , lastArg )
105
+ } else if e .Command == "PONG" {
106
+ ns , _ := strconv .ParseInt (lastArg , 10 , 64 )
107
+ delta := time .Duration (time .Now ().UnixNano () - ns )
170
108
171
109
if c .Logger != nil {
172
- c .Logger .Debug ( "<-- " , strings . TrimRight ( line , " \r \n " ) )
110
+ c .Logger .Info ( "!!! Lag: " , delta )
173
111
}
174
-
175
- // Parse the event from our line
176
- e := ParseEvent (line )
177
-
178
- // Now that we have the event parsed, do some preprocessing on it
179
- lastArg := e .Trailing ()
180
-
181
- // Clean up CTCP stuff so everyone
182
- // doesn't have to parse it manually
183
- if e .Command == "PRIVMSG" && len (lastArg ) > 0 && lastArg [0 ] == '\x01' {
184
- e .Command = "CTCP"
185
-
186
- if i := strings .LastIndex (lastArg , "\x01 " ); i > - 1 {
187
- e .Args [len (e .Args )- 1 ] = lastArg [1 :i ]
188
- }
189
- } else if e .Command == "PING" {
190
- c .Writef ("PONG :%s" , lastArg )
191
- } else if e .Command == "PONG" {
192
- ns , _ := strconv .ParseInt (lastArg , 10 , 64 )
193
- delta := time .Duration (time .Now ().UnixNano () - ns )
194
-
195
- if c .Logger != nil {
196
- c .Logger .Info ("!!! Lag:" , delta )
197
- }
198
- } else if e .Command == "NICK" {
199
- if e .Identity .Nick == c .currentNick && len (e .Args ) > 0 {
200
- c .currentNick = e .Args [0 ]
201
- }
202
- } else if e .Command == "001" {
112
+ } else if e .Command == "NICK" {
113
+ if e .Identity .Nick == c .currentNick && len (e .Args ) > 0 {
203
114
c .currentNick = e .Args [0 ]
204
- } else if e .Command == "437" || e .Command == "433" {
205
- c .currentNick = c .currentNick + "_"
206
- c .Writef ("NICK %s" , c .currentNick )
207
- }
208
-
209
- c .handler .HandleEvent (c , e )
210
- }
211
- }
212
-
213
- func (c * Client ) writeLoop () error {
214
- // Set up a rate limiter
215
- // Based on https://code.google.com/p/go-wiki/wiki/RateLimiting
216
- // 2 lps with a burst of 5
217
- throttle := make (chan time.Time , 5 )
218
- ticker := time .NewTicker (500 * time .Millisecond )
219
- defer ticker .Stop ()
220
-
221
- go func () {
222
- for ns := range ticker .C {
223
- select {
224
- case throttle <- ns :
225
- default :
226
- }
227
- }
228
- }()
229
-
230
- for {
231
- select {
232
- case line := <- c .write :
233
- select {
234
- case <- throttle :
235
- if c .Logger != nil {
236
- c .Logger .Debug ("-->" , line )
237
- }
238
- c .conn .Write ([]byte (line ))
239
- c .conn .Write ([]byte ("\r \n " ))
240
- case <- c .t .Dying ():
241
- }
242
- case <- c .t .Dying ():
243
- return nil
244
115
}
116
+ } else if e .Command == "001" {
117
+ c .currentNick = e .Args [0 ]
118
+ } else if e .Command == "437" || e .Command == "433" {
119
+ c .currentNick = c .currentNick + "_"
120
+ c .Writef ("NICK %s" , c .currentNick )
245
121
}
246
- }
247
122
248
- func (c * Client ) cleanupLoop () error {
249
- select {
250
- case <- c .t .Dying ():
251
- c .conn .Close ()
252
- }
253
- return nil
254
- }
255
-
256
- func prepend (e interface {}, v []interface {}) []interface {} {
257
- var vc []interface {}
258
-
259
- vc = append (vc , e )
260
- vc = append (vc , v ... )
261
-
262
- return vc
123
+ return e , nil
263
124
}
264
125
265
126
func (c * Client ) MentionReply (e * Event , format string , v ... interface {}) error {
@@ -270,14 +131,14 @@ func (c *Client) MentionReply(e *Event, format string, v ...interface{}) error {
270
131
271
132
if e .FromChannel () {
272
133
format = "%s: " + format
273
-
274
134
v = prepend (e .Identity .Nick , v )
275
135
}
276
136
277
137
return c .Reply (e , format , v ... )
278
138
}
279
139
280
140
func (c * Client ) CTCPReply (e * Event , format string , v ... interface {}) error {
141
+ // Sanity check
281
142
if len (e .Args ) < 1 || len (e .Args [0 ]) < 1 {
282
143
return errors .New ("Invalid IRC event" )
283
144
}
0 commit comments