Live video on the AT Protocol
79
fork

Configure Feed

Select the types of activity you want to include in your feed.

at eli/localhost-dev-is-back 88 lines 2.7 kB view raw
1package spxrpc 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 "regexp" 8 "strconv" 9 "strings" 10 11 "github.com/bluesky-social/indigo/api/bsky" 12 "github.com/labstack/echo/v4" 13 "stream.place/streamplace/pkg/log" 14 "stream.place/streamplace/pkg/model" 15) 16 17var FeedSkeletonRE = regexp.MustCompile(`^at://did:(web|plc):([a-z0-9\.\-]+)/app.bsky.feed.generator/([a-z0-9\.\-]+)$`) 18 19func parseFeedSkeleton(did string) (string, string, error) { 20 matches := FeedSkeletonRE.FindStringSubmatch(did) 21 if len(matches) != 4 { 22 return "", "", echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid feed parameter: %s", did)) 23 } 24 return fmt.Sprintf("did:%s:%s", matches[1], matches[2]), matches[3], nil 25} 26 27const FeedLiveStreams = "live-streams" 28const FeedAllStreams = "all-streams" 29 30func (s *Server) handleAppBskyFeedGetFeedSkeleton(ctx context.Context, inCursor string, feed string, limit int) (*bsky.FeedGetFeedSkeleton_Output, error) { 31 _, name, err := parseFeedSkeleton(feed) 32 if err != nil { 33 return nil, err 34 } 35 var ts int64 36 if inCursor != "" { 37 parts := strings.Split(inCursor, "::") 38 if len(parts) != 2 { 39 return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid cursor format") 40 } 41 ts, err = strconv.ParseInt(parts[0], 10, 64) 42 if err != nil { 43 return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid cursor timestamp") 44 } 45 } 46 var posts []model.FeedPost 47 outCursor := "" 48 if name == FeedAllStreams { 49 posts, err = s.model.ListFeedPostsByType("livestream", limit, ts) 50 if err != nil { 51 return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to list feed posts: %v", err)) 52 } 53 if len(posts) > 0 { 54 last := posts[len(posts)-1] 55 ts := last.CreatedAt.UnixMilli() 56 outCursor = fmt.Sprintf("%d::%s", ts, last.CID) 57 } 58 } else if name == FeedLiveStreams { 59 segs, err := s.model.MostRecentSegments() 60 if err != nil { 61 return nil, echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to get recent segments: %v", err)) 62 } 63 for _, seg := range segs { 64 ls, err := s.model.GetLatestLivestreamForRepo(seg.RepoDID) 65 if err != nil { 66 log.Error(ctx, "failed to get latest livestream, skipping", "repoDID", seg.RepoDID, "error", err) 67 continue 68 } 69 posts = append(posts, model.FeedPost{ 70 URI: ls.PostURI, 71 }) 72 } 73 } else { 74 return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid feed name: %s", name)) 75 } 76 res := bsky.FeedGetFeedSkeleton_Output{ 77 Feed: []*bsky.FeedDefs_SkeletonFeedPost{}, 78 } 79 if outCursor != "" { 80 res.Cursor = &outCursor 81 } 82 for _, post := range posts { 83 res.Feed = append(res.Feed, &bsky.FeedDefs_SkeletonFeedPost{ 84 Post: post.URI, 85 }) 86 } 87 return &res, nil 88}