Introduce soju.im/pre-away

The IRCv3 spec is stalled, so let's just ship a vendored extension
for now.

References: https://github.com/ircv3/ircv3-specifications/pull/514
This commit is contained in:
Simon Ser 2023-02-02 19:48:38 +01:00
parent 0ee94759f7
commit c36bb342fb
3 changed files with 115 additions and 11 deletions

91
doc/ext/pre-away.md Normal file
View File

@ -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

View File

@ -229,11 +229,12 @@ var permanentDownstreamCaps = map[string]string{
"draft/read-marker": "", "draft/read-marker": "",
"soju.im/account-required": "",
"soju.im/bouncer-networks": "", "soju.im/bouncer-networks": "",
"soju.im/bouncer-networks-notify": "", "soju.im/bouncer-networks-notify": "",
"soju.im/no-implicit-names": "", "soju.im/no-implicit-names": "",
"soju.im/pre-away": "",
"soju.im/read": "", "soju.im/read": "",
"soju.im/account-required": "",
"soju.im/webpush": "", "soju.im/webpush": "",
} }
@ -741,6 +742,14 @@ func (dc *downstreamConn) handleMessageUnregistered(ctx context.Context, msg *ir
Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"}, 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: default:
dc.logger.Printf("unhandled message: %v", msg) dc.logger.Printf("unhandled message: %v", msg)
return newUnknownCommandError(msg.Command) return newUnknownCommandError(msg.Command)
@ -2575,16 +2584,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
dc.away = nil dc.away = nil
} }
cmd := irc.RPL_NOWAWAY dc.SendMessage(ctx, generateAwayReply(dc.away != nil))
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},
})
uc := dc.upstream() uc := dc.upstream()
if uc != nil { if uc != nil {

13
irc.go
View File

@ -278,3 +278,16 @@ func isNumeric(cmd string) bool {
} }
return true 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},
}
}