A demo of a Bluesky feed generator in Go

Allow interactions configured

Changed files
+61 -8
cmd
register-feed
+1
.env-example
··· 5 5 FEED_DISPLAY_NAME="My demo feed" 6 6 FEED_DESCRIPTION="This is a demo feed" 7 7 FEED_DID="did:web:demo-feed.com" 8 + ACCEPTS_INTERACTIONS="true"
+14 -8
cmd/register-feed/main.go
··· 33 33 } 34 34 35 35 type registerRecord struct { 36 - Did string `json:"did"` 37 - DisplayName string `json:"displayName"` 38 - Description string `json:"description"` 39 - CreatedAt time.Time `json:"createdAt"` 36 + Did string `json:"did"` 37 + DisplayName string `json:"displayName"` 38 + Description string `json:"description"` 39 + CreatedAt time.Time `json:"createdAt"` 40 + AcceptsInteractions bool `json:"acceptsInteractions"` 40 41 } 41 42 42 43 func main() { ··· 136 137 if feedDID == "" { 137 138 return fmt.Errorf("FEED_DID environment not set") 138 139 } 140 + acceptsInteractions := false 141 + if os.Getenv("ACCEPTS_INTERACTIONS") == "true" { 142 + acceptsInteractions = true 143 + } 139 144 140 145 reqData := registerFeedGen{ 141 146 Repo: auth.Did, 142 147 Collection: "app.bsky.feed.generator", 143 148 Rkey: feedName, 144 149 Record: registerRecord{ 145 - Did: feedDID, 146 - DisplayName: feedDisplayName, 147 - Description: feedDescription, 148 - CreatedAt: time.Now(), 150 + Did: feedDID, 151 + DisplayName: feedDisplayName, 152 + Description: feedDescription, 153 + CreatedAt: time.Now(), 154 + AcceptsInteractions: acceptsInteractions, 149 155 }, 150 156 } 151 157
+44
handlers.go
··· 4 4 "context" 5 5 "encoding/json" 6 6 "fmt" 7 + "io" 7 8 "log/slog" 8 9 "net/http" 9 10 "net/url" ··· 110 111 } 111 112 112 113 _, _ = w.Write(b) 114 + } 115 + 116 + // FeedInteractions details the interactions that a user had with a feed when they viewed it 117 + type FeedInteractions struct { 118 + Interactions []Interaction `json:"interactions"` 119 + } 120 + 121 + type Interaction struct { 122 + Item string `json:"item"` 123 + Event string `json:"event"` 124 + } 125 + 126 + // HandleFeedInteractions will handle when the client sends back a feed interaction so you can improve 127 + // the feed quality for the user 128 + func (s *Server) HandleFeedInteractions(w http.ResponseWriter, r *http.Request) { 129 + slog.Debug("handle feed interactions") 130 + userDID, err := getRequestUserDID(r) 131 + if err != nil { 132 + slog.Error("validate user auth", "error", err) 133 + http.Error(w, "validate auth", http.StatusUnauthorized) 134 + return 135 + } 136 + 137 + body, err := io.ReadAll(r.Body) 138 + if err != nil { 139 + slog.Error("read feed interactions request body", "error", err) 140 + http.Error(w, "read body", http.StatusBadRequest) 141 + return 142 + } 143 + 144 + var feedInteractions FeedInteractions 145 + err = json.Unmarshal(body, &feedInteractions) 146 + if err != nil { 147 + slog.Error("decode feed interactions request body", "error", err) 148 + http.Error(w, "decode body", http.StatusBadRequest) 149 + return 150 + } 151 + 152 + // here is where you would likely do something with the data that is sent to you such as improving the 153 + // data you store for a users feed 154 + for _, interaction := range feedInteractions.Interactions { 155 + slog.Info("interaction for user", "user", userDID, "item", interaction.Item, "interaction", interaction.Event) 156 + } 113 157 } 114 158 115 159 // WellKnownResponse is what's returned on a well-known endpoint
+1
readme.md
··· 25 25 * FEED_DISPLAY_NAME - This is the name you will give your feed that users will be able to see 26 26 * FEED_DESCRIPTION - This is a description of your feed that users will be able to see 27 27 * 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") 28 + * ACCEPTS_INTERACTIONS - Set this to be true if you wish your feed to accepts interactions such as "show more" or "show less" 28 29 29 30 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` 30 31
+1
server.go
··· 42 42 mux := http.NewServeMux() 43 43 mux.HandleFunc("/xrpc/app.bsky.feed.getFeedSkeleton", srv.HandleGetFeedSkeleton) 44 44 mux.HandleFunc("/xrpc/app.bsky.feed.describeFeedGenerator", srv.HandleDescribeFeedGenerator) 45 + mux.HandleFunc("POST /xrpc/app.bsky.feed.sendInteractions", srv.HandleFeedInteractions) 45 46 mux.HandleFunc("/.well-known/did.json", srv.HandleWellKnown) 46 47 addr := fmt.Sprintf("0.0.0.0:%d", port) 47 48