import { atUriOf, datetimeOf, didOf, nsidOf, type AtUri, type Datetime, type Did, type GitOid, type KnotOrigin, type Rkey, type RawResourceUri } from "#lib/api/syntax.ts"; import { parseCanonicalResourceUri } from "@atcute/lexicons/syntax"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import type { BobbinContext, XrpcRequestInit } from "#lib/api/client.ts"; import { jsonGet } from "#lib/api/_request.ts"; import { createCob, eraseCob, updateCob, type CobRecord, type CobRef, type KnotBlobReceipt } from "#lib/api/cob.ts"; import type { RecordView } from "#lib/api/records.ts"; import { deleteRecord, putRecord } from "#lib/api/write.ts"; import type * as OrgTangledFeedComment from "#lib/api/lexicons/types/org/tangled/feed/comment.ts"; import type * as OrgTangledFeedDefs from "#lib/api/lexicons/types/org/tangled/feed/defs.ts"; export type CommentRecord = OrgTangledFeedComment.Main; export const COMMENT_COLLECTION = nsidOf("org.tangled.feed.comment"); // subject and replyTo have to name the current version or the knot refuses export interface CommentDraft { subject: CobRef; replyTo?: CobRef; text: string; blobs?: KnotBlobReceipt[]; embed?: { repo: Did; commit: GitOid }; } const imageEmbeds = (blobs: readonly KnotBlobReceipt[]) => blobs.map((image) => ({ $type: "org.tangled.markup.embed.image" as const, image })); const commentRecord = ( { subject, replyTo, text, blobs, embed }: CommentDraft, createdAt = datetimeOf(new Date().toISOString()) ): CommentRecord => ({ $type: "org.tangled.feed.comment", subject: { $type: "com.atproto.repo.strongRef", ...subject }, replyTo, body: { $type: "org.tangled.markup.markdown", text, ...(blobs?.length ? { blobs: imageEmbeds(blobs) } : {}) }, ...(embed ? { embed: { $type: "org.tangled.embed.commit", repo: embed.repo, commit: { $type: "org.tangled.git.oid", oid: embed.commit } } } : {}), createdAt }); export const putComment = async ( agent: OAuthUserAgent, rkey: Rkey, draft: CommentDraft, createdAt?: Datetime ): Promise & { uri: RawResourceUri }> => { const record = commentRecord(draft, createdAt); const { uri, cid } = await putRecord(agent, COMMENT_COLLECTION, rkey, record); return { uri, cid, value: record }; }; export const deleteComment = async (agent: OAuthUserAgent, rkey: Rkey): Promise<{ uri: AtUri }> => { const uri = atUriOf(`at://${agent.sub}/${COMMENT_COLLECTION}/${rkey}`); await deleteRecord(agent, COMMENT_COLLECTION, rkey); return { uri }; }; export const createComment = async ( agent: OAuthUserAgent, knot: KnotOrigin, draft: CommentDraft ): Promise> => { const record = commentRecord(draft); const { repo } = parseCanonicalResourceUri(draft.subject.uri); return { ...(await createCob(agent, knot, didOf(repo), COMMENT_COLLECTION, record)), value: record }; }; export const editComment = ( agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef, text: string ): Promise> => updateCob(agent, knot, from, (current) => ({ ...current, body: { ...current.body, text } })); export const eraseComment = ( agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef ): Promise => eraseCob(agent, knot, from); export interface CommentListPage { cursor?: string | null; items: OrgTangledFeedDefs.CommentView[]; } export const listComments = ( ctx: BobbinContext, subject: AtUri, filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, init?: XrpcRequestInit ) => jsonGet(ctx, "org.tangled.feed.listComments", { subject, ...filter }, init);