commit b46a755
delthas
·
2022-02-10 14:44:05 +0000 UTC
parent 506b4f5
Add support for the soju.im/read capability and READ command See: https://github.com/emersion/soju/blob/c7f0634ec8ee94425547b159bc36705582151012/doc/read.md
6 files changed,
+93,
-0
M
app.go
+10,
-0
1@@ -221,6 +221,12 @@ func (app *App) eventLoop() {
2 }
3
4 if !app.pasting {
5+ if netID, buffer, timestamp := app.win.UpdateRead(); buffer != "" {
6+ s := app.sessions[netID]
7+ if s != nil {
8+ s.ReadSet(buffer, timestamp)
9+ }
10+ }
11 app.setStatus()
12 app.updatePrompt()
13 app.setBufferNumbers()
14@@ -741,6 +747,7 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
15 if _, added := app.win.AddBuffer(netID, "", buffer); added {
16 app.monitor[netID][buffer] = struct{}{}
17 s.MonitorAdd(buffer)
18+ s.ReadGet(buffer)
19 s.NewHistoryRequest(buffer).
20 WithLimit(500).
21 Before(msg.TimeOrNow())
22@@ -763,6 +770,7 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
23 continue
24 }
25 s.MonitorAdd(target)
26+ s.ReadGet(target)
27 app.win.AddBuffer(netID, "", target)
28 // CHATHISTORY BEFORE excludes its bound, so add 1ms
29 // (precision of the time tag) to include that last message.
30@@ -809,6 +817,8 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
31 if !bounds.IsZero() {
32 app.messageBounds[boundKey{netID, ev.Target}] = bounds
33 }
34+ case irc.ReadEvent:
35+ app.win.SetRead(netID, ev.Target, ev.Timestamp)
36 case irc.BouncerNetworkEvent:
37 _, added := app.win.AddBuffer(ev.ID, ev.Name, "")
38 if added {
+2,
-0
1@@ -343,6 +343,7 @@ func commandDoMsg(app *App, args []string) (err error) {
2 if buffer != "" && !s.IsChannel(target) {
3 app.monitor[netID][buffer] = struct{}{}
4 s.MonitorAdd(buffer)
5+ s.ReadGet(buffer)
6 app.win.AddBuffer(netID, "", buffer)
7 }
8
9@@ -452,6 +453,7 @@ func commandDoQuery(app *App, args []string) (err error) {
10 return fmt.Errorf("cannot query a channel, use JOIN instead")
11 }
12 s.MonitorAdd(target)
13+ s.ReadGet(target)
14 i, _ := app.win.AddBuffer(netID, "", target)
15 s.NewHistoryRequest(target).WithLimit(200).Before(time.Now())
16 app.win.JumpBufferIndex(i)
+5,
-0
1@@ -94,6 +94,11 @@ type HistoryTargetsEvent struct {
2 Targets map[string]time.Time
3 }
4
5+type ReadEvent struct {
6+ Target string
7+ Timestamp time.Time
8+}
9+
10 type BouncerNetworkEvent struct {
11 ID string
12 Name string
+33,
-0
1@@ -60,6 +60,7 @@ var SupportedCapabilities = map[string]struct{}{
2 "draft/chathistory": {},
3 "draft/event-playback": {},
4 "soju.im/bouncer-networks": {},
5+ "soju.im/read": {},
6 }
7
8 // Values taken by the "@+typing=" client tag. TypingUnspec means the value or
9@@ -426,6 +427,18 @@ func (s *Session) TypingStop(target string) {
10 s.out <- NewMessage("TAGMSG", target).WithTag("+typing", "done")
11 }
12
13+func (s *Session) ReadGet(target string) {
14+ if _, ok := s.enabledCaps["soju.im/read"]; ok {
15+ s.out <- NewMessage("READ", target)
16+ }
17+}
18+
19+func (s *Session) ReadSet(target string, timestamp time.Time) {
20+ if _, ok := s.enabledCaps["soju.im/read"]; ok {
21+ s.out <- NewMessage("READ", target, formatTimestamp(timestamp))
22+ }
23+}
24+
25 func (s *Session) MonitorAdd(target string) {
26 targetCf := s.casemap(target)
27 if _, ok := s.monitors[targetCf]; !ok {
28@@ -1237,6 +1250,26 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
29 Time: msg.TimeOrNow(),
30 }, nil
31 }
32+ case "READ":
33+ if len(msg.Params) < 2 {
34+ break
35+ }
36+ var target, timestamp string
37+ if err := msg.ParseParams(&target, ×tamp); err != nil {
38+ return nil, err
39+ }
40+ if !strings.HasPrefix(timestamp, "timestamp=") {
41+ return nil, nil
42+ }
43+ timestamp = strings.TrimPrefix(timestamp, "timestamp=")
44+ t, ok := parseTimestamp(timestamp)
45+ if !ok {
46+ return nil, nil
47+ }
48+ return ReadEvent{
49+ Target: target,
50+ Timestamp: t,
51+ }, nil
52 case "BOUNCER":
53 if len(msg.Params) < 3 {
54 break
+34,
-0
1@@ -182,6 +182,7 @@ type buffer struct {
2 title string
3 highlights int
4 unread bool
5+ read time.Time
6
7 lines []Line
8 topic string
9@@ -389,6 +390,39 @@ func (bs *BufferList) SetTopic(netID, title string, topic string) {
10 b.topic = topic
11 }
12
13+func (bs *BufferList) SetRead(netID, title string, timestamp time.Time) {
14+ idx := bs.idx(netID, title)
15+ if idx < 0 {
16+ return
17+ }
18+ b := &bs.list[idx]
19+ if len(b.lines) > 0 && !b.lines[len(b.lines)-1].At.After(timestamp) {
20+ b.highlights = 0
21+ b.unread = false
22+ }
23+ if b.read.Before(timestamp) {
24+ b.read = timestamp
25+ }
26+}
27+
28+func (bs *BufferList) UpdateRead() (netID, title string, timestamp time.Time) {
29+ b := &bs.list[bs.current]
30+ var line *Line
31+ y := 0
32+ for i := len(b.lines) - 1; 0 <= i; i-- {
33+ line = &b.lines[i]
34+ if y >= b.scrollAmt {
35+ break
36+ }
37+ y += len(line.NewLines(bs.tlInnerWidth)) + 1
38+ }
39+ if line != nil && line.At.After(b.read) {
40+ b.read = line.At
41+ return b.netID, b.title, b.read
42+ }
43+ return "", "", time.Time{}
44+}
45+
46 func (bs *BufferList) Current() (netID, title string) {
47 b := &bs.list[bs.current]
48 return b.netID, b.title
M
ui/ui.go
+9,
-0
1@@ -3,6 +3,7 @@ package ui
2 import (
3 "strings"
4 "sync/atomic"
5+ "time"
6
7 "git.sr.ht/~taiite/senpai/irc"
8
9@@ -236,6 +237,14 @@ func (ui *UI) SetTopic(netID, buffer string, topic string) {
10 ui.bs.SetTopic(netID, buffer, topic)
11 }
12
13+func (ui *UI) SetRead(netID, buffer string, timestamp time.Time) {
14+ ui.bs.SetRead(netID, buffer, timestamp)
15+}
16+
17+func (ui *UI) UpdateRead() (netID, buffer string, timestamp time.Time) {
18+ return ui.bs.UpdateRead()
19+}
20+
21 func (ui *UI) SetStatus(status string) {
22 ui.status = status
23 }