diff --git a/live-embed/src/Post.tsx b/live-embed/src/Post.tsx
index 96b7b91..c69ff18 100644
--- a/live-embed/src/Post.tsx
+++ b/live-embed/src/Post.tsx
@@ -1,64 +1,30 @@
import { useEffect, useState } from 'react';
-
-const nicerSource = source => ({
- 'app.bsky.feed.like:subject.uri': 'like', // likes
- 'app.bsky.feed.repost:subject.uri': 'repost', // reposts
- 'app.bsky.feed.post:embed.record.uri': 'quote', // normal quotes
- 'app.bsky.feed.post:embed.record.record.uri': 'quote', // RecordWithMedia quotes
- 'app.bsky.feed.post:reply.root.uri': 'reply', // all child replies
-}[source]);
+import { getPostStats } from './constellation';
+import linkSources from './linkSources';
export function Post({ atUri, updatedLinks }) {
const [baseStats, setBaseStats] = useState({});
-
- const nicerStats = {};
-
- for (const [collection, paths] of Object.entries(baseStats)) {
- for (const [oldStylePath, counts] of Object.entries(paths)) {
- const newStylePath = `${collection}:${oldStylePath.slice(1)}`;
- const name = nicerSource(newStylePath);
- if (!name) continue; // perils of constellation's (soon-deprecated) /all
- if (name === 'like') {
- nicerStats[name] = counts.distinct_dids;
- } else {
- nicerStats[name] = counts.records;
- }
- }
- }
+ const liveStats = { ...baseStats };
for (const [key, val] of Object.entries(updatedLinks)) {
- const name = nicerSource(key);
- if (!nicerStats[name]) nicerStats[name] = 0;
- nicerStats[name] += val;
+ const name = linkSources[key];
+ if (!liveStats[name]) liveStats[name] = 0;
+ liveStats[name] += val;
}
useEffect(() => {
- let cancel = false;
-
- (async () => {
- try {
- const url = new URL('/links/all', 'https://constellation.microcosm.blue');
- url.searchParams.set('target', atUri);
- const res = await fetch(url);
- if (!res.ok) throw new Error(res);
- const { links } = await res.json();
- setBaseStats(links);
- } catch (e) {
- console.warn('fetching base stats failed', e);
- }
- })();
-
- return () => cancel = true;
+ let alive = true;
+ getPostStats(atUri).then(
+ stats => alive && setBaseStats(stats),
+ e => console.warn('fetching base stats failed', e));
+ return () => alive = false;
}, [atUri]);
- useState()
-
-
return (
{atUri}
- {JSON.stringify(nicerStats)}
+ {JSON.stringify(liveStats)}
);
diff --git a/live-embed/src/constellation.ts b/live-embed/src/constellation.ts
new file mode 100644
index 0000000..4961d06
--- /dev/null
+++ b/live-embed/src/constellation.ts
@@ -0,0 +1,39 @@
+import linkSources from './linkSources';
+
+/**
+ * get nice historical counts from constellation
+ *
+ * constellation's api still uses separated collection/path sources, and
+ * likes should be distinct where everything else is record counts.
+ *
+ * constellation still can only specify one link source per request or /all
+ *
+ * handles stuff like that
+ **/
+export async function getPostStats(
+ atUri: string,
+ endpoint: string = 'https://constellation.microcosm.blue'
+) {
+ const url = new URL('/links/all', endpoint);
+ url.searchParams.set('target', atUri);
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(res);
+ const { links } = await res.json();
+
+ const niceLinks = {};
+
+ for (const [collection, paths] of Object.entries(links)) {
+ for (const [oldStylePath, counts] of Object.entries(paths)) {
+ const newStylePath = `${collection}:${oldStylePath.slice(1)}`;
+ const name = linkSources[newStylePath];
+ if (!name) continue; // perils of constellation's (soon-deprecated) /all
+ if (name === 'like') {
+ niceLinks[name] = counts.distinct_dids;
+ } else {
+ niceLinks[name] = counts.records;
+ }
+ }
+ }
+
+ return niceLinks;
+}
diff --git a/live-embed/src/linkSources.ts b/live-embed/src/linkSources.ts
new file mode 100644
index 0000000..1d33217
--- /dev/null
+++ b/live-embed/src/linkSources.ts
@@ -0,0 +1,9 @@
+const linkSources = {
+ 'app.bsky.feed.like:subject.uri': 'like',
+ 'app.bsky.feed.repost:subject.uri': 'repost', // actual repost
+ 'app.bsky.feed.post:embed.record.uri': 'repost', // normal quote (grouped for count)
+ 'app.bsky.feed.post:embed.record.record.uri': 'repost', // RecordWithMedia quote (grouped for count)
+ 'app.bsky.feed.post:reply.root.uri': 'reply', // root: count all descendent replies
+};
+
+export { linkSources as default };
diff --git a/live-embed/src/samplePosts.ts b/live-embed/src/samplePosts.ts
index 0f91975..f1024e7 100644
--- a/live-embed/src/samplePosts.ts
+++ b/live-embed/src/samplePosts.ts
@@ -1,8 +1,9 @@
+import { getPostStats } from './constellation';
const SEKELETON_API = 'https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton';
const FEED = 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot';
const POLL_DELAY = 9000;
-const POST_LIMIT = 10;
+const POST_LIMIT = 5;
async function getFeed() {
const url = new URL(SEKELETON_API);
@@ -31,13 +32,29 @@ export function rotatingPair(onRotate: any) {
const { feed } = await getFeed();
if (dying) return;
+ const withStats = await Promise.all(feed.map(async ({ post }) => {
+ if (seen.has(post)) return { post, total: 0 };
+ let stats = {};
+ try {
+ stats = await getPostStats(post);
+ } catch (e) {
+ console.warn('failed to get stats from constellation', e);
+ }
+ const total = Array.from(Object.values(stats)).reduce((a, b) => a + b, 0);
+ return ({ post, total })
+ }))
+ if (dying) return;
+
+ // idk if sorting by most interactions yields more-interactive posts but eh
+ withStats.sort(({ total: a }, { total: b }) => b - a);
+
// special case: first load
if (A === null && B === null) {
- if (feed.length < 2) throw new Error('feed returned fewer than two posts to start');
- seen.add(A = feed[0].post);
- seen.add(B = feed[1].post);
+ if (withStats.length < 2) throw new Error('withStats returned fewer than two posts to start');
+ seen.add(A = withStats[0].post);
+ seen.add(B = withStats[1].post);
} else {
- for (const { post } of feed) {
+ for (const { post } of withStats) {
if (seen.has(post)) {
continue;
}
@@ -53,7 +70,7 @@ export function rotatingPair(onRotate: any) {
}
onRotate([A, B]);
} catch (e) {
- console.error('hmm, failed to get feed', e);
+ console.error('hmm, failed to get withStats', e);
}
timer = setTimeout(next, POLL_DELAY);
}
diff --git a/live-embed/src/spacedust.ts b/live-embed/src/spacedust.ts
index 2b010f3..4960b1f 100644
--- a/live-embed/src/spacedust.ts
+++ b/live-embed/src/spacedust.ts
@@ -1,3 +1,4 @@
+import linkSources from './linkSources';
type SpacedustStatus = 'disconnected' | 'connecting' | 'connected';
@@ -22,13 +23,7 @@ export class Spacedust {
#subjects: string[];
#subjectsDirty: boolean = false; // in case we try to update while disconnected
- #sources: string[] = [ // hard-coding for demo
- "app.bsky.feed.like:subject.uri", // likes
- "app.bsky.feed.repost:subject.uri", // reposts
- "app.bsky.feed.post:embed.record.uri", // normal quotes
- "app.bsky.feed.post:embed.record.record.uri", // RecordWithMedia quotes
- "app.bsky.feed.post:reply.root.uri", // all child replies
- ];
+ #sources: string[] = Object.keys(linkSources); // hard-coding for demo
#eol: boolean = false; // flag: we should shut down
constructor(