summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--irc/session.go41
-rw-r--r--irc/tokens.go59
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
+}