Live video on the AT Protocol
79
fork

Configure Feed

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

at v0.8.13 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}