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
+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+}