···1-# Todo for beta version
2-3-- site.standard
4- - move description to markdownDescription and set description as text only
5-6-- allow editing on mobile
7-8-- get automatic layout for mobile if only edited on desktop (and vice versa)
9-10-- add cards in middle of current position (both mobile and desktop version)
11-12-- show nsfw warnings
13-14-- card with big call to action button "create your blento"
15-16-- ask to fill with some default cards on page creation
17-18-- when adding images try to add them in a size that best fits aspect ratio
19-20-- onboarding?
21-22-- switch sidebar to a quick list of available cards with search function
23-24-- test
25- - selfhosting
26-27-- guestbook card
28-29-- onboarding?
30-31-- switch sidebar to a quick list of available cards with search function
32-33-- test
34- - selfhosting
35-36-- guestbook card
37-38-- analytics?
39-40-- refresh recently updated blentos (move to top of list, update profiles every 24 hours)
41-42-- server side oauth?
···101 return response.data;
102}
103000000000000000000000000000000104/**
105 * Creates an AT Protocol client for a user's PDS.
106 * @param did - The DID of the user
···370 };
371 };
372}) {
0373 did ??= user.did;
374375 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`;
···465 return profile.did;
466 }
467}
000000000000000000000000000000000000000000000000000000000000000000000
···101 return response.data;
102}
103104+export async function getBlentoOrBskyProfile(data: { did: Did; client?: Client }): Promise<
105+ Awaited<ReturnType<typeof getDetailedProfile>> & {
106+ hasBlento: boolean;
107+ }
108+> {
109+ let blentoProfile;
110+ try {
111+ // try getting blento profile first
112+ blentoProfile = await getRecord({
113+ collection: 'site.standard.publication',
114+ did: data?.did,
115+ rkey: 'blento.self',
116+ client: data?.client
117+ });
118+ } catch {
119+ console.error('error getting blento profile, falling back to bsky profile');
120+ }
121+122+ const response = await getDetailedProfile(data);
123+124+ return {
125+ did: data.did,
126+ handle: response?.handle,
127+ displayName: blentoProfile?.value?.name || response?.displayName || response?.handle,
128+ avatar: (getCDNImageBlobUrl({ did: data?.did, blob: blentoProfile?.value?.icon }) ||
129+ response?.avatar) as `${string}:${string}`,
130+ hasBlento: Boolean(blentoProfile.value)
131+ };
132+}
133+134/**
135 * Creates an AT Protocol client for a user's PDS.
136 * @param did - The DID of the user
···400 };
401 };
402}) {
403+ if (!blob || !did) return;
404 did ??= user.did;
405406 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`;
···496 return profile.did;
497 }
498}
499+500+/**
501+ * Fetches a post's thread including replies.
502+ * @param uri - The AT URI of the post
503+ * @param depth - How many levels of replies to fetch (default 1)
504+ * @param client - The client to use (defaults to public Bluesky API)
505+ * @returns The thread data or undefined on failure
506+ */
507+export async function getPostThread({
508+ uri,
509+ depth = 1,
510+ client
511+}: {
512+ uri: string;
513+ depth?: number;
514+ client?: Client;
515+}) {
516+ client ??= new Client({
517+ handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' })
518+ });
519+520+ const response = await client.get('app.bsky.feed.getPostThread', {
521+ params: { uri: uri as ResourceUri, depth }
522+ });
523+524+ if (!response.ok) return;
525+526+ return response.data.thread;
527+}
528+529+/**
530+ * Creates a Bluesky post on the authenticated user's account.
531+ * @param text - The post text
532+ * @param facets - Optional rich text facets (links, mentions, etc.)
533+ * @returns The response containing the post's URI and CID
534+ * @throws If the user is not logged in
535+ */
536+export async function createPost({
537+ text,
538+ facets
539+}: {
540+ text: string;
541+ facets?: Array<{
542+ index: { byteStart: number; byteEnd: number };
543+ features: Array<{ $type: string; uri?: string; did?: string; tag?: string }>;
544+ }>;
545+}) {
546+ if (!user.client || !user.did) throw new Error('No client or did');
547+548+ const record: Record<string, unknown> = {
549+ $type: 'app.bsky.feed.post',
550+ text,
551+ createdAt: new Date().toISOString()
552+ };
553+554+ if (facets) {
555+ record.facets = facets;
556+ }
557+558+ const response = await user.client.post('com.atproto.repo.createRecord', {
559+ input: {
560+ collection: 'app.bsky.feed.post',
561+ repo: user.did,
562+ record
563+ }
564+ });
565+566+ return response;
567+}