your personal website on atproto - mirror
blento.app
1/**
2 * ATProto helper script for debugging.
3 *
4 * Usage:
5 * npx tsx scripts/atproto.ts listRecords <handle> <collection>
6 * npx tsx scripts/atproto.ts getRecord <handle> <collection> <rkey>
7 *
8 * Examples:
9 * npx tsx scripts/atproto.ts listRecords japan.selfhosted.social app.blento.card
10 * npx tsx scripts/atproto.ts getRecord japan.selfhosted.social app.blento.card self
11 */
12
13async function resolveHandle(handle: string): Promise<string> {
14 // Try DNS-based resolution via DoH
15 const dnsRes = await fetch(
16 `https://mozilla.cloudflare-dns.com/dns-query?name=_atproto.${handle}&type=TXT`,
17 { headers: { Accept: 'application/dns-json' } }
18 );
19 const dns = await dnsRes.json();
20 for (const answer of dns.Answer ?? []) {
21 const match = answer.data?.replace(/"/g, '').match(/^did=(.+)$/);
22 if (match) return match[1];
23 }
24
25 // Fallback: HTTP well-known
26 const httpRes = await fetch(`https://${handle}/.well-known/atproto-did`);
27 if (httpRes.ok) {
28 const did = (await httpRes.text()).trim();
29 if (did.startsWith('did:')) return did;
30 }
31
32 throw new Error(`Could not resolve handle: ${handle}`);
33}
34
35async function resolvePDS(did: string): Promise<string> {
36 let docUrl: string;
37 if (did.startsWith('did:plc:')) {
38 docUrl = `https://plc.directory/${did}`;
39 } else if (did.startsWith('did:web:')) {
40 const host = did.replace('did:web:', '');
41 docUrl = `https://${host}/.well-known/did.json`;
42 } else {
43 throw new Error(`Unsupported DID method: ${did}`);
44 }
45
46 const res = await fetch(docUrl);
47 if (!res.ok) throw new Error(`Failed to fetch DID document: ${res.status}`);
48 const doc = await res.json();
49
50 for (const service of doc.service ?? []) {
51 if (service.id === '#atproto_pds') {
52 return service.serviceEndpoint;
53 }
54 }
55 throw new Error('No #atproto_pds service found in DID document');
56}
57
58async function listRecords(handle: string, collection: string) {
59 const did = await resolveHandle(handle);
60 const pds = await resolvePDS(did);
61 console.error(`Resolved: ${handle} → ${did} @ ${pds}`);
62
63 const allRecords: any[] = [];
64 let cursor: string | undefined;
65
66 do {
67 const params = new URLSearchParams({
68 repo: did,
69 collection,
70 limit: '100'
71 });
72 if (cursor) params.set('cursor', cursor);
73
74 const res = await fetch(`${pds}/xrpc/com.atproto.repo.listRecords?${params}`);
75 if (!res.ok) {
76 const body = await res.text();
77 throw new Error(`listRecords failed: ${res.status} ${body}`);
78 }
79 const data = await res.json();
80 allRecords.push(...data.records);
81 cursor = data.cursor;
82 } while (cursor);
83
84 console.log(JSON.stringify(allRecords, null, 2));
85}
86
87async function getRecord(handle: string, collection: string, rkey: string) {
88 const did = await resolveHandle(handle);
89 const pds = await resolvePDS(did);
90 console.error(`Resolved: ${handle} → ${did} @ ${pds}`);
91
92 const params = new URLSearchParams({ repo: did, collection, rkey });
93 const res = await fetch(`${pds}/xrpc/com.atproto.repo.getRecord?${params}`);
94 if (!res.ok) {
95 const body = await res.text();
96 throw new Error(`getRecord failed: ${res.status} ${body}`);
97 }
98 const data = await res.json();
99 console.log(JSON.stringify(data, null, 2));
100}
101
102// CLI
103const [, , command, ...args] = process.argv;
104
105switch (command) {
106 case 'listRecords':
107 if (args.length < 2) {
108 console.error('Usage: listRecords <handle> <collection>');
109 process.exit(1);
110 }
111 listRecords(args[0], args[1]).catch((e) => {
112 console.error(e.message);
113 process.exit(1);
114 });
115 break;
116
117 case 'getRecord':
118 if (args.length < 3) {
119 console.error('Usage: getRecord <handle> <collection> <rkey>');
120 process.exit(1);
121 }
122 getRecord(args[0], args[1], args[2]).catch((e) => {
123 console.error(e.message);
124 process.exit(1);
125 });
126 break;
127
128 default:
129 console.error('Commands: listRecords, getRecord');
130 console.error(' listRecords <handle> <collection>');
131 console.error(' getRecord <handle> <collection> <rkey>');
132 process.exit(1);
133}