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:
parent
81e4930931
commit
732b581eb2
@ -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(),
|
||||||
|
@ -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
52
irc.go
@ -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 {
|
||||||
|
33
upstream.go
33
upstream.go
@ -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{
|
||||||
|
Loading…
Reference in New Issue
Block a user