diff --git a/doc/ext/pre-away.md b/doc/ext/pre-away.md new file mode 100644 index 0000000..1117313 --- /dev/null +++ b/doc/ext/pre-away.md @@ -0,0 +1,91 @@ +--- +title: "`pre-away` Extension" +layout: spec +meta-description: A capability allowing client connections to be marked AWAY during connection registration +work-in-progress: true +copyrights: + - + name: "Shivaram Lingamneni" + period: "2023" + email: "slingamn@cs.stanford.edu" +--- + +## Notes for implementing experimental vendor version + +This is an experimental specification for a vendored extension. + +No guarantees are made regarding the stability of this extension. +Backwards-incompatible changes can be made at any time without prior notice. + +Software implementing this work-in-progress specification MUST NOT use the +unprefixed `pre-away` CAP name. Instead, implementations SHOULD use the +`soju.im/pre-away` CAP name to be interoperable with other software implementing +a compatible work-in-progress version. + +## Introduction + +Some IRC server implementations offer a mode of operation where a single +nickname can be associated with multiple concurrent client connections, or no +client connections. Typically, such implementations are bouncers (i.e., +intermediaries between the client and another server), but some are full server +implementations. + +Such implementations may wish to update publicly visible state depending on the +status of the user's actual client connections. For example, if the user has no +active connections, it may be desirable to mark them as AWAY, then mark them +un-AWAY if they reconnect. However, a client implementation may wish to connect +without active involvement from the user, e.g. to retrieve +[chathistory][], in which case it would be undesirable to suggest +that the user is present. This extension provides a mechanism for such clients +to flag their connections as automatically initiated, so servers can disregard +them for this or other purposes related to user presence. + +## Implementation + +This specification introduces a new capability, `soju.im/pre-away`. Clients +wishing to make use of this specification MUST negotiate the capability; this +gives the server more information about the context and meaning of the client's +`AWAY` commands. + +If the capability has been negotiated, servers MUST accept the `AWAY` command +before connection registration has completed. The `AWAY` command has its normal +semantics, except that servers SHOULD treat the form: + + AWAY * + +i.e. an `AWAY` message consisting of the single character `*`, as indicating +that the user is not present for an unspecified reason. Clients MAY also send +`AWAY *` post-registration to indicate that the user is no longer present for an +unspecified reason. + +In its conventional form: + + AWAY :Gone to lunch. Back in 5 + +the `AWAY` command MAY be used pre-registration to set a human-readable away +message associated with the connection as usual. Similarly, `AWAY` with no +parameters indicates that the user is present. + +If the client's nickname was not already present on the server, then `AWAY` +pre-registration sets the away message but does not inhibit reporting of the +change in nickname status, e.g. via [monitor][]. + +Clients that have negotiated this capability and subsequently receive `*` as an +away message (for example, in `301 RPL_AWAY` or [away-notify][]) +SHOULD treat it as indicating that the user is not present for an unspecified +reason. Servers MAY substitute a human-readable message for the `*` if it would +otherwise be relayed as an away message. + +## Implementation considerations + +This section is non-normative. + +In general, the server-side aggregation of away statuses across multiple +connections is outside the scope of this specification. However, in most cases, +an away message of `*` should be treated as though the connection did not exist +at all (for example, it should not supersede a human-readable `AWAY` message set +by another connection, even if it is more recent). + +[chathistory]: https://ircv3.net/specs/extensions/chathistory +[monitor]: https://ircv3.net/specs/extensions/monitor +[away-notify]: https://ircv3.net/specs/extensions/away-notify diff --git a/downstream.go b/downstream.go index 3616b45..c9d9718 100644 --- a/downstream.go +++ b/downstream.go @@ -229,11 +229,12 @@ var permanentDownstreamCaps = map[string]string{ "draft/read-marker": "", + "soju.im/account-required": "", "soju.im/bouncer-networks": "", "soju.im/bouncer-networks-notify": "", "soju.im/no-implicit-names": "", + "soju.im/pre-away": "", "soju.im/read": "", - "soju.im/account-required": "", "soju.im/webpush": "", } @@ -741,6 +742,14 @@ func (dc *downstreamConn) handleMessageUnregistered(ctx context.Context, msg *ir Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"}, }} } + case "AWAY": + if len(msg.Params) > 0 { + dc.away = &msg.Params[0] + } else { + dc.away = nil + } + + dc.SendMessage(ctx, generateAwayReply(dc.away != nil)) default: dc.logger.Printf("unhandled message: %v", msg) return newUnknownCommandError(msg.Command) @@ -2575,16 +2584,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. dc.away = nil } - cmd := irc.RPL_NOWAWAY - desc := "You have been marked as being away" - if dc.away == nil { - cmd = irc.RPL_UNAWAY - desc = "You are no longer marked as being away" - } - dc.SendMessage(ctx, &irc.Message{ - Command: cmd, - Params: []string{dc.nick, desc}, - }) + dc.SendMessage(ctx, generateAwayReply(dc.away != nil)) uc := dc.upstream() if uc != nil { diff --git a/irc.go b/irc.go index dbad3b1..d116446 100644 --- a/irc.go +++ b/irc.go @@ -278,3 +278,16 @@ func isNumeric(cmd string) bool { } return true } + +func generateAwayReply(away bool) *irc.Message { + cmd := irc.RPL_NOWAWAY + desc := "You have been marked as being away" + if !away { + cmd = irc.RPL_UNAWAY + desc = "You are no longer marked as being away" + } + return &irc.Message{ + Command: cmd, + Params: []string{"*", desc}, + } +}