From 732b581eb2b600d8daefbf29d77b5199ec8671ae Mon Sep 17 00:00:00 2001 From: delthas Date: Thu, 30 Apr 2020 23:39:59 +0200 Subject: [PATCH] Add support for multiple user channel memberships User channel memberships are actually a set of memberships, not a single value. This introduces memberships, a type representing a set of memberships, stored as an array of memberships ordered by descending rank. This also adds multi-prefix to the permanent downstream and upstream capabilities, so that we try to get all possible channel memberships. --- bridge.go | 4 ++-- downstream.go | 26 +++++++++++++++++++------ irc.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++---- upstream.go | 33 ++++++++++++++++++------------- 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/bridge.go b/bridge.go index 33f2f73..c6080af 100644 --- a/bridge.go +++ b/bridge.go @@ -38,8 +38,8 @@ func sendNames(dc *downstreamConn, ch *upstreamChannel) { downstreamName := dc.marshalEntity(ch.conn.network, ch.Name) - for nick, membership := range ch.Members { - s := membership.String() + dc.marshalEntity(ch.conn.network, nick) + for nick, memberships := range ch.Members { + s := memberships.Format(dc) + dc.marshalEntity(ch.conn.network, nick) dc.SendMessage(&irc.Message{ Prefix: dc.srv.prefix(), diff --git a/downstream.go b/downstream.go index b96f510..6030256 100644 --- a/downstream.go +++ b/downstream.go @@ -61,6 +61,13 @@ var permanentDownstreamCaps = map[string]string{ "server-time": "", } +// needAllDownstreamCaps is the list of downstream capabilities that +// require support from all upstreams to be enabled +var needAllDownstreamCaps = map[string]string{ + "away-notify": "", + "multi-prefix": "", +} + type downstreamConn struct { conn @@ -596,15 +603,22 @@ func (dc *downstreamConn) unsetSupportedCap(name string) { } func (dc *downstreamConn) updateSupportedCaps() { - awayNotifySupported := true + supportedCaps := make(map[string]bool) + for cap := range needAllDownstreamCaps { + supportedCaps[cap] = true + } dc.forEachUpstream(func(uc *upstreamConn) { - awayNotifySupported = awayNotifySupported && uc.caps["away-notify"] + for cap, supported := range supportedCaps { + supportedCaps[cap] = supported && uc.caps[cap] + } }) - if awayNotifySupported { - dc.setSupportedCap("away-notify", "") - } else { - dc.unsetSupportedCap("away-notify") + for cap, supported := range supportedCaps { + if supported { + dc.setSupportedCap(cap, needAllDownstreamCaps[cap]) + } else { + dc.unsetSupportedCap(cap) + } } } diff --git a/irc.go b/irc.go index 4f148b8..ea3cc5f 100644 --- a/irc.go +++ b/irc.go @@ -176,11 +176,57 @@ var stdMemberships = []membership{ {'v', '+'}, // voice } -func (m *membership) String() string { - if m == nil { - return "" +// memberships always sorted by descending membership rank +type memberships []membership + +func (m *memberships) Add(availableMemberships []membership, newMembership membership) { + l := *m + i := 0 + for _, availableMembership := range availableMemberships { + if i >= len(l) { + break + } + if l[i] == availableMembership { + if availableMembership == newMembership { + // we already have this membership + return + } + i++ + continue + } + if availableMembership == newMembership { + break + } } - return string(m.Prefix) + // insert newMembership at i + l = append(l, membership{}) + copy(l[i+1:], l[i:]) + l[i] = newMembership + *m = l +} + +func (m *memberships) Remove(oldMembership membership) { + l := *m + for i, currentMembership := range l { + if currentMembership == oldMembership { + *m = append(l[:i], l[i+1:]...) + return + } + } +} + +func (m memberships) Format(dc *downstreamConn) string { + if !dc.caps["multi-prefix"] { + if len(m) == 0 { + return "" + } + return string(m[0].Prefix) + } + prefixes := make([]byte, len(m)) + for i, membership := range m { + prefixes[i] = membership.Prefix + } + return string(prefixes) } func parseMessageParams(msg *irc.Message, out ...*string) error { diff --git a/upstream.go b/upstream.go index 0cd117a..1f281fe 100644 --- a/upstream.go +++ b/upstream.go @@ -24,6 +24,7 @@ var permanentUpstreamCaps = map[string]bool{ "batch": true, "labeled-response": true, "message-tags": true, + "multi-prefix": true, "server-time": true, } @@ -36,7 +37,7 @@ type upstreamChannel struct { Status channelStatus modes channelModes creationTime string - Members map[string]*membership + Members map[string]*memberships complete bool } @@ -229,13 +230,19 @@ func (uc *upstreamConn) trySendLIST(downstreamID uint64) { } } -func (uc *upstreamConn) parseMembershipPrefix(s string) (membership *membership, nick string) { +func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) { + memberships := make(memberships, 0, 4) + i := 0 for _, m := range uc.availableMemberships { - if m.Prefix == s[0] { - return &m, s[1:] + if i >= len(s) { + break + } + if s[i] == m.Prefix { + memberships = append(memberships, m) + i++ } } - return nil, s + return &memberships, s[i:] } func isWordBoundary(r rune) bool { @@ -644,9 +651,9 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { } for _, ch := range uc.channels { - if membership, ok := ch.Members[msg.Prefix.Name]; ok { + if memberships, ok := ch.Members[msg.Prefix.Name]; ok { delete(ch.Members, msg.Prefix.Name) - ch.Members[newNick] = membership + ch.Members[newNick] = memberships uc.appendLog(ch.Name, msg) uc.appendHistory(ch.Name, msg) } @@ -673,7 +680,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { uc.channels[ch] = &upstreamChannel{ Name: ch, conn: uc, - Members: make(map[string]*membership), + Members: make(map[string]*memberships), } uc.SendMessage(&irc.Message{ @@ -939,8 +946,8 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { channel := dc.marshalEntity(uc.network, name) members := splitSpace(members) for i, member := range members { - membership, nick := uc.parseMembershipPrefix(member) - members[i] = membership.String() + dc.marshalEntity(uc.network, nick) + memberships, nick := uc.parseMembershipPrefix(member) + members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick) } memberStr := strings.Join(members, " ") @@ -960,8 +967,8 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { ch.Status = status for _, s := range splitSpace(members) { - membership, nick := uc.parseMembershipPrefix(s) - ch.Members[nick] = membership + memberships, nick := uc.parseMembershipPrefix(s) + ch.Members[nick] = memberships } case irc.RPL_ENDOFNAMES: var name string @@ -1112,7 +1119,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { for i, channel := range channels { prefix, channel := uc.parseMembershipPrefix(channel) channel = dc.marshalEntity(uc.network, channel) - channelList[i] = prefix.String() + channel + channelList[i] = prefix.Format(dc) + channel } channels := strings.Join(channelList, " ") dc.SendMessage(&irc.Message{