OverviewFilesCommitsIssuesDocsReleasesPeers

favela-gateway / lib / bsky-inbound.js

66 lines · javascript ref:
// 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 }