ソースを参照

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
tags/v2.12.0-rc1
Shivaram Lingamneni 1年前
コミット
1da11ae8ae
コミッターのメールアドレスに関連付けられたアカウントが存在しません
8個のファイルの変更63行の追加38行の削除
  1. 6
    0
      gencapdefs.py
  2. 6
    1
      irc/caps/defs.go
  3. 5
    8
      irc/client.go
  4. 3
    3
      irc/client_lookup_set.go
  5. 3
    2
      irc/commands.go
  6. 28
    14
      irc/getters.go
  7. 9
    7
      irc/handlers.go
  8. 3
    3
      irc/nickname.go

+ 6
- 0
gencapdefs.py ファイルの表示

@@ -195,6 +195,12 @@ CAPDEFS = [
195 195
         url="https://github.com/ircv3/ircv3-specifications/pull/503",
196 196
         standard="proposed IRCv3",
197 197
     ),
198
+    CapDef(
199
+        identifier="Preaway",
200
+        name="draft/pre-away",
201
+        url="https://github.com/ircv3/ircv3-specifications/pull/514",
202
+        standard="proposed IRCv3",
203
+    ),
198 204
     CapDef(
199 205
         identifier="StandardReplies",
200 206
         name="standard-replies",

+ 6
- 1
irc/caps/defs.go ファイルの表示

@@ -7,7 +7,7 @@ package caps
7 7
 
8 8
 const (
9 9
 	// number of recognized capabilities:
10
-	numCapabs = 31
10
+	numCapabs = 32
11 11
 	// length of the uint32 array that represents the bitset:
12 12
 	bitsetLen = 1
13 13
 )
@@ -65,6 +65,10 @@ const (
65 65
 	// https://github.com/ircv3/ircv3-specifications/pull/503
66 66
 	Persistence Capability = iota
67 67
 
68
+	// Preaway is the proposed IRCv3 capability named "draft/pre-away":
69
+	// https://github.com/ircv3/ircv3-specifications/pull/514
70
+	Preaway Capability = iota
71
+
68 72
 	// ReadMarker is the draft IRCv3 capability named "draft/read-marker":
69 73
 	// https://github.com/ircv3/ircv3-specifications/pull/489
70 74
 	ReadMarker Capability = iota
@@ -154,6 +158,7 @@ var (
154 158
 		"draft/languages",
155 159
 		"draft/multiline",
156 160
 		"draft/persistence",
161
+		"draft/pre-away",
157 162
 		"draft/read-marker",
158 163
 		"draft/relaymsg",
159 164
 		"echo-message",

+ 5
- 8
irc/client.go ファイルの表示

@@ -1222,14 +1222,11 @@ func (client *Client) destroy(session *Session) {
1222 1222
 		client.destroyed = true
1223 1223
 	}
1224 1224
 
1225
-	becameAutoAway := false
1226
-	var awayMessage string
1227
-	if alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
1228
-		wasAway := client.awayMessage != ""
1225
+	wasAway := client.awayMessage
1226
+	if client.autoAwayEnabledNoMutex(config) {
1229 1227
 		client.setAutoAwayNoMutex(config)
1230
-		awayMessage = client.awayMessage
1231
-		becameAutoAway = !wasAway && awayMessage != ""
1232 1228
 	}
1229
+	nowAway := client.awayMessage
1233 1230
 
1234 1231
 	if client.registrationTimer != nil {
1235 1232
 		// unconditionally stop; if the client is still unregistered it must be destroyed
@@ -1279,8 +1276,8 @@ func (client *Client) destroy(session *Session) {
1279 1276
 		client.server.stats.Remove(registered, invisible, operator)
1280 1277
 	}
1281 1278
 
1282
-	if becameAutoAway {
1283
-		dispatchAwayNotify(client, true, awayMessage)
1279
+	if !shouldDestroy && wasAway != nowAway {
1280
+		dispatchAwayNotify(client, nowAway)
1284 1281
 	}
1285 1282
 
1286 1283
 	if !shouldDestroy {

+ 3
- 3
irc/client_lookup_set.go ファイルの表示

@@ -84,7 +84,7 @@ func (clients *ClientManager) Remove(client *Client) error {
84 84
 // SetNick sets a client's nickname, validating it against nicknames in use
85 85
 // XXX: dryRun validates a client's ability to claim a nick, without
86 86
 // actually claiming it
87
-func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string, dryRun bool) (setNick string, err error, returnedFromAway bool) {
87
+func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string, dryRun bool) (setNick string, err error, awayChanged bool) {
88 88
 	config := client.server.Config()
89 89
 
90 90
 	var newCfNick, newSkeleton string
@@ -204,7 +204,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
204 204
 				return "", errNicknameInUse, false
205 205
 			}
206 206
 		}
207
-		reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
207
+		reattachSuccessful, numSessions, lastSeen, wasAway, nowAway := currentClient.AddSession(session)
208 208
 		if !reattachSuccessful {
209 209
 			return "", errNicknameInUse, false
210 210
 		}
@@ -219,7 +219,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
219 219
 			currentClient.SetRealname(realname)
220 220
 		}
221 221
 		// successful reattach!
222
-		return newNick, nil, back
222
+		return newNick, nil, wasAway != nowAway
223 223
 	} else if currentClient == client && currentClient.Nick() == newNick {
224 224
 		return "", errNoop, false
225 225
 	}

+ 3
- 2
irc/commands.go ファイルの表示

@@ -89,8 +89,9 @@ func init() {
89 89
 			minParams:    1,
90 90
 		},
91 91
 		"AWAY": {
92
-			handler:   awayHandler,
93
-			minParams: 0,
92
+			handler:      awayHandler,
93
+			usablePreReg: true,
94
+			minParams:    0,
94 95
 		},
95 96
 		"BATCH": {
96 97
 			handler:        batchHandler,

+ 28
- 14
irc/getters.go ファイルの表示

@@ -92,7 +92,7 @@ func (client *Client) AllSessionData(currentSession *Session, hasPrivs bool) (da
92 92
 	return
93 93
 }
94 94
 
95
-func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) {
95
+func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, wasAway, nowAway string) {
96 96
 	config := client.server.Config()
97 97
 	client.stateMutex.Lock()
98 98
 	defer client.stateMutex.Unlock()
@@ -113,14 +113,22 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
113 113
 		client.setLastSeen(time.Now().UTC(), session.deviceID)
114 114
 	}
115 115
 	client.sessions = newSessions
116
-	// TODO(#1551) there should be a cap to opt out of this behavior on a session
117
-	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
118
-		client.awayMessage = ""
119
-		if len(client.sessions) == 1 {
120
-			back = true
116
+	wasAway = client.awayMessage
117
+	if client.autoAwayEnabledNoMutex(config) {
118
+		client.setAutoAwayNoMutex(config)
119
+	} else {
120
+		if session.awayMessage != "" && session.awayMessage != "*" {
121
+			// set the away message
122
+			client.awayMessage = session.awayMessage
123
+		} else if session.awayMessage == "" && !session.awayAt.IsZero() {
124
+			// weird edge case: explicit `AWAY` or `AWAY :` during pre-registration makes the client back
125
+			client.awayMessage = ""
121 126
 		}
127
+		// else: the client sent no AWAY command at all, no-op
128
+		// or: the client sent `AWAY *`, which should not modify the publicly visible away state
122 129
 	}
123
-	return true, len(client.sessions), lastSeen, back
130
+	nowAway = client.awayMessage
131
+	return true, len(client.sessions), lastSeen, wasAway, nowAway
124 132
 }
125 133
 
126 134
 func (client *Client) removeSession(session *Session) (success bool, length int) {
@@ -195,7 +203,7 @@ func (client *Client) Away() (result bool, message string) {
195 203
 	return
196 204
 }
197 205
 
198
-func (session *Session) SetAway(awayMessage string) {
206
+func (session *Session) SetAway(awayMessage string) (wasAway, nowAway string) {
199 207
 	client := session.client
200 208
 	config := client.server.Config()
201 209
 
@@ -205,15 +213,21 @@ func (session *Session) SetAway(awayMessage string) {
205 213
 	session.awayMessage = awayMessage
206 214
 	session.awayAt = time.Now().UTC()
207 215
 
208
-	autoAway := client.registered && client.alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway)
209
-	if autoAway {
216
+	wasAway = client.awayMessage
217
+	if client.autoAwayEnabledNoMutex(config) {
210 218
 		client.setAutoAwayNoMutex(config)
211
-	} else {
219
+	} else if awayMessage != "*" {
212 220
 		client.awayMessage = awayMessage
213
-	}
221
+	} // else: `AWAY *`, should not modify publicly visible away state
222
+	nowAway = client.awayMessage
214 223
 	return
215 224
 }
216 225
 
226
+func (client *Client) autoAwayEnabledNoMutex(config *Config) bool {
227
+	return client.registered && client.alwaysOn &&
228
+		persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway)
229
+}
230
+
217 231
 func (client *Client) setAutoAwayNoMutex(config *Config) {
218 232
 	// aggregate the away statuses of the individual sessions:
219 233
 	var globalAwayState string
@@ -223,8 +237,8 @@ func (client *Client) setAutoAwayNoMutex(config *Config) {
223 237
 			// a session is active, we are not auto-away
224 238
 			client.awayMessage = ""
225 239
 			return
226
-		} else if cSession.awayAt.After(awaySetAt) {
227
-			// choose the latest available away message from any session
240
+		} else if cSession.awayAt.After(awaySetAt) && cSession.awayMessage != "*" {
241
+			// choose the latest valid away message from any session
228 242
 			globalAwayState = cSession.awayMessage
229 243
 			awaySetAt = cSession.awayAt
230 244
 		}

+ 9
- 7
irc/handlers.go ファイルの表示

@@ -447,32 +447,34 @@ func authScramHandler(server *Server, client *Client, session *Session, value []
447 447
 
448 448
 // AWAY [<message>]
449 449
 func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
450
-	var isAway bool
450
+	// #1996: `AWAY :` is treated the same as `AWAY`
451 451
 	var awayMessage string
452 452
 	if len(msg.Params) > 0 {
453 453
 		awayMessage = msg.Params[0]
454 454
 		awayMessage = ircutils.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen)
455 455
 	}
456
-	isAway = (awayMessage != "") // #1996
457 456
 
458
-	rb.session.SetAway(awayMessage)
457
+	wasAway, nowAway := rb.session.SetAway(awayMessage)
459 458
 
460
-	if isAway {
459
+	if nowAway != "" {
461 460
 		rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
462 461
 	} else {
463 462
 		rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
464 463
 	}
465 464
 
466
-	dispatchAwayNotify(client, isAway, awayMessage)
465
+	if client.registered && wasAway != nowAway {
466
+		dispatchAwayNotify(client, nowAway)
467
+	} // else: we'll send it (if applicable) after reattach
468
+
467 469
 	return false
468 470
 }
469 471
 
470
-func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
472
+func dispatchAwayNotify(client *Client, awayMessage string) {
471 473
 	// dispatch away-notify
472 474
 	details := client.Details()
473 475
 	isBot := client.HasMode(modes.Bot)
474 476
 	for session := range client.FriendsMonitors(caps.AwayNotify) {
475
-		if isAway {
477
+		if awayMessage != "" {
476 478
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
477 479
 		} else {
478 480
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")

+ 3
- 3
irc/nickname.go ファイルの表示

@@ -34,7 +34,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
34 34
 	origNickMask := details.nickMask
35 35
 	isSanick := client != target
36 36
 
37
-	assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname, false)
37
+	assignedNickname, err, awayChanged := client.server.clients.SetNick(target, session, nickname, false)
38 38
 	if err == errNicknameInUse {
39 39
 		if !isSanick {
40 40
 			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 *
115 115
 		}
116 116
 	}
117 117
 
118
-	if back {
119
-		dispatchAwayNotify(session.client, false, "")
118
+	if awayChanged {
119
+		dispatchAwayNotify(session.client, session.client.AwayMessage())
120 120
 	}
121 121
 
122 122
 	for _, channel := range target.Channels() {

読み込み中…
キャンセル
保存