// bsky-inbound.js — bsky interactions on a bound DID's posts → the identity's observation feed (hyper-social #82, I2b).
//
// ⛔ THE SAME OBSERVATION SHAPE AS THE FEDIVERSE (I1). The node already holds the identity's observation feed and
// renders it; bsky arrivals are just more rows in it. Nothing on the node changes.
//
// Spacedust ships LOCATORS: the linking record's at:// address and the subject it points at. That is all an
// observation needs — who, what kind, about which post, and the identity it is undone by (a Spacedust `delete` names
// the same linking record). A reply's text is fetched separately, for display only, by the wiring.
//
// ⚠ A favela post behind an at:// rkey is found by recomputing tidFor over the identity's posts (tidFor is one-way),
// so the caller injects `postName(rkey)`.
//
// PURE: no network, no clock.
export const SOURCES = {
REPLY_PARENT: 'app.bsky.feed.post:reply.parent.uri',
REPLY_ROOT: 'app.bsky.feed.post:reply.root.uri',
QUOTE: 'app.bsky.feed.post:embed.record.uri',
QUOTE_MEDIA: 'app.bsky.feed.post:embed.record.record.uri',
LIKE: 'app.bsky.feed.like:subject.uri',
REPOST: 'app.bsky.feed.repost:subject.uri',
FOLLOW: 'app.bsky.graph.follow:subject'
}
// What this version subscribes to. Root-only replies and quotes are deliberately absent (see linkToObservation).
export const WANTED = [SOURCES.LIKE, SOURCES.REPOST, SOURCES.FOLLOW, SOURCES.REPLY_PARENT]
const KIND = {
[SOURCES.LIKE]: 'like',
[SOURCES.REPOST]: 'repost',
[SOURCES.FOLLOW]: 'follow',
[SOURCES.REPLY_PARENT]: 'reply'
}
/**
* One Spacedust link (spacedust.js parseLinkEvent shape) → { op: 'note', obs } | { op: 'remove', activity } | null.
*
* ⛔ NOTHING for what we have no words for yet: a root-only reply is a reply to someone else's post in a thread we
* started; a quote needs its own kind end to end. Nothing for a post with no favela original, and nothing for the
* identity's own actions.
*/
export function linkToObservation (link, { did, feed, postName } = {}) {
if (!link || !did || !feed) return null
const kind = KIND[link.source]
if (!kind) return null
if (link.did === did) return null
const activity = `at://${link.did}/${link.collection}/${link.rkey}`
if (link.operation === 'delete') return { op: 'remove', activity }
if (link.operation !== 'create') return null
let target = null
if (kind === 'follow') {
if (link.subject !== did) return null
} else {
const prefix = `at://${did}/app.bsky.feed.post/`
if (typeof link.subject !== 'string' || !link.subject.startsWith(prefix)) return null
const rkey = link.subject.slice(prefix.length)
const name = postName ? postName(rkey) : null
if (!name) return null
target = { feed, collection: 'post', name }
}
return { op: 'note', obs: { kind, actor: link.did, subject: link.subject, target, activity } }
}
export default { linkToObservation, SOURCES, WANTED }