Browse Source

implement channel modes and messages properly

tags/v0.1.0
Jeremy Latt 10 years ago
parent
commit
488b2ccf8f
6 changed files with 155 additions and 79 deletions
  1. 45
    28
      irc/channel.go
  2. 49
    9
      irc/commands.go
  3. 17
    18
      irc/constants.go
  4. 15
    4
      irc/reply.go
  5. 21
    13
      irc/server.go
  6. 8
    7
      irc/types.go

+ 45
- 28
irc/channel.go View File

@@ -84,6 +84,14 @@ func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) {
84 84
 	}
85 85
 }
86 86
 
87
+func IsPrivMsg(reply Reply) bool {
88
+	strReply, ok := reply.(*StringReply)
89
+	if !ok {
90
+		return false
91
+	}
92
+	return strReply.code == "PRIVMSG"
93
+}
94
+
87 95
 func (channel *Channel) receiveReplies(replies <-chan Reply) {
88 96
 	for reply := range replies {
89 97
 		if channel.destroyed {
@@ -98,9 +106,10 @@ func (channel *Channel) receiveReplies(replies <-chan Reply) {
98 106
 		}
99 107
 		channel.mutex.Lock()
100 108
 		for client := range channel.members {
101
-			if reply.Source() != Identifier(client) {
102
-				client.Reply(reply)
109
+			if IsPrivMsg(reply) && (reply.Source() == Identifier(client)) {
110
+				continue
103 111
 			}
112
+			client.Reply(reply)
104 113
 		}
105 114
 		channel.mutex.Unlock()
106 115
 	}
@@ -187,9 +196,7 @@ func (channel *Channel) Join(client *Client) {
187 196
 	channel.mutex.Unlock()
188 197
 
189 198
 	client.channels.Add(channel)
190
-	reply := RplJoin(client, channel)
191
-	client.Reply(reply)
192
-	channel.Reply(reply)
199
+	channel.Reply(RplJoin(client, channel))
193 200
 	channel.GetTopic(client)
194 201
 	channel.GetUsers(client)
195 202
 }
@@ -216,9 +223,7 @@ func (m *PartCommand) HandleChannel(channel *Channel) {
216 223
 		return
217 224
 	}
218 225
 
219
-	reply := RplPart(client, channel, m.Message())
220
-	client.Reply(reply)
221
-	channel.Reply(reply)
226
+	channel.Reply(RplPart(client, channel, m.Message()))
222 227
 
223 228
 	channel.members.Remove(client)
224 229
 	client.channels.Remove(channel)
@@ -245,9 +250,7 @@ func (m *TopicCommand) HandleChannel(channel *Channel) {
245 250
 
246 251
 		channel.topic = m.topic
247 252
 		channel.GetTopic(client)
248
-		reply := RplTopicMsg(client, channel)
249
-		client.Reply(reply)
250
-		channel.Reply(reply)
253
+		channel.Reply(RplTopicMsg(client, channel))
251 254
 		return
252 255
 	}
253 256
 
@@ -267,10 +270,18 @@ func (m *PrivMsgCommand) HandleChannel(channel *Channel) {
267 270
 func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
268 271
 	client := msg.Client()
269 272
 
270
-	for _, modeOp := range msg.modeOps {
271
-		switch modeOp.mode {
273
+	if len(msg.changes) == 0 {
274
+		client.Reply(RplChannelModeIs(channel))
275
+		return
276
+	}
277
+
278
+	changes := make(ChannelModeChanges, 0)
279
+
280
+	for _, change := range msg.changes {
281
+		switch change.mode {
272 282
 		case BanMask:
273 283
 			// TODO add/remove
284
+
274 285
 			for _, banMask := range channel.banList {
275 286
 				client.Reply(RplBanList(channel, banMask))
276 287
 			}
@@ -282,12 +293,14 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
282 293
 				continue
283 294
 			}
284 295
 
285
-			switch modeOp.op {
296
+			switch change.op {
286 297
 			case Add:
287
-				channel.flags[modeOp.mode] = true
298
+				channel.flags[change.mode] = true
299
+				changes = append(changes, change)
288 300
 
289 301
 			case Remove:
290
-				delete(channel.flags, modeOp.mode)
302
+				delete(channel.flags, change.mode)
303
+				changes = append(changes, change)
291 304
 			}
292 305
 
293 306
 		case Key:
@@ -296,34 +309,33 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
296 309
 				continue
297 310
 			}
298 311
 
299
-			switch modeOp.op {
312
+			switch change.op {
300 313
 			case Add:
301
-				if modeOp.arg == "" {
314
+				if change.arg == "" {
302 315
 					// TODO err reply
303 316
 					continue
304 317
 				}
305 318
 
306
-				channel.key = modeOp.arg
319
+				channel.key = change.arg
320
+				changes = append(changes, change)
307 321
 
308 322
 			case Remove:
309 323
 				channel.key = ""
324
+				changes = append(changes, change)
310 325
 			}
311
-		}
312 326
 
313
-		mmode := ChannelMemberMode(modeOp.mode)
314
-		switch mmode {
315 327
 		case ChannelOperator, Voice:
316 328
 			if !channel.ClientIsOperator(client) {
317 329
 				client.Reply(ErrChanOPrivIsNeeded(channel))
318 330
 				continue
319 331
 			}
320 332
 
321
-			if modeOp.arg == "" {
333
+			if change.arg == "" {
322 334
 				// TODO err reply
323 335
 				continue
324 336
 			}
325 337
 
326
-			target := channel.server.clients[modeOp.arg]
338
+			target := channel.server.clients[change.arg]
327 339
 			if target == nil {
328 340
 				// TODO err reply
329 341
 				continue
@@ -334,16 +346,21 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
334 346
 				continue
335 347
 			}
336 348
 
337
-			switch modeOp.op {
349
+			switch change.op {
338 350
 			case Add:
339
-				channel.members[target][mmode] = true
351
+				channel.members[target][change.mode] = true
352
+				changes = append(changes, change)
353
+
340 354
 			case Remove:
341
-				channel.members[target][mmode] = false
355
+				channel.members[target][change.mode] = false
356
+				changes = append(changes, change)
342 357
 			}
343 358
 		}
344 359
 	}
345 360
 
346
-	client.Reply(RplChannelModeIs(channel))
361
+	if len(changes) > 0 {
362
+		channel.Reply(RplChannelMode(client, channel, changes))
363
+	}
347 364
 }
348 365
 
349 366
 func (m *NoticeCommand) HandleChannel(channel *Channel) {

+ 49
- 9
irc/commands.go View File

@@ -402,6 +402,26 @@ func (change *ModeChange) String() string {
402 402
 	return fmt.Sprintf("%s%s", change.op, change.mode)
403 403
 }
404 404
 
405
+type ModeChanges []ModeChange
406
+
407
+func (changes ModeChanges) String() string {
408
+	if len(changes) == 0 {
409
+		return ""
410
+	}
411
+
412
+	op := changes[0].op
413
+	str := changes[0].op.String()
414
+	for _, change := range changes {
415
+		if change.op == op {
416
+			str += change.mode.String()
417
+		} else {
418
+			op = change.op
419
+			str += " " + change.op.String()
420
+		}
421
+	}
422
+	return str
423
+}
424
+
405 425
 type ModeCommand struct {
406 426
 	BaseCommand
407 427
 	nickname string
@@ -436,27 +456,47 @@ func (cmd *ModeCommand) String() string {
436 456
 	return fmt.Sprintf("MODE(nickname=%s, changes=%s)", cmd.nickname, cmd.changes)
437 457
 }
438 458
 
439
-type ChannelModeOp struct {
459
+type ChannelModeChange struct {
440 460
 	mode ChannelMode
441 461
 	op   ModeOp
442 462
 	arg  string
443 463
 }
444 464
 
445
-func (op *ChannelModeOp) String() string {
465
+func (op *ChannelModeChange) String() string {
446 466
 	return fmt.Sprintf("{%s %s %s}", op.op, op.mode, op.arg)
447 467
 }
448 468
 
469
+type ChannelModeChanges []ChannelModeChange
470
+
471
+func (changes ChannelModeChanges) String() string {
472
+	if len(changes) == 0 {
473
+		return ""
474
+	}
475
+
476
+	str := "+"
477
+	if changes[0].op == Remove {
478
+		str = "-"
479
+	}
480
+	for _, change := range changes {
481
+		str += change.mode.String()
482
+	}
483
+	for _, change := range changes {
484
+		str += " " + change.arg
485
+	}
486
+	return str
487
+}
488
+
449 489
 type ChannelModeCommand struct {
450 490
 	BaseCommand
451 491
 	channel string
452
-	modeOps []ChannelModeOp
492
+	changes ChannelModeChanges
453 493
 }
454 494
 
455 495
 // MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
456 496
 func NewChannelModeCommand(args []string) (editableCommand, error) {
457 497
 	cmd := &ChannelModeCommand{
458 498
 		channel: args[0],
459
-		modeOps: make([]ChannelModeOp, 0),
499
+		changes: make(ChannelModeChanges, 0),
460 500
 	}
461 501
 	args = args[1:]
462 502
 
@@ -472,18 +512,18 @@ func NewChannelModeCommand(args []string) (editableCommand, error) {
472 512
 
473 513
 		skipArgs := 1
474 514
 		for _, mode := range modeArg {
475
-			modeOp := ChannelModeOp{
515
+			change := ChannelModeChange{
476 516
 				mode: ChannelMode(mode),
477 517
 				op:   op,
478 518
 			}
479
-			switch modeOp.mode {
519
+			switch change.mode {
480 520
 			case Key, BanMask, ExceptionMask, InviteMask, UserLimit:
481 521
 				if len(args) > skipArgs {
482
-					modeOp.arg = args[skipArgs]
522
+					change.arg = args[skipArgs]
483 523
 					skipArgs += 1
484 524
 				}
485 525
 			}
486
-			cmd.modeOps = append(cmd.modeOps, modeOp)
526
+			cmd.changes = append(cmd.changes, change)
487 527
 		}
488 528
 		args = args[skipArgs:]
489 529
 	}
@@ -492,7 +532,7 @@ func NewChannelModeCommand(args []string) (editableCommand, error) {
492 532
 }
493 533
 
494 534
 func (msg *ChannelModeCommand) String() string {
495
-	return fmt.Sprintf("MODE(channel=%s, modeOps=%s)", msg.channel, msg.modeOps)
535
+	return fmt.Sprintf("MODE(channel=%s, changes=%s)", msg.channel, msg.changes)
496 536
 }
497 537
 
498 538
 func NewModeCommand(args []string) (editableCommand, error) {

+ 17
- 18
irc/constants.go View File

@@ -173,24 +173,23 @@ const (
173 173
 	ServerNotice  UserMode = 's'
174 174
 	WallOps       UserMode = 'w'
175 175
 
176
-	Anonymous     ChannelMode = 'a' // flag
177
-	BanMask       ChannelMode = 'b' // arg
178
-	ExceptionMask ChannelMode = 'e' // arg
179
-	InviteMask    ChannelMode = 'I' // arg
180
-	InviteOnly    ChannelMode = 'i' // flag
181
-	Key           ChannelMode = 'k' // flag arg
182
-	Moderated     ChannelMode = 'm' // flag
183
-	NoOutside     ChannelMode = 'n' // flag
184
-	OpOnlyTopic   ChannelMode = 't' // flag
185
-	Private       ChannelMode = 'p' // flag
186
-	Quiet         ChannelMode = 'q' // flag
187
-	ReOp          ChannelMode = 'r' // flag
188
-	Secret        ChannelMode = 's' // flag
189
-	UserLimit     ChannelMode = 'l' // flag arg
190
-
191
-	ChannelCreator  ChannelMemberMode = 'O' // flag
192
-	ChannelOperator ChannelMemberMode = 'o' // arg
193
-	Voice           ChannelMemberMode = 'v' // arg
176
+	Anonymous       ChannelMode = 'a' // flag
177
+	BanMask         ChannelMode = 'b' // arg
178
+	ChannelCreator  ChannelMode = 'O' // flag
179
+	ChannelOperator ChannelMode = 'o' // arg
180
+	ExceptionMask   ChannelMode = 'e' // arg
181
+	InviteMask      ChannelMode = 'I' // arg
182
+	InviteOnly      ChannelMode = 'i' // flag
183
+	Key             ChannelMode = 'k' // flag arg
184
+	Moderated       ChannelMode = 'm' // flag
185
+	NoOutside       ChannelMode = 'n' // flag
186
+	OpOnlyTopic     ChannelMode = 't' // flag
187
+	Private         ChannelMode = 'p' // flag
188
+	Quiet           ChannelMode = 'q' // flag
189
+	ReOp            ChannelMode = 'r' // flag
190
+	Secret          ChannelMode = 's' // flag
191
+	UserLimit       ChannelMode = 'l' // flag arg
192
+	Voice           ChannelMode = 'v' // arg
194 193
 )
195 194
 
196 195
 const (

+ 15
- 4
irc/reply.go View File

@@ -144,11 +144,20 @@ func RplJoin(client *Client, channel *Channel) Reply {
144 144
 }
145 145
 
146 146
 func RplPart(client *Client, channel *Channel, message string) Reply {
147
-	return NewStringReply(client, "PART", "%s :%s", channel.name, message)
147
+	return NewStringReply(client, "PART", "%s :%s", channel, message)
148
+}
149
+
150
+func RplMode(client *Client, changes ModeChanges) Reply {
151
+	return NewStringReply(client, "MODE", "%s :%s", client.Nick(), changes)
152
+}
153
+
154
+func RplChannelMode(client *Client, channel *Channel,
155
+	changes ChannelModeChanges) Reply {
156
+	return NewStringReply(client, "MODE", "%s %s", channel, changes)
148 157
 }
149 158
 
150 159
 func RplTopicMsg(source Identifier, channel *Channel) Reply {
151
-	return NewStringReply(source, "TOPIC", "%s :%s", channel.name, channel.topic)
160
+	return NewStringReply(source, "TOPIC", "%s :%s", channel, channel.topic)
152 161
 }
153 162
 
154 163
 func RplPing(server *Server, target Identifier) Reply {
@@ -207,9 +216,11 @@ func RplTopic(channel *Channel) Reply {
207 216
 		"%s :%s", channel.name, channel.topic)
208 217
 }
209 218
 
219
+// <nick> <channel>
220
+// NB: correction in errata
210 221
 func RplInvitingMsg(channel *Channel, invitee *Client) Reply {
211 222
 	return NewNumericReply(channel.server, RPL_INVITING,
212
-		"%s %s", channel.name, invitee.Nick())
223
+		"%s %s", invitee.Nick(), channel.name)
213 224
 }
214 225
 
215 226
 func RplNamReply(channel *Channel, names []string) *NumericReply {
@@ -238,7 +249,7 @@ func RplEndOfWhois(server *Server) Reply {
238 249
 
239 250
 func RplChannelModeIs(channel *Channel) Reply {
240 251
 	return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s",
241
-		channel.name, channel.ModeString())
252
+		channel, channel.ModeString())
242 253
 }
243 254
 
244 255
 // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]

+ 21
- 13
irc/server.go View File

@@ -270,7 +270,6 @@ func (m *NickCommand) HandleRegServer(s *Server) {
270 270
 
271 271
 	client.nick = m.nickname
272 272
 	s.clients.Add(client)
273
-	client.Reply(RplNick(client, m.nickname))
274 273
 	s.tryRegister(client)
275 274
 }
276 275
 
@@ -414,22 +413,31 @@ func (m *PrivMsgCommand) HandleServer(s *Server) {
414 413
 func (m *ModeCommand) HandleServer(s *Server) {
415 414
 	client := m.Client()
416 415
 	target := s.clients[m.nickname]
417
-	if client == target {
418
-		for _, change := range m.changes {
419
-			if change.mode == Invisible {
420
-				switch change.op {
421
-				case Add:
422
-					client.invisible = true
423
-				case Remove:
424
-					client.invisible = false
425
-				}
416
+	// TODO other auth
417
+	if client != target {
418
+		client.Reply(ErrUsersDontMatch(s))
419
+		return
420
+	}
421
+
422
+	changes := make(ModeChanges, 0)
423
+
424
+	for _, change := range m.changes {
425
+		if change.mode == Invisible {
426
+			switch change.op {
427
+			case Add:
428
+				client.invisible = true
429
+				changes = append(changes, change)
430
+
431
+			case Remove:
432
+				client.invisible = false
433
+				changes = append(changes, change)
426 434
 			}
427 435
 		}
428
-		client.Reply(RplUModeIs(s, client))
429
-		return
430 436
 	}
431 437
 
432
-	client.Reply(ErrUsersDontMatch(s))
438
+	if len(changes) > 0 {
439
+		client.Reply(RplMode(client, changes))
440
+	}
433 441
 }
434 442
 
435 443
 func (m *WhoisCommand) HandleServer(server *Server) {

+ 8
- 7
irc/types.go View File

@@ -15,6 +15,10 @@ type Mask string
15 15
 // add, remove, list modes
16 16
 type ModeOp rune
17 17
 
18
+func (op ModeOp) String() string {
19
+	return string(op)
20
+}
21
+
18 22
 // user mode flags
19 23
 type UserMode rune
20 24
 
@@ -37,9 +41,6 @@ func (mode ChannelMode) String() string {
37 41
 	return fmt.Sprintf("%c", mode)
38 42
 }
39 43
 
40
-// user-channel mode flags
41
-type ChannelMemberMode rune
42
-
43 44
 type ChannelNameMap map[string]*Channel
44 45
 
45 46
 func (channels ChannelNameMap) Add(channel *Channel) error {
@@ -84,19 +85,19 @@ func (clients ClientNameMap) Remove(client *Client) error {
84 85
 	return nil
85 86
 }
86 87
 
87
-type ChannelMemberModeSet map[ChannelMemberMode]bool
88
+type ChannelModeSet map[ChannelMode]bool
88 89
 
89
-type ClientSet map[*Client]ChannelMemberModeSet
90
+type ClientSet map[*Client]ChannelModeSet
90 91
 
91 92
 func (clients ClientSet) Add(client *Client) {
92
-	clients[client] = make(ChannelMemberModeSet)
93
+	clients[client] = make(ChannelModeSet)
93 94
 }
94 95
 
95 96
 func (clients ClientSet) Remove(client *Client) {
96 97
 	delete(clients, client)
97 98
 }
98 99
 
99
-func (clients ClientSet) HasMode(client *Client, mode ChannelMemberMode) bool {
100
+func (clients ClientSet) HasMode(client *Client, mode ChannelMode) bool {
100 101
 	modes, ok := clients[client]
101 102
 	if !ok {
102 103
 		return false

Loading…
Cancel
Save