From 374f4c7a2603ca400147c3bbb597577e18ae4bb8 Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Sat, 29 Nov 2025 22:04:01 +0000 Subject: [PATCH] Allow interactions configured --- .env-example | 1 + cmd/register-feed/main.go | 22 +++++++++++++------- handlers.go | 44 +++++++++++++++++++++++++++++++++++++++ readme.md | 1 + server.go | 1 + 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/.env-example b/.env-example index b7761b2..eab24d6 100644 --- a/.env-example +++ b/.env-example @@ -5,3 +5,4 @@ FEED_NAME="demo-feed" FEED_DISPLAY_NAME="My demo feed" FEED_DESCRIPTION="This is a demo feed" FEED_DID="did:web:demo-feed.com" +ACCEPTS_INTERACTIONS="true" diff --git a/cmd/register-feed/main.go b/cmd/register-feed/main.go index ce52271..cd27e2c 100644 --- a/cmd/register-feed/main.go +++ b/cmd/register-feed/main.go @@ -33,10 +33,11 @@ type registerFeedGen struct { } type registerRecord struct { - Did string `json:"did"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - CreatedAt time.Time `json:"createdAt"` + Did string `json:"did"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` + AcceptsInteractions bool `json:"acceptsInteractions"` } func main() { @@ -136,16 +137,21 @@ func Register(auth *auth, httpClient http.Client) error { if feedDID == "" { return fmt.Errorf("FEED_DID environment not set") } + acceptsInteractions := false + if os.Getenv("ACCEPTS_INTERACTIONS") == "true" { + acceptsInteractions = true + } reqData := registerFeedGen{ Repo: auth.Did, Collection: "app.bsky.feed.generator", Rkey: feedName, Record: registerRecord{ - Did: feedDID, - DisplayName: feedDisplayName, - Description: feedDescription, - CreatedAt: time.Now(), + Did: feedDID, + DisplayName: feedDisplayName, + Description: feedDescription, + CreatedAt: time.Now(), + AcceptsInteractions: acceptsInteractions, }, } diff --git a/handlers.go b/handlers.go index 300d493..3d92701 100644 --- a/handlers.go +++ b/handlers.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "log/slog" "net/http" "net/url" @@ -112,6 +113,49 @@ func (s *Server) HandleDescribeFeedGenerator(w http.ResponseWriter, r *http.Requ _, _ = w.Write(b) } +// FeedInteractions details the interactions that a user had with a feed when they viewed it +type FeedInteractions struct { + Interactions []Interaction `json:"interactions"` +} + +type Interaction struct { + Item string `json:"item"` + Event string `json:"event"` +} + +// HandleFeedInteractions will handle when the client sends back a feed interaction so you can improve +// the feed quality for the user +func (s *Server) HandleFeedInteractions(w http.ResponseWriter, r *http.Request) { + slog.Debug("handle feed interactions") + userDID, err := getRequestUserDID(r) + if err != nil { + slog.Error("validate user auth", "error", err) + http.Error(w, "validate auth", http.StatusUnauthorized) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + slog.Error("read feed interactions request body", "error", err) + http.Error(w, "read body", http.StatusBadRequest) + return + } + + var feedInteractions FeedInteractions + err = json.Unmarshal(body, &feedInteractions) + if err != nil { + slog.Error("decode feed interactions request body", "error", err) + http.Error(w, "decode body", http.StatusBadRequest) + return + } + + // here is where you would likely do something with the data that is sent to you such as improving the + // data you store for a users feed + for _, interaction := range feedInteractions.Interactions { + slog.Info("interaction for user", "user", userDID, "item", interaction.Item, "interaction", interaction.Event) + } +} + // WellKnownResponse is what's returned on a well-known endpoint type WellKnownResponse struct { Context []string `json:"@context"` diff --git a/readme.md b/readme.md index c332d96..e6647eb 100644 --- a/readme.md +++ b/readme.md @@ -25,6 +25,7 @@ A few environment variables are required to run the app. Use the `example.env` f * FEED_DISPLAY_NAME - This is the name you will give your feed that users will be able to see * FEED_DESCRIPTION - This is a description of your feed that users will be able to see * FEED_DID - This is the DID that will be used to register the record. Unless you know what you are doing it's best to use `did:web:` + FEED_HOST_NAME (eg "did:web:demo-feed.com") +* ACCEPTS_INTERACTIONS - Set this to be true if you wish your feed to accepts interactions such as "show more" or "show less" First you need to run the feed generator by building the application `go build -o demo-feed-generator ./cmd/feed-generator/main.go` and then running it `./demo-feed-generator` diff --git a/server.go b/server.go index 344d5a3..828461a 100644 --- a/server.go +++ b/server.go @@ -42,6 +42,7 @@ func NewServer(port int, feedHost, feedName string, postStore PostStore) (*Serve mux := http.NewServeMux() mux.HandleFunc("/xrpc/app.bsky.feed.getFeedSkeleton", srv.HandleGetFeedSkeleton) mux.HandleFunc("/xrpc/app.bsky.feed.describeFeedGenerator", srv.HandleDescribeFeedGenerator) + mux.HandleFunc("POST /xrpc/app.bsky.feed.sendInteractions", srv.HandleFeedInteractions) mux.HandleFunc("/.well-known/did.json", srv.HandleWellKnown) addr := fmt.Sprintf("0.0.0.0:%d", port) -- 2.43.0