~emersion/soju

a2c207d357fe734f57c0f761541a12246c17ebd6 — Simon Ser 6 months ago 76e332b
Relay detached channel backlog as BouncerServ NOTICE if necessary

Instead of ignoring detached channels wehn replaying backlog,
process them as usual and relay messages as BouncerServ NOTICEs
if necessary. Advance the delivery receipts as if the channel was
attached.

Closes: https://todo.sr.ht/~emersion/soju/98
3 files changed, 54 insertions(+), 21 deletions(-)

M downstream.go
M upstream.go
M user.go
M downstream.go => downstream.go +25 -6
@@ 1030,9 1030,8 @@ func (dc *downstreamConn) sendTargetBacklog(net *network, target, msgID string) 
	if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
		return
	}
	if ch := net.channels.Value(target); ch != nil && ch.Detached {
		return
	}

	ch := net.channels.Value(target)

	limit := 4000
	targetCM := net.casemap(target)


@@ 1056,10 1055,16 @@ func (dc *downstreamConn) sendTargetBacklog(net *network, target, msgID string) 
			continue
		}

		if dc.caps["batch"] {
			msg.Tags["batch"] = irc.TagValue(batchRef)
		if ch != nil && ch.Detached {
			if net.detachedMessageNeedsRelay(ch, msg) {
				dc.relayDetachedMessage(net, msg)
			}
		} else {
			if dc.caps["batch"] {
				msg.Tags["batch"] = irc.TagValue(batchRef)
			}
			dc.SendMessage(dc.marshalMessage(msg, net))
		}
		dc.SendMessage(dc.marshalMessage(msg, net))
	}

	if dc.caps["batch"] {


@@ 1071,6 1076,20 @@ func (dc *downstreamConn) sendTargetBacklog(net *network, target, msgID string) 
	}
}

func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
	if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
		return
	}

	sender := msg.Prefix.Name
	target, text := msg.Params[0], msg.Params[1]
	if net.isHighlight(msg) {
		sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
	} else {
		sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
	}
}

func (dc *downstreamConn) runUntilRegistered() error {
	for !dc.registered {
		msg, err := dc.ReadMessage()

M upstream.go => upstream.go +8 -15
@@ 391,10 391,10 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
			ch := uc.network.channels.Value(target)
			if ch != nil {
				if ch.Detached {
					uc.handleDetachedMessage(msg.Prefix.Name, text, ch)
					uc.handleDetachedMessage(ch, msg)
				}

				highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick)
				highlight := uc.network.isHighlight(msg)
				if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
					uc.updateChannelAutoDetach(target)
				}


@@ 1437,18 1437,13 @@ 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) {
func (uc *upstreamConn) handleDetachedMessage(ch *Channel, msg *irc.Message) {
	if uc.network.detachedMessageNeedsRelay(ch, msg) {
		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))
			}
			dc.relayDetachedMessage(uc.network, msg)
		})
	}
	if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && highlight) {
	if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && uc.network.isHighlight(msg)) {
		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)


@@ 1743,12 1738,10 @@ func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstr

	// Don't forward messages if it's a detached channel
	ch := uc.network.channels.Value(target)
	if ch != nil && ch.Detached {
		return
	}
	detached := ch != nil && ch.Detached

	uc.forEachDownstream(func(dc *downstreamConn) {
		if dc != origin || dc.caps["echo-message"] {
		if !detached && (dc != origin || dc.caps["echo-message"]) {
			dc.sendMessageWithID(dc.marshalMessage(msg, uc.network), msgID)
		} else {
			dc.advanceMessageWithID(msg, msgID)

M user.go => user.go +21 -0
@@ 351,6 351,27 @@ func (net *network) storeClientDeliveryReceipts(clientName string) {
	}
}

func (net *network) isHighlight(msg *irc.Message) bool {
	if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
		return false
	}

	text := msg.Params[1]

	nick := net.Nick
	if net.conn != nil {
		nick = net.conn.nick
	}

	// TODO: use case-mapping aware comparison here
	return msg.Prefix.Name != nick && isHighlight(text, nick)
}

func (net *network) detachedMessageNeedsRelay(ch *Channel, msg *irc.Message) bool {
	highlight := net.isHighlight(msg)
	return ch.RelayDetached == FilterMessage || ((ch.RelayDetached == FilterHighlight || ch.RelayDetached == FilterDefault) && highlight)
}

type user struct {
	User
	srv    *Server