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