-
Notifications
You must be signed in to change notification settings - Fork 5
/
enjoyfeed.go
227 lines (185 loc) · 4.6 KB
/
enjoyfeed.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package main
import (
"context"
"encoding/json"
"fmt"
"sort"
"time"
bsky "github.com/bluesky-social/indigo/api/bsky"
"github.com/whyrusleeping/algoz/models"
. "github.com/whyrusleeping/algoz/models"
)
type EnjoyFeed struct {
s *Server
}
func (f *EnjoyFeed) Name() string {
return "enjoyfeed"
}
func (f *EnjoyFeed) Description() string {
return "a hopefully enjoyable feed"
}
func (f *EnjoyFeed) GetFeed(ctx context.Context, u *User, lim int, curs *string) (*bsky.FeedGetFeedSkeleton_Output, error) {
if !u.HasFollowsScraped() {
if err := f.s.scrapeFollowsForUser(ctx, u); err != nil {
return nil, err
}
}
fposts, err := f.postsFromFriends(ctx, u)
if err != nil {
return nil, err
}
if len(fposts) > lim {
fposts = fposts[:lim]
}
skelposts, err := f.s.postsToFeed(ctx, fposts)
if err != nil {
return nil, err
}
return &bsky.FeedGetFeedSkeleton_Output{
Cursor: nil,
Feed: skelposts,
}, nil
}
func (f *EnjoyFeed) networkTopPosts(ctx context.Context) ([]PostRef, error) {
yesterday := time.Now().Add(time.Hour * -24)
var prefs []PostRef
if err := f.s.db.Order("likes DESC").Limit(20).Find(&prefs, "reply_to = 0 AND created_at > ?", yesterday).Error; err != nil {
return nil, err
}
return prefs, nil
}
func (f *EnjoyFeed) postsFromFriends(ctx context.Context, u *User) ([]PostRef, error) {
friends, err := f.getFriendsForUser(ctx, u)
if err != nil {
return nil, err
}
var prefs []PostRef
if err := f.s.db.Table("post_refs").Where("uid in (?)", friends).Order("created_at DESC").Limit(200).Scan(&prefs).Error; err != nil {
return nil, err
}
return prefs, nil
}
type uidResult struct {
Uid uint
Val int
}
type friendStats struct {
ID uint
Replies int
RepliesTo int
LikedPosts int
RenderedScore int
Handle string
}
func (fs *friendStats) Score() int {
if fs.RepliesTo == 0 {
return 0
}
base := (fs.Replies + 1) * (fs.RepliesTo * 4)
base += (fs.LikedPosts * 3)
return base
}
func (f *EnjoyFeed) getFriendsForUser(ctx context.Context, u *User) ([]uint, error) {
cutoff := time.Now().Add(time.Hour * 24 * -30)
// how many times other users have replied to this user
var repliesToUser []uidResult
if err := f.s.db.Raw(`SELECT uid, COUNT(*) as val
FROM post_refs
WHERE reply_to IN (
SELECT id
FROM post_refs
WHERE uid = ? AND post_refs.created_at > ?
)
GROUP BY uid
ORDER BY val DESC
LIMIT 50
`, u.ID, cutoff).Scan(&repliesToUser).Error; err != nil {
return nil, err
}
// how many times this user has replied to other users
var userRepliesTo []uidResult
if err := f.s.db.Raw(`select p2.uid, COUNT(*) as val
FROM post_refs p1
INNER JOIN post_refs p2 ON p1.reply_to = p2.id
WHERE p1.uid = ? AND p2.uid != ? AND p1.created_at > ?
GROUP BY p2.uid
ORDER BY val DESC
LIMIT 50
`, u.ID, u.ID, cutoff).Scan(&userRepliesTo).Error; err != nil {
return nil, err
}
// how many times this user likes posts by other users
var userLikesPosts []uidResult
if err := f.s.db.Raw(`SELECT post_refs.uid, count(*) AS val
FROM feed_likes
LEFT JOIN post_refs ON feed_likes.ref = post_refs.id
WHERE feed_likes.uid = ? AND post_refs.created_at > ?
GROUP BY post_refs.uid
ORDER BY val DESC
LIMIT 50
`, u.ID, cutoff).Scan(&userLikesPosts).Error; err != nil {
return nil, err
}
stats := make(map[uint]*friendStats)
getf := func(id uint) *friendStats {
f, ok := stats[id]
if !ok {
f = new(friendStats)
f.ID = id
stats[id] = f
}
return f
}
for _, ur := range repliesToUser {
f := getf(ur.Uid)
f.Replies = ur.Val
}
for _, ur := range userRepliesTo {
f := getf(ur.Uid)
f.RepliesTo = ur.Val
}
for _, ur := range userLikesPosts {
f := getf(ur.Uid)
f.LikedPosts = ur.Val
}
var list []*friendStats
for _, v := range stats {
v.RenderedScore = v.Score()
list = append(list, v)
}
sort.Slice(list, func(i, j int) bool {
return list[i].Score() > list[j].Score()
})
var uids []uint
for _, l := range list {
if l.RenderedScore > 0 {
uids = append(uids, l.ID)
}
}
var omap []models.User
if err := f.s.db.Find(&omap, "id in (?)", uids).Error; err != nil {
return nil, err
}
for _, m := range omap {
stats[m.ID].Handle = m.Handle
}
b, _ := json.Marshal(list)
fmt.Println(string(b))
var out []uint
for _, ur := range list {
out = append(out, ur.ID)
if len(out) >= 30 {
break
}
}
return out, nil
}
func (f *EnjoyFeed) HandlePost(context.Context, *User, *PostRef, *bsky.FeedPost) error {
return nil
}
func (f *EnjoyFeed) HandleLike(context.Context, *User, *bsky.FeedPost) error {
return nil
}
func (f *EnjoyFeed) HandleRepost(context.Context, *User, *postInfo, string) error {
return nil
}