Skip to content

Commit

Permalink
robot, spoken: forget all recent traces when target of CLEARCHAT
Browse files Browse the repository at this point in the history
  • Loading branch information
zephyrtronium committed Aug 20, 2024
1 parent 1fc2c5a commit cc976d0
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 3 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/zephyrtronium/robot

go 1.22.0
go 1.23.0

require (
github.com/BurntSushi/toml v1.4.0
Expand Down
38 changes: 38 additions & 0 deletions spoken/spoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
_ "embed"
"encoding/json"
"fmt"
"iter"
"time"

"zombiezen.com/go/sqlite"
Expand Down Expand Up @@ -103,6 +104,43 @@ func (h *History) Trace(ctx context.Context, tag, msg string) ([]string, time.Ti
return trace, time.Unix(0, tm), nil
}

func once2[K, V any](k K, v V) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
yield(k, v)
}
}

// AllSince provides an iterator over all trace IDs since the given time.
func (h *History) Since(ctx context.Context, tag string, tm time.Time) iter.Seq2[string, error] {
conn, err := h.db.Take(ctx)
defer h.db.Put(conn)
if err != nil {
return once2("", fmt.Errorf("couldn't get conn to find recent traces: %w", err))
}
const sel = `SELECT DISTINCT value FROM spoken, JSON_EACH(spoken.trace) WHERE tag = :tag AND time >= :time`
st, err := conn.Prepare(sel)
if err != nil {
return once2("", fmt.Errorf("couldn't prepare statement to find recent traces: %w", err))
}
st.SetText(":tag", tag)
st.SetInt64(":time", tm.UnixNano())
return func(yield func(string, error) bool) {
for {
ok, err := st.Step()
if err != nil {
yield("", fmt.Errorf("couldn't get recent traces: %w", err))
return
}
if !ok {
return
}
if !yield(st.ColumnText(0), nil) {
return
}
}
}
}

//go:embed schema.sql
var schemaSQL string

Expand Down
90 changes: 90 additions & 0 deletions spoken/spoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,93 @@ func TestTrace(t *testing.T) {
})
}
}

func TestSince(t *testing.T) {
// Create test fixture first.
ctx := context.Background()
db := testDB(ctx)
h, err := spoken.Open(ctx, db)
if err != nil {
t.Fatal(err)
}
insert := []struct {
tag string
msg string
trace string
time int64
}{
{"kessoku", "bocchi", `["1"]`, 10},
{"kessoku", "ryo", `["2"]`, 20},
{"sickhack", "bocchi", `["3"]`, 30},
{"kessoku", "ryo", `["4"]`, 40},
}
{
conn, err := db.Take(ctx)
if err != nil {
t.Fatalf("couldn't get conn: %v", err)
}
st, err := conn.Prepare("INSERT INTO spoken (tag, msg, trace, time, meta) VALUES (:tag, :msg, JSONB(:trace), :time, JSONB('{}'))")
if err != nil {
t.Fatalf("couldn't prep insert: %v", err)
}
for _, r := range insert {
st.SetText(":tag", r.tag)
st.SetText(":msg", r.msg)
st.SetText(":trace", r.trace)
st.SetInt64(":time", r.time)
_, err := st.Step()
if err != nil {
t.Errorf("failed to insert %v: %v", r, err)
}
if err := st.Reset(); err != nil {
t.Errorf("couldn't reset: %v", err)
}
}
if err := st.Finalize(); err != nil {
t.Fatalf("couldn't finalize insert: %v", err)
}
db.Put(conn)
}

cases := []struct {
name string
tag string
time int64
want []string
}{
{
name: "none",
tag: "kessoku",
time: 1000,
want: nil,
},
{
name: "some",
tag: "kessoku",
time: 15,
want: []string{"2", "4"},
},
{
name: "tagged",
tag: "sickhack",
time: 15,
want: []string{"3"},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var got []string
for id, err := range h.Since(ctx, c.tag, time.Unix(0, c.time)) {
if err != nil {
t.Error(err)
continue
}
got = append(got, id)
}
slices.Sort(got)
if !slices.Equal(c.want, got) {
t.Errorf("wrong ids: want %q, got %q", c.want, got)
}
})
}
}
25 changes: 23 additions & 2 deletions tmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,29 @@ func (robo *Robot) clearchat(ctx context.Context, group *errgroup.Group, msg *tm
}
}
case msg.Trailing == robo.tmi.me: // TODO(zeph): get own user id
// TODO(zeph): forget all recent generated traces
return
work = func(ctx context.Context) {
// We use the send tag because we are forgetting something we sent.
tag := ch.Send
slog.InfoContext(ctx, "forget recent generated", slog.String("channel", msg.To()), slog.String("tag", tag))
for id, err := range robo.spoken.Since(ctx, tag, msg.Time().Add(-15*time.Minute)) {
if err != nil {
slog.ErrorContext(ctx, "failed to get recent traces",
slog.Any("err", err),
slog.String("channel", msg.To()),
slog.String("tag", tag),
)
continue
}
if err := robo.brain.ForgetMessage(ctx, tag, id); err != nil {
slog.ErrorContext(ctx, "failed to forget from recent trace",
slog.Any("err", err),
slog.String("channel", msg.To()),
slog.String("tag", tag),
slog.String("id", id),
)
}
}
}
default:
// Delete from user.
// We use the user's current and previous userhash, since userhashes
Expand Down

0 comments on commit cc976d0

Please sign in to comment.