Something went wrong. Try again.
Resolve your Bluesky avatar with a human URL silhouette.town
atproto deno
Something went wrong. Try again.
silhouette main.ts
4.4 kB · 165 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166import { STATUS_CODE, STATUS_TEXT } from "@std/http";import { Client, simpleFetchHandler } from "@atcute/client";import { is } from "@atcute/lexicons";import { isActorIdentifier } from "@atcute/lexicons/syntax";import { AppBskyActorProfile } from "@atcute/bluesky";
const ROOT = new URLPattern({ pathname: "/" });const IDENTIFIER_ROUTE = new URLPattern({ pathname: "/avatar/:identifier" });const slingshot = new Client({ handler: simpleFetchHandler({ service: "https://slingshot.microcosm.blue" }),});
function generateSpiel(origin: string) { return `<h1> silhouette</h1>
<p> Quickly get the URL to your Bluesky profile photo using an identifier (handle or DID). Go to:</p>
<p> <code>${origin}/avatar/<your-identifier></code></p>
<p> For example: <a href="${origin}/avatar/graham.systems"> ${origin}/avatar/graham.systems </a></p>`;}
// Algorithm:// 1. Resolve minidoc from identifier in URL// 2. Resolve Bluesky actor profile from PDS// 3. Return 303 with profile picture blob URL
export async function handler(req: Request): Promise<Response> { if (ROOT.exec(req.url)) { const url = new URL(req.url); return new Response(generateSpiel(url.origin), { status: STATUS_CODE.OK, statusText: STATUS_TEXT[STATUS_CODE.OK], headers: { "Content-Type": "text/html", }, }); }
const match = IDENTIFIER_ROUTE.exec(req.url);
if (!match) { return new Response(null, { status: STATUS_CODE.NotFound, statusText: STATUS_TEXT[STATUS_CODE.NotFound], }); } else if (req.method !== "GET") { return new Response(null, { status: STATUS_CODE.MethodNotAllowed, statusText: STATUS_TEXT[STATUS_CODE.MethodNotAllowed], }); }
const identifier = match.pathname.groups.identifier;
if (!isActorIdentifier(identifier)) { return Response.json({ identifier, message: `invalid identifier ${identifier}`, }, { status: STATUS_CODE.BadRequest, statusText: STATUS_TEXT[STATUS_CODE.BadRequest], }); }
const minidoc = await slingshot.get( "com.bad-example.identity.resolveMiniDoc", { params: { identifier, }, }, );
if (!minidoc.ok) { return Response.json({ identifier, message: `failed to resolve identifier ${identifier}`, }, { status: STATUS_CODE.InternalServerError, statusText: STATUS_TEXT[STATUS_CODE.InternalServerError], }); } else if (minidoc.status > 400) { return Response.json({ identifier, message: `error from Slingshot`, error: minidoc.data, }, { status: STATUS_CODE.InternalServerError, statusText: STATUS_TEXT[STATUS_CODE.InternalServerError], }); }
const pds = new Client({ handler: simpleFetchHandler({ service: minidoc.data.pds }), });
const record = await pds.get("com.atproto.repo.getRecord", { params: { collection: "app.bsky.actor.profile", rkey: "self", repo: minidoc.data.did, }, });
if (!record.ok || record.status > 400) { return Response.json({ identifier, pds: minidoc.data.pds, message: `failed to resolve app.bsky.actor.profile for ${identifier}`, }, { status: record.status || STATUS_CODE.InternalServerError, statusText: STATUS_TEXT[ STATUS_CODE.InternalServerError ], }); }
const profile = record.data.value; if (!is(AppBskyActorProfile.mainSchema, profile)) { return Response.json({ identifier, pds: minidoc.data.pds, message: `profile record does not match app.bsky.actor.profile`, profile, }, { status: STATUS_CODE.PreconditionFailed, statusText: STATUS_TEXT[STATUS_CODE.PreconditionFailed], }); } else if (!profile.avatar) { return Response.json({ identifier, pds: minidoc.data.pds, message: "profile has no avatar", }, { status: STATUS_CODE.NotFound, statusText: STATUS_TEXT[STATUS_CODE.NotFound], }); }
const blobUrl = new URL("/xrpc/com.atproto.sync.getBlob", minidoc.data.pds); const cid = "ref" in profile.avatar ? profile.avatar.ref.$link : profile.avatar.cid;
blobUrl.searchParams.set("did", minidoc.data.did); blobUrl.searchParams.set("cid", cid);
return Response.redirect(blobUrl, STATUS_CODE.SeeOther);}
if (import.meta.main) { Deno.serve(handler);}