your personal website on atproto - mirror
blento.app
1import { getRecord, listRecords, parseUri, resolveHandle } from '$lib/atproto';
2import type { Did, Handle } from '@atcute/lexicons';
3import { isDid } from '@atcute/lexicons/syntax';
4
5interface GalleryItem {
6 value: {
7 gallery: string;
8 item: string;
9 position?: number;
10 };
11}
12
13// Parse grain.social gallery URLs
14// https://grain.social/profile/atproto.boston/gallery/3megtiuwqs62w
15export function parseGrainGalleryUrl(url: string): { handle: string; rkey: string } | null {
16 const match = url.match(/grain\.social\/profile\/([^/]+)\/gallery\/([A-Za-z0-9]+)/);
17 if (!match) return null;
18 return { handle: match[1], rkey: match[2] };
19}
20
21export async function loadGrainGalleryData(items: { cardData: Record<string, unknown> }[]) {
22 const itemsData: Record<string, unknown[]> = {};
23
24 const galleryItems: Record<string, GalleryItem[] | undefined> = {
25 'social.grain.gallery.item': undefined
26 };
27
28 for (const item of items) {
29 if (!item.cardData.galleryUri) continue;
30
31 const galleryUri = item.cardData.galleryUri as string;
32 const parsedUri = parseUri(galleryUri);
33
34 if (parsedUri?.collection === 'social.grain.gallery') {
35 let repo = parsedUri.repo;
36
37 // Resolve handle to DID if needed
38 if (!isDid(repo)) {
39 const did = await resolveHandle({ handle: repo as Handle });
40 if (!did) continue;
41 repo = did;
42 }
43
44 // Construct DID-based URI for filtering (PDS records use DID-based URIs)
45 const didBasedGalleryUri = `at://${repo}/social.grain.gallery/${parsedUri.rkey}`;
46
47 const itemCollection = 'social.grain.gallery.item';
48
49 if (!galleryItems[itemCollection]) {
50 galleryItems[itemCollection] = (await listRecords({
51 did: repo as Did,
52 collection: itemCollection
53 })) as unknown as GalleryItem[];
54 }
55
56 const galleryItemsList = galleryItems['social.grain.gallery.item'];
57 if (!galleryItemsList) continue;
58
59 const images = galleryItemsList
60 .filter((i) => i.value.gallery === didBasedGalleryUri)
61 .map(async (i) => {
62 const itemData = parseUri(i.value.item);
63 if (!itemData) return null;
64 const record = await getRecord({
65 did: itemData.repo as Did,
66 collection: itemData.collection!,
67 rkey: itemData.rkey
68 });
69 return { ...record, value: { ...record.value, ...i.value } };
70 });
71
72 // Store under original key so the component can look it up
73 itemsData[galleryUri] = await Promise.all(images);
74 }
75 }
76
77 return itemsData;
78}