Add customizable auto-detaching, auto-reattaching, relaying.

This uses the fields added previously to the Channel struct to implement
the actual detaching/reattaching/relaying logic.

The `FilterDefault` values of the messages filters are currently
hardcoded.

The values of the message filters are not currently user-settable.

This introduces a new user event, eventChannelDetach, which stores an
upstreamConn (which might become invalid at the time of processing), and
a channel name, used for auto-detaching. Every time the channel detach
timer is refreshed (by receveing a message, etc.), a new timer is
created on the upstreamChannel, which will dispatch this event after the
duration (and discards the previous timer, if any).
This commit is contained in:
delthas 2020-11-30 22:08:33 +01:00 committed by Simon Ser
parent 939c087754
commit a76b22bf29
3 changed files with 168 additions and 59 deletions

View File

@ -1157,13 +1157,23 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
Params: params, Params: params,
}) })
ch := &Channel{Name: upstreamName, Key: key, Detached: false} var ch *Channel
if current, ok := uc.network.channels[ch.Name]; ok && key == "" { var ok bool
if ch, ok = uc.network.channels[upstreamName]; ok {
// Don't clear the channel key if there's one set // Don't clear the channel key if there's one set
// TODO: add a way to unset the channel key // TODO: add a way to unset the channel key
ch.Key = current.Key if key != "" {
ch.Key = key
}
uc.network.attach(ch)
} else {
ch = &Channel{
Name: upstreamName,
Key: key,
}
uc.network.channels[upstreamName] = ch
} }
if err := uc.network.createUpdateChannel(ch); err != nil { if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err) dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
} }
} }
@ -1185,9 +1195,19 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
} }
if strings.EqualFold(reason, "detach") { if strings.EqualFold(reason, "detach") {
ch := &Channel{Name: upstreamName, Detached: true} var ch *Channel
if err := uc.network.createUpdateChannel(ch); err != nil { var ok bool
dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err) if ch, ok = uc.network.channels[upstreamName]; ok {
uc.network.detach(ch)
} else {
ch = &Channel{
Name: name,
Detached: true,
}
uc.network.channels[upstreamName] = ch
}
if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
} }
} else { } else {
params := []string{upstreamName} params := []string{upstreamName}
@ -1613,6 +1633,8 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
Params: []string{upstreamName, text}, Params: []string{upstreamName, text},
} }
uc.produce(upstreamName, echoMsg, dc) uc.produce(upstreamName, echoMsg, dc)
uc.updateChannelAutoDetach(upstreamName)
} }
case "NOTICE": case "NOTICE":
var targetsStr, text string var targetsStr, text string
@ -1636,6 +1658,8 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
Command: "NOTICE", Command: "NOTICE",
Params: []string{upstreamName, unmarshaledText}, Params: []string{upstreamName, unmarshaledText},
}) })
uc.updateChannelAutoDetach(upstreamName)
} }
case "TAGMSG": case "TAGMSG":
var targetsStr string var targetsStr string
@ -1658,6 +1682,8 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
Command: "TAGMSG", Command: "TAGMSG",
Params: []string{upstreamName}, Params: []string{upstreamName},
}) })
uc.updateChannelAutoDetach(upstreamName)
} }
case "INVITE": case "INVITE":
var user, channel string var user, channel string

View File

@ -50,6 +50,25 @@ type upstreamChannel struct {
creationTime string creationTime string
Members map[string]*memberships Members map[string]*memberships
complete bool complete bool
detachTimer *time.Timer
}
func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) {
if uc.detachTimer != nil {
uc.detachTimer.Stop()
uc.detachTimer = nil
}
if dur == 0 {
return
}
uc.detachTimer = time.AfterFunc(dur, func() {
uc.conn.network.user.events <- eventChannelDetach{
uc: uc.conn,
name: uc.Name,
}
})
} }
type upstreamConn struct { type upstreamConn struct {
@ -403,14 +422,19 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
if target == uc.nick { if target == uc.nick {
target = msg.Prefix.Name target = msg.Prefix.Name
} }
uc.produce(target, msg, nil)
highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick) if ch, ok := uc.network.channels[target]; ok {
if ch, ok := uc.network.channels[target]; ok && ch.Detached && highlight { if ch.Detached {
uc.forEachDownstream(func(dc *downstreamConn) { uc.handleDetachedMessage(msg.Prefix.Name, text, ch)
sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), msg.Prefix.Name, text)) }
})
highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick)
if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
uc.updateChannelAutoDetach(target)
}
} }
uc.produce(target, msg, nil)
} }
case "CAP": case "CAP":
var subCmd string var subCmd string
@ -736,6 +760,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
conn: uc, conn: uc,
Members: make(map[string]*memberships), Members: make(map[string]*memberships),
} }
uc.updateChannelAutoDetach(ch)
uc.SendMessage(&irc.Message{ uc.SendMessage(&irc.Message{
Command: "MODE", Command: "MODE",
@ -766,7 +791,10 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
for _, ch := range strings.Split(channels, ",") { for _, ch := range strings.Split(channels, ",") {
if msg.Prefix.Name == uc.nick { if msg.Prefix.Name == uc.nick {
uc.logger.Printf("parted channel %q", ch) uc.logger.Printf("parted channel %q", ch)
delete(uc.channels, ch) if uch, ok := uc.channels[ch]; ok {
delete(uc.channels, ch)
uch.updateAutoDetach(0)
}
} else { } else {
ch, err := uc.getChannel(ch) ch, err := uc.getChannel(ch)
if err != nil { if err != nil {
@ -1408,6 +1436,25 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
return nil return nil
} }
func (uc *upstreamConn) handleDetachedMessage(sender string, text string, ch *Channel) {
highlight := sender != uc.nick && isHighlight(text, uc.nick)
if ch.RelayDetached == FilterMessage || ((ch.RelayDetached == FilterHighlight || ch.RelayDetached == FilterDefault) && highlight) {
uc.forEachDownstream(func(dc *downstreamConn) {
if highlight {
sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), sender, text))
} else {
sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), sender, text))
}
})
}
if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && highlight) {
uc.network.attach(ch)
if err := uc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
uc.logger.Printf("failed to update channel %q: %v", ch.Name, err)
}
}
}
func (uc *upstreamConn) handleSupportedCaps(capsStr string) { func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
caps := strings.Fields(capsStr) caps := strings.Fields(capsStr)
for _, s := range caps { for _, s := range caps {
@ -1701,3 +1748,11 @@ func (uc *upstreamConn) updateAway() {
} }
uc.away = away uc.away = away
} }
func (uc *upstreamConn) updateChannelAutoDetach(name string) {
if uch, ok := uc.channels[name]; ok {
if ch, ok := uc.network.channels[name]; ok && !ch.Detached {
uch.updateAutoDetach(ch.DetachAfter)
}
}
}

118
user.go
View File

@ -48,6 +48,11 @@ type eventDownstreamDisconnected struct {
dc *downstreamConn dc *downstreamConn
} }
type eventChannelDetach struct {
uc *upstreamConn
name string
}
type eventStop struct{} type eventStop struct{}
type networkHistory struct { type networkHistory struct {
@ -176,56 +181,59 @@ func (net *network) stop() {
} }
} }
func (net *network) createUpdateChannel(ch *Channel) error { func (net *network) detach(ch *Channel) {
if current, ok := net.channels[ch.Name]; ok { if ch.Detached {
ch.ID = current.ID // update channel if it already exists return
} }
if err := net.user.srv.db.StoreChannel(net.ID, ch); err != nil { ch.Detached = true
return err net.user.srv.Logger.Printf("network %q: detaching channel %q", net.GetName(), ch.Name)
}
prev := net.channels[ch.Name]
net.channels[ch.Name] = ch
if prev != nil && prev.Detached != ch.Detached { if net.conn != nil {
history := net.history[ch.Name] if uch, ok := net.conn.channels[ch.Name]; ok {
if ch.Detached { uch.updateAutoDetach(0)
net.user.srv.Logger.Printf("network %q: detaching channel %q", net.GetName(), ch.Name)
net.forEachDownstream(func(dc *downstreamConn) {
net.offlineClients[dc.clientName] = struct{}{}
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "PART",
Params: []string{dc.marshalEntity(net, ch.Name), "Detach"},
})
})
} else {
net.user.srv.Logger.Printf("network %q: attaching channel %q", net.GetName(), ch.Name)
var uch *upstreamChannel
if net.conn != nil {
uch = net.conn.channels[ch.Name]
}
net.forEachDownstream(func(dc *downstreamConn) {
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "JOIN",
Params: []string{dc.marshalEntity(net, ch.Name)},
})
if uch != nil {
forwardChannel(dc, uch)
}
if history != nil {
dc.sendNetworkHistory(net)
}
})
} }
} }
return nil net.forEachDownstream(func(dc *downstreamConn) {
net.offlineClients[dc.clientName] = struct{}{}
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "PART",
Params: []string{dc.marshalEntity(net, ch.Name), "Detach"},
})
})
}
func (net *network) attach(ch *Channel) {
if !ch.Detached {
return
}
ch.Detached = false
net.user.srv.Logger.Printf("network %q: attaching channel %q", net.GetName(), ch.Name)
var uch *upstreamChannel
if net.conn != nil {
uch = net.conn.channels[ch.Name]
net.conn.updateChannelAutoDetach(ch.Name)
}
net.forEachDownstream(func(dc *downstreamConn) {
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "JOIN",
Params: []string{dc.marshalEntity(net, ch.Name)},
})
if uch != nil {
forwardChannel(dc, uch)
}
if net.history[ch.Name] != nil {
dc.sendNetworkHistory(net)
}
})
} }
func (net *network) deleteChannel(name string) error { func (net *network) deleteChannel(name string) error {
@ -233,6 +241,12 @@ func (net *network) deleteChannel(name string) error {
if !ok { if !ok {
return fmt.Errorf("unknown channel %q", name) return fmt.Errorf("unknown channel %q", name)
} }
if net.conn != nil {
if uch, ok := net.conn.channels[ch.Name]; ok {
uch.updateAutoDetach(0)
}
}
if err := net.user.srv.db.DeleteChannel(ch.ID); err != nil { if err := net.user.srv.db.DeleteChannel(ch.ID); err != nil {
return err return err
} }
@ -398,6 +412,16 @@ func (u *user) run() {
if err := uc.handleMessage(msg); err != nil { if err := uc.handleMessage(msg); err != nil {
uc.logger.Printf("failed to handle message %q: %v", msg, err) uc.logger.Printf("failed to handle message %q: %v", msg, err)
} }
case eventChannelDetach:
uc, name := e.uc, e.name
c, ok := uc.network.channels[name]
if !ok || c.Detached {
continue
}
uc.network.detach(c)
if err := uc.srv.db.StoreChannel(uc.network.ID, c); err != nil {
u.srv.Logger.Printf("failed to store updated detached channel %q: %v", c.Name, err)
}
case eventDownstreamConnected: case eventDownstreamConnected:
dc := e.dc dc := e.dc
@ -475,6 +499,10 @@ func (u *user) handleUpstreamDisconnected(uc *upstreamConn) {
uc.endPendingLISTs(true) uc.endPendingLISTs(true)
for _, uch := range uc.channels {
uch.updateAutoDetach(0)
}
uc.forEachDownstream(func(dc *downstreamConn) { uc.forEachDownstream(func(dc *downstreamConn) {
dc.updateSupportedCaps() dc.updateSupportedCaps()
}) })