social bookmarking for atproto
at main 101 lines 2.8 kB view raw
1/* 2 * clippr: a social bookmarking service for the AT Protocol 3 * Copyright (c) 2025 clippr contributors. 4 * SPDX-License-Identifier: AGPL-3.0-only 5 */ 6 7import { SocialClipprActorDefs } from "@clipprjs/lexicons"; 8import type { ErrorResponse } from "./types.js"; 9import { getDidFromHandle, getHandleFromDid } from "../network/converters.js"; 10import { Database } from "../db/database.js"; 11import { usersTable } from "../db/schema.js"; 12import { eq } from "drizzle-orm"; 13 14const db = Database.getInstance().getDb(); 15 16// TODO: Stop leeching off the Bluesky CDN and get the blob directly from the user's PDS 17// 18// Get a CDN URI from a blob's CID 19export async function createAvatarLink( 20 did: string, 21 cid: string, 22): Promise<string> { 23 return `https://cdn.bsky.app/img/avatar/plain/${did}/${cid}`; 24} 25 26export async function createProfileView( 27 actor: string, 28): Promise<SocialClipprActorDefs.ProfileView | ErrorResponse> { 29 let viewHandle; 30 let viewDid; 31 32 if (actor.startsWith("did:")) { 33 viewDid = actor; 34 try { 35 viewHandle = await getHandleFromDid(viewDid); 36 viewHandle = viewHandle.replace("at://", ""); 37 } catch (e: unknown) { 38 if (e instanceof Error) { 39 return { 40 error: "InvalidRequest", 41 message: `Error: A queried URI does not have a valid DID or handle: ${e.message}`, 42 }; 43 } else { 44 return { 45 error: "InvalidRequest", 46 message: 47 "Error: A queried URI does not have a valid DID or handle: unknown error", 48 }; 49 } 50 } 51 } else { 52 viewHandle = actor; 53 viewHandle = viewHandle.replace("at://", ""); 54 try { 55 viewDid = await getDidFromHandle(viewHandle); 56 } catch (e: unknown) { 57 if (e instanceof Error) { 58 return { 59 error: "InvalidRequest", 60 message: `Error: A queried URI does not have a valid DID or handle: ${e.message}`, 61 }; 62 } else { 63 return { 64 error: "InvalidRequest", 65 message: 66 "Error: A queried URI does not have a valid DID or handle: unknown error", 67 }; 68 } 69 } 70 } 71 72 const dbQuery = await db 73 .selectDistinct() 74 .from(usersTable) 75 .where(eq(usersTable.did, viewDid)); 76 77 if (dbQuery.length === 0) { 78 return { 79 error: "InvalidRequest", 80 message: "Could not find a queried URI's profile", 81 } as ErrorResponse; 82 } 83 84 const avatarCid = dbQuery[0]?.avatar; 85 86 let viewAvatar; 87 88 if (avatarCid === undefined || avatarCid === null) { 89 viewAvatar = "https://missing.avatar"; 90 } else viewAvatar = await createAvatarLink(viewDid, avatarCid); 91 92 return { 93 $type: "social.clippr.actor.defs#profileView", 94 did: viewDid as `did:${string}:${string}`, 95 handle: viewHandle as `${string}.${string}`, 96 avatar: (viewAvatar as `${string}:${string}`) || undefined, 97 createdAt: dbQuery[0]?.createdAt.toISOString(), 98 description: dbQuery[0]?.description || undefined, 99 displayName: dbQuery[0]?.displayName || viewHandle, 100 }; 101}