demos for spacedust
1import { getPostStats } from './constellation';
2
3const SEKELETON_API = 'https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton';
4const FEED = 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot';
5const POLL_DELAY = 9000;
6const POST_LIMIT = 5;
7
8async function getFeed() {
9 const url = new URL(SEKELETON_API);
10 url.searchParams.append('feed', FEED);
11 url.searchParams.append('limit', POST_LIMIT.toString());
12 const res = await fetch(url);
13 const data = await res.json();
14 return data;
15}
16
17/**
18 * fetch a pair of posts from discover and alternately replace the first/second
19 *
20 * TODO: check w constellation to prioritize popular posts
21 **/
22export function rotatingPair(onRotate: any) {
23 let timer: number;
24 let dying = false;
25 const seen = new Set(); // TODO: mem leak, slowly
26 let A: string | null = null;
27 let B: string | null = null;
28 let which: 'A' | 'B' = 'A';
29
30 async function next() {
31 console.info('[sample posts: next]');
32 try {
33 const { feed } = await getFeed();
34 if (dying) return;
35
36 const withStats = await Promise.all(feed.map(async ({ post }) => {
37 if (seen.has(post)) return { post, total: 0 };
38 let stats = {};
39 try {
40 stats = await getPostStats(post);
41 } catch (e) {
42 console.warn('failed to get stats from constellation', e);
43 }
44 const total = Array.from(Object.values(stats)).reduce((a, b) => a + b, 0);
45 return ({ post, total })
46 }))
47 if (dying) return;
48
49 // idk if sorting by most interactions yields more-interactive posts but eh
50 withStats.sort(({ total: a }, { total: b }) => b - a);
51
52 // special case: first load
53 if (A === null && B === null) {
54 if (withStats.length < 2) throw new Error('withStats returned fewer than two posts to start');
55 seen.add(A = withStats[0].post);
56 seen.add(B = withStats[1].post);
57 } else {
58 for (const { post } of withStats) {
59 if (seen.has(post)) {
60 continue;
61 }
62 if (which === 'A') {
63 seen.add(B = post);
64 which = 'B';
65 } else {
66 seen.add(A = post);
67 which = 'A';
68 }
69 break;
70 }
71 }
72 onRotate([A, B]);
73 } catch (e) {
74 console.error('hmm, failed to get withStats', e);
75 }
76 timer = setTimeout(next, POLL_DELAY);
77 }
78 setTimeout(next);
79
80 return () => {
81 console.log('clearing');
82 clearTimeout(timer);
83 dying = true;
84 }
85}