~emersion/public-inbox

soju: Add support for the upstream echo-message capability v3 APPLIED

delthas: 1
 Add support for the upstream echo-message capability

 2 files changed, 57 insertions(+), 34 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/public-inbox/patches/30957/mbox | git am -3
Learn more about email & git

[PATCH soju v3] Add support for the upstream echo-message capability Export this patch

This adds support for upstream echo-message. This capability is
enabled when the upstream supports labeled-response.

When it is enabled, we don't echo downstream messages in the downstream
handler, but rather wait for the upstream to echo it, to produce it to
downstreams.

When it is disabled, we keep the same behaviour as before: produce the
message to all downstreams as soon as it is received from the
downstream.

In other words, the main functional difference is that when the upstream
supports labeled-response, the client will now receive an echo for its
messages when the server acknowledges them, rather than when soju acks
them.

Additionally, uc.produce was refactored to take an ID rather than a
downstream.
---
 downstream.go | 43 ++++++++++++++++++++++++-------------------
 upstream.go   | 48 +++++++++++++++++++++++++++++++++---------------
 2 files changed, 57 insertions(+), 34 deletions(-)

diff --git a/downstream.go b/downstream.go
index d90de79..12165af 100644
--- a/downstream.go
+++ b/downstream.go
@@ -2523,27 +2523,32 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
				Params:  upstreamParams,
			})

			echoParams := []string{upstreamName}
			if msg.Command != "TAGMSG" {
				echoParams = append(echoParams, text)
			}
			// If the upstream supports echo message, we'll produce the message
			// when it is echoed from the upstream.
			// Otherwise, produce/log it here because it's the last time we'll see it.
			if !uc.caps.IsEnabled("echo-message") {
				echoParams := []string{upstreamName}
				if msg.Command != "TAGMSG" {
					echoParams = append(echoParams, text)
				}

			echoTags := tags.Copy()
			echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
			if uc.account != "" {
				echoTags["account"] = irc.TagValue(uc.account)
			}
			echoMsg := &irc.Message{
				Tags: echoTags,
				Prefix: &irc.Prefix{
					Name: uc.nick,
					User: uc.username,
					Host: uc.hostname,
				},
				Command: msg.Command,
				Params:  echoParams,
				echoTags := tags.Copy()
				echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
				if uc.account != "" {
					echoTags["account"] = irc.TagValue(uc.account)
				}
				echoMsg := &irc.Message{
					Tags: echoTags,
					Prefix: &irc.Prefix{
						Name: uc.nick,
						User: uc.username,
						Host: uc.hostname,
					},
					Command: msg.Command,
					Params:  echoParams,
				}
				uc.produce(upstreamName, echoMsg, dc.id)
			}
			uc.produce(upstreamName, echoMsg, dc)

			uc.updateChannelAutoDetach(upstreamName)
		}
diff --git a/upstream.go b/upstream.go
index 37dfd9f..9ec0687 100644
--- a/upstream.go
+++ b/upstream.go
@@ -484,15 +484,17 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
		}

		if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
			uc.produce("", msg, nil)
			uc.produce("", msg, 0)
		} else { // regular user message
			target := entity
			if uc.isOurNick(target) {
				target = msg.Prefix.Name
			}

			self := uc.isOurNick(msg.Prefix.Name)

			ch := uc.network.channels.Value(target)
			if ch != nil && msg.Command != "TAGMSG" {
			if ch != nil && msg.Command != "TAGMSG" && !self {
				if ch.Detached {
					uc.handleDetachedMessage(ctx, ch, msg)
				}
@@ -503,7 +505,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
				}
			}

			uc.produce(target, msg, nil)
			uc.produce(target, msg, downstreamID)
		}
	case "CAP":
		var subCmd string
@@ -526,7 +528,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
				break // wait to receive all capabilities
			}

			uc.requestCaps(ctx)
			uc.updateCaps(ctx)

			if uc.requestSASL() {
				break // we'll send CAP END after authentication is completed
@@ -543,7 +545,14 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
			caps := strings.Fields(subParams[0])

			for _, name := range caps {
				if err := uc.handleCapAck(ctx, strings.ToLower(name), subCmd == "ACK"); err != nil {
				var enable bool
				if strings.HasPrefix(name, "-") {
					name = strings.TrimPrefix(name, "-")
					enable = false
				} else {
					enable = subCmd == "ACK"
				}
				if err := uc.handleCapAck(ctx, strings.ToLower(name), enable); err != nil {
					return err
				}
			}
@@ -558,7 +567,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
				return newNeedMoreParamsError(msg.Command)
			}
			uc.handleSupportedCaps(subParams[0])
			uc.requestCaps(ctx)
			uc.updateCaps(ctx)
		case "DEL":
			if len(subParams) < 1 {
				return newNeedMoreParamsError(msg.Command)
@@ -1006,7 +1015,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err

			chMsg := msg.Copy()
			chMsg.Params[0] = ch
			uc.produce(ch, chMsg, nil)
			uc.produce(ch, chMsg, 0)
		}
	case "PART":
		var channels string
@@ -1032,7 +1041,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err

			chMsg := msg.Copy()
			chMsg.Params[0] = ch
			uc.produce(ch, chMsg, nil)
			uc.produce(ch, chMsg, 0)
		}
	case "KICK":
		var channel, user string
@@ -1051,7 +1060,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
			ch.Members.Delete(user)
		}

		uc.produce(channel, msg, nil)
		uc.produce(channel, msg, 0)
	case "QUIT":
		if uc.isOurNick(msg.Prefix.Name) {
			uc.logger.Printf("quit")
@@ -1101,7 +1110,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
		} else {
			ch.Topic = ""
		}
		uc.produce(ch.Name, msg, nil)
		uc.produce(ch.Name, msg, 0)
	case "MODE":
		var name, modeStr string
		if err := parseMessageParams(msg, &name, &modeStr); err != nil {
@@ -1846,7 +1855,7 @@ func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
	}
}

func (uc *upstreamConn) requestCaps(ctx context.Context) {
func (uc *upstreamConn) updateCaps(ctx context.Context) {
	var requestCaps []string
	for c := range permanentUpstreamCaps {
		if uc.caps.IsAvailable(c) && !uc.caps.IsEnabled(c) {
@@ -1854,6 +1863,13 @@ func (uc *upstreamConn) requestCaps(ctx context.Context) {
		}
	}

	echoMessage := uc.caps.IsAvailable("labeled-response")
	if !uc.caps.IsEnabled("echo-message") && echoMessage {
		requestCaps = append(requestCaps, "echo-message")
	} else if uc.caps.IsEnabled("echo-message") && !echoMessage {
		requestCaps = append(requestCaps, "-echo-message")
	}

	if len(requestCaps) == 0 {
		return
	}
@@ -1919,6 +1935,7 @@ func (uc *upstreamConn) handleCapAck(ctx context.Context, name string, ok bool)
			Command: "AUTHENTICATE",
			Params:  []string{auth.Mechanism},
		})
	case "echo-message":
	default:
		if permanentUpstreamCaps[name] {
			break
@@ -2084,9 +2101,10 @@ func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string
// produce appends a message to the logs and forwards it to connected downstream
// connections.
//
// If origin is not nil and origin doesn't support echo-message, the message is
// forwarded to all connections except origin.
func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
// originID is the id of the downstream (origin) that sent the message. If it is not 0
// and origin doesn't support echo-message, the message is forwarded to all
// connections except origin.
func (uc *upstreamConn) produce(target string, msg *irc.Message, originID uint64) {
	var msgID string
	if target != "" {
		msgID = uc.appendLog(target, msg)
@@ -2097,7 +2115,7 @@ func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstr
	detached := ch != nil && ch.Detached

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

base-commit: 17374f209422b23b71dd63330d6806a4b4fd2ae8
-- 
2.30.0
Pushed, thanks!