ScuttleBot
fix: HTTP transport since cursor — avoid full history poll on each interval (#28) Server: handleChannelMessages accepts ?since=<RFC3339Nano> and filters messages server-side, so only new messages are returned. Client: MessagesSince appends ?since= to each channel fetch when since is non-zero; removes redundant client-side at.After(since) filter since the server handles it.
Commit
b71f8ab98c45ddb1e9fb2adacfc1429292f6ad8904e3e6c9359262b1769213fd
Parent
87e69785676cc58…
2 files changed
+13
+5
-4
+13
| --- internal/api/chat.go | ||
| +++ internal/api/chat.go | ||
| @@ -44,10 +44,23 @@ | ||
| 44 | 44 | // Auto-join on first access so the bridge starts tracking this channel. |
| 45 | 45 | s.bridge.JoinChannel(channel) |
| 46 | 46 | msgs := s.bridge.Messages(channel) |
| 47 | 47 | if msgs == nil { |
| 48 | 48 | msgs = []bridge.Message{} |
| 49 | + } | |
| 50 | + // Filter by ?since=<RFC3339> when provided (avoids sending full history on each poll). | |
| 51 | + if sinceStr := r.URL.Query().Get("since"); sinceStr != "" { | |
| 52 | + since, err := time.Parse(time.RFC3339Nano, sinceStr) | |
| 53 | + if err == nil { | |
| 54 | + filtered := msgs[:0] | |
| 55 | + for _, m := range msgs { | |
| 56 | + if m.At.After(since) { | |
| 57 | + filtered = append(filtered, m) | |
| 58 | + } | |
| 59 | + } | |
| 60 | + msgs = filtered | |
| 61 | + } | |
| 49 | 62 | } |
| 50 | 63 | writeJSON(w, http.StatusOK, map[string]any{"messages": msgs}) |
| 51 | 64 | } |
| 52 | 65 | |
| 53 | 66 | func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) { |
| 54 | 67 |
| --- internal/api/chat.go | |
| +++ internal/api/chat.go | |
| @@ -44,10 +44,23 @@ | |
| 44 | // Auto-join on first access so the bridge starts tracking this channel. |
| 45 | s.bridge.JoinChannel(channel) |
| 46 | msgs := s.bridge.Messages(channel) |
| 47 | if msgs == nil { |
| 48 | msgs = []bridge.Message{} |
| 49 | } |
| 50 | writeJSON(w, http.StatusOK, map[string]any{"messages": msgs}) |
| 51 | } |
| 52 | |
| 53 | func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) { |
| 54 |
| --- internal/api/chat.go | |
| +++ internal/api/chat.go | |
| @@ -44,10 +44,23 @@ | |
| 44 | // Auto-join on first access so the bridge starts tracking this channel. |
| 45 | s.bridge.JoinChannel(channel) |
| 46 | msgs := s.bridge.Messages(channel) |
| 47 | if msgs == nil { |
| 48 | msgs = []bridge.Message{} |
| 49 | } |
| 50 | // Filter by ?since=<RFC3339> when provided (avoids sending full history on each poll). |
| 51 | if sinceStr := r.URL.Query().Get("since"); sinceStr != "" { |
| 52 | since, err := time.Parse(time.RFC3339Nano, sinceStr) |
| 53 | if err == nil { |
| 54 | filtered := msgs[:0] |
| 55 | for _, m := range msgs { |
| 56 | if m.At.After(since) { |
| 57 | filtered = append(filtered, m) |
| 58 | } |
| 59 | } |
| 60 | msgs = filtered |
| 61 | } |
| 62 | } |
| 63 | writeJSON(w, http.StatusOK, map[string]any{"messages": msgs}) |
| 64 | } |
| 65 | |
| 66 | func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) { |
| 67 |
+5
-4
| --- pkg/sessionrelay/http.go | ||
| +++ pkg/sessionrelay/http.go | ||
| @@ -72,11 +72,15 @@ | ||
| 72 | 72 | } |
| 73 | 73 | |
| 74 | 74 | func (c *httpConnector) MessagesSince(ctx context.Context, since time.Time) ([]Message, error) { |
| 75 | 75 | out := make([]Message, 0, 32) |
| 76 | 76 | for _, channel := range c.Channels() { |
| 77 | - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/v1/channels/"+channelSlug(channel)+"/messages", nil) | |
| 77 | + url := c.baseURL + "/v1/channels/" + channelSlug(channel) + "/messages" | |
| 78 | + if !since.IsZero() { | |
| 79 | + url += "?since=" + since.UTC().Format(time.RFC3339Nano) | |
| 80 | + } | |
| 81 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | |
| 78 | 82 | if err != nil { |
| 79 | 83 | return nil, err |
| 80 | 84 | } |
| 81 | 85 | c.authorize(req) |
| 82 | 86 | |
| @@ -101,13 +105,10 @@ | ||
| 101 | 105 | for _, msg := range payload.Messages { |
| 102 | 106 | at, err := time.Parse(time.RFC3339Nano, msg.At) |
| 103 | 107 | if err != nil { |
| 104 | 108 | continue |
| 105 | 109 | } |
| 106 | - if !at.After(since) { | |
| 107 | - continue | |
| 108 | - } | |
| 109 | 110 | out = append(out, Message{At: at, Channel: channel, Nick: msg.Nick, Text: msg.Text}) |
| 110 | 111 | } |
| 111 | 112 | } |
| 112 | 113 | sort.Slice(out, func(i, j int) bool { return out[i].At.Before(out[j].At) }) |
| 113 | 114 | return out, nil |
| 114 | 115 |
| --- pkg/sessionrelay/http.go | |
| +++ pkg/sessionrelay/http.go | |
| @@ -72,11 +72,15 @@ | |
| 72 | } |
| 73 | |
| 74 | func (c *httpConnector) MessagesSince(ctx context.Context, since time.Time) ([]Message, error) { |
| 75 | out := make([]Message, 0, 32) |
| 76 | for _, channel := range c.Channels() { |
| 77 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/v1/channels/"+channelSlug(channel)+"/messages", nil) |
| 78 | if err != nil { |
| 79 | return nil, err |
| 80 | } |
| 81 | c.authorize(req) |
| 82 | |
| @@ -101,13 +105,10 @@ | |
| 101 | for _, msg := range payload.Messages { |
| 102 | at, err := time.Parse(time.RFC3339Nano, msg.At) |
| 103 | if err != nil { |
| 104 | continue |
| 105 | } |
| 106 | if !at.After(since) { |
| 107 | continue |
| 108 | } |
| 109 | out = append(out, Message{At: at, Channel: channel, Nick: msg.Nick, Text: msg.Text}) |
| 110 | } |
| 111 | } |
| 112 | sort.Slice(out, func(i, j int) bool { return out[i].At.Before(out[j].At) }) |
| 113 | return out, nil |
| 114 |
| --- pkg/sessionrelay/http.go | |
| +++ pkg/sessionrelay/http.go | |
| @@ -72,11 +72,15 @@ | |
| 72 | } |
| 73 | |
| 74 | func (c *httpConnector) MessagesSince(ctx context.Context, since time.Time) ([]Message, error) { |
| 75 | out := make([]Message, 0, 32) |
| 76 | for _, channel := range c.Channels() { |
| 77 | url := c.baseURL + "/v1/channels/" + channelSlug(channel) + "/messages" |
| 78 | if !since.IsZero() { |
| 79 | url += "?since=" + since.UTC().Format(time.RFC3339Nano) |
| 80 | } |
| 81 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) |
| 82 | if err != nil { |
| 83 | return nil, err |
| 84 | } |
| 85 | c.authorize(req) |
| 86 | |
| @@ -101,13 +105,10 @@ | |
| 105 | for _, msg := range payload.Messages { |
| 106 | at, err := time.Parse(time.RFC3339Nano, msg.At) |
| 107 | if err != nil { |
| 108 | continue |
| 109 | } |
| 110 | out = append(out, Message{At: at, Channel: channel, Nick: msg.Nick, Text: msg.Text}) |
| 111 | } |
| 112 | } |
| 113 | sort.Slice(out, func(i, j int) bool { return out[i].At.Before(out[j].At) }) |
| 114 | return out, nil |
| 115 |