This repository has no description
0

Configure Feed

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

fix feed

+98 -40
.DS_Store

This is a binary file and will not be displayed.

+62 -16
app/src/app/api/bluesky/feed/route.ts
··· 97 97 // Process and return older entries (skip caching) 98 98 const processedEntries = await Promise.all((entries || []).map(async (entry: FlushingRecord) => { 99 99 const authorDid = entry.did; 100 - const authorHandle = await resolveDidToHandle(authorDid) || 'unknown'; 100 + const authorHandle = await resolveDidToHandle(authorDid); 101 101 102 102 if (containsBannedWords(entry.text)) { 103 103 return null; ··· 158 158 const processedEntries = await Promise.all((entries || []).map(async (entry: FlushingRecord) => { 159 159 // Resolve handle from DID 160 160 const authorDid = entry.did; 161 - const authorHandle = await resolveDidToHandle(authorDid) || 'unknown'; 161 + const authorHandle = await resolveDidToHandle(authorDid); 162 162 163 163 // Skip entries with banned content 164 164 if (containsBannedWords(entry.text)) { ··· 250 250 } 251 251 252 252 // Function to attempt to resolve a DID to a handle 253 - // First tries PLC directory, then falls back to Bluesky API if needed 254 - async function resolveDidToHandle(did: string): Promise<string | null> { 253 + // First tries PLC directory, then tries Bluesky API as a fallback 254 + async function resolveDidToHandle(did: string): Promise<string> { 255 255 try { 256 - // Try PLC directory first (faster and doesn't require auth) 256 + // Create a fallback shortened DID to use if all else fails 257 + const shortDid = did.startsWith('did:plc:') ? 258 + `${did.substring(0, 13)}...` : 259 + `${did.substring(0, 10)}...`; 260 + 261 + // Try PLC directory first - most reliable and doesn't require auth 257 262 if (did && did.startsWith('did:plc:')) { 258 - const plcResponse = await fetch(`https://plc.directory/${did}/data`); 259 - if (plcResponse.ok) { 260 - const plcData = await plcResponse.json(); 261 - if (plcData && plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) { 262 - // alsoKnownAs contains values like 'at://user.bsky.social' 263 - const handle = plcData.alsoKnownAs[0].split('//')[1]; 264 - if (handle) return handle; 263 + try { 264 + console.log(`Trying PLC directory for DID: ${did}`); 265 + const plcResponse = await fetch(`https://plc.directory/${did}`); 266 + 267 + if (plcResponse.ok) { 268 + const plcData = await plcResponse.json(); 269 + if (plcData && plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) { 270 + // alsoKnownAs contains values like 'at://user.bsky.social' 271 + for (const aka of plcData.alsoKnownAs) { 272 + if (aka.startsWith('at://')) { 273 + const handle = aka.split('//')[1]; 274 + if (handle) { 275 + console.log(`Resolved ${did} to handle ${handle} via PLC directory`); 276 + return handle; 277 + } 278 + } 279 + } 280 + } 265 281 } 282 + 283 + // Try alternate PLC directory endpoint format 284 + const altPlcResponse = await fetch(`https://plc.directory/${did}/data`); 285 + if (altPlcResponse.ok) { 286 + const altPlcData = await altPlcResponse.json(); 287 + if (altPlcData && altPlcData.alsoKnownAs && altPlcData.alsoKnownAs.length > 0) { 288 + for (const aka of altPlcData.alsoKnownAs) { 289 + if (aka.startsWith('at://')) { 290 + const handle = aka.split('//')[1]; 291 + if (handle) { 292 + console.log(`Resolved ${did} to handle ${handle} via PLC directory (alternate endpoint)`); 293 + return handle; 294 + } 295 + } 296 + } 297 + } 298 + } 299 + } catch (plcError) { 300 + console.warn(`Failed to resolve handle from PLC directory for DID ${did}:`, plcError); 301 + // Continue to next method 266 302 } 267 303 } 268 304 ··· 270 306 console.log(`Falling back to Bluesky API for DID: ${did}`); 271 307 try { 272 308 // Try to resolve DID directly with Bluesky API 273 - await agent.login({ identifier: 'user.bsky.social', password: 'none' }); 309 + await agent.login({ identifier: '', password: '' }); 274 310 const response = await agent.getProfile({ actor: did }); 275 - return response.data.handle; 311 + if (response?.data?.handle) { 312 + console.log(`Resolved ${did} to handle ${response.data.handle} via Bluesky API`); 313 + return response.data.handle; 314 + } 276 315 } catch (apiError) { 277 316 console.error(`Failed to resolve handle with Bluesky API for DID ${did}:`, apiError); 278 - return null; 279 317 } 318 + 319 + // If we get here, all resolution methods failed 320 + console.log(`All resolution methods failed for ${did}, using shortened DID: ${shortDid}`); 321 + return shortDid; 280 322 } catch (error) { 281 323 console.error(`Failed to resolve handle for DID ${did}:`, error); 282 - return null; 324 + // Last resort fallback is the shortened DID 325 + const shortDid = did.startsWith('did:plc:') ? 326 + `${did.substring(0, 13)}...` : 327 + `${did.substring(0, 10)}...`; 328 + return shortDid; 283 329 } 284 330 }
+15 -15
app/src/app/api/bluesky/search/route.ts
··· 1 1 import { NextRequest, NextResponse } from 'next/server'; 2 - import { BskyAgent } from '@atproto/api'; 3 2 4 3 // Configure this route as dynamic to fix static generation issues 5 4 export const dynamic = 'force-dynamic'; ··· 15 14 return NextResponse.json({ suggestions: [] }, { status: 200 }); 16 15 } 17 16 18 - // Create a Bluesky agent instance 19 - const agent = new BskyAgent({ 20 - service: 'https://bsky.social' 21 - }); 17 + // Make a direct fetch request to the Bluesky API endpoint 18 + const apiUrl = `https://bsky.social/xrpc/app.bsky.actor.searchActorsTypeahead?q=${encodeURIComponent(term)}&limit=5`; 22 19 23 - // The Bluesky API requires login even for public APIs, 24 - // but we can use a fake login with empty credentials for this purpose 25 - await agent.login({ identifier: '', password: '' }); 20 + console.log('Fetching from API:', apiUrl); 26 21 27 - // Make a request to the typeahead API 28 - const response = await agent.app.bsky.actor.searchActorsTypeahead({ 29 - term, 30 - limit: 5 22 + const response = await fetch(apiUrl, { 23 + method: 'GET', 24 + headers: { 25 + 'Accept': 'application/json' 26 + } 31 27 }); 32 28 33 - if (!response.success) { 34 - throw new Error('Failed to fetch user suggestions'); 29 + if (!response.ok) { 30 + const errorText = await response.text(); 31 + console.error('API response error:', response.status, errorText); 32 + throw new Error(`API request failed: ${response.status} ${response.statusText}`); 35 33 } 36 34 35 + const data = await response.json(); 36 + 37 37 // Format the response for the client 38 - const suggestions = response.data.actors.map(actor => ({ 38 + const suggestions = data.actors.map((actor: any) => ({ 39 39 did: actor.did, 40 40 handle: actor.handle, 41 41 displayName: actor.displayName,
+10 -8
app/src/app/auth/login/login.module.css
··· 30 30 max-width: 500px; 31 31 width: 100%; 32 32 padding: 2rem; 33 - background-color: white; 33 + background-color: var(--card-background); 34 34 border-radius: 8px; 35 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 35 + box-shadow: 0 4px 12px var(--shadow-color); 36 36 text-align: center; 37 37 } 38 38 ··· 42 42 } 43 43 44 44 .subtitle { 45 - color: #666; 45 + color: var(--timestamp-color); 46 46 font-size: 1.1rem; 47 47 margin: 0 0 1rem 0; 48 48 font-style: italic; 49 49 } 50 50 51 51 .description { 52 - color: #666; 52 + color: var(--text-color); 53 53 margin-bottom: 1.5rem; 54 54 line-height: 1.5; 55 55 } ··· 62 62 .input { 63 63 flex: 1; 64 64 padding: 0.75rem 1rem; 65 - border: 1px solid #ccc; 65 + border: 1px solid var(--input-border); 66 66 border-right: none; 67 67 border-radius: 4px 0 0 4px; 68 68 font-size: 1rem; 69 + background-color: var(--input-background); 70 + color: var(--text-color); 69 71 } 70 72 71 73 .input:focus { 72 74 outline: none; 73 - border-color: var(--primary-color); 75 + border-color: var(--input-focus-border); 74 76 } 75 77 76 78 .loginButton { ··· 94 96 } 95 97 96 98 .helpText { 97 - color: #888; 99 + color: var(--timestamp-color); 98 100 font-size: 0.9rem; 99 101 margin-top: 0.5rem; 100 102 } ··· 133 135 } 134 136 135 137 .input { 136 - border-right: 1px solid #ccc; 138 + border-right: 1px solid var(--input-border); 137 139 border-radius: 4px; 138 140 margin-bottom: 0.5rem; 139 141 }
+2
app/src/app/page.tsx
··· 461 461 </Link> 462 462 <span className={styles.text}> 463 463 {entry.text ? ( 464 + // Check if handle ends with .is 464 465 entry.authorHandle && entry.authorHandle.endsWith('.is') ? 465 466 // For handles ending with .is, remove the "is" prefix if it exists 466 467 (sanitizeText(entry.text).toLowerCase().startsWith('is ') ? ··· 470 471 // For regular handles, display normal text 471 472 (entry.text.length > 60 ? `${sanitizeText(entry.text.substring(0, 60))}...` : sanitizeText(entry.text)) 472 473 ) : ( 474 + // If no text, show default message 473 475 entry.authorHandle && entry.authorHandle.endsWith('.is') ? 474 476 'flushing' : 'is flushing' 475 477 )}
+9 -1
app/src/components/ProfileSearch.tsx
··· 62 62 ? query.trim().substring(1) 63 63 : query.trim(); 64 64 65 + console.log('Fetching suggestions for:', searchTerm); 65 66 const response = await fetch(`/api/bluesky/search?q=${encodeURIComponent(searchTerm)}`); 67 + 68 + const data = await response.json(); 66 69 67 70 if (response.ok) { 68 - const data = await response.json(); 71 + console.log('Search suggestions:', data.suggestions); 69 72 setSuggestions(data.suggestions); 70 73 setShowSuggestions(true); 74 + } else { 75 + console.error('Search API error:', data.error, data.message); 76 + setSuggestions([]); 77 + setShowSuggestions(true); 71 78 } 72 79 } catch (error) { 73 80 console.error('Failed to fetch suggestions:', error); 81 + setSuggestions([]); 74 82 } finally { 75 83 setLoading(false); 76 84 }