commit 01c2715

delthas  ·  2021-07-12 09:43:22 +0000 UTC
parent 63d17a7
Ratelimit +typing and send +typing=done once only

Fixes: #8
Fixes: #41
4 files changed,  +43, -5
M go.mod
M go.sum
M go.mod
+1, -0
1@@ -6,5 +6,6 @@ require (
2 	github.com/gdamore/tcell/v2 v2.3.11
3 	github.com/mattn/go-runewidth v0.0.10
4 	golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
5+	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
6 	gopkg.in/yaml.v2 v2.3.0
7 )
M go.sum
+2, -0
1@@ -14,6 +14,8 @@ golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlp
2 golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
3 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
4 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
5+golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
6+golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+32, -5
 1@@ -11,6 +11,8 @@ import (
 2 	"time"
 3 	"unicode"
 4 	"unicode/utf8"
 5+
 6+	"golang.org/x/time/rate"
 7 )
 8 
 9 type SASLClient interface {
10@@ -102,8 +104,8 @@ type Session struct {
11 	out          chan<- Message
12 	closed       bool
13 	registered   bool
14-	typings      *Typings             // incoming typing notifications.
15-	typingStamps map[string]time.Time // user typing instants.
16+	typings      *Typings               // incoming typing notifications.
17+	typingStamps map[string]typingStamp // user typing instants.
18 
19 	nick   string
20 	nickCf string // casemapped nickname.
21@@ -134,7 +136,7 @@ func NewSession(out chan<- Message, params SessionParams) *Session {
22 	s := &Session{
23 		out:           out,
24 		typings:       NewTypings(),
25-		typingStamps:  map[string]time.Time{},
26+		typingStamps:  map[string]typingStamp{},
27 		nick:          params.Nickname,
28 		nickCf:        CasemapASCII(params.Nickname),
29 		user:          params.Username,
30@@ -342,10 +344,19 @@ func (s *Session) Typing(target string) {
31 	}
32 	targetCf := s.casemap(target)
33 	now := time.Now()
34-	if t, ok := s.typingStamps[targetCf]; ok && now.Sub(t).Seconds() < 3.0 {
35+	t, ok := s.typingStamps[targetCf]
36+	if ok && ((t.Type == TypingActive && now.Sub(t.Last).Seconds() < 3.0) || !t.Limit.Allow()) {
37 		return
38 	}
39-	s.typingStamps[targetCf] = now
40+	if !ok {
41+		t.Limit = rate.NewLimiter(rate.Limit(1.0/3.0), 5)
42+		t.Limit.Reserve() // will always be OK
43+	}
44+	s.typingStamps[targetCf] = typingStamp{
45+		Last:  now,
46+		Type:  TypingActive,
47+		Limit: t.Limit,
48+	}
49 	s.out <- NewMessage("TAGMSG", target).WithTag("+typing", "active")
50 }
51 
52@@ -353,6 +364,22 @@ func (s *Session) TypingStop(target string) {
53 	if !s.HasCapability("message-tags") {
54 		return
55 	}
56+	targetCf := s.casemap(target)
57+	now := time.Now()
58+	t, ok := s.typingStamps[targetCf]
59+	if ok && (t.Type == TypingDone || !t.Limit.Allow()) {
60+		// don't send a +typing=done again if the last typing we sent was a +typing=done
61+		return
62+	}
63+	if !ok {
64+		t.Limit = rate.NewLimiter(rate.Limit(1), 5)
65+		t.Limit.Reserve() // will always be OK
66+	}
67+	s.typingStamps[targetCf] = typingStamp{
68+		Last:  now,
69+		Type:  TypingDone,
70+		Limit: t.Limit,
71+	}
72 	s.out <- NewMessage("TAGMSG", target).WithTag("+typing", "done")
73 }
74 
+8, -0
 1@@ -3,6 +3,8 @@ package irc
 2 import (
 3 	"sync"
 4 	"time"
 5+
 6+	"golang.org/x/time/rate"
 7 )
 8 
 9 // Typing is an event of Name actively typing in Target.
10@@ -86,3 +88,9 @@ func (ts *Typings) List(target string) []string {
11 	}
12 	return res
13 }
14+
15+type typingStamp struct {
16+	Last  time.Time
17+	Type  int
18+	Limit *rate.Limiter
19+}