mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-11-04 07:47:25 +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".
 | 
			
		||||
        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
 | 
			
		||||
    # hostname/IP) by the HostServ service
 | 
			
		||||
    vhosts:
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ import (
 | 
			
		||||
	"github.com/oragono/oragono/irc/connection_limits"
 | 
			
		||||
	"github.com/oragono/oragono/irc/email"
 | 
			
		||||
	"github.com/oragono/oragono/irc/ldap"
 | 
			
		||||
	"github.com/oragono/oragono/irc/modes"
 | 
			
		||||
	"github.com/oragono/oragono/irc/passwd"
 | 
			
		||||
	"github.com/oragono/oragono/irc/utils"
 | 
			
		||||
	"github.com/tidwall/buntdb"
 | 
			
		||||
@ -40,6 +41,7 @@ const (
 | 
			
		||||
	keyAccountChannels         = "account.channels %s" // channels registered to the account
 | 
			
		||||
	keyAccountJoinedChannels   = "account.joinedto %s" // channels a persistent client has joined
 | 
			
		||||
	keyAccountLastSeen         = "account.lastseen %s"
 | 
			
		||||
	keyAccountModes            = "account.modes %s" // user modes for the always-on client as a string
 | 
			
		||||
 | 
			
		||||
	keyVHostQueueAcctToId = "vhostQueue %s"
 | 
			
		||||
	vhostRequestIdx       = "vhostQueue"
 | 
			
		||||
@ -127,7 +129,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) {
 | 
			
		||||
		account, err := am.LoadAccount(accountName)
 | 
			
		||||
		if err == nil && account.Verified &&
 | 
			
		||||
			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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	key := fmt.Sprintf(keyAccountLastSeen, account)
 | 
			
		||||
	var val string
 | 
			
		||||
@ -1884,6 +1908,7 @@ type AccountSettings struct {
 | 
			
		||||
	AlwaysOn         PersistentStatus
 | 
			
		||||
	AutoreplayMissed bool
 | 
			
		||||
	DMHistory        HistoryStatus
 | 
			
		||||
	AutoAway         PersistentStatus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientAccount represents a user account.
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ type Client struct {
 | 
			
		||||
	accountRegDate     time.Time
 | 
			
		||||
	accountSettings    AccountSettings
 | 
			
		||||
	away               bool
 | 
			
		||||
	autoAway           bool
 | 
			
		||||
	awayMessage        string
 | 
			
		||||
	brbTimer           BrbTimer
 | 
			
		||||
	channels           ChannelSet
 | 
			
		||||
@ -359,7 +360,7 @@ func (server *Server) RunClient(conn IRCConn) {
 | 
			
		||||
	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()
 | 
			
		||||
	config := server.Config()
 | 
			
		||||
	if lastSeen.IsZero() {
 | 
			
		||||
@ -382,9 +383,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
 | 
			
		||||
		alwaysOn: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ApplyUserModeChanges(client, config.Accounts.defaultUserModes, false, nil)
 | 
			
		||||
 | 
			
		||||
	client.SetMode(modes.TLS, true)
 | 
			
		||||
	for _, m := range uModes {
 | 
			
		||||
		client.SetMode(m, true)
 | 
			
		||||
	}
 | 
			
		||||
	client.writerSemaphore.Initialize(1)
 | 
			
		||||
	client.history.Initialize(0, 0)
 | 
			
		||||
	client.brbTimer.Initialize(client)
 | 
			
		||||
@ -393,7 +395,7 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
 | 
			
		||||
 | 
			
		||||
	client.resizeHistory(config)
 | 
			
		||||
 | 
			
		||||
	_, err := server.clients.SetNick(client, nil, account.Name)
 | 
			
		||||
	_, err, _ := server.clients.SetNick(client, nil, account.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
 | 
			
		||||
		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
 | 
			
		||||
		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) {
 | 
			
		||||
@ -588,6 +596,7 @@ func (client *Client) run(session *Session) {
 | 
			
		||||
 | 
			
		||||
	isReattach := client.Registered()
 | 
			
		||||
	if isReattach {
 | 
			
		||||
		session.idletimer.Touch()
 | 
			
		||||
		if session.resumeDetails != nil {
 | 
			
		||||
			session.playResume()
 | 
			
		||||
			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
 | 
			
		||||
// has no more sessions.
 | 
			
		||||
func (client *Client) destroy(session *Session) {
 | 
			
		||||
	config := client.server.Config()
 | 
			
		||||
	var sessionsToDestroy []*Session
 | 
			
		||||
 | 
			
		||||
	client.stateMutex.Lock()
 | 
			
		||||
@ -1225,6 +1235,17 @@ func (client *Client) destroy(session *Session) {
 | 
			
		||||
		client.dirtyBits |= IncludeLastSeen
 | 
			
		||||
	}
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	// 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if autoAway {
 | 
			
		||||
		dispatchAwayNotify(client, true, awayMessage)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !shouldDestroy {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@ -1610,6 +1635,7 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
 | 
			
		||||
const (
 | 
			
		||||
	IncludeChannels uint = 1 << iota
 | 
			
		||||
	IncludeLastSeen
 | 
			
		||||
	IncludeUserModes
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (client *Client) markDirty(dirtyBits uint) {
 | 
			
		||||
@ -1668,4 +1694,18 @@ func (client *Client) performWrite() {
 | 
			
		||||
	if (dirtyBits & IncludeLastSeen) != 0 {
 | 
			
		||||
		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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success, _, _ := oldClient.AddSession(session)
 | 
			
		||||
	success, _, _, _ := oldClient.AddSession(session)
 | 
			
		||||
	if !success {
 | 
			
		||||
		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
 | 
			
		||||
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()
 | 
			
		||||
 | 
			
		||||
	var newCfNick, newSkeleton string
 | 
			
		||||
@ -134,24 +134,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
 | 
			
		||||
 | 
			
		||||
	if useAccountName {
 | 
			
		||||
		if registered && newNick != accountName && newNick != "" {
 | 
			
		||||
			return "", errNickAccountMismatch
 | 
			
		||||
			return "", errNickAccountMismatch, false
 | 
			
		||||
		}
 | 
			
		||||
		newNick = accountName
 | 
			
		||||
		newCfNick = account
 | 
			
		||||
		newSkeleton, err = Skeleton(newNick)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", errNicknameInvalid
 | 
			
		||||
			return "", errNicknameInvalid, false
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		newNick = strings.TrimSpace(newNick)
 | 
			
		||||
		if len(newNick) == 0 {
 | 
			
		||||
			return "", errNickMissing
 | 
			
		||||
			return "", errNickMissing, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
 | 
			
		||||
			newCfNick, err = CasefoldName(newNick)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", errNicknameInvalid
 | 
			
		||||
				return "", errNicknameInvalid, false
 | 
			
		||||
			}
 | 
			
		||||
			if !config.Accounts.NickReservation.guestRegexpFolded.MatchString(newCfNick) {
 | 
			
		||||
				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)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", errNicknameInvalid
 | 
			
		||||
			return "", errNicknameInvalid, false
 | 
			
		||||
		}
 | 
			
		||||
		if len(newNick) > config.Limits.NickLen || len(newCfNick) > config.Limits.NickLen {
 | 
			
		||||
			return "", errNicknameInvalid
 | 
			
		||||
			return "", errNicknameInvalid, false
 | 
			
		||||
		}
 | 
			
		||||
		newSkeleton, err = Skeleton(newNick)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", errNicknameInvalid
 | 
			
		||||
			return "", errNicknameInvalid, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
 | 
			
		||||
			return "", errNicknameInvalid
 | 
			
		||||
			return "", errNicknameInvalid, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
 | 
			
		||||
		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 {
 | 
			
		||||
		// these conditions forbid reattaching to an existing session:
 | 
			
		||||
		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
 | 
			
		||||
			return "", errNicknameInUse
 | 
			
		||||
			return "", errNicknameInUse, false
 | 
			
		||||
		}
 | 
			
		||||
		// check TLS modes
 | 
			
		||||
		if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
 | 
			
		||||
			if useAccountName {
 | 
			
		||||
				// #955: this is fatal because they can't fix it by trying a different nick
 | 
			
		||||
				return "", errInsecureReattach
 | 
			
		||||
				return "", errInsecureReattach, false
 | 
			
		||||
			} else {
 | 
			
		||||
				return "", errNicknameInUse
 | 
			
		||||
				return "", errNicknameInUse, false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
 | 
			
		||||
		reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
 | 
			
		||||
		if !reattachSuccessful {
 | 
			
		||||
			return "", errNicknameInUse
 | 
			
		||||
			return "", errNicknameInUse, false
 | 
			
		||||
		}
 | 
			
		||||
		if numSessions == 1 {
 | 
			
		||||
			invisible := currentClient.HasMode(modes.Invisible)
 | 
			
		||||
@ -232,24 +232,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
 | 
			
		||||
		// for performance reasons
 | 
			
		||||
		currentClient.SetNames("user", realname, true)
 | 
			
		||||
		// successful reattach!
 | 
			
		||||
		return newNick, nil
 | 
			
		||||
		return newNick, nil, back
 | 
			
		||||
	} else if currentClient == client && currentClient.Nick() == newNick {
 | 
			
		||||
		// 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
 | 
			
		||||
		// the proposed nickname is e.g. alice and the current nickname is Guest-alice)
 | 
			
		||||
		return "", errNoop
 | 
			
		||||
		return "", errNoop, false
 | 
			
		||||
	}
 | 
			
		||||
	// analogous checks for skeletons
 | 
			
		||||
	skeletonHolder := clients.bySkeleton[newSkeleton]
 | 
			
		||||
	if skeletonHolder != nil && skeletonHolder != client {
 | 
			
		||||
		return "", errNicknameInUse
 | 
			
		||||
		return "", errNicknameInUse, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clients.removeInternal(client)
 | 
			
		||||
	clients.byNick[newCfNick] = client
 | 
			
		||||
	clients.bySkeleton[newSkeleton] = client
 | 
			
		||||
	client.updateNick(newNick, newCfNick, newSkeleton)
 | 
			
		||||
	return newNick, nil
 | 
			
		||||
	return newNick, nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (clients *ClientManager) AllClients() (result []*Client) {
 | 
			
		||||
 | 
			
		||||
@ -221,6 +221,7 @@ type MulticlientConfig struct {
 | 
			
		||||
	Enabled          bool
 | 
			
		||||
	AllowedByDefault bool             `yaml:"allowed-by-default"`
 | 
			
		||||
	AlwaysOn         PersistentStatus `yaml:"always-on"`
 | 
			
		||||
	AutoAway         PersistentStatus `yaml:"auto-away"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type throttleConfig struct {
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,7 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
 | 
			
		||||
	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()
 | 
			
		||||
	defer client.stateMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
@ -106,7 +106,13 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
 | 
			
		||||
		lastSeen = client.lastSeen
 | 
			
		||||
	}
 | 
			
		||||
	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) {
 | 
			
		||||
 | 
			
		||||
@ -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"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dispatchAwayNotify(client, isAway, awayMessage)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
 | 
			
		||||
	// dispatch away-notify
 | 
			
		||||
	details := client.Details()
 | 
			
		||||
	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")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(applied) != 0 {
 | 
			
		||||
		client.markDirty(IncludeUserModes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// return the changes we could actually apply
 | 
			
		||||
	return applied
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
 | 
			
		||||
	hadNick := details.nick != "*"
 | 
			
		||||
	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 {
 | 
			
		||||
		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
 | 
			
		||||
	} 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() {
 | 
			
		||||
		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]
 | 
			
		||||
3. 'on'         [history stored in a permanent database, if available]
 | 
			
		||||
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,
 | 
			
		||||
			enabled:      servCmdRequiresAuthEnabled,
 | 
			
		||||
@ -412,6 +416,18 @@ func displaySetting(settingName string, settings AccountSettings, client *Client
 | 
			
		||||
		} else {
 | 
			
		||||
			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":
 | 
			
		||||
		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)))
 | 
			
		||||
@ -530,6 +546,17 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin
 | 
			
		||||
				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":
 | 
			
		||||
		var newValue HistoryStatus
 | 
			
		||||
		newValue, err = historyStatusFromString(params[1])
 | 
			
		||||
 | 
			
		||||
@ -439,6 +439,9 @@ accounts:
 | 
			
		||||
        # "disabled", "opt-in", "opt-out", or "mandatory".
 | 
			
		||||
        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
 | 
			
		||||
    # hostname/IP) by the HostServ service
 | 
			
		||||
    vhosts:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user