···
114
114
115
115
// Step 2: Get the PDS service endpoint from PLC directory
116
116
let serviceEndpoint = 'https://bsky.social'; // Start with bsky.social as fallback
117
117
+
let servicePds = null; // Store the actual PDS domain for logging
117
118
try {
119
119
+
console.log(`Looking up PDS endpoint for DID: ${did}`);
118
120
const plcResponse = await fetch(`https://plc.directory/${did}/data`);
119
121
120
122
if (plcResponse.ok) {
121
123
const plcData = await plcResponse.json();
124
124
+
console.log(`Got PLC directory data for ${did}`);
122
125
123
126
// Extract service endpoint from PLC data
124
127
if (plcData && plcData.service) {
···
129
132
130
133
if (pdsService && pdsService.endpoint) {
131
134
serviceEndpoint = pdsService.endpoint;
135
135
+
136
136
+
// Extract just the domain for reference
137
137
+
try {
138
138
+
const serviceUrl = new URL(pdsService.endpoint);
139
139
+
servicePds = serviceUrl.hostname;
140
140
+
console.log(`Found PDS service for ${handle} at ${serviceEndpoint} (${servicePds})`);
141
141
+
} catch (e) {
142
142
+
console.warn(`Could not parse service URL: ${pdsService.endpoint}`);
143
143
+
}
132
144
}
133
145
}
146
146
+
} else {
147
147
+
console.warn(`PLC directory lookup failed for ${did}: ${plcResponse.status} ${plcResponse.statusText}`);
134
148
}
135
149
} catch (error: any) {
136
150
console.warn(`Failed to get service endpoint from PLC directory: ${error.message}`);
···
149
163
});
150
164
151
165
if (!recordsResponse.ok) {
152
152
-
// Special case for handling third-party PDS
153
153
-
if (handle.includes('.') && !handle.endsWith('bsky.social') && !handle.endsWith('flushes.app') && !handle.endsWith('flushing.im')) {
154
154
-
const domain = handle.split('.').slice(1).join('.');
155
155
-
156
156
-
console.log(`Trying direct domain access before falling back: ${domain}`);
157
157
-
try {
158
158
-
const directUrl = `https://${domain}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
159
159
-
160
160
-
const directResponse = await fetch(directUrl, {
161
161
-
headers: { 'Accept': 'application/json' }
162
162
-
});
163
163
-
164
164
-
if (directResponse.ok) {
165
165
-
console.log(`Successfully accessed records directly from ${domain}`);
166
166
-
const directData = await directResponse.json();
166
166
+
// Log complete error information - this helps diagnose third-party PDS issues
167
167
+
console.warn(`Failed to get records from ${serviceEndpoint}`);
168
168
+
try {
169
169
+
const errorText = await recordsResponse.text();
170
170
+
console.error(`Error response from ${serviceEndpoint}: ${errorText}`);
171
171
+
} catch (e) {
172
172
+
console.error(`Could not read error response: ${e}`);
173
173
+
}
174
174
+
175
175
+
// Try appropriate fallback, with different strategies for different PDS hosts
176
176
+
if (serviceEndpoint !== 'https://public.api.bsky.app') {
177
177
+
// 1. If we have a servicePds from the PLC directory, try using it directly
178
178
+
if (servicePds) {
179
179
+
try {
180
180
+
console.log(`Trying direct PDS domain: https://${servicePds}`);
181
181
+
const pdsDirectUrl = `https://${servicePds}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
167
182
168
168
-
// Process the direct response...
169
169
-
// Process the direct response...
170
170
-
const directEntries = directData.records
171
171
-
.map((record: any) => {
172
172
-
const text = record.value.text || '';
173
173
-
if (containsBannedWords(text)) return null;
174
174
-
175
175
-
return {
176
176
-
id: record.uri,
177
177
-
uri: record.uri,
178
178
-
cid: record.cid,
179
179
-
did: did,
180
180
-
text: sanitizeText(text),
181
181
-
emoji: record.value.emoji || '🚽',
182
182
-
created_at: record.value.createdAt
183
183
-
};
184
184
-
})
185
185
-
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
186
186
-
187
187
-
// Calculate emoji stats
188
188
-
const directEmojiCounts = new Map<string, number>();
189
189
-
directEntries.forEach((entry: ProfileEntry) => {
190
190
-
const emoji = entry.emoji?.trim() || '🚽';
191
191
-
if (APPROVED_EMOJIS.includes(emoji)) {
192
192
-
directEmojiCounts.set(emoji, (directEmojiCounts.get(emoji) || 0) + 1);
193
193
-
} else {
194
194
-
directEmojiCounts.set('🚽', (directEmojiCounts.get('🚽') || 0) + 1);
195
195
-
}
183
183
+
const pdsDirectResponse = await fetch(pdsDirectUrl, {
184
184
+
headers: { 'Accept': 'application/json' }
196
185
});
197
186
198
198
-
const directEmojiStats = Array.from(directEmojiCounts.entries())
199
199
-
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
200
200
-
.sort((a, b) => b.count - a.count);
187
187
+
if (pdsDirectResponse.ok) {
188
188
+
console.log(`Successfully accessed records directly from PDS domain: ${servicePds}`);
189
189
+
const directData = await pdsDirectResponse.json();
190
190
+
191
191
+
// Process the direct response
192
192
+
const directEntries = directData.records
193
193
+
.map((record: any) => {
194
194
+
const text = record.value.text || '';
195
195
+
if (containsBannedWords(text)) return null;
196
196
+
197
197
+
return {
198
198
+
id: record.uri,
199
199
+
uri: record.uri,
200
200
+
cid: record.cid,
201
201
+
did: did,
202
202
+
text: sanitizeText(text),
203
203
+
emoji: record.value.emoji || '🚽',
204
204
+
created_at: record.value.createdAt
205
205
+
};
206
206
+
})
207
207
+
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
208
208
+
209
209
+
// Calculate emoji stats
210
210
+
const directEmojiCounts = new Map<string, number>();
211
211
+
directEntries.forEach((entry: ProfileEntry) => {
212
212
+
const emoji = entry.emoji?.trim() || '🚽';
213
213
+
if (APPROVED_EMOJIS.includes(emoji)) {
214
214
+
directEmojiCounts.set(emoji, (directEmojiCounts.get(emoji) || 0) + 1);
215
215
+
} else {
216
216
+
directEmojiCounts.set('🚽', (directEmojiCounts.get('🚽') || 0) + 1);
217
217
+
}
218
218
+
});
219
219
+
220
220
+
const directEmojiStats = Array.from(directEmojiCounts.entries())
221
221
+
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
222
222
+
.sort((a, b) => b.count - a.count);
223
223
+
224
224
+
return NextResponse.json({
225
225
+
entries: directEntries,
226
226
+
count: directEntries.length,
227
227
+
cursor: directData.cursor,
228
228
+
profile: userProfile,
229
229
+
emojiStats: directEmojiStats,
230
230
+
serviceEndpoint: `https://${servicePds}`,
231
231
+
directPds: true
232
232
+
});
233
233
+
} else {
234
234
+
console.warn(`PDS direct access failed: ${await pdsDirectResponse.text()}`);
235
235
+
}
236
236
+
} catch (pdsErr) {
237
237
+
console.error(`Error with direct PDS domain access: ${pdsErr}`);
238
238
+
}
239
239
+
}
240
240
+
241
241
+
// 2. For third-party domain handles, try using the handle's domain
242
242
+
if (handle.includes('.') && !handle.endsWith('bsky.social') && !handle.endsWith('flushes.app') && !handle.endsWith('flushing.im')) {
243
243
+
const domain = handle.split('.').slice(1).join('.');
244
244
+
try {
245
245
+
console.log(`Trying handle domain access: https://${domain}`);
246
246
+
const domainUrl = `https://${domain}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
201
247
202
202
-
return NextResponse.json({
203
203
-
entries: directEntries,
204
204
-
count: directEntries.length,
205
205
-
cursor: directData.cursor,
206
206
-
profile: userProfile,
207
207
-
emojiStats: directEmojiStats,
208
208
-
serviceEndpoint: `https://${domain}`,
209
209
-
directPds: true
248
248
+
const domainResponse = await fetch(domainUrl, {
249
249
+
headers: { 'Accept': 'application/json' }
210
250
});
211
211
-
} else {
212
212
-
console.warn(`Direct access failed: ${await directResponse.text()}`);
251
251
+
252
252
+
if (domainResponse.ok) {
253
253
+
console.log(`Successfully accessed records from handle domain: ${domain}`);
254
254
+
const domainData = await domainResponse.json();
255
255
+
256
256
+
// Process the domain response
257
257
+
const domainEntries = domainData.records
258
258
+
.map((record: any) => {
259
259
+
const text = record.value.text || '';
260
260
+
if (containsBannedWords(text)) return null;
261
261
+
262
262
+
return {
263
263
+
id: record.uri,
264
264
+
uri: record.uri,
265
265
+
cid: record.cid,
266
266
+
did: did,
267
267
+
text: sanitizeText(text),
268
268
+
emoji: record.value.emoji || '🚽',
269
269
+
created_at: record.value.createdAt
270
270
+
};
271
271
+
})
272
272
+
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
273
273
+
274
274
+
// Calculate emoji stats
275
275
+
const domainEmojiCounts = new Map<string, number>();
276
276
+
domainEntries.forEach((entry: ProfileEntry) => {
277
277
+
const emoji = entry.emoji?.trim() || '🚽';
278
278
+
if (APPROVED_EMOJIS.includes(emoji)) {
279
279
+
domainEmojiCounts.set(emoji, (domainEmojiCounts.get(emoji) || 0) + 1);
280
280
+
} else {
281
281
+
domainEmojiCounts.set('🚽', (domainEmojiCounts.get('🚽') || 0) + 1);
282
282
+
}
283
283
+
});
284
284
+
285
285
+
const domainEmojiStats = Array.from(domainEmojiCounts.entries())
286
286
+
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
287
287
+
.sort((a, b) => b.count - a.count);
288
288
+
289
289
+
return NextResponse.json({
290
290
+
entries: domainEntries,
291
291
+
count: domainEntries.length,
292
292
+
cursor: domainData.cursor,
293
293
+
profile: userProfile,
294
294
+
emojiStats: domainEmojiStats,
295
295
+
serviceEndpoint: `https://${domain}`,
296
296
+
handleDomain: true
297
297
+
});
298
298
+
} else {
299
299
+
console.warn(`Handle domain access failed: ${await domainResponse.text()}`);
300
300
+
}
301
301
+
} catch (domainErr) {
302
302
+
console.error(`Error with handle domain access: ${domainErr}`);
213
303
}
214
214
-
} catch (directErr) {
215
215
-
console.error(`Error with direct domain access: ${directErr}`);
216
304
}
217
217
-
}
218
218
-
219
219
-
// If direct access failed or not applicable, try with public API endpoint
220
220
-
if (serviceEndpoint !== 'https://public.api.bsky.app') {
221
221
-
console.warn(`Failed to get records from ${serviceEndpoint}, trying public API endpoint`);
222
222
-
// Log the error for debugging third-party PDS issues
223
223
-
try {
224
224
-
const errorText = await recordsResponse.text();
225
225
-
console.error(`Error response from ${serviceEndpoint}: ${errorText}`);
226
226
-
} catch (e) {
227
227
-
console.error(`Could not read error response: ${e}`);
228
228
-
}
305
305
+
306
306
+
// 3. Last resort: try public API
307
307
+
console.warn(`All direct approaches failed, trying public API fallback`);
229
308
const fallbackUrl = `https://public.api.bsky.app/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
230
309
231
310
const fallbackResponse = await fetch(fallbackUrl, {
···
365
444
cursor: recordsData.cursor,
366
445
profile: userProfile,
367
446
emojiStats,
368
368
-
serviceEndpoint // Include the endpoint we used for debugging
447
447
+
serviceEndpoint, // Include the endpoint we used for debugging
448
448
+
servicePds // Include the extracted PDS domain
369
449
});
370
450
} catch (error: any) {
371
451
console.error('Error fetching records:', error);