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.
This commit is contained in:
delthas 2020-04-30 23:39:59 +02:00 committed by Simon Ser
parent 81e4930931
commit 732b581eb2
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
4 changed files with 92 additions and 25 deletions

View file

@ -38,8 +38,8 @@ func sendNames(dc *downstreamConn, ch *upstreamChannel) {
downstreamName := dc.marshalEntity(ch.conn.network, ch.Name) downstreamName := dc.marshalEntity(ch.conn.network, ch.Name)
for nick, membership := range ch.Members { for nick, memberships := range ch.Members {
s := membership.String() + dc.marshalEntity(ch.conn.network, nick) s := memberships.Format(dc) + dc.marshalEntity(ch.conn.network, nick)
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(), Prefix: dc.srv.prefix(),

View file

@ -61,6 +61,13 @@ var permanentDownstreamCaps = map[string]string{
"server-time": "", "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 { type downstreamConn struct {
conn conn
@ -596,15 +603,22 @@ func (dc *downstreamConn) unsetSupportedCap(name string) {
} }
func (dc *downstreamConn) updateSupportedCaps() { func (dc *downstreamConn) updateSupportedCaps() {
awayNotifySupported := true supportedCaps := make(map[string]bool)
for cap := range needAllDownstreamCaps {
supportedCaps[cap] = true
}
dc.forEachUpstream(func(uc *upstreamConn) { dc.forEachUpstream(func(uc *upstreamConn) {
awayNotifySupported = awayNotifySupported && uc.caps["away-notify"] for cap, supported := range supportedCaps {
supportedCaps[cap] = supported && uc.caps[cap]
}
}) })
if awayNotifySupported { for cap, supported := range supportedCaps {
dc.setSupportedCap("away-notify", "") if supported {
dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
} else { } else {
dc.unsetSupportedCap("away-notify") dc.unsetSupportedCap(cap)
}
} }
} }

52
irc.go
View file

@ -176,11 +176,57 @@ var stdMemberships = []membership{
{'v', '+'}, // voice {'v', '+'}, // voice
} }
func (m *membership) String() string { // memberships always sorted by descending membership rank
if m == nil { 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
}
}
// 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 ""
} }
return string(m.Prefix) 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 { func parseMessageParams(msg *irc.Message, out ...*string) error {

View file

@ -24,6 +24,7 @@ var permanentUpstreamCaps = map[string]bool{
"batch": true, "batch": true,
"labeled-response": true, "labeled-response": true,
"message-tags": true, "message-tags": true,
"multi-prefix": true,
"server-time": true, "server-time": true,
} }
@ -36,7 +37,7 @@ type upstreamChannel struct {
Status channelStatus Status channelStatus
modes channelModes modes channelModes
creationTime string creationTime string
Members map[string]*membership Members map[string]*memberships
complete bool 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 { for _, m := range uc.availableMemberships {
if m.Prefix == s[0] { if i >= len(s) {
return &m, s[1:] break
}
if s[i] == m.Prefix {
memberships = append(memberships, m)
i++
} }
} }
return nil, s return &memberships, s[i:]
} }
func isWordBoundary(r rune) bool { func isWordBoundary(r rune) bool {
@ -644,9 +651,9 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
} }
for _, ch := range uc.channels { 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) delete(ch.Members, msg.Prefix.Name)
ch.Members[newNick] = membership ch.Members[newNick] = memberships
uc.appendLog(ch.Name, msg) uc.appendLog(ch.Name, msg)
uc.appendHistory(ch.Name, msg) uc.appendHistory(ch.Name, msg)
} }
@ -673,7 +680,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
uc.channels[ch] = &upstreamChannel{ uc.channels[ch] = &upstreamChannel{
Name: ch, Name: ch,
conn: uc, conn: uc,
Members: make(map[string]*membership), Members: make(map[string]*memberships),
} }
uc.SendMessage(&irc.Message{ uc.SendMessage(&irc.Message{
@ -939,8 +946,8 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
channel := dc.marshalEntity(uc.network, name) channel := dc.marshalEntity(uc.network, name)
members := splitSpace(members) members := splitSpace(members)
for i, member := range members { for i, member := range members {
membership, nick := uc.parseMembershipPrefix(member) memberships, nick := uc.parseMembershipPrefix(member)
members[i] = membership.String() + dc.marshalEntity(uc.network, nick) members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
} }
memberStr := strings.Join(members, " ") memberStr := strings.Join(members, " ")
@ -960,8 +967,8 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
ch.Status = status ch.Status = status
for _, s := range splitSpace(members) { for _, s := range splitSpace(members) {
membership, nick := uc.parseMembershipPrefix(s) memberships, nick := uc.parseMembershipPrefix(s)
ch.Members[nick] = membership ch.Members[nick] = memberships
} }
case irc.RPL_ENDOFNAMES: case irc.RPL_ENDOFNAMES:
var name string var name string
@ -1112,7 +1119,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
for i, channel := range channels { for i, channel := range channels {
prefix, channel := uc.parseMembershipPrefix(channel) prefix, channel := uc.parseMembershipPrefix(channel)
channel = dc.marshalEntity(uc.network, channel) channel = dc.marshalEntity(uc.network, channel)
channelList[i] = prefix.String() + channel channelList[i] = prefix.Format(dc) + channel
} }
channels := strings.Join(channelList, " ") channels := strings.Join(channelList, " ")
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{