//// Client for [Constellation](https://constellation.microcosm.blue), the //// microcosm atproto-wide backlink index: which records link to a given //// record, identity, or URL. import atproto/xrpc.{type Client, type XrpcError} import gleam/dynamic/decode import gleam/int import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/uri pub const default_host = "https://constellation.microcosm.blue" /// One linking record; `record_uri` turns it back into an at-uri. pub type Backlink { Backlink(did: String, collection: String, rkey: String) } pub type BacklinksPage { BacklinksPage(total: Int, records: List(Backlink), cursor: Option(String)) } /// Records linking to `subject` (an at-uri, DID, or plain URL). `source` names /// where the link must appear: `:`, e.g. /// `app.bsky.feed.like:subject.uri`. pub fn get_backlinks( client: Client, host: String, subject subject: String, source source: String, limit limit: Int, cursor cursor: Option(String), ) -> Result(BacklinksPage, XrpcError) { let params = [ #("subject", subject), #("source", source), #("limit", int.to_string(limit)), ] let params = case cursor { Some(c) -> list.append(params, [#("cursor", c)]) None -> params } let url = host <> "/xrpc/blue.microcosm.links.getBacklinks?" <> uri.query_to_string(params) use resp <- result.try(xrpc.get(client, url, None)) xrpc.parse(resp.body, page_decoder()) } pub fn record_uri(backlink: Backlink) -> String { "at://" <> backlink.did <> "/" <> backlink.collection <> "/" <> backlink.rkey } fn page_decoder() -> decode.Decoder(BacklinksPage) { use total <- decode.field("total", decode.int) use records <- decode.field("records", decode.list(backlink_decoder())) use cursor <- decode.optional_field( "cursor", None, decode.optional(decode.string), ) decode.success(BacklinksPage(total:, records:, cursor:)) } fn backlink_decoder() -> decode.Decoder(Backlink) { use did <- decode.field("did", decode.string) use collection <- decode.field("collection", decode.string) use rkey <- decode.field("rkey", decode.string) decode.success(Backlink(did:, collection:, rkey:)) }