this repo has no description

feat: add popular

+120
+16
cmd/feedweb/main.go
··· 1 1 package main 2 2 3 3 import ( 4 + "database/sql" 4 5 "golang.org/x/text/language" 6 + "math" 5 7 "net/http" 6 8 "strconv" 7 9 ··· 9 11 "github.com/bluesky-social/indigo/atproto/syntax" 10 12 "github.com/edavis/bsky-feeds/pkg/feeds" 11 13 "github.com/edavis/bsky-feeds/pkg/mostliked" 14 + "github.com/edavis/bsky-feeds/pkg/popular" 12 15 "github.com/labstack/echo/v4" 13 16 "github.com/labstack/echo/v4/middleware" 17 + sqlite "github.com/mattn/go-sqlite3" 14 18 ) 15 19 16 20 type SkeletonRequest struct { ··· 29 33 var generators = FeedLookup{ 30 34 "at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/most-liked": mostliked.Feed, 31 35 "at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/most-liked-dev": mostliked.Feed, 36 + 37 + "at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/popular": popular.Feed, 38 + "at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/popular-dev": popular.Feed, 32 39 } 33 40 34 41 func getFeedSkeleton(c echo.Context) error { ··· 85 92 } 86 93 87 94 func main() { 95 + sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{ 96 + ConnectHook: func(conn *sqlite.SQLiteConn) error { 97 + if err := conn.RegisterFunc("exp", math.Exp, true); err != nil { 98 + return err 99 + } 100 + return nil 101 + }, 102 + }) 103 + 88 104 e := echo.New() 89 105 e.Use(middleware.Logger()) 90 106 e.Use(middleware.Recover())
+104
pkg/popular/generator.go
··· 1 + package popular 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + "log" 8 + "strconv" 9 + "strings" 10 + 11 + appbsky "github.com/bluesky-social/indigo/api/bsky" 12 + "github.com/edavis/bsky-feeds/pkg/feeds" 13 + ) 14 + 15 + type PostRow struct { 16 + Uri string 17 + Score float64 18 + } 19 + 20 + func getPosts(ctx context.Context, dbCnx *sql.DB, langs []string, limit, offset int) ([]PostRow, error) { 21 + var queryParams []any 22 + var query strings.Builder 23 + fmt.Fprintf(&query, "select posts.uri, likes * exp( -1 * ( ( unixepoch('now') - create_ts ) / 1800.0 ) ) as score from posts") 24 + if len(langs) > 0 { 25 + fmt.Fprintf(&query, " left join langs on posts.uri = langs.uri where lang in (") 26 + for idx, lang := range langs { 27 + if idx > 0 { 28 + fmt.Fprintf(&query, ",") 29 + } 30 + fmt.Fprintf(&query, "?") 31 + queryParams = append(queryParams, lang) 32 + } 33 + fmt.Fprintf(&query, ")") 34 + } 35 + fmt.Fprintf(&query, " order by score desc limit ? offset ?") 36 + queryParams = append(queryParams, limit, offset) 37 + 38 + fmt.Printf("query: %v %+v\n", query.String(), queryParams) 39 + 40 + rows, err := dbCnx.QueryContext(ctx, query.String(), queryParams...) 41 + if err != nil { 42 + return nil, err 43 + } 44 + defer rows.Close() 45 + 46 + var posts []PostRow 47 + for rows.Next() { 48 + var post PostRow 49 + if err := rows.Scan(&post.Uri, &post.Score); err != nil { 50 + return nil, err 51 + } 52 + posts = append(posts, post) 53 + } 54 + 55 + return posts, nil 56 + } 57 + 58 + func Feed(params feeds.FeedgenParams) appbsky.FeedGetFeedSkeleton_Output { 59 + ctx := context.Background() 60 + dbCnx, err := sql.Open("sqlite3_custom", "data/mostliked.db?_journal=WAL&_fk=on") 61 + if err != nil { 62 + log.Printf("error opening db: %v\n", err) 63 + } 64 + defer dbCnx.Close() 65 + 66 + var langs []string 67 + for _, lang := range params.Langs { 68 + langs = append(langs, lang.String()) 69 + } 70 + 71 + offset := 0 72 + if params.Cursor != "" { 73 + if parsed, err := strconv.Atoi(params.Cursor); err == nil { 74 + offset = parsed 75 + } else { 76 + log.Printf("error converting cursor: %v\n", err) 77 + } 78 + } 79 + 80 + rows, err := getPosts(ctx, dbCnx, langs, params.Limit, offset) 81 + if err != nil { 82 + log.Printf("error fetching rows: %v\n", err) 83 + } 84 + 85 + var cursor string 86 + posts := make([]*appbsky.FeedDefs_SkeletonFeedPost, 0, params.Limit) 87 + 88 + for _, row := range rows { 89 + posts = append(posts, &appbsky.FeedDefs_SkeletonFeedPost{Post: row.Uri}) 90 + } 91 + 92 + skeleton := appbsky.FeedGetFeedSkeleton_Output{ 93 + Feed: posts, 94 + } 95 + 96 + offset += len(posts) 97 + cursor = strconv.Itoa(offset) 98 + 99 + if len(posts) == params.Limit { 100 + skeleton.Cursor = &cursor 101 + } 102 + 103 + return skeleton 104 + }