mirror of
https://github.com/ergochat/ergo.git
synced 2025-02-16 13:40:48 +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",
|
url="https://github.com/ircv3/ircv3-specifications/pull/503",
|
||||||
standard="proposed IRCv3",
|
standard="proposed IRCv3",
|
||||||
),
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="Preaway",
|
||||||
|
name="draft/pre-away",
|
||||||
|
url="https://github.com/ircv3/ircv3-specifications/pull/514",
|
||||||
|
standard="proposed IRCv3",
|
||||||
|
),
|
||||||
CapDef(
|
CapDef(
|
||||||
identifier="StandardReplies",
|
identifier="StandardReplies",
|
||||||
name="standard-replies",
|
name="standard-replies",
|
||||||
|
@ -7,7 +7,7 @@ package caps
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// number of recognized capabilities:
|
// number of recognized capabilities:
|
||||||
numCapabs = 31
|
numCapabs = 32
|
||||||
// length of the uint32 array that represents the bitset:
|
// length of the uint32 array that represents the bitset:
|
||||||
bitsetLen = 1
|
bitsetLen = 1
|
||||||
)
|
)
|
||||||
@ -65,6 +65,10 @@ const (
|
|||||||
// https://github.com/ircv3/ircv3-specifications/pull/503
|
// https://github.com/ircv3/ircv3-specifications/pull/503
|
||||||
Persistence Capability = iota
|
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":
|
// ReadMarker is the draft IRCv3 capability named "draft/read-marker":
|
||||||
// https://github.com/ircv3/ircv3-specifications/pull/489
|
// https://github.com/ircv3/ircv3-specifications/pull/489
|
||||||
ReadMarker Capability = iota
|
ReadMarker Capability = iota
|
||||||
@ -154,6 +158,7 @@ var (
|
|||||||
"draft/languages",
|
"draft/languages",
|
||||||
"draft/multiline",
|
"draft/multiline",
|
||||||
"draft/persistence",
|
"draft/persistence",
|
||||||
|
"draft/pre-away",
|
||||||
"draft/read-marker",
|
"draft/read-marker",
|
||||||
"draft/relaymsg",
|
"draft/relaymsg",
|
||||||
"echo-message",
|
"echo-message",
|
||||||
|
@ -1222,14 +1222,11 @@ func (client *Client) destroy(session *Session) {
|
|||||||
client.destroyed = true
|
client.destroyed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
becameAutoAway := false
|
wasAway := client.awayMessage
|
||||||
var awayMessage string
|
if client.autoAwayEnabledNoMutex(config) {
|
||||||
if alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
|
|
||||||
wasAway := client.awayMessage != ""
|
|
||||||
client.setAutoAwayNoMutex(config)
|
client.setAutoAwayNoMutex(config)
|
||||||
awayMessage = client.awayMessage
|
|
||||||
becameAutoAway = !wasAway && awayMessage != ""
|
|
||||||
}
|
}
|
||||||
|
nowAway := client.awayMessage
|
||||||
|
|
||||||
if client.registrationTimer != nil {
|
if client.registrationTimer != nil {
|
||||||
// unconditionally stop; if the client is still unregistered it must be destroyed
|
// 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)
|
client.server.stats.Remove(registered, invisible, operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if becameAutoAway {
|
if !shouldDestroy && wasAway != nowAway {
|
||||||
dispatchAwayNotify(client, true, awayMessage)
|
dispatchAwayNotify(client, nowAway)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !shouldDestroy {
|
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
|
// SetNick sets a client's nickname, validating it against nicknames in use
|
||||||
// XXX: dryRun validates a client's ability to claim a nick, without
|
// XXX: dryRun validates a client's ability to claim a nick, without
|
||||||
// actually claiming it
|
// 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()
|
config := client.server.Config()
|
||||||
|
|
||||||
var newCfNick, newSkeleton string
|
var newCfNick, newSkeleton string
|
||||||
@ -204,7 +204,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
return "", errNicknameInUse, false
|
return "", errNicknameInUse, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
|
reattachSuccessful, numSessions, lastSeen, wasAway, nowAway := currentClient.AddSession(session)
|
||||||
if !reattachSuccessful {
|
if !reattachSuccessful {
|
||||||
return "", errNicknameInUse, false
|
return "", errNicknameInUse, false
|
||||||
}
|
}
|
||||||
@ -219,7 +219,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
currentClient.SetRealname(realname)
|
currentClient.SetRealname(realname)
|
||||||
}
|
}
|
||||||
// successful reattach!
|
// successful reattach!
|
||||||
return newNick, nil, back
|
return newNick, nil, wasAway != nowAway
|
||||||
} else if currentClient == client && currentClient.Nick() == newNick {
|
} else if currentClient == client && currentClient.Nick() == newNick {
|
||||||
return "", errNoop, false
|
return "", errNoop, false
|
||||||
}
|
}
|
||||||
|
@ -89,8 +89,9 @@ func init() {
|
|||||||
minParams: 1,
|
minParams: 1,
|
||||||
},
|
},
|
||||||
"AWAY": {
|
"AWAY": {
|
||||||
handler: awayHandler,
|
handler: awayHandler,
|
||||||
minParams: 0,
|
usablePreReg: true,
|
||||||
|
minParams: 0,
|
||||||
},
|
},
|
||||||
"BATCH": {
|
"BATCH": {
|
||||||
handler: batchHandler,
|
handler: batchHandler,
|
||||||
|
@ -92,7 +92,7 @@ func (client *Client) AllSessionData(currentSession *Session, hasPrivs bool) (da
|
|||||||
return
|
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()
|
config := client.server.Config()
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
defer client.stateMutex.Unlock()
|
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.setLastSeen(time.Now().UTC(), session.deviceID)
|
||||||
}
|
}
|
||||||
client.sessions = newSessions
|
client.sessions = newSessions
|
||||||
// TODO(#1551) there should be a cap to opt out of this behavior on a session
|
wasAway = client.awayMessage
|
||||||
if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
|
if client.autoAwayEnabledNoMutex(config) {
|
||||||
client.awayMessage = ""
|
client.setAutoAwayNoMutex(config)
|
||||||
if len(client.sessions) == 1 {
|
} else {
|
||||||
back = true
|
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) {
|
func (client *Client) removeSession(session *Session) (success bool, length int) {
|
||||||
@ -195,7 +203,7 @@ func (client *Client) Away() (result bool, message string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) SetAway(awayMessage string) {
|
func (session *Session) SetAway(awayMessage string) (wasAway, nowAway string) {
|
||||||
client := session.client
|
client := session.client
|
||||||
config := client.server.Config()
|
config := client.server.Config()
|
||||||
|
|
||||||
@ -205,15 +213,21 @@ func (session *Session) SetAway(awayMessage string) {
|
|||||||
session.awayMessage = awayMessage
|
session.awayMessage = awayMessage
|
||||||
session.awayAt = time.Now().UTC()
|
session.awayAt = time.Now().UTC()
|
||||||
|
|
||||||
autoAway := client.registered && client.alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway)
|
wasAway = client.awayMessage
|
||||||
if autoAway {
|
if client.autoAwayEnabledNoMutex(config) {
|
||||||
client.setAutoAwayNoMutex(config)
|
client.setAutoAwayNoMutex(config)
|
||||||
} else {
|
} else if awayMessage != "*" {
|
||||||
client.awayMessage = awayMessage
|
client.awayMessage = awayMessage
|
||||||
}
|
} // else: `AWAY *`, should not modify publicly visible away state
|
||||||
|
nowAway = client.awayMessage
|
||||||
return
|
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) {
|
func (client *Client) setAutoAwayNoMutex(config *Config) {
|
||||||
// aggregate the away statuses of the individual sessions:
|
// aggregate the away statuses of the individual sessions:
|
||||||
var globalAwayState string
|
var globalAwayState string
|
||||||
@ -223,8 +237,8 @@ func (client *Client) setAutoAwayNoMutex(config *Config) {
|
|||||||
// a session is active, we are not auto-away
|
// a session is active, we are not auto-away
|
||||||
client.awayMessage = ""
|
client.awayMessage = ""
|
||||||
return
|
return
|
||||||
} else if cSession.awayAt.After(awaySetAt) {
|
} else if cSession.awayAt.After(awaySetAt) && cSession.awayMessage != "*" {
|
||||||
// choose the latest available away message from any session
|
// choose the latest valid away message from any session
|
||||||
globalAwayState = cSession.awayMessage
|
globalAwayState = cSession.awayMessage
|
||||||
awaySetAt = cSession.awayAt
|
awaySetAt = cSession.awayAt
|
||||||
}
|
}
|
||||||
|
@ -447,32 +447,34 @@ func authScramHandler(server *Server, client *Client, session *Session, value []
|
|||||||
|
|
||||||
// AWAY [<message>]
|
// AWAY [<message>]
|
||||||
func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
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
|
var awayMessage string
|
||||||
if len(msg.Params) > 0 {
|
if len(msg.Params) > 0 {
|
||||||
awayMessage = msg.Params[0]
|
awayMessage = msg.Params[0]
|
||||||
awayMessage = ircutils.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen)
|
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"))
|
rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
|
||||||
} else {
|
} else {
|
||||||
rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
|
func dispatchAwayNotify(client *Client, awayMessage string) {
|
||||||
// dispatch away-notify
|
// dispatch away-notify
|
||||||
details := client.Details()
|
details := client.Details()
|
||||||
isBot := client.HasMode(modes.Bot)
|
isBot := client.HasMode(modes.Bot)
|
||||||
for session := range client.FriendsMonitors(caps.AwayNotify) {
|
for session := range client.FriendsMonitors(caps.AwayNotify) {
|
||||||
if isAway {
|
if awayMessage != "" {
|
||||||
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
|
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
|
||||||
} else {
|
} else {
|
||||||
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")
|
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
|
origNickMask := details.nickMask
|
||||||
isSanick := client != target
|
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 err == errNicknameInUse {
|
||||||
if !isSanick {
|
if !isSanick {
|
||||||
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, details.nick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
|
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 {
|
if awayChanged {
|
||||||
dispatchAwayNotify(session.client, false, "")
|
dispatchAwayNotify(session.client, session.client.AwayMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, channel := range target.Channels() {
|
for _, channel := range target.Channels() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user