Browse Source

Split modes into a subpackage (this is painful, but will force us to simplify and improve the API for interacting with modes)

tags/v0.11.0-beta
Daniel Oaks 6 years ago
parent
commit
3634d0601e
12 changed files with 541 additions and 517 deletions
  1. 65
    86
      irc/channel.go
  2. 3
    2
      irc/chanserv.go
  3. 5
    4
      irc/client.go
  4. 5
    2
      irc/commands.go
  5. 3
    2
      irc/gateways.go
  6. 8
    5
      irc/getters.go
  7. 48
    47
      irc/handlers.go
  8. 88
    327
      irc/modes.go
  9. 286
    0
      irc/modes/modes.go
  10. 4
    3
      irc/roleplay.go
  11. 21
    15
      irc/server.go
  12. 5
    24
      irc/types.go

+ 65
- 86
irc/channel.go View File

@@ -15,6 +15,7 @@ import (
15 15
 
16 16
 	"github.com/goshuirc/irc-go/ircmsg"
17 17
 	"github.com/oragono/oragono/irc/caps"
18
+	"github.com/oragono/oragono/irc/modes"
18 19
 )
19 20
 
20 21
 var (
@@ -23,8 +24,8 @@ var (
23 24
 
24 25
 // Channel represents a channel that clients can join.
25 26
 type Channel struct {
26
-	flags             ModeSet
27
-	lists             map[Mode]*UserMaskSet
27
+	flags             modes.ModeSet
28
+	lists             map[modes.Mode]*UserMaskSet
28 29
 	key               string
29 30
 	members           MemberSet
30 31
 	membersCache      []*Client  // allow iteration over channel members without holding the lock
@@ -53,11 +54,11 @@ func NewChannel(s *Server, name string, addDefaultModes bool, regInfo *Registere
53 54
 
54 55
 	channel := &Channel{
55 56
 		createdTime: time.Now(), // may be overwritten by applyRegInfo
56
-		flags:       make(ModeSet),
57
-		lists: map[Mode]*UserMaskSet{
58
-			BanMask:    NewUserMaskSet(),
59
-			ExceptMask: NewUserMaskSet(),
60
-			InviteMask: NewUserMaskSet(),
57
+		flags:       make(modes.ModeSet),
58
+		lists: map[modes.Mode]*UserMaskSet{
59
+			modes.BanMask:    NewUserMaskSet(),
60
+			modes.ExceptMask: NewUserMaskSet(),
61
+			modes.InviteMask: NewUserMaskSet(),
61 62
 		},
62 63
 		members:        make(MemberSet),
63 64
 		name:           name,
@@ -88,13 +89,13 @@ func (channel *Channel) applyRegInfo(chanReg *RegisteredChannel) {
88 89
 	channel.name = chanReg.Name
89 90
 	channel.createdTime = chanReg.RegisteredAt
90 91
 	for _, mask := range chanReg.Banlist {
91
-		channel.lists[BanMask].Add(mask)
92
+		channel.lists[modes.BanMask].Add(mask)
92 93
 	}
93 94
 	for _, mask := range chanReg.Exceptlist {
94
-		channel.lists[ExceptMask].Add(mask)
95
+		channel.lists[modes.ExceptMask].Add(mask)
95 96
 	}
96 97
 	for _, mask := range chanReg.Invitelist {
97
-		channel.lists[InviteMask].Add(mask)
98
+		channel.lists[modes.InviteMask].Add(mask)
98 99
 	}
99 100
 }
100 101
 
@@ -111,13 +112,13 @@ func (channel *Channel) ExportRegistration(includeLists bool) (info RegisteredCh
111 112
 	info.RegisteredAt = channel.registeredTime
112 113
 
113 114
 	if includeLists {
114
-		for mask := range channel.lists[BanMask].masks {
115
+		for mask := range channel.lists[modes.BanMask].masks {
115 116
 			info.Banlist = append(info.Banlist, mask)
116 117
 		}
117
-		for mask := range channel.lists[ExceptMask].masks {
118
+		for mask := range channel.lists[modes.ExceptMask].masks {
118 119
 			info.Exceptlist = append(info.Exceptlist, mask)
119 120
 		}
120
-		for mask := range channel.lists[InviteMask].masks {
121
+		for mask := range channel.lists[modes.InviteMask].masks {
121 122
 			info.Invitelist = append(info.Invitelist, mask)
122 123
 		}
123 124
 	}
@@ -201,7 +202,7 @@ func (channel *Channel) Names(client *Client) {
201 202
 }
202 203
 
203 204
 // ClientIsAtLeast returns whether the client has at least the given channel privilege.
204
-func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool {
205
+func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
205 206
 	channel.stateMutex.RLock()
206 207
 	defer channel.stateMutex.RUnlock()
207 208
 
@@ -211,7 +212,7 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool {
211 212
 	}
212 213
 
213 214
 	// check regular modes
214
-	for _, mode := range ChannelPrivModes {
215
+	for _, mode := range modes.ChannelPrivModes {
215 216
 		if channel.members.HasMode(client, mode) {
216 217
 			return true
217 218
 		}
@@ -224,27 +225,6 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool {
224 225
 	return false
225 226
 }
226 227
 
227
-// Prefixes returns a list of prefixes for the given set of channel modes.
228
-func (modes ModeSet) Prefixes(isMultiPrefix bool) string {
229
-	var prefixes string
230
-
231
-	// add prefixes in order from highest to lowest privs
232
-	for _, mode := range ChannelPrivModes {
233
-		if modes[mode] {
234
-			prefixes += ChannelModePrefixes[mode]
235
-		}
236
-	}
237
-	if modes[Voice] {
238
-		prefixes += ChannelModePrefixes[Voice]
239
-	}
240
-
241
-	if !isMultiPrefix && len(prefixes) > 1 {
242
-		prefixes = string(prefixes[0])
243
-	}
244
-
245
-	return prefixes
246
-}
247
-
248 228
 func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) string {
249 229
 	channel.stateMutex.RLock()
250 230
 	defer channel.stateMutex.RUnlock()
@@ -263,11 +243,11 @@ func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool
263 243
 	clientModes := channel.members[client]
264 244
 	targetModes := channel.members[target]
265 245
 	result := false
266
-	for _, mode := range ChannelPrivModes {
246
+	for _, mode := range modes.ChannelPrivModes {
267 247
 		if clientModes[mode] {
268 248
 			result = true
269 249
 			// admins cannot kick other admins
270
-			if mode == ChannelAdmin && targetModes[ChannelAdmin] {
250
+			if mode == modes.ChannelAdmin && targetModes[modes.ChannelAdmin] {
271 251
 				result = false
272 252
 			}
273 253
 			break
@@ -318,18 +298,18 @@ func (channel *Channel) hasClient(client *Client) bool {
318 298
 
319 299
 // <mode> <mode params>
320 300
 func (channel *Channel) modeStrings(client *Client) (result []string) {
321
-	isMember := client.HasMode(Operator) || channel.hasClient(client)
301
+	isMember := client.HasMode(modes.Operator) || channel.hasClient(client)
322 302
 	showKey := isMember && (channel.key != "")
323 303
 	showUserLimit := channel.userLimit > 0
324 304
 
325
-	modes := "+"
305
+	mods := "+"
326 306
 
327 307
 	// flags with args
328 308
 	if showKey {
329
-		modes += Key.String()
309
+		mods += modes.Key.String()
330 310
 	}
331 311
 	if showUserLimit {
332
-		modes += UserLimit.String()
312
+		mods += modes.UserLimit.String()
333 313
 	}
334 314
 
335 315
 	channel.stateMutex.RLock()
@@ -337,10 +317,10 @@ func (channel *Channel) modeStrings(client *Client) (result []string) {
337 317
 
338 318
 	// flags
339 319
 	for mode := range channel.flags {
340
-		modes += mode.String()
320
+		mods += mode.String()
341 321
 	}
342 322
 
343
-	result = []string{modes}
323
+	result = []string{mods}
344 324
 
345 325
 	// args for flags with args: The order must match above to keep
346 326
 	// positional arguments in place.
@@ -390,15 +370,15 @@ func (channel *Channel) Join(client *Client, key string) {
390 370
 		return
391 371
 	}
392 372
 
393
-	isInvited := channel.lists[InviteMask].Match(client.nickMaskCasefolded)
394
-	if channel.flags[InviteOnly] && !isInvited {
373
+	isInvited := channel.lists[modes.InviteMask].Match(client.nickMaskCasefolded)
374
+	if channel.flags[modes.InviteOnly] && !isInvited {
395 375
 		client.Send(nil, client.server.name, ERR_INVITEONLYCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "i"))
396 376
 		return
397 377
 	}
398 378
 
399
-	if channel.lists[BanMask].Match(client.nickMaskCasefolded) &&
379
+	if channel.lists[modes.BanMask].Match(client.nickMaskCasefolded) &&
400 380
 		!isInvited &&
401
-		!channel.lists[ExceptMask].Match(client.nickMaskCasefolded) {
381
+		!channel.lists[modes.ExceptMask].Match(client.nickMaskCasefolded) {
402 382
 		client.Send(nil, client.server.name, ERR_BANNEDFROMCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "b"))
403 383
 		return
404 384
 	}
@@ -423,11 +403,11 @@ func (channel *Channel) Join(client *Client, key string) {
423 403
 
424 404
 	// give channel mode if necessary
425 405
 	newChannel := firstJoin && !channel.IsRegistered()
426
-	var givenMode *Mode
406
+	var givenMode *modes.Mode
427 407
 	if client.AccountName() == channel.registeredFounder {
428
-		givenMode = &ChannelFounder
408
+		givenMode = &modes.ChannelFounder
429 409
 	} else if newChannel {
430
-		givenMode = &ChannelOperator
410
+		givenMode = &modes.ChannelOperator
431 411
 	}
432 412
 	if givenMode != nil {
433 413
 		channel.stateMutex.Lock()
@@ -492,12 +472,12 @@ func (channel *Channel) SendTopic(client *Client) {
492 472
 
493 473
 // SetTopic sets the topic of this channel, if the client is allowed to do so.
494 474
 func (channel *Channel) SetTopic(client *Client, topic string) {
495
-	if !(client.flags[Operator] || channel.hasClient(client)) {
475
+	if !(client.flags[modes.Operator] || channel.hasClient(client)) {
496 476
 		client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
497 477
 		return
498 478
 	}
499 479
 
500
-	if channel.HasMode(OpOnlyTopic) && !channel.ClientIsAtLeast(client, ChannelOperator) {
480
+	if channel.HasMode(modes.OpOnlyTopic) && !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
501 481
 		client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
502 482
 		return
503 483
 	}
@@ -525,32 +505,32 @@ func (channel *Channel) CanSpeak(client *Client) bool {
525 505
 	defer channel.stateMutex.RUnlock()
526 506
 
527 507
 	_, hasClient := channel.members[client]
528
-	if channel.flags[NoOutside] && !hasClient {
508
+	if channel.flags[modes.NoOutside] && !hasClient {
529 509
 		return false
530 510
 	}
531
-	if channel.flags[Moderated] && !channel.ClientIsAtLeast(client, Voice) {
511
+	if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
532 512
 		return false
533 513
 	}
534
-	if channel.flags[RegisteredOnly] && client.account == &NoAccount {
514
+	if channel.flags[modes.RegisteredOnly] && client.account == &NoAccount {
535 515
 		return false
536 516
 	}
537 517
 	return true
538 518
 }
539 519
 
540 520
 // TagMsg sends a tag message to everyone in this channel who can accept them.
541
-func (channel *Channel) TagMsg(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) {
521
+func (channel *Channel) TagMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) {
542 522
 	channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil)
543 523
 }
544 524
 
545 525
 // sendMessage sends a given message to everyone on this channel.
546
-func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) {
526
+func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) {
547 527
 	if !channel.CanSpeak(client) {
548 528
 		client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
549 529
 		return
550 530
 	}
551 531
 
552 532
 	// for STATUSMSG
553
-	var minPrefixMode Mode
533
+	var minPrefixMode modes.Mode
554 534
 	if minPrefix != nil {
555 535
 		minPrefixMode = *minPrefix
556 536
 	}
@@ -587,23 +567,23 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capab
587 567
 }
588 568
 
589 569
 // SplitPrivMsg sends a private message to everyone in this channel.
590
-func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
570
+func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
591 571
 	channel.sendSplitMessage(msgid, "PRIVMSG", minPrefix, clientOnlyTags, client, &message)
592 572
 }
593 573
 
594 574
 // SplitNotice sends a private message to everyone in this channel.
595
-func (channel *Channel) SplitNotice(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
575
+func (channel *Channel) SplitNotice(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
596 576
 	channel.sendSplitMessage(msgid, "NOTICE", minPrefix, clientOnlyTags, client, &message)
597 577
 }
598 578
 
599
-func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) {
579
+func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) {
600 580
 	if !channel.CanSpeak(client) {
601 581
 		client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
602 582
 		return
603 583
 	}
604 584
 
605 585
 	// for STATUSMSG
606
-	var minPrefixMode Mode
586
+	var minPrefixMode modes.Mode
607 587
 	if minPrefix != nil {
608 588
 		minPrefixMode = *minPrefix
609 589
 	}
@@ -628,8 +608,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *Mode, cli
628 608
 	}
629 609
 }
630 610
 
631
-func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
632
-	op ModeOp, nick string) *ModeChange {
611
+func (channel *Channel) applyModeMemberNoMutex(client *Client, mode modes.Mode, op modes.ModeOp, nick string) *modes.ModeChange {
633 612
 	if nick == "" {
634 613
 		//TODO(dan): shouldn't this be handled before it reaches this function?
635 614
 		client.Send(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", client.t("Not enough parameters"))
@@ -647,7 +626,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
647 626
 	modeset, exists := channel.members[target]
648 627
 	var already bool
649 628
 	if exists {
650
-		enable := op == Add
629
+		enable := op == modes.Add
651 630
 		already = modeset[mode] == enable
652 631
 		modeset[mode] = enable
653 632
 	}
@@ -659,25 +638,25 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
659 638
 	} else if already {
660 639
 		return nil
661 640
 	} else {
662
-		return &ModeChange{
663
-			op:   op,
664
-			mode: mode,
665
-			arg:  nick,
641
+		return &modes.ModeChange{
642
+			Op:   op,
643
+			Mode: mode,
644
+			Arg:  nick,
666 645
 		}
667 646
 	}
668 647
 }
669 648
 
670 649
 // ShowMaskList shows the given list to the client.
671
-func (channel *Channel) ShowMaskList(client *Client, mode Mode) {
650
+func (channel *Channel) ShowMaskList(client *Client, mode modes.Mode) {
672 651
 	// choose appropriate modes
673 652
 	var rpllist, rplendoflist string
674
-	if mode == BanMask {
653
+	if mode == modes.BanMask {
675 654
 		rpllist = RPL_BANLIST
676 655
 		rplendoflist = RPL_ENDOFBANLIST
677
-	} else if mode == ExceptMask {
656
+	} else if mode == modes.ExceptMask {
678 657
 		rpllist = RPL_EXCEPTLIST
679 658
 		rplendoflist = RPL_ENDOFEXCEPTLIST
680
-	} else if mode == InviteMask {
659
+	} else if mode == modes.InviteMask {
681 660
 		rpllist = RPL_INVITELIST
682 661
 		rplendoflist = RPL_ENDOFINVITELIST
683 662
 	}
@@ -693,28 +672,28 @@ func (channel *Channel) ShowMaskList(client *Client, mode Mode) {
693 672
 	client.Send(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list"))
694 673
 }
695 674
 
696
-func (channel *Channel) applyModeMask(client *Client, mode Mode, op ModeOp, mask string) bool {
675
+func (channel *Channel) applyModeMask(client *Client, mode modes.Mode, op modes.ModeOp, mask string) bool {
697 676
 	list := channel.lists[mode]
698 677
 	if list == nil {
699 678
 		// This should never happen, but better safe than panicky.
700 679
 		return false
701 680
 	}
702 681
 
703
-	if (op == List) || (mask == "") {
682
+	if (op == modes.List) || (mask == "") {
704 683
 		channel.ShowMaskList(client, mode)
705 684
 		return false
706 685
 	}
707 686
 
708
-	if !channel.ClientIsAtLeast(client, ChannelOperator) {
687
+	if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
709 688
 		client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
710 689
 		return false
711 690
 	}
712 691
 
713
-	if op == Add {
692
+	if op == modes.Add {
714 693
 		return list.Add(mask)
715 694
 	}
716 695
 
717
-	if op == Remove {
696
+	if op == modes.Remove {
718 697
 		return list.Remove(mask)
719 698
 	}
720 699
 
@@ -732,11 +711,11 @@ func (channel *Channel) Quit(client *Client) {
732 711
 }
733 712
 
734 713
 func (channel *Channel) Kick(client *Client, target *Client, comment string) {
735
-	if !(client.flags[Operator] || channel.hasClient(client)) {
714
+	if !(client.flags[modes.Operator] || channel.hasClient(client)) {
736 715
 		client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
737 716
 		return
738 717
 	}
739
-	if !channel.ClientIsAtLeast(client, ChannelOperator) {
718
+	if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
740 719
 		client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
741 720
 		return
742 721
 	}
@@ -765,7 +744,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string) {
765 744
 
766 745
 // Invite invites the given client to the channel, if the inviter can do so.
767 746
 func (channel *Channel) Invite(invitee *Client, inviter *Client) {
768
-	if channel.flags[InviteOnly] && !channel.ClientIsAtLeast(inviter, ChannelOperator) {
747
+	if channel.flags[modes.InviteOnly] && !channel.ClientIsAtLeast(inviter, modes.ChannelOperator) {
769 748
 		inviter.Send(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, channel.name, inviter.t("You're not a channel operator"))
770 749
 		return
771 750
 	}
@@ -776,15 +755,15 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
776 755
 	}
777 756
 
778 757
 	//TODO(dan): handle this more nicely, keep a list of last X invited channels on invitee rather than explicitly modifying the invite list?
779
-	if channel.flags[InviteOnly] {
758
+	if channel.flags[modes.InviteOnly] {
780 759
 		nmc := invitee.NickCasefolded()
781 760
 		channel.stateMutex.Lock()
782
-		channel.lists[InviteMask].Add(nmc)
761
+		channel.lists[modes.InviteMask].Add(nmc)
783 762
 		channel.stateMutex.Unlock()
784 763
 	}
785 764
 
786 765
 	for _, member := range channel.Members() {
787
-		if member.capabilities.Has(caps.InviteNotify) && member != inviter && member != invitee && channel.ClientIsAtLeast(member, Halfop) {
766
+		if member.capabilities.Has(caps.InviteNotify) && member != inviter && member != invitee && channel.ClientIsAtLeast(member, modes.Halfop) {
788 767
 			member.Send(nil, inviter.NickMaskString(), "INVITE", invitee.Nick(), channel.name)
789 768
 		}
790 769
 	}
@@ -792,7 +771,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
792 771
 	//TODO(dan): should inviter.server.name here be inviter.nickMaskString ?
793 772
 	inviter.Send(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name)
794 773
 	invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name)
795
-	if invitee.flags[Away] {
774
+	if invitee.flags[modes.Away] {
796 775
 		inviter.Send(nil, inviter.server.name, RPL_AWAY, invitee.nick, invitee.awayMessage)
797 776
 	}
798 777
 }

+ 3
- 2
irc/chanserv.go View File

@@ -8,6 +8,7 @@ import (
8 8
 	"strings"
9 9
 
10 10
 	"github.com/goshuirc/irc-go/ircfmt"
11
+	"github.com/oragono/oragono/irc/modes"
11 12
 	"github.com/oragono/oragono/irc/sno"
12 13
 )
13 14
 
@@ -55,7 +56,7 @@ func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
55 56
 		}
56 57
 
57 58
 		channelInfo := server.channels.Get(channelKey)
58
-		if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, ChannelOperator) {
59
+		if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) {
59 60
 			client.ChanServNotice(client.t("You must be an oper on the channel to register it"))
60 61
 			return
61 62
 		}
@@ -81,7 +82,7 @@ func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
81 82
 		server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
82 83
 
83 84
 		// give them founder privs
84
-		change := channelInfo.applyModeMemberNoMutex(client, ChannelFounder, Add, client.NickCasefolded())
85
+		change := channelInfo.applyModeMemberNoMutex(client, modes.ChannelFounder, modes.Add, client.NickCasefolded())
85 86
 		if change != nil {
86 87
 			//TODO(dan): we should change the name of String and make it return a slice here
87 88
 			//TODO(dan): unify this code with code in modes.go

+ 5
- 4
irc/client.go View File

@@ -21,6 +21,7 @@ import (
21 21
 	"github.com/goshuirc/irc-go/ircmsg"
22 22
 	ident "github.com/oragono/go-ident"
23 23
 	"github.com/oragono/oragono/irc/caps"
24
+	"github.com/oragono/oragono/irc/modes"
24 25
 	"github.com/oragono/oragono/irc/sno"
25 26
 	"github.com/oragono/oragono/irc/utils"
26 27
 )
@@ -50,7 +51,7 @@ type Client struct {
50 51
 	class              *OperClass
51 52
 	ctime              time.Time
52 53
 	exitedSnomaskSent  bool
53
-	flags              map[Mode]bool
54
+	flags              map[modes.Mode]bool
54 55
 	hasQuit            bool
55 56
 	hops               int
56 57
 	hostname           string
@@ -95,7 +96,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
95 96
 		capVersion:     caps.Cap301,
96 97
 		channels:       make(ChannelSet),
97 98
 		ctime:          now,
98
-		flags:          make(map[Mode]bool),
99
+		flags:          make(map[modes.Mode]bool),
99 100
 		server:         server,
100 101
 		socket:         &socket,
101 102
 		account:        &NoAccount,
@@ -107,7 +108,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
107 108
 
108 109
 	client.recomputeMaxlens()
109 110
 	if isTLS {
110
-		client.flags[TLS] = true
111
+		client.flags[modes.TLS] = true
111 112
 
112 113
 		// error is not useful to us here anyways so we can ignore it
113 114
 		client.certfp, _ = client.socket.CertFP()
@@ -348,7 +349,7 @@ func (client *Client) TryResume() {
348 349
 		return
349 350
 	}
350 351
 
351
-	if !oldClient.HasMode(TLS) || !client.HasMode(TLS) {
352
+	if !oldClient.HasMode(modes.TLS) || !client.HasMode(modes.TLS) {
352 353
 		client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must have TLS"))
353 354
 		return
354 355
 	}

+ 5
- 2
irc/commands.go View File

@@ -5,7 +5,10 @@
5 5
 
6 6
 package irc
7 7
 
8
-import "github.com/goshuirc/irc-go/ircmsg"
8
+import (
9
+	"github.com/goshuirc/irc-go/ircmsg"
10
+	"github.com/oragono/oragono/irc/modes"
11
+)
9 12
 
10 13
 // Command represents a command accepted from a client.
11 14
 type Command struct {
@@ -24,7 +27,7 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
24 27
 		client.Send(nil, server.name, ERR_NOTREGISTERED, client.nick, client.t("You need to register before you can use that command"))
25 28
 		return false
26 29
 	}
27
-	if cmd.oper && !client.flags[Operator] {
30
+	if cmd.oper && !client.flags[modes.Operator] {
28 31
 		client.Send(nil, server.name, ERR_NOPRIVILEGES, client.nick, client.t("Permission Denied - You're not an IRC operator"))
29 32
 		return false
30 33
 	}

+ 3
- 2
irc/gateways.go View File

@@ -10,6 +10,7 @@ import (
10 10
 	"fmt"
11 11
 	"net"
12 12
 
13
+	"github.com/oragono/oragono/irc/modes"
13 14
 	"github.com/oragono/oragono/irc/passwd"
14 15
 	"github.com/oragono/oragono/irc/utils"
15 16
 )
@@ -82,9 +83,9 @@ func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (exiting bool)
82 83
 	// set tls info
83 84
 	client.certfp = ""
84 85
 	if tls {
85
-		client.flags[TLS] = true
86
+		client.flags[modes.TLS] = true
86 87
 	} else {
87
-		delete(client.flags, TLS)
88
+		delete(client.flags, modes.TLS)
88 89
 	}
89 90
 
90 91
 	return false

+ 8
- 5
irc/getters.go View File

@@ -3,7 +3,10 @@
3 3
 
4 4
 package irc
5 5
 
6
-import "github.com/oragono/oragono/irc/isupport"
6
+import (
7
+	"github.com/oragono/oragono/irc/isupport"
8
+	"github.com/oragono/oragono/irc/modes"
9
+)
7 10
 
8 11
 func (server *Server) ISupport() *isupport.List {
9 12
 	server.configurableStateMutex.RLock()
@@ -41,7 +44,7 @@ func (server *Server) WebIRCConfig() []webircConfig {
41 44
 	return server.webirc
42 45
 }
43 46
 
44
-func (server *Server) DefaultChannelModes() Modes {
47
+func (server *Server) DefaultChannelModes() modes.Modes {
45 48
 	server.configurableStateMutex.RLock()
46 49
 	defer server.configurableStateMutex.RUnlock()
47 50
 	return server.defaultChannelModes
@@ -107,7 +110,7 @@ func (client *Client) AccountName() string {
107 110
 	return client.account.Name
108 111
 }
109 112
 
110
-func (client *Client) HasMode(mode Mode) bool {
113
+func (client *Client) HasMode(mode modes.Mode) bool {
111 114
 	client.stateMutex.RLock()
112 115
 	defer client.stateMutex.RUnlock()
113 116
 	return client.flags[mode]
@@ -180,7 +183,7 @@ func (channel *Channel) setKey(key string) {
180 183
 	channel.stateMutex.Unlock()
181 184
 }
182 185
 
183
-func (channel *Channel) HasMode(mode Mode) bool {
186
+func (channel *Channel) HasMode(mode modes.Mode) bool {
184 187
 	channel.stateMutex.RLock()
185 188
 	defer channel.stateMutex.RUnlock()
186 189
 	return channel.flags[mode]
@@ -193,7 +196,7 @@ func (channel *Channel) Founder() string {
193 196
 }
194 197
 
195 198
 // set a channel mode, return whether it was already set
196
-func (channel *Channel) setMode(mode Mode, enable bool) (already bool) {
199
+func (channel *Channel) setMode(mode modes.Mode, enable bool) (already bool) {
197 200
 	channel.stateMutex.Lock()
198 201
 	already = (channel.flags[mode] == enable)
199 202
 	if !already {

+ 48
- 47
irc/handlers.go View File

@@ -27,6 +27,7 @@ import (
27 27
 	"github.com/goshuirc/irc-go/ircmsg"
28 28
 	"github.com/oragono/oragono/irc/caps"
29 29
 	"github.com/oragono/oragono/irc/custime"
30
+	"github.com/oragono/oragono/irc/modes"
30 31
 	"github.com/oragono/oragono/irc/passwd"
31 32
 	"github.com/oragono/oragono/irc/sno"
32 33
 	"github.com/oragono/oragono/irc/utils"
@@ -480,30 +481,30 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
480 481
 	}
481 482
 
482 483
 	if isAway {
483
-		client.flags[Away] = true
484
+		client.flags[modes.Away] = true
484 485
 	} else {
485
-		delete(client.flags, Away)
486
+		delete(client.flags, modes.Away)
486 487
 	}
487 488
 	client.awayMessage = text
488 489
 
489
-	var op ModeOp
490
-	if client.flags[Away] {
491
-		op = Add
490
+	var op modes.ModeOp
491
+	if client.flags[modes.Away] {
492
+		op = modes.Add
492 493
 		client.Send(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
493 494
 	} else {
494
-		op = Remove
495
+		op = modes.Remove
495 496
 		client.Send(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
496 497
 	}
497 498
 	//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
498
-	modech := ModeChanges{ModeChange{
499
-		mode: Away,
500
-		op:   op,
499
+	modech := modes.ModeChanges{modes.ModeChange{
500
+		Mode: modes.Away,
501
+		Op:   op,
501 502
 	}}
502 503
 	client.Send(nil, server.name, "MODE", client.nick, modech.String())
503 504
 
504 505
 	// dispatch away-notify
505 506
 	for friend := range client.Friends(caps.AwayNotify) {
506
-		if client.flags[Away] {
507
+		if client.flags[modes.Away] {
507 508
 			friend.SendFromClient("", client, nil, "AWAY", client.awayMessage)
508 509
 		} else {
509 510
 			friend.SendFromClient("", client, nil, "AWAY")
@@ -581,7 +582,7 @@ func csHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
581 582
 
582 583
 // DEBUG GCSTATS/NUMGOROUTINE/etc
583 584
 func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
584
-	if !client.flags[Operator] {
585
+	if !client.flags[modes.Operator] {
585 586
 		return false
586 587
 	}
587 588
 
@@ -854,7 +855,7 @@ Get an explanation of <argument>, or "index" for a list of help topics.`))
854 855
 
855 856
 	// handle index
856 857
 	if argument == "index" {
857
-		if client.flags[Operator] {
858
+		if client.flags[modes.Operator] {
858 859
 			client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndexOpers))
859 860
 		} else {
860 861
 			client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndex))
@@ -864,7 +865,7 @@ Get an explanation of <argument>, or "index" for a list of help topics.`))
864 865
 
865 866
 	helpHandler, exists := Help[argument]
866 867
 
867
-	if exists && (!helpHandler.oper || (helpHandler.oper && client.flags[Operator])) {
868
+	if exists && (!helpHandler.oper || (helpHandler.oper && client.flags[modes.Operator])) {
868 869
 		if helpHandler.textGenerator != nil {
869 870
 			client.sendHelp(strings.ToUpper(argument), client.t(helpHandler.textGenerator(client)))
870 871
 		} else {
@@ -1336,7 +1337,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1336 1337
 
1337 1338
 	if len(channels) == 0 {
1338 1339
 		for _, channel := range server.channels.Channels() {
1339
-			if !client.flags[Operator] && channel.flags[Secret] {
1340
+			if !client.flags[modes.Operator] && channel.flags[modes.Secret] {
1340 1341
 				continue
1341 1342
 			}
1342 1343
 			if matcher.Matches(channel) {
@@ -1345,14 +1346,14 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1345 1346
 		}
1346 1347
 	} else {
1347 1348
 		// limit regular users to only listing one channel
1348
-		if !client.flags[Operator] {
1349
+		if !client.flags[modes.Operator] {
1349 1350
 			channels = channels[:1]
1350 1351
 		}
1351 1352
 
1352 1353
 		for _, chname := range channels {
1353 1354
 			casefoldedChname, err := CasefoldChannel(chname)
1354 1355
 			channel := server.channels.Get(casefoldedChname)
1355
-			if err != nil || channel == nil || (!client.flags[Operator] && channel.flags[Secret]) {
1356
+			if err != nil || channel == nil || (!client.flags[modes.Operator] && channel.flags[modes.Secret]) {
1356 1357
 				if len(chname) > 0 {
1357 1358
 					client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, client.t("No such channel"))
1358 1359
 				}
@@ -1374,10 +1375,10 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1374 1375
 
1375 1376
 	for _, onlineusers := range server.clients.AllClients() {
1376 1377
 		totalcount++
1377
-		if onlineusers.flags[Invisible] {
1378
+		if onlineusers.flags[modes.Invisible] {
1378 1379
 			invisiblecount++
1379 1380
 		}
1380
-		if onlineusers.flags[Operator] {
1381
+		if onlineusers.flags[modes.Operator] {
1381 1382
 			opercount++
1382 1383
 		}
1383 1384
 	}
@@ -1409,7 +1410,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1409 1410
 	}
1410 1411
 
1411 1412
 	// applied mode changes
1412
-	applied := make(ModeChanges, 0)
1413
+	applied := make(modes.ModeChanges, 0)
1413 1414
 
1414 1415
 	if 1 < len(msg.Params) {
1415 1416
 		// parse out real mode changes
@@ -1431,11 +1432,11 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1431 1432
 	// save changes to banlist/exceptlist/invexlist
1432 1433
 	var banlistUpdated, exceptlistUpdated, invexlistUpdated bool
1433 1434
 	for _, change := range applied {
1434
-		if change.mode == BanMask {
1435
+		if change.Mode == modes.BanMask {
1435 1436
 			banlistUpdated = true
1436
-		} else if change.mode == ExceptMask {
1437
+		} else if change.Mode == modes.ExceptMask {
1437 1438
 			exceptlistUpdated = true
1438
-		} else if change.mode == InviteMask {
1439
+		} else if change.Mode == modes.InviteMask {
1439 1440
 			invexlistUpdated = true
1440 1441
 		}
1441 1442
 	}
@@ -1483,12 +1484,12 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1483 1484
 	}
1484 1485
 
1485 1486
 	// applied mode changes
1486
-	applied := make(ModeChanges, 0)
1487
+	applied := make(modes.ModeChanges, 0)
1487 1488
 
1488 1489
 	if 1 < len(msg.Params) {
1489 1490
 		// parse out real mode changes
1490 1491
 		params := msg.Params[1:]
1491
-		changes, unknown := ParseUserModeChanges(params...)
1492
+		changes, unknown := modes.ParseUserModeChanges(params...)
1492 1493
 
1493 1494
 		// alert for unknown mode changes
1494 1495
 		for char := range unknown {
@@ -1506,7 +1507,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1506 1507
 		client.Send(nil, client.nickMaskString, "MODE", targetNick, applied.String())
1507 1508
 	} else if hasPrivs {
1508 1509
 		client.Send(nil, target.nickMaskString, RPL_UMODEIS, targetNick, target.ModeString())
1509
-		if client.flags[LocalOperator] || client.flags[Operator] {
1510
+		if client.flags[modes.LocalOperator] || client.flags[modes.Operator] {
1510 1511
 			masks := server.snomasks.String(client)
1511 1512
 			if 0 < len(masks) {
1512 1513
 				client.Send(nil, target.nickMaskString, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
@@ -1687,8 +1688,8 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1687 1688
 		if i > maxTargets-1 {
1688 1689
 			break
1689 1690
 		}
1690
-		prefixes, targetString := SplitChannelMembershipPrefixes(targetString)
1691
-		lowestPrefix := GetLowestChannelModePrefix(prefixes)
1691
+		prefixes, targetString := modes.SplitChannelMembershipPrefixes(targetString)
1692
+		lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
1692 1693
 
1693 1694
 		target, cerr := CasefoldChannel(targetString)
1694 1695
 		if cerr == nil {
@@ -1727,7 +1728,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1727 1728
 			msgid := server.generateMessageID()
1728 1729
 			// restrict messages appropriately when +R is set
1729 1730
 			// intentionally make the sending user think the message went through fine
1730
-			if !user.flags[RegisteredOnly] || client.registered {
1731
+			if !user.flags[modes.RegisteredOnly] || client.registered {
1731 1732
 				user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
1732 1733
 			}
1733 1734
 			if client.capabilities.Has(caps.EchoMessage) {
@@ -1788,7 +1789,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1788 1789
 		client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
1789 1790
 		return true
1790 1791
 	}
1791
-	if client.flags[Operator] == true {
1792
+	if client.flags[modes.Operator] == true {
1792 1793
 		client.Send(nil, server.name, ERR_UNKNOWNERROR, "OPER", client.t("You're already opered-up!"))
1793 1794
 		return false
1794 1795
 	}
@@ -1803,7 +1804,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1803 1804
 		return true
1804 1805
 	}
1805 1806
 
1806
-	client.flags[Operator] = true
1807
+	client.flags[modes.Operator] = true
1807 1808
 	client.operName = name
1808 1809
 	client.class = oper.Class
1809 1810
 	client.whoisLine = oper.WhoisLine
@@ -1819,9 +1820,9 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1819 1820
 	}
1820 1821
 
1821 1822
 	// set new modes
1822
-	var applied ModeChanges
1823
+	var applied modes.ModeChanges
1823 1824
 	if 0 < len(oper.Modes) {
1824
-		modeChanges, unknownChanges := ParseUserModeChanges(strings.Split(oper.Modes, " ")...)
1825
+		modeChanges, unknownChanges := modes.ParseUserModeChanges(strings.Split(oper.Modes, " ")...)
1825 1826
 		applied = client.applyUserModeChanges(true, modeChanges)
1826 1827
 		if 0 < len(unknownChanges) {
1827 1828
 			var runes string
@@ -1834,9 +1835,9 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1834 1835
 
1835 1836
 	client.Send(nil, server.name, RPL_YOUREOPER, client.nick, client.t("You are now an IRC operator"))
1836 1837
 
1837
-	applied = append(applied, ModeChange{
1838
-		mode: Operator,
1839
-		op:   Add,
1838
+	applied = append(applied, modes.ModeChange{
1839
+		Mode: modes.Operator,
1840
+		Op:   modes.Add,
1840 1841
 	})
1841 1842
 	client.Send(nil, server.name, "MODE", client.nick, applied.String())
1842 1843
 
@@ -1912,8 +1913,8 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
1912 1913
 		if i > maxTargets-1 {
1913 1914
 			break
1914 1915
 		}
1915
-		prefixes, targetString := SplitChannelMembershipPrefixes(targetString)
1916
-		lowestPrefix := GetLowestChannelModePrefix(prefixes)
1916
+		prefixes, targetString := modes.SplitChannelMembershipPrefixes(targetString)
1917
+		lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
1917 1918
 
1918 1919
 		// eh, no need to notify them
1919 1920
 		if len(targetString) < 1 {
@@ -1955,13 +1956,13 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
1955 1956
 			msgid := server.generateMessageID()
1956 1957
 			// restrict messages appropriately when +R is set
1957 1958
 			// intentionally make the sending user think the message went through fine
1958
-			if !user.flags[RegisteredOnly] || client.registered {
1959
+			if !user.flags[modes.RegisteredOnly] || client.registered {
1959 1960
 				user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
1960 1961
 			}
1961 1962
 			if client.capabilities.Has(caps.EchoMessage) {
1962 1963
 				client.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
1963 1964
 			}
1964
-			if user.flags[Away] {
1965
+			if user.flags[modes.Away] {
1965 1966
 				//TODO(dan): possibly implement cooldown of away notifications to users
1966 1967
 				client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
1967 1968
 			}
@@ -2059,7 +2060,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (resul
2059 2060
 		return
2060 2061
 	}
2061 2062
 	//TODO(dan): allow IRCops to do this?
2062
-	if !channel.ClientIsAtLeast(client, Operator) {
2063
+	if !channel.ClientIsAtLeast(client, modes.Operator) {
2063 2064
 		errorResponse(RenamePrivsNeeded, oldName)
2064 2065
 		return
2065 2066
 	}
@@ -2171,8 +2172,8 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
2171 2172
 		if i > maxTargets-1 {
2172 2173
 			break
2173 2174
 		}
2174
-		prefixes, targetString := SplitChannelMembershipPrefixes(targetString)
2175
-		lowestPrefix := GetLowestChannelModePrefix(prefixes)
2175
+		prefixes, targetString := modes.SplitChannelMembershipPrefixes(targetString)
2176
+		lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
2176 2177
 
2177 2178
 		// eh, no need to notify them
2178 2179
 		if len(targetString) < 1 {
@@ -2212,7 +2213,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
2212 2213
 			if client.capabilities.Has(caps.EchoMessage) {
2213 2214
 				client.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick)
2214 2215
 			}
2215
-			if user.flags[Away] {
2216
+			if user.flags[modes.Away] {
2216 2217
 				//TODO(dan): possibly implement cooldown of away notifications to users
2217 2218
 				client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
2218 2219
 			}
@@ -2424,10 +2425,10 @@ func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
2424 2425
 
2425 2426
 		var isOper, isAway string
2426 2427
 
2427
-		if target.flags[Operator] {
2428
+		if target.flags[modes.Operator] {
2428 2429
 			isOper = "*"
2429 2430
 		}
2430
-		if target.flags[Away] {
2431
+		if target.flags[modes.Away] {
2431 2432
 			isAway = "-"
2432 2433
 		} else {
2433 2434
 			isAway = "+"
@@ -2478,7 +2479,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
2478 2479
 			lkey := strings.ToLower(key)
2479 2480
 			if lkey == "tls" || lkey == "secure" {
2480 2481
 				// only accept "tls" flag if the gateway's connection to us is secure as well
2481
-				if client.flags[TLS] || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
2482
+				if client.flags[modes.TLS] || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
2482 2483
 					secure = true
2483 2484
 				}
2484 2485
 			}
@@ -2567,7 +2568,7 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
2567 2568
 		return false
2568 2569
 	}
2569 2570
 
2570
-	if client.flags[Operator] {
2571
+	if client.flags[modes.Operator] {
2571 2572
 		masks := strings.Split(masksString, ",")
2572 2573
 		for _, mask := range masks {
2573 2574
 			casefoldedMask, err := Casefold(mask)

+ 88
- 327
irc/modes.go View File

@@ -9,298 +9,59 @@ import (
9 9
 	"strconv"
10 10
 	"strings"
11 11
 
12
+	"github.com/oragono/oragono/irc/modes"
12 13
 	"github.com/oragono/oragono/irc/sno"
13 14
 )
14 15
 
15
-// ModeOp is an operation performed with modes
16
-type ModeOp rune
17
-
18
-func (op ModeOp) String() string {
19
-	return string(op)
20
-}
21
-
22
-const (
23
-	// Add is used when adding the given key.
24
-	Add ModeOp = '+'
25
-	// List is used when listing modes (for instance, listing the current bans on a channel).
26
-	List ModeOp = '='
27
-	// Remove is used when taking away the given key.
28
-	Remove ModeOp = '-'
29
-)
30
-
31
-// Mode represents a user/channel/server mode
32
-type Mode rune
33
-
34
-func (mode Mode) String() string {
35
-	return string(mode)
36
-}
37
-
38
-// ModeChange is a single mode changing
39
-type ModeChange struct {
40
-	mode Mode
41
-	op   ModeOp
42
-	arg  string
43
-}
44
-
45
-func (change *ModeChange) String() (str string) {
46
-	if (change.op == Add) || (change.op == Remove) {
47
-		str = change.op.String()
48
-	}
49
-	str += change.mode.String()
50
-	if change.arg != "" {
51
-		str += " " + change.arg
52
-	}
53
-	return
54
-}
55
-
56
-// ModeChanges are a collection of 'ModeChange's
57
-type ModeChanges []ModeChange
58
-
59
-func (changes ModeChanges) String() string {
60
-	if len(changes) == 0 {
61
-		return ""
62
-	}
63
-
64
-	op := changes[0].op
65
-	str := changes[0].op.String()
66
-
67
-	for _, change := range changes {
68
-		if change.op != op {
69
-			op = change.op
70
-			str += change.op.String()
71
-		}
72
-		str += change.mode.String()
73
-	}
74
-
75
-	for _, change := range changes {
76
-		if change.arg == "" {
77
-			continue
78
-		}
79
-		str += " " + change.arg
80
-	}
81
-	return str
82
-}
83
-
84
-// Modes is just a raw list of modes
85
-type Modes []Mode
86
-
87
-func (modes Modes) String() string {
88
-	strs := make([]string, len(modes))
89
-	for index, mode := range modes {
90
-		strs[index] = mode.String()
91
-	}
92
-	return strings.Join(strs, "")
93
-}
94
-
95
-// User Modes
96
-const (
97
-	Away            Mode = 'a'
98
-	Bot             Mode = 'B'
99
-	Invisible       Mode = 'i'
100
-	LocalOperator   Mode = 'O'
101
-	Operator        Mode = 'o'
102
-	Restricted      Mode = 'r'
103
-	RegisteredOnly  Mode = 'R'
104
-	ServerNotice    Mode = 's'
105
-	TLS             Mode = 'Z'
106
-	UserRoleplaying Mode = 'E'
107
-	WallOps         Mode = 'w'
108
-)
109
-
110
-var (
111
-	// SupportedUserModes are the user modes that we actually support (modifying).
112
-	SupportedUserModes = Modes{
113
-		Away, Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying,
114
-	}
115
-	// supportedUserModesString acts as a cache for when we introduce users
116
-	supportedUserModesString = SupportedUserModes.String()
117
-)
118
-
119
-// Channel Modes
120
-const (
121
-	BanMask         Mode = 'b' // arg
122
-	ChanRoleplaying Mode = 'E' // flag
123
-	ExceptMask      Mode = 'e' // arg
124
-	InviteMask      Mode = 'I' // arg
125
-	InviteOnly      Mode = 'i' // flag
126
-	Key             Mode = 'k' // flag arg
127
-	Moderated       Mode = 'm' // flag
128
-	NoOutside       Mode = 'n' // flag
129
-	OpOnlyTopic     Mode = 't' // flag
130
-	// RegisteredOnly mode is reused here from umode definition
131
-	Secret    Mode = 's' // flag
132
-	UserLimit Mode = 'l' // flag arg
133
-)
134
-
135 16
 var (
136
-	ChannelFounder  Mode = 'q' // arg
137
-	ChannelAdmin    Mode = 'a' // arg
138
-	ChannelOperator Mode = 'o' // arg
139
-	Halfop          Mode = 'h' // arg
140
-	Voice           Mode = 'v' // arg
141
-
142
-	// SupportedChannelModes are the channel modes that we support.
143
-	SupportedChannelModes = Modes{
144
-		BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
145
-		Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, UserLimit,
146
-	}
147
-	// supportedChannelModesString acts as a cache for when we introduce users
148
-	supportedChannelModesString = SupportedChannelModes.String()
149
-
150 17
 	// DefaultChannelModes are enabled on brand new channels when they're created.
151 18
 	// this can be overridden in the `channels` config, with the `default-modes` key
152
-	DefaultChannelModes = Modes{
153
-		NoOutside, OpOnlyTopic,
154
-	}
155
-
156
-	// ChannelPrivModes holds the list of modes that are privileged, ie founder/op/halfop, in order.
157
-	// voice is not in this list because it cannot perform channel operator actions.
158
-	ChannelPrivModes = Modes{
159
-		ChannelFounder, ChannelAdmin, ChannelOperator, Halfop,
160
-	}
161
-
162
-	ChannelModePrefixes = map[Mode]string{
163
-		ChannelFounder:  "~",
164
-		ChannelAdmin:    "&",
165
-		ChannelOperator: "@",
166
-		Halfop:          "%",
167
-		Voice:           "+",
19
+	DefaultChannelModes = modes.Modes{
20
+		modes.NoOutside, modes.OpOnlyTopic,
168 21
 	}
169 22
 )
170 23
 
171
-//
172
-// channel membership prefixes
173
-//
174
-
175
-// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
176
-func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
177
-	name = target
178
-	for {
179
-		if len(name) > 0 && strings.Contains("~&@%+", string(name[0])) {
180
-			prefixes += string(name[0])
181
-			name = name[1:]
182
-		} else {
183
-			break
184
-		}
185
-	}
186
-
187
-	return prefixes, name
188
-}
189
-
190
-// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
191
-func GetLowestChannelModePrefix(prefixes string) *Mode {
192
-	var lowest *Mode
193
-
194
-	if strings.Contains(prefixes, "+") {
195
-		lowest = &Voice
196
-	} else {
197
-		for i, mode := range ChannelPrivModes {
198
-			if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
199
-				lowest = &ChannelPrivModes[i]
200
-			}
201
-		}
202
-	}
203
-
204
-	return lowest
205
-}
206
-
207
-//
208
-// commands
209
-//
210
-
211
-// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
212
-func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
213
-	changes := make(ModeChanges, 0)
214
-	unknown := make(map[rune]bool)
215
-
216
-	op := List
217
-
218
-	if 0 < len(params) {
219
-		modeArg := params[0]
220
-		skipArgs := 1
221
-
222
-		for _, mode := range modeArg {
223
-			if mode == '-' || mode == '+' {
224
-				op = ModeOp(mode)
225
-				continue
226
-			}
227
-			change := ModeChange{
228
-				mode: Mode(mode),
229
-				op:   op,
230
-			}
231
-
232
-			// put arg into modechange if needed
233
-			switch Mode(mode) {
234
-			case ServerNotice:
235
-				// always require arg
236
-				if len(params) > skipArgs {
237
-					change.arg = params[skipArgs]
238
-					skipArgs++
239
-				} else {
240
-					continue
241
-				}
242
-			}
243
-
244
-			var isKnown bool
245
-			for _, supportedMode := range SupportedUserModes {
246
-				if rune(supportedMode) == mode {
247
-					isKnown = true
248
-					break
249
-				}
250
-			}
251
-			if !isKnown {
252
-				unknown[mode] = true
253
-				continue
254
-			}
255
-
256
-			changes = append(changes, change)
257
-		}
258
-	}
259
-
260
-	return changes, unknown
261
-}
262
-
263 24
 // applyUserModeChanges applies the given changes, and returns the applied changes.
264
-func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) ModeChanges {
265
-	applied := make(ModeChanges, 0)
25
+func (client *Client) applyUserModeChanges(force bool, changes modes.ModeChanges) modes.ModeChanges {
26
+	applied := make(modes.ModeChanges, 0)
266 27
 
267 28
 	for _, change := range changes {
268
-		switch change.mode {
269
-		case Bot, Invisible, WallOps, UserRoleplaying, Operator, LocalOperator, RegisteredOnly:
270
-			switch change.op {
271
-			case Add:
272
-				if !force && (change.mode == Operator || change.mode == LocalOperator) {
29
+		switch change.Mode {
30
+		case modes.Bot, modes.Invisible, modes.WallOps, modes.UserRoleplaying, modes.Operator, modes.LocalOperator, modes.RegisteredOnly:
31
+			switch change.Op {
32
+			case modes.Add:
33
+				if !force && (change.Mode == modes.Operator || change.Mode == modes.LocalOperator) {
273 34
 					continue
274 35
 				}
275 36
 
276
-				if client.flags[change.mode] {
37
+				if client.flags[change.Mode] {
277 38
 					continue
278 39
 				}
279
-				client.flags[change.mode] = true
40
+				client.flags[change.Mode] = true
280 41
 				applied = append(applied, change)
281 42
 
282
-			case Remove:
283
-				if !client.flags[change.mode] {
43
+			case modes.Remove:
44
+				if !client.flags[change.Mode] {
284 45
 					continue
285 46
 				}
286
-				delete(client.flags, change.mode)
47
+				delete(client.flags, change.Mode)
287 48
 				applied = append(applied, change)
288 49
 			}
289 50
 
290
-		case ServerNotice:
291
-			if !client.flags[Operator] {
51
+		case modes.ServerNotice:
52
+			if !client.flags[modes.Operator] {
292 53
 				continue
293 54
 			}
294 55
 			var masks []sno.Mask
295
-			if change.op == Add || change.op == Remove {
296
-				for _, char := range change.arg {
56
+			if change.Op == modes.Add || change.Op == modes.Remove {
57
+				for _, char := range change.Arg {
297 58
 					masks = append(masks, sno.Mask(char))
298 59
 				}
299 60
 			}
300
-			if change.op == Add {
61
+			if change.Op == modes.Add {
301 62
 				client.server.snomasks.AddMasks(client, masks...)
302 63
 				applied = append(applied, change)
303
-			} else if change.op == Remove {
64
+			} else if change.Op == modes.Remove {
304 65
 				client.server.snomasks.RemoveMasks(client, masks...)
305 66
 				applied = append(applied, change)
306 67
 			}
@@ -314,28 +75,28 @@ func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) Mode
314 75
 }
315 76
 
316 77
 // ParseDefaultChannelModes parses the `default-modes` line of the config
317
-func ParseDefaultChannelModes(config *Config) Modes {
78
+func ParseDefaultChannelModes(config *Config) modes.Modes {
318 79
 	if config.Channels.DefaultModes == nil {
319 80
 		// not present in config, fall back to compile-time default
320 81
 		return DefaultChannelModes
321 82
 	}
322 83
 	modeChangeStrings := strings.Split(strings.TrimSpace(*config.Channels.DefaultModes), " ")
323 84
 	modeChanges, _ := ParseChannelModeChanges(modeChangeStrings...)
324
-	defaultChannelModes := make(Modes, 0)
85
+	defaultChannelModes := make(modes.Modes, 0)
325 86
 	for _, modeChange := range modeChanges {
326
-		if modeChange.op == Add {
327
-			defaultChannelModes = append(defaultChannelModes, modeChange.mode)
87
+		if modeChange.Op == modes.Add {
88
+			defaultChannelModes = append(defaultChannelModes, modeChange.Mode)
328 89
 		}
329 90
 	}
330 91
 	return defaultChannelModes
331 92
 }
332 93
 
333 94
 // ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
334
-func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
335
-	changes := make(ModeChanges, 0)
95
+func ParseChannelModeChanges(params ...string) (modes.ModeChanges, map[rune]bool) {
96
+	changes := make(modes.ModeChanges, 0)
336 97
 	unknown := make(map[rune]bool)
337 98
 
338
-	op := List
99
+	op := modes.List
339 100
 
340 101
 	if 0 < len(params) {
341 102
 		modeArg := params[0]
@@ -343,35 +104,35 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
343 104
 
344 105
 		for _, mode := range modeArg {
345 106
 			if mode == '-' || mode == '+' {
346
-				op = ModeOp(mode)
107
+				op = modes.ModeOp(mode)
347 108
 				continue
348 109
 			}
349
-			change := ModeChange{
350
-				mode: Mode(mode),
351
-				op:   op,
110
+			change := modes.ModeChange{
111
+				Mode: modes.Mode(mode),
112
+				Op:   op,
352 113
 			}
353 114
 
354 115
 			// put arg into modechange if needed
355
-			switch Mode(mode) {
356
-			case BanMask, ExceptMask, InviteMask:
116
+			switch modes.Mode(mode) {
117
+			case modes.BanMask, modes.ExceptMask, modes.InviteMask:
357 118
 				if len(params) > skipArgs {
358
-					change.arg = params[skipArgs]
119
+					change.Arg = params[skipArgs]
359 120
 					skipArgs++
360 121
 				} else {
361
-					change.op = List
122
+					change.Op = modes.List
362 123
 				}
363
-			case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
124
+			case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
364 125
 				if len(params) > skipArgs {
365
-					change.arg = params[skipArgs]
126
+					change.Arg = params[skipArgs]
366 127
 					skipArgs++
367 128
 				} else {
368 129
 					continue
369 130
 				}
370
-			case Key, UserLimit:
131
+			case modes.Key, modes.UserLimit:
371 132
 				// don't require value when removing
372
-				if change.op == Add {
133
+				if change.Op == modes.Add {
373 134
 					if len(params) > skipArgs {
374
-						change.arg = params[skipArgs]
135
+						change.Arg = params[skipArgs]
375 136
 						skipArgs++
376 137
 					} else {
377 138
 						continue
@@ -380,19 +141,19 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
380 141
 			}
381 142
 
382 143
 			var isKnown bool
383
-			for _, supportedMode := range SupportedChannelModes {
144
+			for _, supportedMode := range modes.SupportedChannelModes {
384 145
 				if rune(supportedMode) == mode {
385 146
 					isKnown = true
386 147
 					break
387 148
 				}
388 149
 			}
389
-			for _, supportedMode := range ChannelPrivModes {
150
+			for _, supportedMode := range modes.ChannelPrivModes {
390 151
 				if rune(supportedMode) == mode {
391 152
 					isKnown = true
392 153
 					break
393 154
 				}
394 155
 			}
395
-			if mode == rune(Voice) {
156
+			if mode == rune(modes.Voice) {
396 157
 				isKnown = true
397 158
 			}
398 159
 			if !isKnown {
@@ -408,40 +169,40 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
408 169
 }
409 170
 
410 171
 // ApplyChannelModeChanges applies a given set of mode changes.
411
-func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes ModeChanges) ModeChanges {
172
+func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes modes.ModeChanges) modes.ModeChanges {
412 173
 	// so we only output one warning for each list type when full
413
-	listFullWarned := make(map[Mode]bool)
174
+	listFullWarned := make(map[modes.Mode]bool)
414 175
 
415
-	clientIsOp := channel.ClientIsAtLeast(client, ChannelOperator)
176
+	clientIsOp := channel.ClientIsAtLeast(client, modes.ChannelOperator)
416 177
 	var alreadySentPrivError bool
417 178
 
418
-	applied := make(ModeChanges, 0)
179
+	applied := make(modes.ModeChanges, 0)
419 180
 
420
-	isListOp := func(change ModeChange) bool {
421
-		return (change.op == List) || (change.arg == "")
181
+	isListOp := func(change modes.ModeChange) bool {
182
+		return (change.Op == modes.List) || (change.Arg == "")
422 183
 	}
423 184
 
424
-	hasPrivs := func(change ModeChange) bool {
185
+	hasPrivs := func(change modes.ModeChange) bool {
425 186
 		if isSamode {
426 187
 			return true
427 188
 		}
428
-		switch change.mode {
429
-		case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
189
+		switch change.Mode {
190
+		case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
430 191
 			// Admins can't give other people Admin or remove it from others
431
-			if change.mode == ChannelAdmin {
192
+			if change.Mode == modes.ChannelAdmin {
432 193
 				return false
433 194
 			}
434
-			if change.op == List {
195
+			if change.Op == modes.List {
435 196
 				return true
436 197
 			}
437
-			cfarg, _ := CasefoldName(change.arg)
438
-			if change.op == Remove && cfarg == client.nickCasefolded {
198
+			cfarg, _ := CasefoldName(change.Arg)
199
+			if change.Op == modes.Remove && cfarg == client.nickCasefolded {
439 200
 				// "There is no restriction, however, on anyone `deopping' themselves"
440 201
 				// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
441 202
 				return true
442 203
 			}
443
-			return channel.ClientIsAtLeast(client, change.mode)
444
-		case BanMask:
204
+			return channel.ClientIsAtLeast(client, change.Mode)
205
+		case modes.BanMask:
445 206
 			// #163: allow unprivileged users to list ban masks
446 207
 			return clientIsOp || isListOp(change)
447 208
 		default:
@@ -458,77 +219,77 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
458 219
 			continue
459 220
 		}
460 221
 
461
-		switch change.mode {
462
-		case BanMask, ExceptMask, InviteMask:
222
+		switch change.Mode {
223
+		case modes.BanMask, modes.ExceptMask, modes.InviteMask:
463 224
 			if isListOp(change) {
464
-				channel.ShowMaskList(client, change.mode)
225
+				channel.ShowMaskList(client, change.Mode)
465 226
 				continue
466 227
 			}
467 228
 
468 229
 			// confirm mask looks valid
469
-			mask, err := Casefold(change.arg)
230
+			mask, err := Casefold(change.Arg)
470 231
 			if err != nil {
471 232
 				continue
472 233
 			}
473 234
 
474
-			switch change.op {
475
-			case Add:
476
-				if channel.lists[change.mode].Length() >= client.server.Limits().ChanListModes {
477
-					if !listFullWarned[change.mode] {
478
-						client.Send(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.mode.String(), client.t("Channel list is full"))
479
-						listFullWarned[change.mode] = true
235
+			switch change.Op {
236
+			case modes.Add:
237
+				if channel.lists[change.Mode].Length() >= client.server.Limits().ChanListModes {
238
+					if !listFullWarned[change.Mode] {
239
+						client.Send(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.Mode.String(), client.t("Channel list is full"))
240
+						listFullWarned[change.Mode] = true
480 241
 					}
481 242
 					continue
482 243
 				}
483 244
 
484
-				channel.lists[change.mode].Add(mask)
245
+				channel.lists[change.Mode].Add(mask)
485 246
 				applied = append(applied, change)
486 247
 
487
-			case Remove:
488
-				channel.lists[change.mode].Remove(mask)
248
+			case modes.Remove:
249
+				channel.lists[change.Mode].Remove(mask)
489 250
 				applied = append(applied, change)
490 251
 			}
491 252
 
492
-		case UserLimit:
493
-			switch change.op {
494
-			case Add:
495
-				val, err := strconv.ParseUint(change.arg, 10, 64)
253
+		case modes.UserLimit:
254
+			switch change.Op {
255
+			case modes.Add:
256
+				val, err := strconv.ParseUint(change.Arg, 10, 64)
496 257
 				if err == nil {
497 258
 					channel.setUserLimit(val)
498 259
 					applied = append(applied, change)
499 260
 				}
500 261
 
501
-			case Remove:
262
+			case modes.Remove:
502 263
 				channel.setUserLimit(0)
503 264
 				applied = append(applied, change)
504 265
 			}
505 266
 
506
-		case Key:
507
-			switch change.op {
508
-			case Add:
509
-				channel.setKey(change.arg)
267
+		case modes.Key:
268
+			switch change.Op {
269
+			case modes.Add:
270
+				channel.setKey(change.Arg)
510 271
 
511
-			case Remove:
272
+			case modes.Remove:
512 273
 				channel.setKey("")
513 274
 			}
514 275
 			applied = append(applied, change)
515 276
 
516
-		case InviteOnly, Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, ChanRoleplaying:
517
-			if change.op == List {
277
+		case modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.RegisteredOnly, modes.Secret, modes.ChanRoleplaying:
278
+			if change.Op == modes.List {
518 279
 				continue
519 280
 			}
520 281
 
521
-			already := channel.setMode(change.mode, change.op == Add)
282
+			already := channel.setMode(change.Mode, change.Op == modes.Add)
522 283
 			if !already {
523 284
 				applied = append(applied, change)
524 285
 			}
525 286
 
526
-		case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
527
-			if change.op == List {
287
+		case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
288
+			if change.Op == modes.List {
528 289
 				continue
529 290
 			}
530 291
 
531
-			change := channel.applyModeMemberNoMutex(client, change.mode, change.op, change.arg)
292
+			change := channel.applyModeMemberNoMutex(client, change.Mode, change.Op, change.Arg)
532 293
 			if change != nil {
533 294
 				applied = append(applied, *change)
534 295
 			}

+ 286
- 0
irc/modes/modes.go View File

@@ -0,0 +1,286 @@
1
+// Copyright (c) 2012-2014 Jeremy Latt
2
+// Copyright (c) 2014-2015 Edmund Huber
3
+// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
4
+// released under the MIT license
5
+
6
+package modes
7
+
8
+import (
9
+	"strings"
10
+)
11
+
12
+var (
13
+	// SupportedUserModes are the user modes that we actually support (modifying).
14
+	SupportedUserModes = Modes{
15
+		Away, Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying,
16
+	}
17
+
18
+	// SupportedChannelModes are the channel modes that we support.
19
+	SupportedChannelModes = Modes{
20
+		BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
21
+		Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, UserLimit,
22
+	}
23
+)
24
+
25
+// ModeOp is an operation performed with modes
26
+type ModeOp rune
27
+
28
+func (op ModeOp) String() string {
29
+	return string(op)
30
+}
31
+
32
+const (
33
+	// Add is used when adding the given key.
34
+	Add ModeOp = '+'
35
+	// List is used when listing modes (for instance, listing the current bans on a channel).
36
+	List ModeOp = '='
37
+	// Remove is used when taking away the given key.
38
+	Remove ModeOp = '-'
39
+)
40
+
41
+// Mode represents a user/channel/server mode
42
+type Mode rune
43
+
44
+func (mode Mode) String() string {
45
+	return string(mode)
46
+}
47
+
48
+// ModeChange is a single mode changing
49
+type ModeChange struct {
50
+	Mode Mode
51
+	Op   ModeOp
52
+	Arg  string
53
+}
54
+
55
+func (change *ModeChange) String() (str string) {
56
+	if (change.Op == Add) || (change.Op == Remove) {
57
+		str = change.Op.String()
58
+	}
59
+	str += change.Mode.String()
60
+	if change.Arg != "" {
61
+		str += " " + change.Arg
62
+	}
63
+	return
64
+}
65
+
66
+// ModeChanges are a collection of 'ModeChange's
67
+type ModeChanges []ModeChange
68
+
69
+func (changes ModeChanges) String() string {
70
+	if len(changes) == 0 {
71
+		return ""
72
+	}
73
+
74
+	op := changes[0].Op
75
+	str := changes[0].Op.String()
76
+
77
+	for _, change := range changes {
78
+		if change.Op != op {
79
+			op = change.Op
80
+			str += change.Op.String()
81
+		}
82
+		str += change.Mode.String()
83
+	}
84
+
85
+	for _, change := range changes {
86
+		if change.Arg == "" {
87
+			continue
88
+		}
89
+		str += " " + change.Arg
90
+	}
91
+	return str
92
+}
93
+
94
+// Modes is just a raw list of modes
95
+type Modes []Mode
96
+
97
+func (modes Modes) String() string {
98
+	strs := make([]string, len(modes))
99
+	for index, mode := range modes {
100
+		strs[index] = mode.String()
101
+	}
102
+	return strings.Join(strs, "")
103
+}
104
+
105
+// User Modes
106
+const (
107
+	Away            Mode = 'a'
108
+	Bot             Mode = 'B'
109
+	Invisible       Mode = 'i'
110
+	LocalOperator   Mode = 'O'
111
+	Operator        Mode = 'o'
112
+	Restricted      Mode = 'r'
113
+	RegisteredOnly  Mode = 'R'
114
+	ServerNotice    Mode = 's'
115
+	TLS             Mode = 'Z'
116
+	UserRoleplaying Mode = 'E'
117
+	WallOps         Mode = 'w'
118
+)
119
+
120
+// Channel Modes
121
+const (
122
+	BanMask         Mode = 'b' // arg
123
+	ChanRoleplaying Mode = 'E' // flag
124
+	ExceptMask      Mode = 'e' // arg
125
+	InviteMask      Mode = 'I' // arg
126
+	InviteOnly      Mode = 'i' // flag
127
+	Key             Mode = 'k' // flag arg
128
+	Moderated       Mode = 'm' // flag
129
+	NoOutside       Mode = 'n' // flag
130
+	OpOnlyTopic     Mode = 't' // flag
131
+	// RegisteredOnly mode is reused here from umode definition
132
+	Secret    Mode = 's' // flag
133
+	UserLimit Mode = 'l' // flag arg
134
+)
135
+
136
+var (
137
+	ChannelFounder  Mode = 'q' // arg
138
+	ChannelAdmin    Mode = 'a' // arg
139
+	ChannelOperator Mode = 'o' // arg
140
+	Halfop          Mode = 'h' // arg
141
+	Voice           Mode = 'v' // arg
142
+
143
+	// ChannelPrivModes holds the list of modes that are privileged, ie founder/op/halfop, in order.
144
+	// voice is not in this list because it cannot perform channel operator actions.
145
+	ChannelPrivModes = Modes{
146
+		ChannelFounder, ChannelAdmin, ChannelOperator, Halfop,
147
+	}
148
+
149
+	ChannelModePrefixes = map[Mode]string{
150
+		ChannelFounder:  "~",
151
+		ChannelAdmin:    "&",
152
+		ChannelOperator: "@",
153
+		Halfop:          "%",
154
+		Voice:           "+",
155
+	}
156
+)
157
+
158
+//
159
+// channel membership prefixes
160
+//
161
+
162
+// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
163
+func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
164
+	name = target
165
+	for {
166
+		if len(name) > 0 && strings.Contains("~&@%+", string(name[0])) {
167
+			prefixes += string(name[0])
168
+			name = name[1:]
169
+		} else {
170
+			break
171
+		}
172
+	}
173
+
174
+	return prefixes, name
175
+}
176
+
177
+// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
178
+func GetLowestChannelModePrefix(prefixes string) *Mode {
179
+	var lowest *Mode
180
+
181
+	if strings.Contains(prefixes, "+") {
182
+		lowest = &Voice
183
+	} else {
184
+		for i, mode := range ChannelPrivModes {
185
+			if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
186
+				lowest = &ChannelPrivModes[i]
187
+			}
188
+		}
189
+	}
190
+
191
+	return lowest
192
+}
193
+
194
+//
195
+// commands
196
+//
197
+
198
+// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
199
+func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
200
+	changes := make(ModeChanges, 0)
201
+	unknown := make(map[rune]bool)
202
+
203
+	op := List
204
+
205
+	if 0 < len(params) {
206
+		modeArg := params[0]
207
+		skipArgs := 1
208
+
209
+		for _, mode := range modeArg {
210
+			if mode == '-' || mode == '+' {
211
+				op = ModeOp(mode)
212
+				continue
213
+			}
214
+			change := ModeChange{
215
+				Mode: Mode(mode),
216
+				Op:   op,
217
+			}
218
+
219
+			// put arg into modechange if needed
220
+			switch Mode(mode) {
221
+			case ServerNotice:
222
+				// always require arg
223
+				if len(params) > skipArgs {
224
+					change.Arg = params[skipArgs]
225
+					skipArgs++
226
+				} else {
227
+					continue
228
+				}
229
+			}
230
+
231
+			var isKnown bool
232
+			for _, supportedMode := range SupportedUserModes {
233
+				if rune(supportedMode) == mode {
234
+					isKnown = true
235
+					break
236
+				}
237
+			}
238
+			if !isKnown {
239
+				unknown[mode] = true
240
+				continue
241
+			}
242
+
243
+			changes = append(changes, change)
244
+		}
245
+	}
246
+
247
+	return changes, unknown
248
+}
249
+
250
+// ModeSet holds a set of modes.
251
+type ModeSet map[Mode]bool
252
+
253
+// String returns the modes in this set.
254
+func (set ModeSet) String() string {
255
+	if len(set) == 0 {
256
+		return ""
257
+	}
258
+	strs := make([]string, len(set))
259
+	index := 0
260
+	for mode := range set {
261
+		strs[index] = mode.String()
262
+		index++
263
+	}
264
+	return strings.Join(strs, "")
265
+}
266
+
267
+// Prefixes returns a list of prefixes for the given set of channel modes.
268
+func (set ModeSet) Prefixes(isMultiPrefix bool) string {
269
+	var prefixes string
270
+
271
+	// add prefixes in order from highest to lowest privs
272
+	for _, mode := range ChannelPrivModes {
273
+		if set[mode] {
274
+			prefixes += ChannelModePrefixes[mode]
275
+		}
276
+	}
277
+	if set[Voice] {
278
+		prefixes += ChannelModePrefixes[Voice]
279
+	}
280
+
281
+	if !isMultiPrefix && len(prefixes) > 1 {
282
+		prefixes = string(prefixes[0])
283
+	}
284
+
285
+	return prefixes
286
+}

+ 4
- 3
irc/roleplay.go View File

@@ -7,6 +7,7 @@ import (
7 7
 	"fmt"
8 8
 
9 9
 	"github.com/oragono/oragono/irc/caps"
10
+	"github.com/oragono/oragono/irc/modes"
10 11
 )
11 12
 
12 13
 const (
@@ -34,7 +35,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
34 35
 			return
35 36
 		}
36 37
 
37
-		if !channel.flags[ChanRoleplaying] {
38
+		if !channel.flags[modes.ChanRoleplaying] {
38 39
 			client.Send(nil, client.server.name, ERR_CANNOTSENDRP, channel.name, client.t("Channel doesn't have roleplaying mode available"))
39 40
 			return
40 41
 		}
@@ -53,7 +54,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
53 54
 			return
54 55
 		}
55 56
 
56
-		if !user.flags[UserRoleplaying] {
57
+		if !user.flags[modes.UserRoleplaying] {
57 58
 			client.Send(nil, client.server.name, ERR_CANNOTSENDRP, user.nick, client.t("User doesn't have roleplaying mode enabled"))
58 59
 			return
59 60
 		}
@@ -62,7 +63,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
62 63
 		if client.capabilities.Has(caps.EchoMessage) {
63 64
 			client.Send(nil, source, "PRIVMSG", user.nick, message)
64 65
 		}
65
-		if user.flags[Away] {
66
+		if user.flags[modes.Away] {
66 67
 			//TODO(dan): possibly implement cooldown of away notifications to users
67 68
 			client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
68 69
 		}

+ 21
- 15
irc/server.go View File

@@ -29,6 +29,7 @@ import (
29 29
 	"github.com/oragono/oragono/irc/isupport"
30 30
 	"github.com/oragono/oragono/irc/languages"
31 31
 	"github.com/oragono/oragono/irc/logger"
32
+	"github.com/oragono/oragono/irc/modes"
32 33
 	"github.com/oragono/oragono/irc/passwd"
33 34
 	"github.com/oragono/oragono/irc/sno"
34 35
 	"github.com/oragono/oragono/irc/utils"
@@ -43,6 +44,11 @@ var (
43 44
 	couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line()
44 45
 
45 46
 	RenamePrivsNeeded = errors.New("Only chanops can rename channels")
47
+
48
+	// supportedUserModesString acts as a cache for when we introduce users
49
+	supportedUserModesString = modes.SupportedUserModes.String()
50
+	// supportedChannelModesString acts as a cache for when we introduce users
51
+	supportedChannelModesString = modes.SupportedChannelModes.String()
46 52
 )
47 53
 
48 54
 // Limits holds the maximum limits for various things such as topic lengths.
@@ -89,7 +95,7 @@ type Server struct {
89 95
 	connectionLimiter            *connection_limits.Limiter
90 96
 	connectionThrottler          *connection_limits.Throttler
91 97
 	ctime                        time.Time
92
-	defaultChannelModes          Modes
98
+	defaultChannelModes          modes.Modes
93 99
 	dlines                       *DLineManager
94 100
 	loggingRawIO                 bool
95 101
 	isupport                     *isupport.List
@@ -179,7 +185,7 @@ func (server *Server) setISupport() {
179 185
 	isupport := isupport.NewList()
180 186
 	isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
181 187
 	isupport.Add("CASEMAPPING", "ascii")
182
-	isupport.Add("CHANMODES", strings.Join([]string{Modes{BanMask, ExceptMask, InviteMask}.String(), "", Modes{UserLimit, Key}.String(), Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ","))
188
+	isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), "", modes.Modes{modes.UserLimit, modes.Key}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret}.String()}, ","))
183 189
 	isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
184 190
 	isupport.Add("CHANTYPES", "#")
185 191
 	isupport.Add("ELIST", "U")
@@ -224,7 +230,7 @@ func (server *Server) setISupport() {
224 230
 	server.configurableStateMutex.Unlock()
225 231
 }
226 232
 
227
-func loadChannelList(channel *Channel, list string, maskMode Mode) {
233
+func loadChannelList(channel *Channel, list string, maskMode modes.Mode) {
228 234
 	if list == "" {
229 235
 		return
230 236
 	}
@@ -584,7 +590,7 @@ func (client *Client) WhoisChannelsNames(target *Client) []string {
584 590
 	var chstrs []string
585 591
 	for _, channel := range client.Channels() {
586 592
 		// channel is secret and the target can't see it
587
-		if !target.flags[Operator] && channel.HasMode(Secret) && !channel.hasClient(target) {
593
+		if !target.flags[modes.Operator] && channel.HasMode(modes.Secret) && !channel.hasClient(target) {
588 594
 			continue
589 595
 		}
590 596
 		chstrs = append(chstrs, channel.ClientPrefixes(client, isMultiPrefix)+channel.name)
@@ -605,17 +611,17 @@ func (client *Client) getWhoisOf(target *Client) {
605 611
 	if target.class != nil {
606 612
 		client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine)
607 613
 	}
608
-	if client.flags[Operator] || client == target {
614
+	if client.flags[modes.Operator] || client == target {
609 615
 		client.Send(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), client.t("Actual user@host, Actual IP"))
610 616
 	}
611
-	if target.flags[TLS] {
617
+	if target.flags[modes.TLS] {
612 618
 		client.Send(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
613 619
 	}
614 620
 	accountName := target.AccountName()
615 621
 	if accountName != "" {
616 622
 		client.Send(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, accountName, client.t("is logged in as"))
617 623
 	}
618
-	if target.flags[Bot] {
624
+	if target.flags[modes.Bot] {
619 625
 		client.Send(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
620 626
 	}
621 627
 
@@ -628,7 +634,7 @@ func (client *Client) getWhoisOf(target *Client) {
628 634
 		client.Send(nil, client.server.name, RPL_WHOISLANGUAGE, params...)
629 635
 	}
630 636
 
631
-	if target.certfp != "" && (client.flags[Operator] || client == target) {
637
+	if target.certfp != "" && (client.flags[modes.Operator] || client == target) {
632 638
 		client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), target.certfp))
633 639
 	}
634 640
 	client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
@@ -641,12 +647,12 @@ func (target *Client) rplWhoReply(channel *Channel, client *Client) {
641 647
 	channelName := "*"
642 648
 	flags := ""
643 649
 
644
-	if client.HasMode(Away) {
650
+	if client.HasMode(modes.Away) {
645 651
 		flags = "G"
646 652
 	} else {
647 653
 		flags = "H"
648 654
 	}
649
-	if client.HasMode(Operator) {
655
+	if client.HasMode(modes.Operator) {
650 656
 		flags += "*"
651 657
 	}
652 658
 
@@ -659,7 +665,7 @@ func (target *Client) rplWhoReply(channel *Channel, client *Client) {
659 665
 
660 666
 func whoChannel(client *Client, channel *Channel, friends ClientSet) {
661 667
 	for _, member := range channel.Members() {
662
-		if !client.flags[Invisible] || friends[client] {
668
+		if !client.flags[modes.Invisible] || friends[client] {
663 669
 			client.rplWhoReply(channel, member)
664 670
 		}
665 671
 	}
@@ -1095,7 +1101,7 @@ func (server *Server) setupListeners(config *Config) {
1095 1101
 }
1096 1102
 
1097 1103
 // GetDefaultChannelModes returns our default channel modes.
1098
-func (server *Server) GetDefaultChannelModes() Modes {
1104
+func (server *Server) GetDefaultChannelModes() modes.Modes {
1099 1105
 	server.configurableStateMutex.RLock()
1100 1106
 	defer server.configurableStateMutex.RUnlock()
1101 1107
 	return server.defaultChannelModes
@@ -1130,11 +1136,11 @@ func (matcher *elistMatcher) Matches(channel *Channel) bool {
1130 1136
 func (target *Client) RplList(channel *Channel) {
1131 1137
 	// get the correct number of channel members
1132 1138
 	var memberCount int
1133
-	if target.flags[Operator] || channel.hasClient(target) {
1139
+	if target.flags[modes.Operator] || channel.hasClient(target) {
1134 1140
 		memberCount = len(channel.Members())
1135 1141
 	} else {
1136 1142
 		for _, member := range channel.Members() {
1137
-			if !member.HasMode(Invisible) {
1143
+			if !member.HasMode(modes.Invisible) {
1138 1144
 				memberCount++
1139 1145
 			}
1140 1146
 		}
@@ -1162,7 +1168,7 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1162 1168
 	}
1163 1169
 
1164 1170
 	// limit regular users to only listing one channel
1165
-	if !client.flags[Operator] {
1171
+	if !client.flags[modes.Operator] {
1166 1172
 		channels = channels[:1]
1167 1173
 	}
1168 1174
 

+ 5
- 24
irc/types.go View File

@@ -5,26 +5,7 @@
5 5
 
6 6
 package irc
7 7
 
8
-import (
9
-	"strings"
10
-)
11
-
12
-// ModeSet holds a set of modes.
13
-type ModeSet map[Mode]bool
14
-
15
-// String returns the modes in this set.
16
-func (set ModeSet) String() string {
17
-	if len(set) == 0 {
18
-		return ""
19
-	}
20
-	strs := make([]string, len(set))
21
-	index := 0
22
-	for mode := range set {
23
-		strs[index] = mode.String()
24
-		index++
25
-	}
26
-	return strings.Join(strs, "")
27
-}
8
+import "github.com/oragono/oragono/irc/modes"
28 9
 
29 10
 // ClientSet is a set of clients.
30 11
 type ClientSet map[*Client]bool
@@ -45,11 +26,11 @@ func (clients ClientSet) Has(client *Client) bool {
45 26
 }
46 27
 
47 28
 // MemberSet is a set of members with modes.
48
-type MemberSet map[*Client]ModeSet
29
+type MemberSet map[*Client]modes.ModeSet
49 30
 
50 31
 // Add adds the given client to this set.
51 32
 func (members MemberSet) Add(member *Client) {
52
-	members[member] = make(ModeSet)
33
+	members[member] = make(modes.ModeSet)
53 34
 }
54 35
 
55 36
 // Remove removes the given client from this set.
@@ -64,7 +45,7 @@ func (members MemberSet) Has(member *Client) bool {
64 45
 }
65 46
 
66 47
 // HasMode returns true if the given client is in this set with the given mode.
67
-func (members MemberSet) HasMode(member *Client, mode Mode) bool {
48
+func (members MemberSet) HasMode(member *Client, mode modes.Mode) bool {
68 49
 	modes, ok := members[member]
69 50
 	if !ok {
70 51
 		return false
@@ -73,7 +54,7 @@ func (members MemberSet) HasMode(member *Client, mode Mode) bool {
73 54
 }
74 55
 
75 56
 // AnyHasMode returns true if any of our clients has the given mode.
76
-func (members MemberSet) AnyHasMode(mode Mode) bool {
57
+func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
77 58
 	for _, modes := range members {
78 59
 		if modes[mode] {
79 60
 			return true

Loading…
Cancel
Save