Podcasts hosted on ATProto
1import { BskyAgent } from '@atproto/api';
2import dotenv from 'dotenv';
3
4dotenv.config();
5
6const agent = new BskyAgent({
7 service: process.env.ATPROTO_SERVICE || 'https://bsky.social'
8});
9
10let isAuthenticated = false;
11
12export async function authenticate() {
13 if (isAuthenticated) return agent;
14
15 try {
16 await agent.login({
17 identifier: process.env.ATPROTO_IDENTIFIER,
18 password: process.env.ATPROTO_PASSWORD
19 });
20 isAuthenticated = true;
21 console.log('✅ Authenticated with AT Protocol');
22 return agent;
23 } catch (error) {
24 console.error('❌ Authentication failed:', error);
25 throw error;
26 }
27}
28
29export const atprotoClient = {
30 getAgent: async () => {
31 if (!isAuthenticated) {
32 await authenticate();
33 }
34 return agent;
35 },
36
37 uploadBlob: async (data, mimeType) => {
38 const authenticatedAgent = await atprotoClient.getAgent();
39 const response = await authenticatedAgent.uploadBlob(data, {
40 encoding: mimeType
41 });
42 return response.data.blob;
43 },
44
45 getBlob: async (did, cid) => {
46 // Fetch blob from the user's PDS using the public sync endpoint
47 // Extract PDS from the DID (assumes did:plc: or did:web:)
48 let pdsUrl;
49
50 if (did.startsWith('did:plc:')) {
51 // For did:plc, we need to resolve it to find the PDS
52 // For now, try common PDS endpoints
53 const didResponse = await fetch(`https://plc.directory/${did}`);
54 if (didResponse.ok) {
55 const didDoc = await didResponse.json();
56 // Find PDS service endpoint
57 const pdsService = didDoc.service?.find(s => s.id === '#atproto_pds');
58 pdsUrl = pdsService?.serviceEndpoint || 'https://bsky.social';
59 } else {
60 pdsUrl = 'https://bsky.social';
61 }
62 } else {
63 pdsUrl = 'https://bsky.social';
64 }
65
66 const url = `${pdsUrl}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`;
67 console.log('Fetching blob from:', url);
68
69 const response = await fetch(url);
70 console.log('Blob response:', response.status, response.statusText);
71
72 return response;
73 }
74};