import ky from "npm:ky"; import QuickLRU from "npm:quick-lru"; import { createHash } from "node:crypto"; import { config } from "../config.ts"; import * as ATPAPI from "npm:@atproto/api"; const cache = new QuickLRU({ maxSize: 10000 }); function simpleHashAuth(auth: string): string { return createHash("sha256").update(auth).digest("hex"); } export async function cachedFetch(url: string, auth?: string) { const cacheKey = auth ? `${url}|${simpleHashAuth(auth)}` : url; if (cache.has(cacheKey)) return cache.get(cacheKey); const data = await ky .get(url, { headers: { Authorization: `${auth}`, }, }) .json(); cache.set(cacheKey, data); return data; } export type SlingshotMiniDoc = { did: string; handle: string; pds: string; signing_key: string; }; let preferences: any = undefined; export function searchParamsToJson( params: URLSearchParams ): Record { const result: Record = {}; for (const [key, value] of params.entries()) { if (result.hasOwnProperty(key)) { const existing = result[key]; if (Array.isArray(existing)) { existing.push(value); } else { result[key] = [existing, value]; } } else { result[key] = value; } } return result; } export async function resolveIdentity( actor: string ): Promise { const url = `${config.slingshot}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${actor}`; return (await cachedFetch(url)) as SlingshotMiniDoc; } export async function getRecord({ pds, did, collection, rkey, }: { pds: string; did: string; collection: string; rkey: string; }): Promise<{ cursor?: string; records: GetRecord[] }> { const url = `${pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}`; const result = (await cachedFetch(url)) as { cursor?: string; records: GetRecord[]; }; return result as { cursor?: string; records: { uri: string; cid: string; value: ATPAPI.AppBskyFeedPost.Record; }[]; }; } export async function listPostRecords({ pds, did, limit = 50, cursor, }: { pds: string; did: string; limit: number; cursor?: string; }): Promise<{ cursor?: string; records: { uri: string; cid: string; value: ATPAPI.AppBskyFeedPost.Record }[]; }> { const url = `${pds}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=app.bsky.feed.post&limit=${limit}${ cursor ? `&cursor=${cursor}` : "" }`; const result = (await cachedFetch(url)) as { cursor?: string; records: GetRecord[]; }; return result as { cursor?: string; records: { uri: string; cid: string; value: ATPAPI.AppBskyFeedPost.Record; }[]; }; } // async function getProfileRecord(did: string): Promise { // const url = `${slingshoturl}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.actor.profile&rkey=self`; // const result = await cachedFetch(url) as GetRecord; // return result.value as ATPAPI.AppBskyActorProfile.Record; // } export function buildBlobUrl( pds: string, did: string, cid: string ): string | undefined { if (!pds || !did || !cid) return undefined; return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`; } export type ConstellationDistinctDids = { total: number; linking_dids: string[]; cursor: string; }; export type GetRecord = { uri: string; cid: string; value: Record; }; export function didWebToHttps(did: string) { if (!did.startsWith("did:web:")) return null; const parts = did.slice("did:web:".length).split(":"); const [domain, ...path] = parts; return `https://${domain}${path.length ? "/" + path.join("/") : ""}`; } export async function getSlingshotRecord( did: string, collection: string, rkey: string ): Promise { const identity = await resolveIdentity(did); //const url = `${config.slingshot}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}`; const url = `${identity.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}`; const result = (await cachedFetch(url)) as GetRecord; return result as GetRecord; } export async function getUniqueCount({ did, collection, path, }: { did: string; collection: string; path: string; }): Promise { const url = `${config.constellation}/links/count/distinct-dids?target=${did}&collection=${collection}&path=${path}`; const result = (await cachedFetch(url)) as ConstellationDistinctDids; return result.total; } export function withCors(headers: HeadersInit = {}) { return { "Access-Control-Allow-Origin": "*", ...headers, }; }