diff options
author | Hubert Hirtz <hubert@hirtzfr.eu> | 2020-09-02 00:06:37 +0200 |
---|---|---|
committer | Hubert Hirtz <hubert@hirtzfr.eu> | 2020-09-02 16:00:57 +0200 |
commit | 3ea19ff21e5cda8afca7f8114e18466e9cf7663e (patch) | |
tree | 833a97485ebd98a2d1fd805db6f3eeacaf30127a /irc | |
parent | irc: Reset typing ratelimiter after sent message (diff) |
Typing indicator timeout
Diffstat (limited to 'irc')
-rw-r--r-- | irc/states.go | 34 | ||||
-rw-r--r-- | irc/typing.go | 64 |
2 files changed, 93 insertions, 5 deletions
diff --git a/irc/states.go b/irc/states.go index 3ba5572..e560bdf 100644 --- a/irc/states.go +++ b/irc/states.go @@ -139,6 +139,7 @@ type Session struct { running atomic.Value // bool registered bool + typings *Typings typingStamps map[string]time.Time nick string @@ -165,6 +166,7 @@ func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error) acts: make(chan action, 64), evts: make(chan Event, 64), debug: params.Debug, + typings: NewTypings(), typingStamps: map[string]time.Time{}, nick: params.Nickname, nickCf: CasemapASCII(params.Nickname), @@ -267,6 +269,17 @@ func (s *Session) Names(channel string) []Member { return names } +func (s *Session) Typings(target string) []string { + targetCf := s.Casemap(target) + var res []string + for t := range s.typings.targets { + if targetCf == t.Target { + res = append(res, s.users[t.Name].Name.Name) + } + } + return res +} + func (s *Session) ChannelsSharedWith(name string) []string { var user *User if u, ok := s.users[s.Casemap(name)]; ok { @@ -420,6 +433,13 @@ func (s *Session) run() { } else { err = s.handleStart(msg) } + case t := <-s.typings.Stops(): + s.evts <- TagEvent{ + User: s.users[t.Name].Name, + Target: s.channels[t.Target].Name, + Typing: TypingDone, + Time: time.Now(), + } } if err != nil { @@ -686,12 +706,12 @@ func (s *Session) handle(msg Message) (err error) { if u, ok := s.users[nickCf]; ok { delete(c.Members, u) s.cleanUser(u) - t := msg.TimeOrNow() + s.typings.Done(channelCf, nickCf) s.evts <- UserPartEvent{ User: msg.Prefix.Copy(), Channel: c.Name, - Time: t, + Time: msg.TimeOrNow(), } } } @@ -699,20 +719,20 @@ func (s *Session) handle(msg Message) (err error) { nickCf := s.Casemap(msg.Prefix.Name) if u, ok := s.users[nickCf]; ok { - t := msg.TimeOrNow() var channels []string - for _, c := range s.channels { + for channelCf, c := range s.channels { if _, ok := c.Members[u]; ok { channels = append(channels, c.Name) delete(c.Members, u) s.cleanUser(u) + s.typings.Done(channelCf, nickCf) } } s.evts <- UserQuitEvent{ User: msg.Prefix.Copy(), Channels: channels, - Time: t, + Time: msg.TimeOrNow(), } } case rplNamreply: @@ -776,10 +796,13 @@ func (s *Session) handle(msg Message) (err error) { if t, ok := msg.Tags["+typing"]; ok { if t == "active" { typing = TypingActive + s.typings.Active(targetCf, nickCf) } else if t == "paused" { typing = TypingPaused + s.typings.Active(targetCf, nickCf) } else if t == "done" { typing = TypingDone + s.typings.Done(targetCf, nickCf) } } else { break @@ -859,6 +882,7 @@ func (s *Session) handle(msg Message) (err error) { func (s *Session) privmsgToEvent(msg Message) (ev MessageEvent) { targetCf := s.Casemap(msg.Params[0]) + s.typings.Done(targetCf, s.Casemap(msg.Prefix.Name)) ev = MessageEvent{ User: msg.Prefix.Copy(), // TODO correctly casemap Target: msg.Params[0], // TODO correctly casemap diff --git a/irc/typing.go b/irc/typing.go new file mode 100644 index 0000000..a62a420 --- /dev/null +++ b/irc/typing.go @@ -0,0 +1,64 @@ +package irc + +import ( + "sync" + "time" +) + +type Typing struct { + Target string + Name string +} + +type Typings struct { + l sync.Mutex + targets map[Typing]time.Time + timeouts chan Typing + stops chan Typing +} + +func NewTypings() *Typings { + ts := &Typings{ + targets: map[Typing]time.Time{}, + timeouts: make(chan Typing, 16), + stops: make(chan Typing, 16), + } + go func() { + for { + t := <-ts.timeouts + now := time.Now() + ts.l.Lock() + oldT, ok := ts.targets[t] + if ok && 6.0 < now.Sub(oldT).Seconds() { + delete(ts.targets, t) + ts.l.Unlock() + ts.stops <- t + } else { + ts.l.Unlock() + } + } + }() + return ts +} + +func (ts *Typings) Stops() <-chan Typing { + return ts.stops +} + +func (ts *Typings) Active(target, name string) { + t := Typing{target, name} + ts.l.Lock() + ts.targets[t] = time.Now() + ts.l.Unlock() + + go func() { + time.Sleep(6 * time.Second) + ts.timeouts <- t + }() +} + +func (ts *Typings) Done(target, name string) { + ts.l.Lock() + delete(ts.targets, Typing{target, name}) + ts.l.Unlock() +} |