3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-22 18:52:41 +01:00

implement draft/pre-away (#2044)

* implement draft/pre-away
* clean up some subtleties in auto-away aggregation.
* consistently apply auto-away only to always-on
* `AWAY *` should not produce user-visible changes wherever possible
This commit is contained in:
Shivaram Lingamneni 2023-02-04 21:50:14 -08:00 committed by GitHub
parent 12f7796933
commit 1da11ae8ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 63 additions and 38 deletions

View File

@ -195,6 +195,12 @@ CAPDEFS = [
url="https://github.com/ircv3/ircv3-specifications/pull/503",
standard="proposed IRCv3",
),
CapDef(
identifier="Preaway",
name="draft/pre-away",
url="https://github.com/ircv3/ircv3-specifications/pull/514",
standard="proposed IRCv3",
),
CapDef(
identifier="StandardReplies",
name="standard-replies",

View File

@ -7,7 +7,7 @@ package caps
const (
// number of recognized capabilities:
numCapabs = 31
numCapabs = 32
// length of the uint32 array that represents the bitset:
bitsetLen = 1
)
@ -65,6 +65,10 @@ const (
// https://github.com/ircv3/ircv3-specifications/pull/503
Persistence Capability = iota
// Preaway is the proposed IRCv3 capability named "draft/pre-away":
// https://github.com/ircv3/ircv3-specifications/pull/514
Preaway Capability = iota
// ReadMarker is the draft IRCv3 capability named "draft/read-marker":
// https://github.com/ircv3/ircv3-specifications/pull/489
ReadMarker Capability = iota
@ -154,6 +158,7 @@ var (
"draft/languages",
"draft/multiline",
"draft/persistence",
"draft/pre-away",
"draft/read-marker",
"draft/relaymsg",
"echo-message",

View File

@ -1222,14 +1222,11 @@ func (client *Client) destroy(session *Session) {
client.destroyed = true
}
becameAutoAway := false
var awayMessage string
if alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
wasAway := client.awayMessage != ""
wasAway := client.awayMessage
if client.autoAwayEnabledNoMutex(config) {
client.setAutoAwayNoMutex(config)
awayMessage = client.awayMessage
becameAutoAway = !wasAway && awayMessage != ""
}
nowAway := client.awayMessage
if client.registrationTimer != nil {
// unconditionally stop; if the client is still unregistered it must be destroyed
@ -1279,8 +1276,8 @@ func (client *Client) destroy(session *Session) {
client.server.stats.Remove(registered, invisible, operator)
}
if becameAutoAway {
dispatchAwayNotify(client, true, awayMessage)
if !shouldDestroy && wasAway != nowAway {
dispatchAwayNotify(client, nowAway)
}
if !shouldDestroy {

View File

@ -84,7 +84,7 @@ func (clients *ClientManager) Remove(client *Client) error {
// SetNick sets a client's nickname, validating it against nicknames in use
// XXX: dryRun validates a client's ability to claim a nick, without
// actually claiming it
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string, dryRun bool) (setNick string, err error, returnedFromAway bool) {
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string, dryRun bool) (setNick string, err error, awayChanged bool) {
config := client.server.Config()
var newCfNick, newSkeleton string
@ -204,7 +204,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
return "", errNicknameInUse, false
}
}
reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
reattachSuccessful, numSessions, lastSeen, wasAway, nowAway := currentClient.AddSession(session)
if !reattachSuccessful {
return "", errNicknameInUse, false
}
@ -219,7 +219,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
currentClient.SetRealname(realname)
}
// successful reattach!
return newNick, nil, back
return newNick, nil, wasAway != nowAway
} else if currentClient == client && currentClient.Nick() == newNick {
return "", errNoop, false
}

View File

@ -89,8 +89,9 @@ func init() {
minParams: 1,
},
"AWAY": {
handler: awayHandler,
minParams: 0,
handler: awayHandler,
usablePreReg: true,
minParams: 0,
},
"BATCH": {
handler: batchHandler,

View File

@ -92,7 +92,7 @@ func (client *Client) AllSessionData(currentSession *Session, hasPrivs bool) (da
return
}
func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) {
func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, wasAway, nowAway string) {
config := client.server.Config()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
@ -113,14 +113,22 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
client.setLastSeen(time.Now().UTC(), session.deviceID)
}
client.sessions = newSessions
// TODO(#1551) there should be a cap to opt out of this behavior on a session
if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
client.awayMessage = ""
if len(client.sessions) == 1 {
back = true
wasAway = client.awayMessage
if client.autoAwayEnabledNoMutex(config) {
client.setAutoAwayNoMutex(config)
} else {
if session.awayMessage != "" && session.awayMessage != "*" {
// set the away message
client.awayMessage = session.awayMessage
} else if session.awayMessage == "" && !session.awayAt.IsZero() {
// weird edge case: explicit `AWAY` or `AWAY :` during pre-registration makes the client back
client.awayMessage = ""
}
// else: the client sent no AWAY command at all, no-op
// or: the client sent `AWAY *`, which should not modify the publicly visible away state
}
return true, len(client.sessions), lastSeen, back
nowAway = client.awayMessage
return true, len(client.sessions), lastSeen, wasAway, nowAway
}
func (client *Client) removeSession(session *Session) (success bool, length int) {
@ -195,7 +203,7 @@ func (client *Client) Away() (result bool, message string) {
return
}
func (session *Session) SetAway(awayMessage string) {
func (session *Session) SetAway(awayMessage string) (wasAway, nowAway string) {
client := session.client
config := client.server.Config()
@ -205,15 +213,21 @@ func (session *Session) SetAway(awayMessage string) {
session.awayMessage = awayMessage
session.awayAt = time.Now().UTC()
autoAway := client.registered && client.alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway)
if autoAway {
wasAway = client.awayMessage
if client.autoAwayEnabledNoMutex(config) {
client.setAutoAwayNoMutex(config)
} else {
} else if awayMessage != "*" {
client.awayMessage = awayMessage
}
} // else: `AWAY *`, should not modify publicly visible away state
nowAway = client.awayMessage
return
}
func (client *Client) autoAwayEnabledNoMutex(config *Config) bool {
return client.registered && client.alwaysOn &&
persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway)
}
func (client *Client) setAutoAwayNoMutex(config *Config) {
// aggregate the away statuses of the individual sessions:
var globalAwayState string
@ -223,8 +237,8 @@ func (client *Client) setAutoAwayNoMutex(config *Config) {
// a session is active, we are not auto-away
client.awayMessage = ""
return
} else if cSession.awayAt.After(awaySetAt) {
// choose the latest available away message from any session
} else if cSession.awayAt.After(awaySetAt) && cSession.awayMessage != "*" {
// choose the latest valid away message from any session
globalAwayState = cSession.awayMessage
awaySetAt = cSession.awayAt
}

View File

@ -447,32 +447,34 @@ func authScramHandler(server *Server, client *Client, session *Session, value []
// AWAY [<message>]
func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
var isAway bool
// #1996: `AWAY :` is treated the same as `AWAY`
var awayMessage string
if len(msg.Params) > 0 {
awayMessage = msg.Params[0]
awayMessage = ircutils.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen)
}
isAway = (awayMessage != "") // #1996
rb.session.SetAway(awayMessage)
wasAway, nowAway := rb.session.SetAway(awayMessage)
if isAway {
if nowAway != "" {
rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
} else {
rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
}
dispatchAwayNotify(client, isAway, awayMessage)
if client.registered && wasAway != nowAway {
dispatchAwayNotify(client, nowAway)
} // else: we'll send it (if applicable) after reattach
return false
}
func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
func dispatchAwayNotify(client *Client, awayMessage string) {
// dispatch away-notify
details := client.Details()
isBot := client.HasMode(modes.Bot)
for session := range client.FriendsMonitors(caps.AwayNotify) {
if isAway {
if awayMessage != "" {
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
} else {
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")

View File

@ -34,7 +34,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
origNickMask := details.nickMask
isSanick := client != target
assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname, false)
assignedNickname, err, awayChanged := client.server.clients.SetNick(target, session, nickname, false)
if err == errNicknameInUse {
if !isSanick {
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, details.nick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
@ -115,8 +115,8 @@ func performNickChange(server *Server, client *Client, target *Client, session *
}
}
if back {
dispatchAwayNotify(session.client, false, "")
if awayChanged {
dispatchAwayNotify(session.client, session.client.AwayMessage())
}
for _, channel := range target.Channels() {