This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix record fetching on profiles

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