import { AtUri } from "@atproto/api"; import { getPublicationURL } from "src/utils/getPublicationURL"; import { getConstellationBacklinks } from "src/utils/getPostPageData"; import { getDocumentURL } from "src/utils/getPublicationURL"; import { getDocumentPages, normalizeDocumentRecord, normalizePublicationRecord, } from "src/utils/normalizeRecords"; import { truncatePagesAtMembersDelimiter } from "src/membership"; import { idResolver } from "src/identity"; import { resolveBylineProfiles } from "src/utils/resolveBylineProfiles"; import type { Post } from "actions/reader/getReaderFeed"; type RawDocument = { data: unknown; uri: string; sort_date: string; comments_on_documents: { count: number }[]; document_mentions_in_bsky: { count: number }[]; recommends_on_documents: { count: number }[]; documents_in_publications: { publications: { uri: string; record: unknown; name: string | null; [key: string]: unknown; } | null; }[]; }; export async function enrichDocumentToPost( doc: RawDocument, ): Promise { const pub = doc.documents_in_publications?.[0]?.publications; const uri = new AtUri(doc.uri); const handle = await idResolver.did.resolve(uri.host); const normalizedData = normalizeDocumentRecord(doc.data, doc.uri); if (!normalizedData) { console.log( "[enrichPost] normalizeDocumentRecord returned null for:", doc.uri, ); return null; } // The reader feed has no per-user membership context, so members-only posts // only surface their preview; the post page itself unlocks for members. const docPages = getDocumentPages(normalizedData); if (docPages) truncatePagesAtMembersDelimiter(docPages); const normalizedPubRecord = pub ? normalizePublicationRecord(pub.record) : null; const mentionsCount = await getAccurateMentionsCount( normalizedData, doc.uri, normalizedPubRecord, doc.document_mentions_in_bsky?.[0]?.count || 0, ); const contributors = await resolveBylineProfiles(normalizedData, uri.host); return { publication: pub ? { href: getPublicationURL(pub), pubRecord: normalizedPubRecord, uri: pub.uri || "", } : undefined, ownerDid: handle?.alsoKnownAs?.[0] ? `@${handle.alsoKnownAs[0].slice(5)}` : null, contributors, documents: { comments_on_documents: doc.comments_on_documents, document_mentions_in_bsky: doc.document_mentions_in_bsky, recommends_on_documents: doc.recommends_on_documents, mentionsCount, data: normalizedData, uri: doc.uri, sort_date: doc.sort_date, }, }; } async function getAccurateMentionsCount( normalizedData: NonNullable>, docUri: string, normalizedPubRecord: ReturnType, dbMentionsCount: number, ): Promise { const postUrl = getDocumentURL(normalizedData, docUri, normalizedPubRecord); const absoluteUrl = postUrl.startsWith("/") ? `https://leaflet.pub${postUrl}` : postUrl; const constellationBacklinks = await getConstellationBacklinks(absoluteUrl); const uniqueBacklinkCount = new Set(constellationBacklinks.map((b) => b.uri)) .size; return dbMentionsCount + uniqueBacklinkCount; }