Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement draft/pre-away #2044

Merged
merged 3 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gencapdefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@
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",
Expand Down
7 changes: 6 additions & 1 deletion irc/caps/defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -154,6 +158,7 @@ var (
"draft/languages",
"draft/multiline",
"draft/persistence",
"draft/pre-away",
"draft/read-marker",
"draft/relaymsg",
"echo-message",
Expand Down
13 changes: 5 additions & 8 deletions irc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions irc/client_lookup_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions irc/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ func init() {
minParams: 1,
},
"AWAY": {
handler: awayHandler,
minParams: 0,
handler: awayHandler,
usablePreReg: true,
minParams: 0,
},
"BATCH": {
handler: batchHandler,
Expand Down
42 changes: 28 additions & 14 deletions irc/getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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) {
Expand Down Expand Up @@ -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()

Expand All @@ -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
Expand All @@ -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
}
Expand Down
16 changes: 9 additions & 7 deletions irc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 3 additions & 3 deletions irc/nickname.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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() {
Expand Down