alpha
Login
or
Join now
atpota.to
/
flushes.app
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
fix record fetching on profiles
author
damedotblog
date
1 year ago
(Jun 14, 2025, 1:02 PM -0400)
commit
3456d437
3456d437fa3c69c33fae365515ade8464642fcdd
parent
8dca250e
8dca250e57b9d020b70b1021ee3e4c476b42ec67
+85
-336
1 changed file
Expand all
Collapse all
Unified
Split
app
src
app
api
bluesky
profile
route.ts
+85
-336
app/src/app/api/bluesky/profile/route.ts
Reviewed
···
271
271
- Service PDS Host: ${servicePds || 'unknown'}
272
272
`);
273
273
274
274
-
// Check if the serviceEndpoint includes "/xrpc" already, which some PDS endpoints might
275
275
-
let listRecordsUrl;
276
276
-
if (serviceEndpoint.endsWith('/xrpc')) {
277
277
-
listRecordsUrl = `${serviceEndpoint}/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
278
278
-
} else {
279
279
-
listRecordsUrl = `${serviceEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
280
280
-
}
274
274
+
// Construct the listRecords URL using the service endpoint
275
275
+
const listRecordsUrl = `${serviceEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
281
276
282
277
console.log(`Fetching records from ${listRecordsUrl}`);
283
278
···
297
292
console.error(`Could not read error response: ${e}`);
298
293
}
299
294
300
300
-
// Try appropriate fallback, with different strategies for different PDS hosts
301
301
-
if (serviceEndpoint !== 'https://public.api.bsky.app') {
302
302
-
// 1. If we have a servicePds from the PLC directory, try using it directly
303
303
-
if (servicePds) {
304
304
-
try {
305
305
-
console.log(`Trying direct PDS domain: https://${servicePds}`);
306
306
-
307
307
-
// Special case for known working domains
308
308
-
if (handle === 'mackuba.eu') {
309
309
-
console.log('Detected mackuba.eu, using known working endpoint');
310
310
-
try {
311
311
-
const specialUrl = `https://lab.martianbase.net/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
312
312
-
console.log(`Trying special URL: ${specialUrl}`);
313
313
-
314
314
-
const specialResponse = await fetch(specialUrl, {
315
315
-
headers: { 'Accept': 'application/json' }
316
316
-
});
317
317
-
318
318
-
if (specialResponse.ok) {
319
319
-
console.log('Special URL succeeded!');
320
320
-
const specialData = await specialResponse.json();
321
321
-
322
322
-
// Process the special response
323
323
-
const specialEntries = specialData.records
324
324
-
.map((record: any) => {
325
325
-
const text = record.value.text || '';
326
326
-
if (containsBannedWords(text)) return null;
327
327
-
328
328
-
return {
329
329
-
id: record.uri,
330
330
-
uri: record.uri,
331
331
-
cid: record.cid,
332
332
-
did: did,
333
333
-
text: sanitizeText(text),
334
334
-
emoji: record.value.emoji || '🚽',
335
335
-
created_at: record.value.createdAt
336
336
-
};
337
337
-
})
338
338
-
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
339
339
-
340
340
-
// Calculate emoji stats
341
341
-
const specialEmojiCounts = new Map<string, number>();
342
342
-
specialEntries.forEach((entry: ProfileEntry) => {
343
343
-
const emoji = entry.emoji?.trim() || '🚽';
344
344
-
if (APPROVED_EMOJIS.includes(emoji)) {
345
345
-
specialEmojiCounts.set(emoji, (specialEmojiCounts.get(emoji) || 0) + 1);
346
346
-
} else {
347
347
-
specialEmojiCounts.set('🚽', (specialEmojiCounts.get('🚽') || 0) + 1);
348
348
-
}
349
349
-
});
350
350
-
351
351
-
const specialEmojiStats = Array.from(specialEmojiCounts.entries())
352
352
-
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
353
353
-
.sort((a, b) => b.count - a.count);
354
354
-
355
355
-
return NextResponse.json({
356
356
-
entries: specialEntries,
357
357
-
count: specialEntries.length,
358
358
-
cursor: specialData.cursor,
359
359
-
profile: userProfile,
360
360
-
emojiStats: specialEmojiStats,
361
361
-
serviceEndpoint: 'https://lab.martianbase.net',
362
362
-
specialHandling: true
363
363
-
});
364
364
-
} else {
365
365
-
console.warn(`Special URL failed: ${specialResponse.status}`);
366
366
-
}
367
367
-
} catch (specialErr) {
368
368
-
console.error(`Error with special URL: ${specialErr}`);
369
369
-
}
370
370
-
}
371
371
-
372
372
-
// Some PDS services have different URL structures
373
373
-
const urls = [
374
374
-
`https://${servicePds}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`,
375
375
-
// Try without /xrpc prefix in case it's already in the hostname
376
376
-
`https://${servicePds}/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`,
377
377
-
// Try explicit lab subdomain if we're using martianbase.net
378
378
-
...(servicePds.includes('martianbase.net') ?
379
379
-
[`https://lab.martianbase.net/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`] :
380
380
-
[])
381
381
-
];
382
382
-
383
383
-
// Start with the most common pattern
384
384
-
let pdsDirectResponse: Response | null = null;
385
385
-
let pdsDirectUrl = '';
386
386
-
let directData: any = null;
387
387
-
let succeeded = false;
388
388
-
389
389
-
for (const url of urls) {
390
390
-
try {
391
391
-
console.log(`Attempting URL: ${url}`);
392
392
-
pdsDirectUrl = url;
393
393
-
pdsDirectResponse = await fetch(url, {
394
394
-
headers: { 'Accept': 'application/json' }
395
395
-
});
396
396
-
397
397
-
if (pdsDirectResponse.ok) {
398
398
-
console.log(`Success with URL: ${url}`);
399
399
-
directData = await pdsDirectResponse.json();
400
400
-
succeeded = true;
401
401
-
break;
402
402
-
} else {
403
403
-
console.warn(`Failed with URL ${url}: ${pdsDirectResponse?.status || 'unknown status'}`);
404
404
-
}
405
405
-
} catch (urlErr) {
406
406
-
console.error(`Error trying URL ${url}: ${urlErr}`);
407
407
-
}
408
408
-
}
409
409
-
410
410
-
if (succeeded && directData) {
411
411
-
console.log(`Successfully accessed records directly from PDS domain: ${servicePds}`);
412
412
-
413
413
-
// Process the direct response
414
414
-
const directEntries = directData.records
415
415
-
.map((record: any) => {
416
416
-
const text = record.value.text || '';
417
417
-
if (containsBannedWords(text)) return null;
418
418
-
419
419
-
return {
420
420
-
id: record.uri,
421
421
-
uri: record.uri,
422
422
-
cid: record.cid,
423
423
-
did: did,
424
424
-
text: sanitizeText(text),
425
425
-
emoji: record.value.emoji || '🚽',
426
426
-
created_at: record.value.createdAt
427
427
-
};
428
428
-
})
429
429
-
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
430
430
-
431
431
-
// Calculate emoji stats
432
432
-
const directEmojiCounts = new Map<string, number>();
433
433
-
directEntries.forEach((entry: ProfileEntry) => {
434
434
-
const emoji = entry.emoji?.trim() || '🚽';
435
435
-
if (APPROVED_EMOJIS.includes(emoji)) {
436
436
-
directEmojiCounts.set(emoji, (directEmojiCounts.get(emoji) || 0) + 1);
437
437
-
} else {
438
438
-
directEmojiCounts.set('🚽', (directEmojiCounts.get('🚽') || 0) + 1);
439
439
-
}
295
295
+
// If we have a servicePds from the PLC directory, try using it directly
296
296
+
if (servicePds) {
297
297
+
try {
298
298
+
console.log(`Trying direct PDS domain: https://${servicePds}`);
299
299
+
300
300
+
// Try multiple URL patterns for the PDS domain
301
301
+
const urls = [
302
302
+
`https://${servicePds}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`,
303
303
+
// Try without /xrpc prefix in case it's already in the hostname
304
304
+
`https://${servicePds}/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`
305
305
+
];
306
306
+
307
307
+
let pdsDirectResponse: Response | null = null;
308
308
+
let pdsDirectUrl = '';
309
309
+
let directData: any = null;
310
310
+
let succeeded = false;
311
311
+
312
312
+
for (const url of urls) {
313
313
+
try {
314
314
+
console.log(`Attempting URL: ${url}`);
315
315
+
pdsDirectUrl = url;
316
316
+
pdsDirectResponse = await fetch(url, {
317
317
+
headers: { 'Accept': 'application/json' }
440
318
});
441
319
442
442
-
const directEmojiStats = Array.from(directEmojiCounts.entries())
443
443
-
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
444
444
-
.sort((a, b) => b.count - a.count);
445
445
-
446
446
-
return NextResponse.json({
447
447
-
entries: directEntries,
448
448
-
count: directEntries.length,
449
449
-
cursor: directData.cursor,
450
450
-
profile: userProfile,
451
451
-
emojiStats: directEmojiStats,
452
452
-
serviceEndpoint: `https://${servicePds}`,
453
453
-
directPds: true
454
454
-
});
455
455
-
} else if (pdsDirectResponse) {
456
456
-
try {
457
457
-
const errorText = await pdsDirectResponse.text();
458
458
-
console.warn(`PDS direct access failed: ${errorText}`);
459
459
-
} catch (e) {
460
460
-
console.warn(`PDS direct access failed: Could not read response text`);
320
320
+
if (pdsDirectResponse.ok) {
321
321
+
console.log(`Success with URL: ${url}`);
322
322
+
directData = await pdsDirectResponse.json();
323
323
+
succeeded = true;
324
324
+
break;
325
325
+
} else {
326
326
+
console.warn(`Failed with URL ${url}: ${pdsDirectResponse?.status || 'unknown status'}`);
461
327
}
462
462
-
} else {
463
463
-
console.warn(`PDS direct access failed: No valid response`);
328
328
+
} catch (urlErr) {
329
329
+
console.error(`Error trying URL ${url}: ${urlErr}`);
464
330
}
465
465
-
} catch (pdsErr) {
466
466
-
console.error(`Error with direct PDS domain access: ${pdsErr}`);
467
331
}
468
468
-
}
469
469
-
470
470
-
// 2. For third-party domain handles, try using the handle's domain
471
471
-
if (handle.includes('.') && !handle.endsWith('public.api.bsky.app') && !handle.endsWith('flushes.app') && !handle.endsWith('flushing.im')) {
472
472
-
const domain = handle.split('.').slice(1).join('.');
473
473
-
try {
474
474
-
console.log(`Trying handle domain access: https://${domain}`);
332
332
+
333
333
+
if (succeeded && directData) {
334
334
+
console.log(`Successfully accessed records directly from PDS domain: ${servicePds}`);
475
335
476
476
-
// Try multiple URL patterns for handle domain
477
477
-
const urls = [
478
478
-
`https://${domain}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`,
479
479
-
`https://${domain}/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`
480
480
-
];
481
481
-
482
482
-
let domainResponse: Response | null = null;
483
483
-
let domainData: any = null;
484
484
-
let succeeded = false;
485
485
-
486
486
-
for (const url of urls) {
487
487
-
try {
488
488
-
console.log(`Attempting URL: ${url}`);
489
489
-
domainResponse = await fetch(url, {
490
490
-
headers: { 'Accept': 'application/json' }
491
491
-
});
336
336
+
// Process the direct response
337
337
+
const directEntries = directData.records
338
338
+
.map((record: any) => {
339
339
+
const text = record.value.text || '';
340
340
+
if (containsBannedWords(text)) return null;
492
341
493
493
-
if (domainResponse.ok) {
494
494
-
console.log(`Success with URL: ${url}`);
495
495
-
domainData = await domainResponse.json();
496
496
-
succeeded = true;
497
497
-
break;
498
498
-
} else {
499
499
-
console.warn(`Failed with URL ${url}: ${domainResponse?.status || 'unknown status'}`);
500
500
-
}
501
501
-
} catch (urlErr) {
502
502
-
console.error(`Error trying URL ${url}: ${urlErr}`);
503
503
-
}
504
504
-
}
342
342
+
return {
343
343
+
id: record.uri,
344
344
+
uri: record.uri,
345
345
+
cid: record.cid,
346
346
+
did: did,
347
347
+
text: sanitizeText(text),
348
348
+
emoji: record.value.emoji || '🚽',
349
349
+
created_at: record.value.createdAt
350
350
+
};
351
351
+
})
352
352
+
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
505
353
506
506
-
if (succeeded && domainData) {
507
507
-
console.log(`Successfully accessed records from handle domain: ${domain}`);
508
508
-
509
509
-
// Process the domain response
510
510
-
const domainEntries = domainData.records
511
511
-
.map((record: any) => {
512
512
-
const text = record.value.text || '';
513
513
-
if (containsBannedWords(text)) return null;
514
514
-
515
515
-
return {
516
516
-
id: record.uri,
517
517
-
uri: record.uri,
518
518
-
cid: record.cid,
519
519
-
did: did,
520
520
-
text: sanitizeText(text),
521
521
-
emoji: record.value.emoji || '🚽',
522
522
-
created_at: record.value.createdAt
523
523
-
};
524
524
-
})
525
525
-
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null);
526
526
-
527
527
-
// Calculate emoji stats
528
528
-
const domainEmojiCounts = new Map<string, number>();
529
529
-
domainEntries.forEach((entry: ProfileEntry) => {
530
530
-
const emoji = entry.emoji?.trim() || '🚽';
531
531
-
if (APPROVED_EMOJIS.includes(emoji)) {
532
532
-
domainEmojiCounts.set(emoji, (domainEmojiCounts.get(emoji) || 0) + 1);
533
533
-
} else {
534
534
-
domainEmojiCounts.set('🚽', (domainEmojiCounts.get('🚽') || 0) + 1);
535
535
-
}
536
536
-
});
537
537
-
538
538
-
const domainEmojiStats = Array.from(domainEmojiCounts.entries())
539
539
-
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
540
540
-
.sort((a, b) => b.count - a.count);
541
541
-
542
542
-
return NextResponse.json({
543
543
-
entries: domainEntries,
544
544
-
count: domainEntries.length,
545
545
-
cursor: domainData.cursor,
546
546
-
profile: userProfile,
547
547
-
emojiStats: domainEmojiStats,
548
548
-
serviceEndpoint: `https://${domain}`,
549
549
-
handleDomain: true
550
550
-
});
551
551
-
} else if (domainResponse) {
552
552
-
try {
553
553
-
const errorText = await domainResponse.text();
554
554
-
console.warn(`Handle domain access failed: ${errorText}`);
555
555
-
} catch (e) {
556
556
-
console.warn(`Handle domain access failed: Could not read response text`);
354
354
+
// Calculate emoji stats
355
355
+
const directEmojiCounts = new Map<string, number>();
356
356
+
directEntries.forEach((entry: ProfileEntry) => {
357
357
+
const emoji = entry.emoji?.trim() || '🚽';
358
358
+
if (APPROVED_EMOJIS.includes(emoji)) {
359
359
+
directEmojiCounts.set(emoji, (directEmojiCounts.get(emoji) || 0) + 1);
360
360
+
} else {
361
361
+
directEmojiCounts.set('🚽', (directEmojiCounts.get('🚽') || 0) + 1);
557
362
}
558
558
-
} else {
559
559
-
console.warn(`Handle domain access failed: No valid response`);
560
560
-
}
561
561
-
} catch (domainErr) {
562
562
-
console.error(`Error with handle domain access: ${domainErr}`);
563
563
-
}
564
564
-
}
565
565
-
566
566
-
// 3. Last resort: try public API
567
567
-
console.warn(`All direct approaches failed, trying public API fallback`);
568
568
-
const fallbackUrl = `https://public.api.bsky.app/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent(FLUSHING_STATUS_NSID)}&limit=${MAX_ENTRIES}`;
569
569
-
570
570
-
const fallbackResponse = await fetch(fallbackUrl, {
571
571
-
headers: {
572
572
-
'Accept': 'application/json'
573
573
-
}
574
574
-
});
575
575
-
576
576
-
if (!fallbackResponse.ok) {
577
577
-
return NextResponse.json(
578
578
-
{ error: `Failed to fetch records: ${fallbackResponse.statusText}` },
579
579
-
{ status: fallbackResponse.status }
580
580
-
);
581
581
-
}
582
582
-
583
583
-
const fallbackData = await fallbackResponse.json();
584
584
-
585
585
-
// Transform the records into our format and filter out banned content
586
586
-
const transformedEntries = fallbackData.records
587
587
-
.map((record: any) => {
588
588
-
const text = record.value.text || '';
363
363
+
});
589
364
590
590
-
// Skip entries with banned content
591
591
-
if (containsBannedWords(text)) {
592
592
-
return null;
593
593
-
}
365
365
+
const directEmojiStats = Array.from(directEmojiCounts.entries())
366
366
+
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
367
367
+
.sort((a, b) => b.count - a.count);
594
368
595
595
-
return {
596
596
-
id: record.uri,
597
597
-
uri: record.uri,
598
598
-
cid: record.cid,
599
599
-
did: did,
600
600
-
text: sanitizeText(text), // Sanitize text
601
601
-
emoji: record.value.emoji || '🚽',
602
602
-
created_at: record.value.createdAt
603
603
-
};
604
604
-
})
605
605
-
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null); // Remove filtered entries
606
606
-
607
607
-
// Calculate emoji statistics
608
608
-
const emojiCounts = new Map<string, number>();
609
609
-
610
610
-
// Process entries to count emojis
611
611
-
transformedEntries.forEach((entry: ProfileEntry) => {
612
612
-
if (entry.emoji) {
613
613
-
// Default to toilet emoji if empty
614
614
-
const emoji = entry.emoji.trim() || '🚽';
615
615
-
// Only count approved emojis
616
616
-
if (APPROVED_EMOJIS.includes(emoji)) {
617
617
-
emojiCounts.set(emoji, (emojiCounts.get(emoji) || 0) + 1);
618
618
-
} else {
619
619
-
// Count as default toilet emoji if not approved
620
620
-
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
369
369
+
return NextResponse.json({
370
370
+
entries: directEntries,
371
371
+
count: directEntries.length,
372
372
+
cursor: directData.cursor,
373
373
+
profile: userProfile,
374
374
+
emojiStats: directEmojiStats,
375
375
+
serviceEndpoint: `https://${servicePds}`,
376
376
+
directPds: true
377
377
+
});
378
378
+
} else if (pdsDirectResponse) {
379
379
+
try {
380
380
+
const errorText = await pdsDirectResponse.text();
381
381
+
console.warn(`PDS direct access failed: ${errorText}`);
382
382
+
} catch (e) {
383
383
+
console.warn(`PDS direct access failed: Could not read response text`);
621
384
}
622
385
} else {
623
623
-
// Count default toilet emoji if no emoji specified
624
624
-
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
386
386
+
console.warn(`PDS direct access failed: No valid response`);
625
387
}
626
626
-
});
627
627
-
628
628
-
// Convert to array and sort by count (most popular first)
629
629
-
const emojiStats = Array.from(emojiCounts.entries())
630
630
-
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
631
631
-
.sort((a, b) => b.count - a.count);
632
632
-
633
633
-
return NextResponse.json({
634
634
-
entries: transformedEntries,
635
635
-
count: transformedEntries.length,
636
636
-
cursor: fallbackData.cursor,
637
637
-
profile: userProfile,
638
638
-
emojiStats,
639
639
-
serviceEndpoint: 'https://public.api.bsky.app', // Record which endpoint was used
640
640
-
fallback: true
641
641
-
});
388
388
+
} catch (pdsErr) {
389
389
+
console.error(`Error with direct PDS domain access: ${pdsErr}`);
390
390
+
}
642
391
}
643
392
393
393
+
// If all attempts fail, return error
644
394
return NextResponse.json(
645
395
{ error: `Failed to fetch records: ${recordsResponse.statusText}` },
646
396
{ status: recordsResponse.status }
···
696
446
const emojiStats = Array.from(emojiCounts.entries())
697
447
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
698
448
.sort((a, b) => b.count - a.count);
699
699
-
700
449
701
450
return NextResponse.json({
702
451
entries: transformedEntries,