tangled
alpha
login
or
join now
parakeet.at
/
parakeet
63
fork
atom
Parakeet is a Rust-based Bluesky AppServer aiming to implement most of the functionality required to support the Bluesky client
appview
atproto
bluesky
rust
appserver
63
fork
atom
overview
issues
12
pulls
pipelines
make author feeds work like bluesky
mia.omg.lol
5 months ago
6bfd6ae7
f652576d
verified
This commit was signed with the committer's
known signature
.
mia.omg.lol
SSH Key Fingerprint:
SHA256:eb+NhC0QEl+XKRuFP/97oH6LEz0TXTKPXGDIAI5y7CQ=
+113
-53
3 changed files
expand all
collapse all
unified
split
parakeet
src
hydration
posts.rs
xrpc
app_bsky
feed
likes.rs
posts.rs
+102
-41
parakeet/src/hydration/posts.rs
···
187
187
.collect()
188
188
}
189
189
190
190
-
pub async fn hydrate_feed_posts(&self, posts: Vec<String>) -> HashMap<String, FeedViewPost> {
190
190
+
pub async fn hydrate_feed_posts(
191
191
+
&self,
192
192
+
posts: Vec<String>,
193
193
+
author_threads_only: bool,
194
194
+
) -> HashMap<String, FeedViewPost> {
191
195
let stats = self.loaders.post_stats.load_many(posts.clone()).await;
192
196
let posts = self.loaders.posts.load_many(posts).await;
193
197
···
199
203
200
204
let post_labels = self.get_label_many(&post_uris).await;
201
205
let viewer_data = self.get_post_viewer_states(&post_uris).await;
202
202
-
let embeds = self.hydrate_embeds(post_uris).await;
206
206
+
let embeds = self.hydrate_embeds(post_uris.clone()).await;
203
207
204
208
// we shouldn't show the parent when the post violates a threadgate.
205
209
let reply_refs = posts
···
211
215
212
216
let reply_posts = self.hydrate_posts(reply_refs).await;
213
217
214
214
-
posts
218
218
+
// hydrate all the posts.
219
219
+
let mut posts = posts
215
220
.into_iter()
216
216
-
.filter_map(|(post_uri, (post, _))| {
217
217
-
let author = authors.get(&post.did)?;
218
218
-
219
219
-
let root = post.root_uri.as_ref().and_then(|uri| reply_posts.get(uri));
220
220
-
let parent = post
221
221
-
.parent_uri
222
222
-
.as_ref()
223
223
-
.and_then(|uri| reply_posts.get(uri));
224
224
-
225
225
-
let reply = if post.parent_uri.is_some() && post.root_uri.is_some() {
226
226
-
Some(ReplyRef {
227
227
-
root: root.cloned().map(postview_to_replyref).unwrap_or(
228
228
-
ReplyRefPost::NotFound {
229
229
-
uri: post.root_uri.as_ref().unwrap().clone(),
230
230
-
not_found: true,
231
231
-
},
232
232
-
),
233
233
-
parent: parent.cloned().map(postview_to_replyref).unwrap_or(
234
234
-
ReplyRefPost::NotFound {
235
235
-
uri: post.parent_uri.as_ref().unwrap().clone(),
236
236
-
not_found: true,
237
237
-
},
238
238
-
),
239
239
-
grandparent_author: None,
240
240
-
})
241
241
-
} else {
242
242
-
None
243
243
-
};
221
221
+
.filter_map(|(post_uri, (raw, _))| {
222
222
+
let root = raw.root_uri.clone();
223
223
+
let parent = raw.parent_uri.clone();
244
224
225
225
+
let author = authors.get(&raw.did)?;
245
226
let embed = embeds.get(&post_uri).cloned();
246
227
let labels = post_labels.get(&post_uri).cloned().unwrap_or_default();
247
228
let stats = stats.get(&post_uri).cloned();
248
229
let viewer = viewer_data.get(&post_uri).cloned();
249
230
let post =
250
250
-
build_postview(post, author.to_owned(), labels, embed, None, viewer, stats);
231
231
+
build_postview(raw, author.to_owned(), labels, embed, None, viewer, stats);
251
232
252
252
-
Some((
253
253
-
post_uri,
254
254
-
FeedViewPost {
255
255
-
post,
256
256
-
reply,
257
257
-
reason: None,
258
258
-
feed_context: None,
259
259
-
},
260
260
-
))
233
233
+
Some((post_uri, (post, root, parent)))
234
234
+
})
235
235
+
.collect::<HashMap<_, _>>();
236
236
+
237
237
+
post_uris
238
238
+
.into_iter()
239
239
+
.filter_map(|post_uri| {
240
240
+
let item = if author_threads_only {
241
241
+
compile_feed_authors_threads_only(&post_uri, &mut posts)?
242
242
+
} else {
243
243
+
compile_feed(&post_uri, &mut posts, &reply_posts)?
244
244
+
};
245
245
+
246
246
+
Some((post_uri, item))
261
247
})
262
248
.collect()
263
249
}
···
301
287
_ => ReplyRefPost::Post(post),
302
288
}
303
289
}
290
290
+
291
291
+
type FeedViewPartData = (PostView, Option<String>, Option<String>);
292
292
+
293
293
+
// this is the 'normal' one that runs in most places
294
294
+
fn compile_feed(
295
295
+
uri: &String,
296
296
+
posts: &mut HashMap<String, FeedViewPartData>,
297
297
+
reply_posts: &HashMap<String, PostView>,
298
298
+
) -> Option<FeedViewPost> {
299
299
+
let (post, root_uri, parent_uri) = posts.remove(uri)?;
300
300
+
301
301
+
let root = root_uri.as_ref().and_then(|uri| reply_posts.get(uri));
302
302
+
let parent = parent_uri.as_ref().and_then(|uri| reply_posts.get(uri));
303
303
+
304
304
+
let reply = if parent_uri.is_some() && root_uri.is_some() {
305
305
+
Some(ReplyRef {
306
306
+
root: root
307
307
+
.cloned()
308
308
+
.map(postview_to_replyref)
309
309
+
.unwrap_or(ReplyRefPost::NotFound {
310
310
+
uri: root_uri.as_ref().unwrap().clone(),
311
311
+
not_found: true,
312
312
+
}),
313
313
+
parent: parent
314
314
+
.cloned()
315
315
+
.map(postview_to_replyref)
316
316
+
.unwrap_or(ReplyRefPost::NotFound {
317
317
+
uri: parent_uri.as_ref().unwrap().clone(),
318
318
+
not_found: true,
319
319
+
}),
320
320
+
grandparent_author: None,
321
321
+
})
322
322
+
} else {
323
323
+
None
324
324
+
};
325
325
+
326
326
+
Some(FeedViewPost {
327
327
+
post,
328
328
+
reply,
329
329
+
reason: None,
330
330
+
feed_context: None,
331
331
+
})
332
332
+
}
333
333
+
334
334
+
// and this one runs in getAuthorFeed when filter=PostsAndAuthorThreads
335
335
+
fn compile_feed_authors_threads_only(
336
336
+
uri: &String,
337
337
+
posts: &mut HashMap<String, FeedViewPartData>,
338
338
+
) -> Option<FeedViewPost> {
339
339
+
let (post, root_uri, parent_uri) = posts.get(uri)?.clone();
340
340
+
341
341
+
let root = root_uri.as_ref().and_then(|root| posts.get(root));
342
342
+
let parent = parent_uri.as_ref().and_then(|parent| posts.get(parent));
343
343
+
344
344
+
let reply = if parent_uri.is_some() && root_uri.is_some() {
345
345
+
Some(ReplyRef {
346
346
+
root: root
347
347
+
.cloned()
348
348
+
.map(|(post, _, _)| postview_to_replyref(post))?,
349
349
+
parent: parent
350
350
+
.cloned()
351
351
+
.map(|(post, _, _)| postview_to_replyref(post))?,
352
352
+
grandparent_author: None,
353
353
+
})
354
354
+
} else {
355
355
+
None
356
356
+
};
357
357
+
358
358
+
Some(FeedViewPost {
359
359
+
post,
360
360
+
reply,
361
361
+
reason: None,
362
362
+
feed_context: None,
363
363
+
})
364
364
+
}
+1
-1
parakeet/src/xrpc/app_bsky/feed/likes.rs
···
62
62
.map(|(_, uri)| uri.clone())
63
63
.collect::<Vec<_>>();
64
64
65
65
-
let mut posts = hyd.hydrate_feed_posts(at_uris).await;
65
65
+
let mut posts = hyd.hydrate_feed_posts(at_uris, false).await;
66
66
67
67
let feed: Vec<_> = results
68
68
.into_iter()
+10
-11
parakeet/src/xrpc/app_bsky/feed/posts.rs
···
123
123
})
124
124
.collect::<Vec<_>>();
125
125
126
126
-
let mut posts = hyd.hydrate_feed_posts(at_uris).await;
126
126
+
let mut posts = hyd.hydrate_feed_posts(at_uris, false).await;
127
127
let mut repost_data = get_skeleton_repost_data(&mut conn, &hyd, repost_skeleton).await;
128
128
129
129
let feed = skeleton
···
152
152
}))
153
153
}
154
154
155
155
-
#[derive(Debug, Deserialize)]
155
155
+
#[derive(Debug, Default, Eq, PartialEq, Deserialize)]
156
156
#[serde(rename_all = "snake_case")]
157
157
pub enum GetAuthorFeedFilter {
158
158
+
#[default]
158
159
PostsWithReplies,
159
160
PostsNoReplies,
160
161
PostsWithMedia,
161
162
PostsAndAuthorThreads,
162
163
PostsWithVideo,
163
163
-
}
164
164
-
165
165
-
impl Default for GetAuthorFeedFilter {
166
166
-
fn default() -> Self {
167
167
-
Self::PostsWithReplies
168
168
-
}
169
164
}
170
165
171
166
#[derive(Debug, Deserialize)]
···
227
222
posts_query = posts_query.filter(schema::author_feeds::sort_at.lt(cursor));
228
223
}
229
224
225
225
+
let author_threads_only = query.filter == GetAuthorFeedFilter::PostsAndAuthorThreads;
230
226
posts_query = match query.filter {
231
227
GetAuthorFeedFilter::PostsWithReplies => {
232
228
posts_query.filter(schema::author_feeds::typ.eq("post"))
···
269
265
.collect::<Vec<_>>();
270
266
271
267
// get the actor for if we have reposted
272
272
-
let profile = hyd.hydrate_profile_basic(did).await.ok_or(Error::server_error(None))?;
268
268
+
let profile = hyd
269
269
+
.hydrate_profile_basic(did)
270
270
+
.await
271
271
+
.ok_or(Error::server_error(None))?;
273
272
274
274
-
let mut posts = hyd.hydrate_feed_posts(at_uris).await;
273
273
+
let mut posts = hyd.hydrate_feed_posts(at_uris, author_threads_only).await;
275
274
276
275
let mut feed: Vec<_> = results
277
276
.into_iter()
···
348
347
.map(|(_, uri)| uri.clone())
349
348
.collect::<Vec<_>>();
350
349
351
351
-
let mut posts = hyd.hydrate_feed_posts(at_uris).await;
350
350
+
let mut posts = hyd.hydrate_feed_posts(at_uris, false).await;
352
351
353
352
let feed = results
354
353
.into_iter()