at main 2.5 kB view raw
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}