mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-25 13:29:27 +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:
parent
12f7796933
commit
1da11ae8ae
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ func init() {
|
||||
},
|
||||
"AWAY": {
|
||||
handler: awayHandler,
|
||||
usablePreReg: true,
|
||||
minParams: 0,
|
||||
},
|
||||
"BATCH": {
|
||||
|
@ -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) {
|
||||
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 = ""
|
||||
if len(client.sessions) == 1 {
|
||||
back = true
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user