commit a7b00a0

Hubert Hirtz  ·  2026-03-10 18:39:10 +0000 UTC
parent 89ba4ff
Check for CLIENTTAGDENY before sending @+typing

ref: https://ircv3.net/specs/extensions/message-tags#rpl_isupport-tokens
1 files changed,  +29, -2
+29, -2
 1@@ -160,6 +160,9 @@ type Session struct {
 2 	listMask      bool
 3 	upload        string
 4 
 5+	clientTagListIsAllow bool // whether clientTagList is an allowlist (true) or a blocklist (false)
 6+	clientTagList        map[string]struct{}
 7+
 8 	users                  map[string]*User        // known users.
 9 	channels               map[string]Channel      // joined channels.
10 	metadata               map[string]Metadata     // known target metadata.
11@@ -200,6 +203,7 @@ func NewSession(out chan<- Message, params SessionParams) *Session {
12 		historyLimit:    100,
13 		prefixSymbols:   "@+",
14 		prefixModes:     "ov",
15+		clientTagList:   map[string]struct{}{},
16 		users:           map[string]*User{},
17 		channels:        map[string]Channel{},
18 		metadata:        map[string]Metadata{},
19@@ -252,6 +256,11 @@ func (s *Session) HasCapability(capability string) bool {
20 	return ok
21 }
22 
23+func (s *Session) CanSendTag(clientTagWithoutThePlus string) bool {
24+	_, ok := s.clientTagList[clientTagWithoutThePlus]
25+	return ok == s.clientTagListIsAllow
26+}
27+
28 func (s *Session) IsBouncer() bool {
29 	if s.HasCapability("soju.im/bouncer-networks") { // soju-compatible
30 		return true
31@@ -522,7 +531,7 @@ func (s *Session) PrivMsg(target, content string) {
32 }
33 
34 func (s *Session) Typing(target string) {
35-	if !s.HasCapability("message-tags") {
36+	if !s.HasCapability("message-tags") || !s.CanSendTag("typing") {
37 		return
38 	}
39 	targetCf := s.casemap(target)
40@@ -544,7 +553,7 @@ func (s *Session) Typing(target string) {
41 }
42 
43 func (s *Session) TypingStop(target string) {
44-	if !s.HasCapability("message-tags") {
45+	if !s.HasCapability("message-tags") || !s.CanSendTag("typing") {
46 		return
47 	}
48 	targetCf := s.casemap(target)
49@@ -2225,6 +2234,24 @@ func (s *Session) updateFeatures(features []string) {
50 					s.users[s.nickCf] = u
51 				}
52 			}
53+		case "CLIENTTAGDENY":
54+			clear(s.clientTagList)
55+			s.clientTagListIsAllow = strings.HasPrefix(value, "*")
56+			tags := strings.Split(value, ",")
57+			if s.clientTagListIsAllow {
58+				tags = tags[1:]
59+			}
60+			for _, tag := range tags {
61+				if s.clientTagListIsAllow {
62+					if !strings.HasPrefix(tag, "-") {
63+						continue
64+					}
65+					tag = tag[1:]
66+				} else if strings.HasPrefix(tag, "-") {
67+					continue
68+				}
69+				s.clientTagList[tag] = struct{}{}
70+			}
71 		case "CHANMODES":
72 			// We only care about the first four params
73 			types := strings.SplitN(value, ",", 5)