소스 검색

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
No account linked to committer's email address
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
         url="https://github.com/ircv3/ircv3-specifications/pull/503",
195
         url="https://github.com/ircv3/ircv3-specifications/pull/503",
196
         standard="proposed IRCv3",
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
     CapDef(
204
     CapDef(
199
         identifier="StandardReplies",
205
         identifier="StandardReplies",
200
         name="standard-replies",
206
         name="standard-replies",

+ 6
- 1
irc/caps/defs.go 파일 보기

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

+ 5
- 8
irc/client.go 파일 보기

1222
 		client.destroyed = true
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
 		client.setAutoAwayNoMutex(config)
1227
 		client.setAutoAwayNoMutex(config)
1230
-		awayMessage = client.awayMessage
1231
-		becameAutoAway = !wasAway && awayMessage != ""
1232
 	}
1228
 	}
1229
+	nowAway := client.awayMessage
1233
 
1230
 
1234
 	if client.registrationTimer != nil {
1231
 	if client.registrationTimer != nil {
1235
 		// unconditionally stop; if the client is still unregistered it must be destroyed
1232
 		// unconditionally stop; if the client is still unregistered it must be destroyed
1279
 		client.server.stats.Remove(registered, invisible, operator)
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
 	if !shouldDestroy {
1283
 	if !shouldDestroy {

+ 3
- 3
irc/client_lookup_set.go 파일 보기

84
 // SetNick sets a client's nickname, validating it against nicknames in use
84
 // SetNick sets a client's nickname, validating it against nicknames in use
85
 // XXX: dryRun validates a client's ability to claim a nick, without
85
 // XXX: dryRun validates a client's ability to claim a nick, without
86
 // actually claiming it
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
 	config := client.server.Config()
88
 	config := client.server.Config()
89
 
89
 
90
 	var newCfNick, newSkeleton string
90
 	var newCfNick, newSkeleton string
204
 				return "", errNicknameInUse, false
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
 		if !reattachSuccessful {
208
 		if !reattachSuccessful {
209
 			return "", errNicknameInUse, false
209
 			return "", errNicknameInUse, false
210
 		}
210
 		}
219
 			currentClient.SetRealname(realname)
219
 			currentClient.SetRealname(realname)
220
 		}
220
 		}
221
 		// successful reattach!
221
 		// successful reattach!
222
-		return newNick, nil, back
222
+		return newNick, nil, wasAway != nowAway
223
 	} else if currentClient == client && currentClient.Nick() == newNick {
223
 	} else if currentClient == client && currentClient.Nick() == newNick {
224
 		return "", errNoop, false
224
 		return "", errNoop, false
225
 	}
225
 	}

+ 3
- 2
irc/commands.go 파일 보기

89
 			minParams:    1,
89
 			minParams:    1,
90
 		},
90
 		},
91
 		"AWAY": {
91
 		"AWAY": {
92
-			handler:   awayHandler,
93
-			minParams: 0,
92
+			handler:      awayHandler,
93
+			usablePreReg: true,
94
+			minParams:    0,
94
 		},
95
 		},
95
 		"BATCH": {
96
 		"BATCH": {
96
 			handler:        batchHandler,
97
 			handler:        batchHandler,

+ 28
- 14
irc/getters.go 파일 보기

92
 	return
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
 	config := client.server.Config()
96
 	config := client.server.Config()
97
 	client.stateMutex.Lock()
97
 	client.stateMutex.Lock()
98
 	defer client.stateMutex.Unlock()
98
 	defer client.stateMutex.Unlock()
113
 		client.setLastSeen(time.Now().UTC(), session.deviceID)
113
 		client.setLastSeen(time.Now().UTC(), session.deviceID)
114
 	}
114
 	}
115
 	client.sessions = newSessions
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
 func (client *Client) removeSession(session *Session) (success bool, length int) {
134
 func (client *Client) removeSession(session *Session) (success bool, length int) {
195
 	return
203
 	return
196
 }
204
 }
197
 
205
 
198
-func (session *Session) SetAway(awayMessage string) {
206
+func (session *Session) SetAway(awayMessage string) (wasAway, nowAway string) {
199
 	client := session.client
207
 	client := session.client
200
 	config := client.server.Config()
208
 	config := client.server.Config()
201
 
209
 
205
 	session.awayMessage = awayMessage
213
 	session.awayMessage = awayMessage
206
 	session.awayAt = time.Now().UTC()
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
 		client.setAutoAwayNoMutex(config)
218
 		client.setAutoAwayNoMutex(config)
211
-	} else {
219
+	} else if awayMessage != "*" {
212
 		client.awayMessage = awayMessage
220
 		client.awayMessage = awayMessage
213
-	}
221
+	} // else: `AWAY *`, should not modify publicly visible away state
222
+	nowAway = client.awayMessage
214
 	return
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
 func (client *Client) setAutoAwayNoMutex(config *Config) {
231
 func (client *Client) setAutoAwayNoMutex(config *Config) {
218
 	// aggregate the away statuses of the individual sessions:
232
 	// aggregate the away statuses of the individual sessions:
219
 	var globalAwayState string
233
 	var globalAwayState string
223
 			// a session is active, we are not auto-away
237
 			// a session is active, we are not auto-away
224
 			client.awayMessage = ""
238
 			client.awayMessage = ""
225
 			return
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
 			globalAwayState = cSession.awayMessage
242
 			globalAwayState = cSession.awayMessage
229
 			awaySetAt = cSession.awayAt
243
 			awaySetAt = cSession.awayAt
230
 		}
244
 		}

+ 9
- 7
irc/handlers.go 파일 보기

447
 
447
 
448
 // AWAY [<message>]
448
 // AWAY [<message>]
449
 func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
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
 	var awayMessage string
451
 	var awayMessage string
452
 	if len(msg.Params) > 0 {
452
 	if len(msg.Params) > 0 {
453
 		awayMessage = msg.Params[0]
453
 		awayMessage = msg.Params[0]
454
 		awayMessage = ircutils.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen)
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
 		rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
460
 		rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
462
 	} else {
461
 	} else {
463
 		rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
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
 	return false
469
 	return false
468
 }
470
 }
469
 
471
 
470
-func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
472
+func dispatchAwayNotify(client *Client, awayMessage string) {
471
 	// dispatch away-notify
473
 	// dispatch away-notify
472
 	details := client.Details()
474
 	details := client.Details()
473
 	isBot := client.HasMode(modes.Bot)
475
 	isBot := client.HasMode(modes.Bot)
474
 	for session := range client.FriendsMonitors(caps.AwayNotify) {
476
 	for session := range client.FriendsMonitors(caps.AwayNotify) {
475
-		if isAway {
477
+		if awayMessage != "" {
476
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
478
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
477
 		} else {
479
 		} else {
478
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")
480
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")

+ 3
- 3
irc/nickname.go 파일 보기

34
 	origNickMask := details.nickMask
34
 	origNickMask := details.nickMask
35
 	isSanick := client != target
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
 	if err == errNicknameInUse {
38
 	if err == errNicknameInUse {
39
 		if !isSanick {
39
 		if !isSanick {
40
 			rb.Add(nil, server.name, ERR_NICKNAMEINUSE, details.nick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
40
 			rb.Add(nil, server.name, ERR_NICKNAMEINUSE, details.nick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
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
 	for _, channel := range target.Channels() {
122
 	for _, channel := range target.Channels() {

Loading…
취소
저장