diff --git a/downstream.go b/downstream.go index bcec8b0..2f809ec 100644 --- a/downstream.go +++ b/downstream.go @@ -1157,13 +1157,23 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { Params: params, }) - ch := &Channel{Name: upstreamName, Key: key, Detached: false} - if current, ok := uc.network.channels[ch.Name]; ok && key == "" { + var ch *Channel + var ok bool + if ch, ok = uc.network.channels[upstreamName]; ok { // Don't clear the channel key if there's one set // 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) } } @@ -1185,9 +1195,19 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { } if strings.EqualFold(reason, "detach") { - ch := &Channel{Name: upstreamName, Detached: true} - if err := uc.network.createUpdateChannel(ch); err != nil { - dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err) + var ch *Channel + var ok bool + 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 { params := []string{upstreamName} @@ -1613,6 +1633,8 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { Params: []string{upstreamName, text}, } uc.produce(upstreamName, echoMsg, dc) + + uc.updateChannelAutoDetach(upstreamName) } case "NOTICE": var targetsStr, text string @@ -1636,6 +1658,8 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { Command: "NOTICE", Params: []string{upstreamName, unmarshaledText}, }) + + uc.updateChannelAutoDetach(upstreamName) } case "TAGMSG": var targetsStr string @@ -1658,6 +1682,8 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { Command: "TAGMSG", Params: []string{upstreamName}, }) + + uc.updateChannelAutoDetach(upstreamName) } case "INVITE": var user, channel string diff --git a/upstream.go b/upstream.go index 3a9df76..aaa3d1f 100644 --- a/upstream.go +++ b/upstream.go @@ -50,6 +50,25 @@ type upstreamChannel struct { creationTime string Members map[string]*memberships 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 { @@ -403,14 +422,19 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { if target == uc.nick { 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 && ch.Detached && highlight { - uc.forEachDownstream(func(dc *downstreamConn) { - sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), msg.Prefix.Name, text)) - }) + if ch, ok := uc.network.channels[target]; ok { + if ch.Detached { + uc.handleDetachedMessage(msg.Prefix.Name, text, ch) + } + + 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": var subCmd string @@ -736,6 +760,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { conn: uc, Members: make(map[string]*memberships), } + uc.updateChannelAutoDetach(ch) uc.SendMessage(&irc.Message{ Command: "MODE", @@ -766,7 +791,10 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { for _, ch := range strings.Split(channels, ",") { if msg.Prefix.Name == uc.nick { 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 { ch, err := uc.getChannel(ch) if err != nil { @@ -1408,6 +1436,25 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { 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) { caps := strings.Fields(capsStr) for _, s := range caps { @@ -1701,3 +1748,11 @@ func (uc *upstreamConn) updateAway() { } 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) + } + } +} diff --git a/user.go b/user.go index b1d0b65..6d54ffc 100644 --- a/user.go +++ b/user.go @@ -48,6 +48,11 @@ type eventDownstreamDisconnected struct { dc *downstreamConn } +type eventChannelDetach struct { + uc *upstreamConn + name string +} + type eventStop struct{} type networkHistory struct { @@ -176,56 +181,59 @@ func (net *network) stop() { } } -func (net *network) createUpdateChannel(ch *Channel) error { - if current, ok := net.channels[ch.Name]; ok { - ch.ID = current.ID // update channel if it already exists +func (net *network) detach(ch *Channel) { + if ch.Detached { + return } - if err := net.user.srv.db.StoreChannel(net.ID, ch); err != nil { - return err - } - prev := net.channels[ch.Name] - net.channels[ch.Name] = ch + ch.Detached = true + net.user.srv.Logger.Printf("network %q: detaching channel %q", net.GetName(), ch.Name) - if prev != nil && prev.Detached != ch.Detached { - history := net.history[ch.Name] - if ch.Detached { - 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) - } - }) + if net.conn != nil { + if uch, ok := net.conn.channels[ch.Name]; ok { + uch.updateAutoDetach(0) } } - 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 { @@ -233,6 +241,12 @@ func (net *network) deleteChannel(name string) error { if !ok { 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 { return err } @@ -398,6 +412,16 @@ func (u *user) run() { if err := uc.handleMessage(msg); err != nil { 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: dc := e.dc @@ -475,6 +499,10 @@ func (u *user) handleUpstreamDisconnected(uc *upstreamConn) { uc.endPendingLISTs(true) + for _, uch := range uc.channels { + uch.updateAutoDetach(0) + } + uc.forEachDownstream(func(dc *downstreamConn) { dc.updateSupportedCaps() })