diff options
author | Hubert Hirtz <hubert@hirtz.pm> | 2021-11-04 14:24:59 +0100 |
---|---|---|
committer | Hubert Hirtz <hubert@hirtz.pm> | 2021-11-06 12:26:16 +0100 |
commit | 9a595ffa7d5cec02e9f85ac8915676f4b6eb5e76 (patch) | |
tree | 554d52bd441ea9e09a2957371e7e087195bbbe03 | |
parent | Revert "Show the current channel topic at the top of the timeline" (diff) |
Take mode changes into account
-rw-r--r-- | irc/session.go | 41 | ||||
-rw-r--r-- | irc/tokens.go | 59 |
2 files changed, 98 insertions, 2 deletions
diff --git a/irc/session.go b/irc/session.go index 139e90e..084c6a7 100644 --- a/irc/session.go +++ b/irc/session.go @@ -117,6 +117,7 @@ type Session struct { // ISUPPORT features casemap func(string) string + chanmodes [4]string chantypes string linelen int historyLimit int @@ -877,14 +878,44 @@ func (s *Session) handleRegistered(msg Message) (Event, error) { }, nil } case "MODE": - var channel string - if err := msg.ParseParams(&channel); err != nil { + var channel, mode string + if err := msg.ParseParams(&channel, &mode); err != nil { return nil, err } channelCf := s.Casemap(channel) if c, ok := s.channels[channelCf]; ok { + modeChanges, err := ParseChannelMode(mode, msg.Params[2:], s.chanmodes, s.prefixModes) + if err != nil { + return nil, err + } + for _, change := range modeChanges { + i := strings.IndexByte(s.prefixModes, change.Mode) + if i < 0 { + continue + } + nickCf := s.Casemap(change.Param) + user := s.users[nickCf] + membership, ok := c.Members[user] + if !ok { + continue + } + var newMembership []byte + if change.Enable { + newMembership = append([]byte(membership), s.prefixSymbols[i]) + sort.Slice(newMembership, func(i, j int) bool { + i = strings.IndexByte(s.prefixSymbols, newMembership[i]) + j = strings.IndexByte(s.prefixSymbols, newMembership[j]) + return i < j + }) + } else if j := strings.IndexByte(membership, s.prefixSymbols[i]); j >= 0 { + newMembership = []byte(membership) + newMembership = append(newMembership[:j], newMembership[j+1:]...) + } + c.Members[user] = string(newMembership) + } + s.channels[channelCf] = c return ModeChangeEvent{ Channel: c.Name, Mode: strings.Join(msg.Params[1:], " "), @@ -1169,6 +1200,12 @@ func (s *Session) updateFeatures(features []string) { default: s.casemap = CasemapRFC1459 } + case "CHANMODES": + // We only care about the first four params + types := strings.SplitN(value, ",", 5) + for i := 0; i < len(types) && i < len(s.chanmodes); i++ { + s.chanmodes[i] = types[i] + } case "CHANTYPES": s.chantypes = value case "CHATHISTORY": diff --git a/irc/tokens.go b/irc/tokens.go index a52a764..b28ef12 100644 --- a/irc/tokens.go +++ b/irc/tokens.go @@ -502,3 +502,62 @@ func ParseNameReply(trailing string, prefixes string) (names []Member) { return } + +// Mode types available in the CHANMODES 005 token. +const ( + ModeTypeA int = iota + ModeTypeB + ModeTypeC + ModeTypeD +) + +type ModeChange struct { + Enable bool + Mode byte + Param string +} + +// ParseChannelMode parses a MODE message for a channel, according to the +// CHANMODES of the server. +func ParseChannelMode(mode string, params []string, chanmodes [4]string, membershipModes string) ([]ModeChange, error) { + var changes []ModeChange + enable := true + paramIdx := 0 + for i := 0; i < len(mode); i++ { + m := mode[i] + if m == '+' || m == '-' { + enable = m == '+' + continue + } + modeType := -1 + for t := 0; t < 4; t++ { + if 0 <= strings.IndexByte(chanmodes[t], m) { + modeType = t + break + } + } + if 0 <= strings.IndexByte(membershipModes, m) { + modeType = ModeTypeB + } else if modeType == -1 { + return nil, fmt.Errorf("unknown mode %c", m) + } + // ref: https://modern.ircdocs.horse/#mode-message + if modeType == ModeTypeA || modeType == ModeTypeB || (enable && modeType == ModeTypeC) { + if len(params) <= paramIdx { + return nil, fmt.Errorf("missing mode params") + } + changes = append(changes, ModeChange{ + Enable: enable, + Mode: m, + Param: params[paramIdx], + }) + paramIdx++ + } else { + changes = append(changes, ModeChange{ + Enable: enable, + Mode: m, + }) + } + } + return changes, nil +} |