mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-29 07:29:31 +01:00
Merge pull request #1036 from slingamn/account_persistence.3
last round of feature changes
This commit is contained in:
commit
06b2cb2efc
@ -413,6 +413,9 @@ accounts:
|
|||||||
# "disabled", "opt-in", "opt-out", or "mandatory".
|
# "disabled", "opt-in", "opt-out", or "mandatory".
|
||||||
always-on: "disabled"
|
always-on: "disabled"
|
||||||
|
|
||||||
|
# whether to mark always-on clients away when they have no active connections:
|
||||||
|
auto-away: "opt-in"
|
||||||
|
|
||||||
# vhosts controls the assignment of vhosts (strings displayed in place of the user's
|
# vhosts controls the assignment of vhosts (strings displayed in place of the user's
|
||||||
# hostname/IP) by the HostServ service
|
# hostname/IP) by the HostServ service
|
||||||
vhosts:
|
vhosts:
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/oragono/oragono/irc/connection_limits"
|
"github.com/oragono/oragono/irc/connection_limits"
|
||||||
"github.com/oragono/oragono/irc/email"
|
"github.com/oragono/oragono/irc/email"
|
||||||
"github.com/oragono/oragono/irc/ldap"
|
"github.com/oragono/oragono/irc/ldap"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
@ -40,6 +41,7 @@ const (
|
|||||||
keyAccountChannels = "account.channels %s" // channels registered to the account
|
keyAccountChannels = "account.channels %s" // channels registered to the account
|
||||||
keyAccountJoinedChannels = "account.joinedto %s" // channels a persistent client has joined
|
keyAccountJoinedChannels = "account.joinedto %s" // channels a persistent client has joined
|
||||||
keyAccountLastSeen = "account.lastseen %s"
|
keyAccountLastSeen = "account.lastseen %s"
|
||||||
|
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
||||||
|
|
||||||
keyVHostQueueAcctToId = "vhostQueue %s"
|
keyVHostQueueAcctToId = "vhostQueue %s"
|
||||||
vhostRequestIdx = "vhostQueue"
|
vhostRequestIdx = "vhostQueue"
|
||||||
@ -127,7 +129,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) {
|
|||||||
account, err := am.LoadAccount(accountName)
|
account, err := am.LoadAccount(accountName)
|
||||||
if err == nil && account.Verified &&
|
if err == nil && account.Verified &&
|
||||||
persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
|
persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
|
||||||
am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSeen(accountName))
|
am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSeen(accountName), am.loadModes(accountName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -594,6 +596,28 @@ func (am *AccountManager) loadChannels(account string) (channels []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) saveModes(account string, uModes modes.Modes) {
|
||||||
|
modeStr := uModes.String()
|
||||||
|
key := fmt.Sprintf(keyAccountModes, account)
|
||||||
|
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
tx.Set(key, modeStr, nil)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) loadModes(account string) (uModes modes.Modes) {
|
||||||
|
key := fmt.Sprintf(keyAccountModes, account)
|
||||||
|
var modeStr string
|
||||||
|
am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
|
modeStr, _ = tx.Get(key)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for _, m := range modeStr {
|
||||||
|
uModes = append(uModes, modes.Mode(m))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (am *AccountManager) saveLastSeen(account string, lastSeen time.Time) {
|
func (am *AccountManager) saveLastSeen(account string, lastSeen time.Time) {
|
||||||
key := fmt.Sprintf(keyAccountLastSeen, account)
|
key := fmt.Sprintf(keyAccountLastSeen, account)
|
||||||
var val string
|
var val string
|
||||||
@ -1884,6 +1908,7 @@ type AccountSettings struct {
|
|||||||
AlwaysOn PersistentStatus
|
AlwaysOn PersistentStatus
|
||||||
AutoreplayMissed bool
|
AutoreplayMissed bool
|
||||||
DMHistory HistoryStatus
|
DMHistory HistoryStatus
|
||||||
|
AutoAway PersistentStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientAccount represents a user account.
|
// ClientAccount represents a user account.
|
||||||
|
@ -48,6 +48,7 @@ type Client struct {
|
|||||||
accountRegDate time.Time
|
accountRegDate time.Time
|
||||||
accountSettings AccountSettings
|
accountSettings AccountSettings
|
||||||
away bool
|
away bool
|
||||||
|
autoAway bool
|
||||||
awayMessage string
|
awayMessage string
|
||||||
brbTimer BrbTimer
|
brbTimer BrbTimer
|
||||||
channels ChannelSet
|
channels ChannelSet
|
||||||
@ -359,7 +360,7 @@ func (server *Server) RunClient(conn IRCConn) {
|
|||||||
client.run(session)
|
client.run(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) {
|
func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time, uModes modes.Modes) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
if lastSeen.IsZero() {
|
if lastSeen.IsZero() {
|
||||||
@ -382,9 +383,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
|
|||||||
alwaysOn: true,
|
alwaysOn: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyUserModeChanges(client, config.Accounts.defaultUserModes, false, nil)
|
|
||||||
|
|
||||||
client.SetMode(modes.TLS, true)
|
client.SetMode(modes.TLS, true)
|
||||||
|
for _, m := range uModes {
|
||||||
|
client.SetMode(m, true)
|
||||||
|
}
|
||||||
client.writerSemaphore.Initialize(1)
|
client.writerSemaphore.Initialize(1)
|
||||||
client.history.Initialize(0, 0)
|
client.history.Initialize(0, 0)
|
||||||
client.brbTimer.Initialize(client)
|
client.brbTimer.Initialize(client)
|
||||||
@ -393,7 +395,7 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
|
|||||||
|
|
||||||
client.resizeHistory(config)
|
client.resizeHistory(config)
|
||||||
|
|
||||||
_, err := server.clients.SetNick(client, nil, account.Name)
|
_, err, _ := server.clients.SetNick(client, nil, account.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
|
server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
|
||||||
return
|
return
|
||||||
@ -409,6 +411,12 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
|
|||||||
// this is *probably* ok as long as the persisted memberships are accurate
|
// this is *probably* ok as long as the persisted memberships are accurate
|
||||||
server.channels.Join(client, chname, "", true, nil)
|
server.channels.Join(client, chname, "", true, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
|
||||||
|
client.autoAway = true
|
||||||
|
client.away = true
|
||||||
|
client.awayMessage = client.t("User is currently disconnected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) resizeHistory(config *Config) {
|
func (client *Client) resizeHistory(config *Config) {
|
||||||
@ -588,6 +596,7 @@ func (client *Client) run(session *Session) {
|
|||||||
|
|
||||||
isReattach := client.Registered()
|
isReattach := client.Registered()
|
||||||
if isReattach {
|
if isReattach {
|
||||||
|
session.idletimer.Touch()
|
||||||
if session.resumeDetails != nil {
|
if session.resumeDetails != nil {
|
||||||
session.playResume()
|
session.playResume()
|
||||||
session.resumeDetails = nil
|
session.resumeDetails = nil
|
||||||
@ -1187,6 +1196,7 @@ func (client *Client) Quit(message string, session *Session) {
|
|||||||
// otherwise, destroys one specific session, only destroying the client if it
|
// otherwise, destroys one specific session, only destroying the client if it
|
||||||
// has no more sessions.
|
// has no more sessions.
|
||||||
func (client *Client) destroy(session *Session) {
|
func (client *Client) destroy(session *Session) {
|
||||||
|
config := client.server.Config()
|
||||||
var sessionsToDestroy []*Session
|
var sessionsToDestroy []*Session
|
||||||
|
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
@ -1225,6 +1235,17 @@ func (client *Client) destroy(session *Session) {
|
|||||||
client.dirtyBits |= IncludeLastSeen
|
client.dirtyBits |= IncludeLastSeen
|
||||||
}
|
}
|
||||||
exitedSnomaskSent := client.exitedSnomaskSent
|
exitedSnomaskSent := client.exitedSnomaskSent
|
||||||
|
|
||||||
|
autoAway := false
|
||||||
|
var awayMessage string
|
||||||
|
if alwaysOn && remainingSessions == 0 && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
|
||||||
|
autoAway = true
|
||||||
|
client.autoAway = true
|
||||||
|
client.away = true
|
||||||
|
awayMessage = config.languageManager.Translate(client.languages, `Disconnected from the server`)
|
||||||
|
client.awayMessage = awayMessage
|
||||||
|
}
|
||||||
|
|
||||||
client.stateMutex.Unlock()
|
client.stateMutex.Unlock()
|
||||||
|
|
||||||
// XXX there is no particular reason to persist this state here rather than
|
// XXX there is no particular reason to persist this state here rather than
|
||||||
@ -1272,6 +1293,10 @@ func (client *Client) destroy(session *Session) {
|
|||||||
client.server.stats.Remove(registered, invisible, operator)
|
client.server.stats.Remove(registered, invisible, operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if autoAway {
|
||||||
|
dispatchAwayNotify(client, true, awayMessage)
|
||||||
|
}
|
||||||
|
|
||||||
if !shouldDestroy {
|
if !shouldDestroy {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1610,6 +1635,7 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
|
|||||||
const (
|
const (
|
||||||
IncludeChannels uint = 1 << iota
|
IncludeChannels uint = 1 << iota
|
||||||
IncludeLastSeen
|
IncludeLastSeen
|
||||||
|
IncludeUserModes
|
||||||
)
|
)
|
||||||
|
|
||||||
func (client *Client) markDirty(dirtyBits uint) {
|
func (client *Client) markDirty(dirtyBits uint) {
|
||||||
@ -1668,4 +1694,18 @@ func (client *Client) performWrite() {
|
|||||||
if (dirtyBits & IncludeLastSeen) != 0 {
|
if (dirtyBits & IncludeLastSeen) != 0 {
|
||||||
client.server.accounts.saveLastSeen(account, lastSeen)
|
client.server.accounts.saveLastSeen(account, lastSeen)
|
||||||
}
|
}
|
||||||
|
if (dirtyBits & IncludeUserModes) != 0 {
|
||||||
|
uModes := make(modes.Modes, 0, len(modes.SupportedUserModes))
|
||||||
|
for _, m := range modes.SupportedUserModes {
|
||||||
|
switch m {
|
||||||
|
case modes.Operator, modes.ServerNotice:
|
||||||
|
// these can't be persisted because they depend on the operator block
|
||||||
|
default:
|
||||||
|
if client.HasMode(m) {
|
||||||
|
uModes = append(uModes, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.server.accounts.saveModes(account, uModes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err e
|
|||||||
return errNickMissing
|
return errNickMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
success, _, _ := oldClient.AddSession(session)
|
success, _, _, _ := oldClient.AddSession(session)
|
||||||
if !success {
|
if !success {
|
||||||
return errNickMissing
|
return errNickMissing
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetNick sets a client's nickname, validating it against nicknames in use
|
// SetNick sets a client's nickname, validating it against nicknames in use
|
||||||
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error) {
|
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error, returnedFromAway bool) {
|
||||||
config := client.server.Config()
|
config := client.server.Config()
|
||||||
|
|
||||||
var newCfNick, newSkeleton string
|
var newCfNick, newSkeleton string
|
||||||
@ -134,24 +134,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
|
|
||||||
if useAccountName {
|
if useAccountName {
|
||||||
if registered && newNick != accountName && newNick != "" {
|
if registered && newNick != accountName && newNick != "" {
|
||||||
return "", errNickAccountMismatch
|
return "", errNickAccountMismatch, false
|
||||||
}
|
}
|
||||||
newNick = accountName
|
newNick = accountName
|
||||||
newCfNick = account
|
newCfNick = account
|
||||||
newSkeleton, err = Skeleton(newNick)
|
newSkeleton, err = Skeleton(newNick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errNicknameInvalid
|
return "", errNicknameInvalid, false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newNick = strings.TrimSpace(newNick)
|
newNick = strings.TrimSpace(newNick)
|
||||||
if len(newNick) == 0 {
|
if len(newNick) == 0 {
|
||||||
return "", errNickMissing
|
return "", errNickMissing, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
|
if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
|
||||||
newCfNick, err = CasefoldName(newNick)
|
newCfNick, err = CasefoldName(newNick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errNicknameInvalid
|
return "", errNicknameInvalid, false
|
||||||
}
|
}
|
||||||
if !config.Accounts.NickReservation.guestRegexpFolded.MatchString(newCfNick) {
|
if !config.Accounts.NickReservation.guestRegexpFolded.MatchString(newCfNick) {
|
||||||
newNick = strings.Replace(config.Accounts.NickReservation.GuestFormat, "*", newNick, 1)
|
newNick = strings.Replace(config.Accounts.NickReservation.GuestFormat, "*", newNick, 1)
|
||||||
@ -163,23 +163,23 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
newCfNick, err = CasefoldName(newNick)
|
newCfNick, err = CasefoldName(newNick)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errNicknameInvalid
|
return "", errNicknameInvalid, false
|
||||||
}
|
}
|
||||||
if len(newNick) > config.Limits.NickLen || len(newCfNick) > config.Limits.NickLen {
|
if len(newNick) > config.Limits.NickLen || len(newCfNick) > config.Limits.NickLen {
|
||||||
return "", errNicknameInvalid
|
return "", errNicknameInvalid, false
|
||||||
}
|
}
|
||||||
newSkeleton, err = Skeleton(newNick)
|
newSkeleton, err = Skeleton(newNick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errNicknameInvalid
|
return "", errNicknameInvalid, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
|
if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
|
||||||
return "", errNicknameInvalid
|
return "", errNicknameInvalid, false
|
||||||
}
|
}
|
||||||
|
|
||||||
reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
|
reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
|
||||||
if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
|
if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
|
||||||
return "", errNicknameReserved
|
return "", errNicknameReserved, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,20 +204,20 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
if currentClient != nil && currentClient != client && session != nil {
|
if currentClient != nil && currentClient != client && session != nil {
|
||||||
// these conditions forbid reattaching to an existing session:
|
// these conditions forbid reattaching to an existing session:
|
||||||
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
|
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
|
||||||
return "", errNicknameInUse
|
return "", errNicknameInUse, false
|
||||||
}
|
}
|
||||||
// check TLS modes
|
// check TLS modes
|
||||||
if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
|
if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
|
||||||
if useAccountName {
|
if useAccountName {
|
||||||
// #955: this is fatal because they can't fix it by trying a different nick
|
// #955: this is fatal because they can't fix it by trying a different nick
|
||||||
return "", errInsecureReattach
|
return "", errInsecureReattach, false
|
||||||
} else {
|
} else {
|
||||||
return "", errNicknameInUse
|
return "", errNicknameInUse, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
|
reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
|
||||||
if !reattachSuccessful {
|
if !reattachSuccessful {
|
||||||
return "", errNicknameInUse
|
return "", errNicknameInUse, false
|
||||||
}
|
}
|
||||||
if numSessions == 1 {
|
if numSessions == 1 {
|
||||||
invisible := currentClient.HasMode(modes.Invisible)
|
invisible := currentClient.HasMode(modes.Invisible)
|
||||||
@ -232,24 +232,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
// for performance reasons
|
// for performance reasons
|
||||||
currentClient.SetNames("user", realname, true)
|
currentClient.SetNames("user", realname, true)
|
||||||
// successful reattach!
|
// successful reattach!
|
||||||
return newNick, nil
|
return newNick, nil, back
|
||||||
} else if currentClient == client && currentClient.Nick() == newNick {
|
} else if currentClient == client && currentClient.Nick() == newNick {
|
||||||
// see #1019: normally no-op nick changes are caught earlier, by performNickChange,
|
// see #1019: normally no-op nick changes are caught earlier, by performNickChange,
|
||||||
// but they are not detected there when force-guest-format is enabled (because
|
// but they are not detected there when force-guest-format is enabled (because
|
||||||
// the proposed nickname is e.g. alice and the current nickname is Guest-alice)
|
// the proposed nickname is e.g. alice and the current nickname is Guest-alice)
|
||||||
return "", errNoop
|
return "", errNoop, false
|
||||||
}
|
}
|
||||||
// analogous checks for skeletons
|
// analogous checks for skeletons
|
||||||
skeletonHolder := clients.bySkeleton[newSkeleton]
|
skeletonHolder := clients.bySkeleton[newSkeleton]
|
||||||
if skeletonHolder != nil && skeletonHolder != client {
|
if skeletonHolder != nil && skeletonHolder != client {
|
||||||
return "", errNicknameInUse
|
return "", errNicknameInUse, false
|
||||||
}
|
}
|
||||||
|
|
||||||
clients.removeInternal(client)
|
clients.removeInternal(client)
|
||||||
clients.byNick[newCfNick] = client
|
clients.byNick[newCfNick] = client
|
||||||
clients.bySkeleton[newSkeleton] = client
|
clients.bySkeleton[newSkeleton] = client
|
||||||
client.updateNick(newNick, newCfNick, newSkeleton)
|
client.updateNick(newNick, newCfNick, newSkeleton)
|
||||||
return newNick, nil
|
return newNick, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clients *ClientManager) AllClients() (result []*Client) {
|
func (clients *ClientManager) AllClients() (result []*Client) {
|
||||||
|
@ -221,6 +221,7 @@ type MulticlientConfig struct {
|
|||||||
Enabled bool
|
Enabled bool
|
||||||
AllowedByDefault bool `yaml:"allowed-by-default"`
|
AllowedByDefault bool `yaml:"allowed-by-default"`
|
||||||
AlwaysOn PersistentStatus `yaml:"always-on"`
|
AlwaysOn PersistentStatus `yaml:"always-on"`
|
||||||
|
AutoAway PersistentStatus `yaml:"auto-away"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type throttleConfig struct {
|
type throttleConfig struct {
|
||||||
|
@ -89,7 +89,7 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time) {
|
func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) {
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
defer client.stateMutex.Unlock()
|
defer client.stateMutex.Unlock()
|
||||||
|
|
||||||
@ -106,7 +106,13 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
|
|||||||
lastSeen = client.lastSeen
|
lastSeen = client.lastSeen
|
||||||
}
|
}
|
||||||
client.sessions = newSessions
|
client.sessions = newSessions
|
||||||
return true, len(client.sessions), lastSeen
|
if client.autoAway {
|
||||||
|
back = true
|
||||||
|
client.autoAway = false
|
||||||
|
client.away = false
|
||||||
|
client.awayMessage = ""
|
||||||
|
}
|
||||||
|
return true, len(client.sessions), lastSeen, back
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) removeSession(session *Session) (success bool, length int) {
|
func (client *Client) removeSession(session *Session) (success bool, length int) {
|
||||||
|
@ -316,6 +316,11 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
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)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
|
||||||
// dispatch away-notify
|
// dispatch away-notify
|
||||||
details := client.Details()
|
details := client.Details()
|
||||||
for session := range client.Friends(caps.AwayNotify) {
|
for session := range client.Friends(caps.AwayNotify) {
|
||||||
@ -325,8 +330,6 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY")
|
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BATCH {+,-}reference-tag type [params...]
|
// BATCH {+,-}reference-tag type [params...]
|
||||||
|
@ -102,6 +102,10 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
|
|||||||
// can't do anything to TLS mode
|
// can't do anything to TLS mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(applied) != 0 {
|
||||||
|
client.markDirty(IncludeUserModes)
|
||||||
|
}
|
||||||
|
|
||||||
// return the changes we could actually apply
|
// return the changes we could actually apply
|
||||||
return applied
|
return applied
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
|
|||||||
hadNick := details.nick != "*"
|
hadNick := details.nick != "*"
|
||||||
origNickMask := details.nickMask
|
origNickMask := details.nickMask
|
||||||
|
|
||||||
assignedNickname, err := client.server.clients.SetNick(target, session, nickname)
|
assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname)
|
||||||
if err == errNicknameInUse {
|
if err == errNicknameInUse {
|
||||||
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
|
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
|
||||||
} else if err == errNicknameReserved {
|
} else if err == errNicknameReserved {
|
||||||
@ -80,6 +80,10 @@ func performNickChange(server *Server, client *Client, target *Client, session *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if back {
|
||||||
|
dispatchAwayNotify(session.client, false, "")
|
||||||
|
}
|
||||||
|
|
||||||
for _, channel := range client.Channels() {
|
for _, channel := range client.Channels() {
|
||||||
channel.AddHistoryItem(histItem, details.account)
|
channel.AddHistoryItem(histItem, details.account)
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,10 @@ how the history of your direct messages is stored. Your options are:
|
|||||||
2. 'ephemeral' [a limited amount of temporary history, not stored on disk]
|
2. 'ephemeral' [a limited amount of temporary history, not stored on disk]
|
||||||
3. 'on' [history stored in a permanent database, if available]
|
3. 'on' [history stored in a permanent database, if available]
|
||||||
4. 'default' [use the server default]`,
|
4. 'default' [use the server default]`,
|
||||||
|
`$bAUTO-AWAY$b
|
||||||
|
'auto-away' is only effective for always-on clients. If enabled, you will
|
||||||
|
automatically be marked away when all your sessions are disconnected, and
|
||||||
|
automatically return from away when you connect again.`,
|
||||||
},
|
},
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
enabled: servCmdRequiresAuthEnabled,
|
enabled: servCmdRequiresAuthEnabled,
|
||||||
@ -412,6 +416,18 @@ func displaySetting(settingName string, settings AccountSettings, client *Client
|
|||||||
} else {
|
} else {
|
||||||
nsNotice(rb, client.t("Your account is not configured to receive autoreplayed missed messages"))
|
nsNotice(rb, client.t("Your account is not configured to receive autoreplayed missed messages"))
|
||||||
}
|
}
|
||||||
|
case "auto-away":
|
||||||
|
stored := settings.AutoAway
|
||||||
|
alwaysOn := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, settings.AlwaysOn)
|
||||||
|
actual := persistenceEnabled(config.Accounts.Multiclient.AutoAway, settings.AutoAway)
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Your stored auto-away setting is: %s"), persistentStatusToString(stored)))
|
||||||
|
if actual && alwaysOn {
|
||||||
|
nsNotice(rb, client.t("Given current server settings, auto-away is enabled for your client"))
|
||||||
|
} else if actual && !alwaysOn {
|
||||||
|
nsNotice(rb, client.t("Because your client is not always-on, auto-away is disabled"))
|
||||||
|
} else if !actual {
|
||||||
|
nsNotice(rb, client.t("Given current server settings, auto-away is disabled for your client"))
|
||||||
|
}
|
||||||
case "dm-history":
|
case "dm-history":
|
||||||
effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory)
|
effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory)
|
||||||
csNotice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory)))
|
csNotice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory)))
|
||||||
@ -530,6 +546,17 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "auto-away":
|
||||||
|
var newValue PersistentStatus
|
||||||
|
newValue, err = persistentStatusFromString(params[1])
|
||||||
|
// "opt-in" and "opt-out" don't make sense as user preferences
|
||||||
|
if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut {
|
||||||
|
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
||||||
|
out = in
|
||||||
|
out.AutoAway = newValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "dm-history":
|
case "dm-history":
|
||||||
var newValue HistoryStatus
|
var newValue HistoryStatus
|
||||||
newValue, err = historyStatusFromString(params[1])
|
newValue, err = historyStatusFromString(params[1])
|
||||||
|
@ -439,6 +439,9 @@ accounts:
|
|||||||
# "disabled", "opt-in", "opt-out", or "mandatory".
|
# "disabled", "opt-in", "opt-out", or "mandatory".
|
||||||
always-on: "opt-in"
|
always-on: "opt-in"
|
||||||
|
|
||||||
|
# whether to mark always-on clients away when they have no active connections:
|
||||||
|
auto-away: "opt-in"
|
||||||
|
|
||||||
# vhosts controls the assignment of vhosts (strings displayed in place of the user's
|
# vhosts controls the assignment of vhosts (strings displayed in place of the user's
|
||||||
# hostname/IP) by the HostServ service
|
# hostname/IP) by the HostServ service
|
||||||
vhosts:
|
vhosts:
|
||||||
|
Loading…
Reference in New Issue
Block a user