commit 9a595ff

Hubert Hirtz  ·  2021-11-04 13:24:59 +0000 UTC
parent bf2a5cb
Take mode changes into account
2 files changed,  +98, -2
+39, -2
 1@@ -117,6 +117,7 @@ type Session struct {
 2 
 3 	// ISUPPORT features
 4 	casemap       func(string) string
 5+	chanmodes     [4]string
 6 	chantypes     string
 7 	linelen       int
 8 	historyLimit  int
 9@@ -877,14 +878,44 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
10 			}, nil
11 		}
12 	case "MODE":
13-		var channel string
14-		if err := msg.ParseParams(&channel); err != nil {
15+		var channel, mode string
16+		if err := msg.ParseParams(&channel, &mode); err != nil {
17 			return nil, err
18 		}
19 
20 		channelCf := s.Casemap(channel)
21 
22 		if c, ok := s.channels[channelCf]; ok {
23+			modeChanges, err := ParseChannelMode(mode, msg.Params[2:], s.chanmodes, s.prefixModes)
24+			if err != nil {
25+				return nil, err
26+			}
27+			for _, change := range modeChanges {
28+				i := strings.IndexByte(s.prefixModes, change.Mode)
29+				if i < 0 {
30+					continue
31+				}
32+				nickCf := s.Casemap(change.Param)
33+				user := s.users[nickCf]
34+				membership, ok := c.Members[user]
35+				if !ok {
36+					continue
37+				}
38+				var newMembership []byte
39+				if change.Enable {
40+					newMembership = append([]byte(membership), s.prefixSymbols[i])
41+					sort.Slice(newMembership, func(i, j int) bool {
42+						i = strings.IndexByte(s.prefixSymbols, newMembership[i])
43+						j = strings.IndexByte(s.prefixSymbols, newMembership[j])
44+						return i < j
45+					})
46+				} else if j := strings.IndexByte(membership, s.prefixSymbols[i]); j >= 0 {
47+					newMembership = []byte(membership)
48+					newMembership = append(newMembership[:j], newMembership[j+1:]...)
49+				}
50+				c.Members[user] = string(newMembership)
51+			}
52+			s.channels[channelCf] = c
53 			return ModeChangeEvent{
54 				Channel: c.Name,
55 				Mode:    strings.Join(msg.Params[1:], " "),
56@@ -1169,6 +1200,12 @@ func (s *Session) updateFeatures(features []string) {
57 			default:
58 				s.casemap = CasemapRFC1459
59 			}
60+		case "CHANMODES":
61+			// We only care about the first four params
62+			types := strings.SplitN(value, ",", 5)
63+			for i := 0; i < len(types) && i < len(s.chanmodes); i++ {
64+				s.chanmodes[i] = types[i]
65+			}
66 		case "CHANTYPES":
67 			s.chantypes = value
68 		case "CHATHISTORY":
+59, -0
 1@@ -502,3 +502,62 @@ func ParseNameReply(trailing string, prefixes string) (names []Member) {
 2 
 3 	return
 4 }
 5+
 6+// Mode types available in the CHANMODES 005 token.
 7+const (
 8+	ModeTypeA int = iota
 9+	ModeTypeB
10+	ModeTypeC
11+	ModeTypeD
12+)
13+
14+type ModeChange struct {
15+	Enable bool
16+	Mode   byte
17+	Param  string
18+}
19+
20+// ParseChannelMode parses a MODE message for a channel, according to the
21+// CHANMODES of the server.
22+func ParseChannelMode(mode string, params []string, chanmodes [4]string, membershipModes string) ([]ModeChange, error) {
23+	var changes []ModeChange
24+	enable := true
25+	paramIdx := 0
26+	for i := 0; i < len(mode); i++ {
27+		m := mode[i]
28+		if m == '+' || m == '-' {
29+			enable = m == '+'
30+			continue
31+		}
32+		modeType := -1
33+		for t := 0; t < 4; t++ {
34+			if 0 <= strings.IndexByte(chanmodes[t], m) {
35+				modeType = t
36+				break
37+			}
38+		}
39+		if 0 <= strings.IndexByte(membershipModes, m) {
40+			modeType = ModeTypeB
41+		} else if modeType == -1 {
42+			return nil, fmt.Errorf("unknown mode %c", m)
43+		}
44+		// ref: https://modern.ircdocs.horse/#mode-message
45+		if modeType == ModeTypeA || modeType == ModeTypeB || (enable && modeType == ModeTypeC) {
46+			if len(params) <= paramIdx {
47+				return nil, fmt.Errorf("missing mode params")
48+			}
49+			changes = append(changes, ModeChange{
50+				Enable: enable,
51+				Mode:   m,
52+				Param:  params[paramIdx],
53+			})
54+			paramIdx++
55+		} else {
56+			changes = append(changes, ModeChange{
57+				Enable: enable,
58+				Mode:   m,
59+			})
60+		}
61+	}
62+	return changes, nil
63+}