~taiite/public-inbox

[RFC senpai] Implement the soju.im/read specification

Details
Message ID
<20210708174845.1966-1-delthas@dille.cc>
DKIM signature
pass
Download raw message
Patch: +97 -10
---
 app.go         |  3 +++
 irc/events.go  |  5 +++++
 irc/session.go | 26 ++++++++++++++++++++++++++
 irc/tokens.go  | 14 +++++---------
 ui/buffers.go  | 40 +++++++++++++++++++++++++++++++++++++++-
 ui/ui.go       |  8 ++++++++
 window.go      | 11 +++++++++++
 7 files changed, 97 insertions(+), 10 deletions(-)

diff --git a/app.go b/app.go
index 01f9bc4..d9e5971 100644
--- a/app.go
+++ b/app.go
@@ -115,6 +115,7 @@ func (app *App) eventLoop() {
		app.handleEvents(evs)
		if !app.pasting {
			app.setStatus()
			app.updateRead()
			app.win.Draw()
		}
	}
@@ -572,6 +573,8 @@ func (app *App) handleIRCEvent(ev interface{}) {
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
		})
	case irc.ReadEvent:
		app.win.SetRead(ev.Target, ev.Time)
	case irc.MessageEvent:
		buffer, line, hlNotification := app.formatMessage(ev)
		app.win.AddLine(buffer, hlNotification, line)
diff --git a/irc/events.go b/irc/events.go
index bdd6914..ea5695d 100644
--- a/irc/events.go
+++ b/irc/events.go
@@ -62,3 +62,8 @@ type HistoryEvent struct {
	Target   string
	Messages []Event
}

type ReadEvent struct {
	Target string
	Time   time.Time
}
diff --git a/irc/session.go b/irc/session.go
index e50312f..bd45321 100644
--- a/irc/session.go
+++ b/irc/session.go
@@ -58,6 +58,7 @@ var SupportedCapabilities = map[string]struct{}{
	"server-time":       {},
	"sasl":              {},
	"setname":           {},
	"soju.im/read":      {},
	"userhost-in-names": {},
}

@@ -282,6 +283,13 @@ func (s *Session) ChangeTopic(channel, topic string) {
	s.out <- NewMessage("TOPIC", channel, topic)
}

func (s *Session) Read(target string, time time.Time) {
	if _, ok := s.enabledCaps["soju.im/read"]; !ok {
		return
	}
	s.out <- NewMessage("READ", target, fmt.Sprintf("timestamp=%s", time.UTC().Format(serverTimeLayout)))
}

func (s *Session) Quit(reason string) {
	s.out <- NewMessage("QUIT", reason)
}
@@ -691,6 +699,24 @@ func (s *Session) handleRegistered(msg Message) Event {
				Topic:   c.Topic,
			}
		}
	case "READ":
		if !strings.HasPrefix(msg.Params[1], "timestamp=") {
			break
		}
		stamp, err := time.Parse(serverTimeLayout, strings.TrimPrefix(msg.Params[1], "timestamp="))
		if err != nil {
			break
		}
		stamp = stamp.In(time.Local)

		name := msg.Params[0]
		if c, ok := s.channels[s.Casemap(msg.Params[0])]; ok {
			name = c.Name
		}
		return ReadEvent{
			Target: name,
			Time:   stamp,
		}
	case "PRIVMSG", "NOTICE":
		targetCf := s.casemap(msg.Params[0])
		nickCf := s.casemap(msg.Prefix.Name)
diff --git a/irc/tokens.go b/irc/tokens.go
index 9c669bb..bec7dbc 100644
--- a/irc/tokens.go
+++ b/irc/tokens.go
@@ -2,12 +2,13 @@ package irc

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
)

const serverTimeLayout = "2006-01-02T15:04:05.000Z"

// CasemapASCII of name is the canonical representation of name according to the
// ascii casemapping.
func CasemapASCII(name string) string {
@@ -416,22 +417,17 @@ func (msg *Message) IsValid() bool {
// Time returns the time when the message has been sent, if present.
func (msg *Message) Time() (t time.Time, ok bool) {
	var tag string
	var year, month, day, hour, minute, second, millis int

	tag, ok = msg.Tags["time"]
	if !ok {
		return
	}

	tag = strings.TrimSuffix(tag, "Z")

	_, err := fmt.Sscanf(tag, "%4d-%2d-%2dT%2d:%2d:%2d.%3d", &year, &month, &day, &hour, &minute, &second, &millis)
	if err != nil || month < 1 || 12 < month {
	t, err := time.Parse(serverTimeLayout, tag)
	if err != nil {
		ok = false
		return
	}

	t = time.Date(year, time.Month(month), day, hour, minute, second, millis*1e6, time.UTC)
	t = t.In(time.Local)
	return
}

diff --git a/ui/buffers.go b/ui/buffers.go
index 0ebaadf..174ade4 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -160,6 +160,7 @@ func (l *Line) NewLines(width int) []int {
type buffer struct {
	title      string
	highlights int
	read       time.Time
	unread     bool

	lines []Line
@@ -277,7 +278,7 @@ func (bs *BufferList) AddLine(title string, highlight bool, line Line) {
		}
	}

	if !line.Mergeable && idx != bs.current {
	if !line.Mergeable && idx != bs.current && !line.At.Before(b.read) {
		b.unread = true
	}
	if highlight && idx != bs.current {
@@ -342,6 +343,43 @@ func (bs *BufferList) CurrentOldestTime() (t *time.Time) {
	return
}

func (bs *BufferList) SetRead(title string, stamp time.Time) {
	idx := bs.idx(title)
	if idx < 0 {
		return
	}
	b := &bs.list[idx]
	if stamp.After(b.read) {
		b.read = stamp
		if len(b.lines) > 0 {
			last := b.lines[len(b.lines)-1].At
			if !stamp.Before(last) {
				b.unread = false
			}
		}
	}
}

func (bs *BufferList) UpdateRead() (string, time.Time) {
	b := &bs.list[bs.current]
	if len(b.lines) == 0 {
		return "", time.Time{}
	}

	// TODO: also update read if we're not at bottom, by using the stamp of the newest visible Line
	lastStamp := b.lines[len(b.lines)-1].At
	if !lastStamp.After(b.read) {
		return "", time.Time{}
	}
	now := time.Now()
	if !now.After(lastStamp) {
		return "", time.Time{}
	}
	b.read = now

	return b.title, now
}

func (bs *BufferList) ScrollUp(n int) {
	b := &bs.list[bs.current]
	if b.isAtTop {
diff --git a/ui/ui.go b/ui/ui.go
index be43272..0a16d8b 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -163,6 +163,14 @@ func (ui *UI) SetPrompt(prompt StyledString) {
	ui.prompt = prompt
}

func (ui *UI) SetRead(buffer string, stamp time.Time) {
	ui.bs.SetRead(buffer, stamp)
}

func (ui *UI) UpdateRead() (string, time.Time) {
	return ui.bs.UpdateRead()
}

func (ui *UI) InputIsCommand() bool {
	return ui.e.IsCommand()
}
diff --git a/window.go b/window.go
index 3d80776..919af7c 100644
--- a/window.go
+++ b/window.go
@@ -61,6 +61,17 @@ func (app *App) setStatus() {
	app.win.SetStatus(status)
}

func (app *App) updateRead() {
	if app.s == nil {
		return
	}
	target, stamp := app.win.UpdateRead()
	if target == "" || target == Home {
		return
	}
	app.s.Read(target, stamp)
}

func identColor(ident string) tcell.Color {
	h := fnv.New32()
	_, _ = h.Write([]byte(ident))
-- 
2.30.0
Reply to thread Export thread (mbox)