Explorar el Código

add auto-away

tags/v2.1.0-rc1
Shivaram Lingamneni hace 4 años
padre
commit
a0f4e90b7e
Se han modificado 10 ficheros con 97 adiciones y 26 borrados
  1. 3
    0
      conventional.yaml
  2. 1
    0
      irc/accounts.go
  3. 24
    1
      irc/client.go
  4. 20
    20
      irc/client_lookup_set.go
  5. 1
    0
      irc/config.go
  6. 8
    2
      irc/getters.go
  7. 5
    2
      irc/handlers.go
  8. 5
    1
      irc/nickname.go
  9. 27
    0
      irc/nickserv.go
  10. 3
    0
      oragono.yaml

+ 3
- 0
conventional.yaml Ver fichero

413
         # "disabled", "opt-in", "opt-out", or "mandatory".
413
         # "disabled", "opt-in", "opt-out", or "mandatory".
414
         always-on: "disabled"
414
         always-on: "disabled"
415
 
415
 
416
+        # whether to mark always-on clients away when they have no active connections:
417
+        auto-away: "opt-out"
418
+
416
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
419
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
417
     # hostname/IP) by the HostServ service
420
     # hostname/IP) by the HostServ service
418
     vhosts:
421
     vhosts:

+ 1
- 0
irc/accounts.go Ver fichero

1884
 	AlwaysOn         PersistentStatus
1884
 	AlwaysOn         PersistentStatus
1885
 	AutoreplayMissed bool
1885
 	AutoreplayMissed bool
1886
 	DMHistory        HistoryStatus
1886
 	DMHistory        HistoryStatus
1887
+	AutoAway         PersistentStatus
1887
 }
1888
 }
1888
 
1889
 
1889
 // ClientAccount represents a user account.
1890
 // ClientAccount represents a user account.

+ 24
- 1
irc/client.go Ver fichero

48
 	accountRegDate     time.Time
48
 	accountRegDate     time.Time
49
 	accountSettings    AccountSettings
49
 	accountSettings    AccountSettings
50
 	away               bool
50
 	away               bool
51
+	autoAway           bool
51
 	awayMessage        string
52
 	awayMessage        string
52
 	brbTimer           BrbTimer
53
 	brbTimer           BrbTimer
53
 	channels           ChannelSet
54
 	channels           ChannelSet
393
 
394
 
394
 	client.resizeHistory(config)
395
 	client.resizeHistory(config)
395
 
396
 
396
-	_, err := server.clients.SetNick(client, nil, account.Name)
397
+	_, err, _ := server.clients.SetNick(client, nil, account.Name)
397
 	if err != nil {
398
 	if err != nil {
398
 		server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
399
 		server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
399
 		return
400
 		return
409
 		// this is *probably* ok as long as the persisted memberships are accurate
410
 		// this is *probably* ok as long as the persisted memberships are accurate
410
 		server.channels.Join(client, chname, "", true, nil)
411
 		server.channels.Join(client, chname, "", true, nil)
411
 	}
412
 	}
413
+
414
+	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
415
+		client.autoAway = true
416
+		client.away = true
417
+		client.awayMessage = client.t("User is currently disconnected")
418
+	}
412
 }
419
 }
413
 
420
 
414
 func (client *Client) resizeHistory(config *Config) {
421
 func (client *Client) resizeHistory(config *Config) {
1187
 // otherwise, destroys one specific session, only destroying the client if it
1194
 // otherwise, destroys one specific session, only destroying the client if it
1188
 // has no more sessions.
1195
 // has no more sessions.
1189
 func (client *Client) destroy(session *Session) {
1196
 func (client *Client) destroy(session *Session) {
1197
+	config := client.server.Config()
1190
 	var sessionsToDestroy []*Session
1198
 	var sessionsToDestroy []*Session
1191
 
1199
 
1192
 	client.stateMutex.Lock()
1200
 	client.stateMutex.Lock()
1225
 		client.dirtyBits |= IncludeLastSeen
1233
 		client.dirtyBits |= IncludeLastSeen
1226
 	}
1234
 	}
1227
 	exitedSnomaskSent := client.exitedSnomaskSent
1235
 	exitedSnomaskSent := client.exitedSnomaskSent
1236
+
1237
+	autoAway := false
1238
+	var awayMessage string
1239
+	if alwaysOn && remainingSessions == 0 && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
1240
+		autoAway = true
1241
+		client.autoAway = true
1242
+		client.away = true
1243
+		awayMessage = config.languageManager.Translate(client.languages, `Disconnected from the server`)
1244
+		client.awayMessage = awayMessage
1245
+	}
1246
+
1228
 	client.stateMutex.Unlock()
1247
 	client.stateMutex.Unlock()
1229
 
1248
 
1230
 	// XXX there is no particular reason to persist this state here rather than
1249
 	// XXX there is no particular reason to persist this state here rather than
1272
 		client.server.stats.Remove(registered, invisible, operator)
1291
 		client.server.stats.Remove(registered, invisible, operator)
1273
 	}
1292
 	}
1274
 
1293
 
1294
+	if autoAway {
1295
+		dispatchAwayNotify(client, true, awayMessage)
1296
+	}
1297
+
1275
 	if !shouldDestroy {
1298
 	if !shouldDestroy {
1276
 		return
1299
 		return
1277
 	}
1300
 	}

+ 20
- 20
irc/client_lookup_set.go Ver fichero

103
 		return errNickMissing
103
 		return errNickMissing
104
 	}
104
 	}
105
 
105
 
106
-	success, _, _ := oldClient.AddSession(session)
106
+	success, _, _, _ := oldClient.AddSession(session)
107
 	if !success {
107
 	if !success {
108
 		return errNickMissing
108
 		return errNickMissing
109
 	}
109
 	}
112
 }
112
 }
113
 
113
 
114
 // SetNick sets a client's nickname, validating it against nicknames in use
114
 // SetNick sets a client's nickname, validating it against nicknames in use
115
-func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error) {
115
+func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error, returnedFromAway bool) {
116
 	config := client.server.Config()
116
 	config := client.server.Config()
117
 
117
 
118
 	var newCfNick, newSkeleton string
118
 	var newCfNick, newSkeleton string
134
 
134
 
135
 	if useAccountName {
135
 	if useAccountName {
136
 		if registered && newNick != accountName && newNick != "" {
136
 		if registered && newNick != accountName && newNick != "" {
137
-			return "", errNickAccountMismatch
137
+			return "", errNickAccountMismatch, false
138
 		}
138
 		}
139
 		newNick = accountName
139
 		newNick = accountName
140
 		newCfNick = account
140
 		newCfNick = account
141
 		newSkeleton, err = Skeleton(newNick)
141
 		newSkeleton, err = Skeleton(newNick)
142
 		if err != nil {
142
 		if err != nil {
143
-			return "", errNicknameInvalid
143
+			return "", errNicknameInvalid, false
144
 		}
144
 		}
145
 	} else {
145
 	} else {
146
 		newNick = strings.TrimSpace(newNick)
146
 		newNick = strings.TrimSpace(newNick)
147
 		if len(newNick) == 0 {
147
 		if len(newNick) == 0 {
148
-			return "", errNickMissing
148
+			return "", errNickMissing, false
149
 		}
149
 		}
150
 
150
 
151
 		if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
151
 		if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
152
 			newCfNick, err = CasefoldName(newNick)
152
 			newCfNick, err = CasefoldName(newNick)
153
 			if err != nil {
153
 			if err != nil {
154
-				return "", errNicknameInvalid
154
+				return "", errNicknameInvalid, false
155
 			}
155
 			}
156
 			if !config.Accounts.NickReservation.guestRegexpFolded.MatchString(newCfNick) {
156
 			if !config.Accounts.NickReservation.guestRegexpFolded.MatchString(newCfNick) {
157
 				newNick = strings.Replace(config.Accounts.NickReservation.GuestFormat, "*", newNick, 1)
157
 				newNick = strings.Replace(config.Accounts.NickReservation.GuestFormat, "*", newNick, 1)
163
 			newCfNick, err = CasefoldName(newNick)
163
 			newCfNick, err = CasefoldName(newNick)
164
 		}
164
 		}
165
 		if err != nil {
165
 		if err != nil {
166
-			return "", errNicknameInvalid
166
+			return "", errNicknameInvalid, false
167
 		}
167
 		}
168
 		if len(newNick) > config.Limits.NickLen || len(newCfNick) > config.Limits.NickLen {
168
 		if len(newNick) > config.Limits.NickLen || len(newCfNick) > config.Limits.NickLen {
169
-			return "", errNicknameInvalid
169
+			return "", errNicknameInvalid, false
170
 		}
170
 		}
171
 		newSkeleton, err = Skeleton(newNick)
171
 		newSkeleton, err = Skeleton(newNick)
172
 		if err != nil {
172
 		if err != nil {
173
-			return "", errNicknameInvalid
173
+			return "", errNicknameInvalid, false
174
 		}
174
 		}
175
 
175
 
176
 		if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
176
 		if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
177
-			return "", errNicknameInvalid
177
+			return "", errNicknameInvalid, false
178
 		}
178
 		}
179
 
179
 
180
 		reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
180
 		reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
181
 		if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
181
 		if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
182
-			return "", errNicknameReserved
182
+			return "", errNicknameReserved, false
183
 		}
183
 		}
184
 	}
184
 	}
185
 
185
 
204
 	if currentClient != nil && currentClient != client && session != nil {
204
 	if currentClient != nil && currentClient != client && session != nil {
205
 		// these conditions forbid reattaching to an existing session:
205
 		// these conditions forbid reattaching to an existing session:
206
 		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
206
 		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
207
-			return "", errNicknameInUse
207
+			return "", errNicknameInUse, false
208
 		}
208
 		}
209
 		// check TLS modes
209
 		// check TLS modes
210
 		if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
210
 		if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
211
 			if useAccountName {
211
 			if useAccountName {
212
 				// #955: this is fatal because they can't fix it by trying a different nick
212
 				// #955: this is fatal because they can't fix it by trying a different nick
213
-				return "", errInsecureReattach
213
+				return "", errInsecureReattach, false
214
 			} else {
214
 			} else {
215
-				return "", errNicknameInUse
215
+				return "", errNicknameInUse, false
216
 			}
216
 			}
217
 		}
217
 		}
218
-		reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
218
+		reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
219
 		if !reattachSuccessful {
219
 		if !reattachSuccessful {
220
-			return "", errNicknameInUse
220
+			return "", errNicknameInUse, false
221
 		}
221
 		}
222
 		if numSessions == 1 {
222
 		if numSessions == 1 {
223
 			invisible := currentClient.HasMode(modes.Invisible)
223
 			invisible := currentClient.HasMode(modes.Invisible)
232
 		// for performance reasons
232
 		// for performance reasons
233
 		currentClient.SetNames("user", realname, true)
233
 		currentClient.SetNames("user", realname, true)
234
 		// successful reattach!
234
 		// successful reattach!
235
-		return newNick, nil
235
+		return newNick, nil, back
236
 	} else if currentClient == client && currentClient.Nick() == newNick {
236
 	} else if currentClient == client && currentClient.Nick() == newNick {
237
 		// see #1019: normally no-op nick changes are caught earlier, by performNickChange,
237
 		// see #1019: normally no-op nick changes are caught earlier, by performNickChange,
238
 		// but they are not detected there when force-guest-format is enabled (because
238
 		// but they are not detected there when force-guest-format is enabled (because
239
 		// the proposed nickname is e.g. alice and the current nickname is Guest-alice)
239
 		// the proposed nickname is e.g. alice and the current nickname is Guest-alice)
240
-		return "", errNoop
240
+		return "", errNoop, false
241
 	}
241
 	}
242
 	// analogous checks for skeletons
242
 	// analogous checks for skeletons
243
 	skeletonHolder := clients.bySkeleton[newSkeleton]
243
 	skeletonHolder := clients.bySkeleton[newSkeleton]
244
 	if skeletonHolder != nil && skeletonHolder != client {
244
 	if skeletonHolder != nil && skeletonHolder != client {
245
-		return "", errNicknameInUse
245
+		return "", errNicknameInUse, false
246
 	}
246
 	}
247
 
247
 
248
 	clients.removeInternal(client)
248
 	clients.removeInternal(client)
249
 	clients.byNick[newCfNick] = client
249
 	clients.byNick[newCfNick] = client
250
 	clients.bySkeleton[newSkeleton] = client
250
 	clients.bySkeleton[newSkeleton] = client
251
 	client.updateNick(newNick, newCfNick, newSkeleton)
251
 	client.updateNick(newNick, newCfNick, newSkeleton)
252
-	return newNick, nil
252
+	return newNick, nil, false
253
 }
253
 }
254
 
254
 
255
 func (clients *ClientManager) AllClients() (result []*Client) {
255
 func (clients *ClientManager) AllClients() (result []*Client) {

+ 1
- 0
irc/config.go Ver fichero

221
 	Enabled          bool
221
 	Enabled          bool
222
 	AllowedByDefault bool             `yaml:"allowed-by-default"`
222
 	AllowedByDefault bool             `yaml:"allowed-by-default"`
223
 	AlwaysOn         PersistentStatus `yaml:"always-on"`
223
 	AlwaysOn         PersistentStatus `yaml:"always-on"`
224
+	AutoAway         PersistentStatus `yaml:"auto-away"`
224
 }
225
 }
225
 
226
 
226
 type throttleConfig struct {
227
 type throttleConfig struct {

+ 8
- 2
irc/getters.go Ver fichero

89
 	return
89
 	return
90
 }
90
 }
91
 
91
 
92
-func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time) {
92
+func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) {
93
 	client.stateMutex.Lock()
93
 	client.stateMutex.Lock()
94
 	defer client.stateMutex.Unlock()
94
 	defer client.stateMutex.Unlock()
95
 
95
 
106
 		lastSeen = client.lastSeen
106
 		lastSeen = client.lastSeen
107
 	}
107
 	}
108
 	client.sessions = newSessions
108
 	client.sessions = newSessions
109
-	return true, len(client.sessions), lastSeen
109
+	if client.autoAway {
110
+		back = true
111
+		client.autoAway = false
112
+		client.away = false
113
+		client.awayMessage = ""
114
+	}
115
+	return true, len(client.sessions), lastSeen, back
110
 }
116
 }
111
 
117
 
112
 func (client *Client) removeSession(session *Session) (success bool, length int) {
118
 func (client *Client) removeSession(session *Session) (success bool, length int) {

+ 5
- 2
irc/handlers.go Ver fichero

316
 		rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
316
 		rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
317
 	}
317
 	}
318
 
318
 
319
+	dispatchAwayNotify(client, isAway, awayMessage)
320
+	return false
321
+}
322
+
323
+func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
319
 	// dispatch away-notify
324
 	// dispatch away-notify
320
 	details := client.Details()
325
 	details := client.Details()
321
 	for session := range client.Friends(caps.AwayNotify) {
326
 	for session := range client.Friends(caps.AwayNotify) {
325
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY")
330
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY")
326
 		}
331
 		}
327
 	}
332
 	}
328
-
329
-	return false
330
 }
333
 }
331
 
334
 
332
 // BATCH {+,-}reference-tag type [params...]
335
 // BATCH {+,-}reference-tag type [params...]

+ 5
- 1
irc/nickname.go Ver fichero

33
 	hadNick := details.nick != "*"
33
 	hadNick := details.nick != "*"
34
 	origNickMask := details.nickMask
34
 	origNickMask := details.nickMask
35
 
35
 
36
-	assignedNickname, err := client.server.clients.SetNick(target, session, nickname)
36
+	assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname)
37
 	if err == errNicknameInUse {
37
 	if err == errNicknameInUse {
38
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
38
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
39
 	} else if err == errNicknameReserved {
39
 	} else if err == errNicknameReserved {
80
 		}
80
 		}
81
 	}
81
 	}
82
 
82
 
83
+	if back {
84
+		dispatchAwayNotify(session.client, false, "")
85
+	}
86
+
83
 	for _, channel := range client.Channels() {
87
 	for _, channel := range client.Channels() {
84
 		channel.AddHistoryItem(histItem, details.account)
88
 		channel.AddHistoryItem(histItem, details.account)
85
 	}
89
 	}

+ 27
- 0
irc/nickserv.go Ver fichero

289
 2. 'ephemeral'  [a limited amount of temporary history, not stored on disk]
289
 2. 'ephemeral'  [a limited amount of temporary history, not stored on disk]
290
 3. 'on'         [history stored in a permanent database, if available]
290
 3. 'on'         [history stored in a permanent database, if available]
291
 4. 'default'    [use the server default]`,
291
 4. 'default'    [use the server default]`,
292
+				`$bAUTO-AWAY$b
293
+'auto-away' is only effective for always-on clients. If enabled, you will
294
+automatically be marked away when all your sessions are disconnected, and
295
+automatically return from away when you connect again.`,
292
 			},
296
 			},
293
 			authRequired: true,
297
 			authRequired: true,
294
 			enabled:      servCmdRequiresAuthEnabled,
298
 			enabled:      servCmdRequiresAuthEnabled,
412
 		} else {
416
 		} else {
413
 			nsNotice(rb, client.t("Your account is not configured to receive autoreplayed missed messages"))
417
 			nsNotice(rb, client.t("Your account is not configured to receive autoreplayed missed messages"))
414
 		}
418
 		}
419
+	case "auto-away":
420
+		stored := settings.AutoAway
421
+		alwaysOn := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, settings.AlwaysOn)
422
+		actual := persistenceEnabled(config.Accounts.Multiclient.AutoAway, settings.AutoAway)
423
+		nsNotice(rb, fmt.Sprintf(client.t("Your stored auto-away setting is: %s"), persistentStatusToString(stored)))
424
+		if actual && alwaysOn {
425
+			nsNotice(rb, client.t("Given current server settings, auto-away is enabled for your client"))
426
+		} else if actual && !alwaysOn {
427
+			nsNotice(rb, client.t("Because your client is not always-on, auto-away is disabled"))
428
+		} else if !actual {
429
+			nsNotice(rb, client.t("Given current server settings, auto-away is disabled for your client"))
430
+		}
415
 	case "dm-history":
431
 	case "dm-history":
416
 		effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory)
432
 		effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory)
417
 		csNotice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory)))
433
 		csNotice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory)))
530
 				return
546
 				return
531
 			}
547
 			}
532
 		}
548
 		}
549
+	case "auto-away":
550
+		var newValue PersistentStatus
551
+		newValue, err = persistentStatusFromString(params[1])
552
+		// "opt-in" and "opt-out" don't make sense as user preferences
553
+		if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut {
554
+			munger = func(in AccountSettings) (out AccountSettings, err error) {
555
+				out = in
556
+				out.AutoAway = newValue
557
+				return
558
+			}
559
+		}
533
 	case "dm-history":
560
 	case "dm-history":
534
 		var newValue HistoryStatus
561
 		var newValue HistoryStatus
535
 		newValue, err = historyStatusFromString(params[1])
562
 		newValue, err = historyStatusFromString(params[1])

+ 3
- 0
oragono.yaml Ver fichero

434
         # "disabled", "opt-in", "opt-out", or "mandatory".
434
         # "disabled", "opt-in", "opt-out", or "mandatory".
435
         always-on: "opt-in"
435
         always-on: "opt-in"
436
 
436
 
437
+        # whether to mark always-on clients away when they have no active connections:
438
+        auto-away: "opt-out"
439
+
437
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
440
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
438
     # hostname/IP) by the HostServ service
441
     # hostname/IP) by the HostServ service
439
     vhosts:
442
     vhosts:

Loading…
Cancelar
Guardar