1package irc
2
3import (
4 "sync"
5 "time"
6
7 "golang.org/x/time/rate"
8)
9
10// Typing is an event of Name actively typing in Target.
11type Typing struct {
12 Target string
13 Name string
14}
15
16// Typings keeps track of typing notification timeouts.
17type Typings struct {
18 l sync.Mutex
19 closed bool // whether Close has been called
20 targets map[Typing]time.Time // @+typing TAGMSG timestamps.
21 timeouts chan Typing // transmits unfiltered timeout notifications.
22 stops chan Typing // transmits filtered timeout notifications.
23}
24
25// NewTypings initializes the Typings structures and filtering coroutine.
26func NewTypings() *Typings {
27 ts := &Typings{
28 targets: map[Typing]time.Time{},
29 timeouts: make(chan Typing, 16),
30 stops: make(chan Typing, 16),
31 }
32 go func() {
33 for t := range ts.timeouts {
34 now := time.Now()
35 ts.l.Lock()
36 oldT, ok := ts.targets[t]
37 if ok && 6.0 < now.Sub(oldT).Seconds() {
38 delete(ts.targets, t)
39 ts.l.Unlock()
40 ts.stops <- t
41 } else {
42 ts.l.Unlock()
43 }
44 }
45 }()
46 return ts
47}
48
49// Close cleanly closes all channels and stops all goroutines.
50func (ts *Typings) Close() {
51 ts.l.Lock()
52 defer ts.l.Unlock()
53
54 close(ts.timeouts)
55 close(ts.stops)
56 ts.closed = true
57}
58
59// Stops is a channel that transmits typing timeouts.
60func (ts *Typings) Stops() <-chan Typing {
61 return ts.stops
62}
63
64// Active should be called when a user is typing to some target.
65func (ts *Typings) Active(target, name string) {
66 ts.l.Lock()
67 t := Typing{target, name}
68 ts.targets[t] = time.Now()
69 ts.l.Unlock()
70
71 go func() {
72 time.Sleep(6 * time.Second)
73
74 ts.l.Lock()
75 closed := ts.closed
76 ts.l.Unlock()
77
78 if !closed {
79 ts.timeouts <- t
80 }
81 }()
82}
83
84// Done should be called when a user is done typing to some target.
85func (ts *Typings) Done(target, name string) {
86 ts.l.Lock()
87 delete(ts.targets, Typing{target, name})
88 ts.l.Unlock()
89}
90
91func (ts *Typings) List(target string) []string {
92 ts.l.Lock()
93 defer ts.l.Unlock()
94
95 var res []string
96 for t := range ts.targets {
97 if target == t.Target {
98 res = append(res, t.Name)
99 }
100 }
101 return res
102}
103
104type typingStamp struct {
105 Last time.Time
106 Type int
107 Limit *rate.Limiter
108}