This repository has no description
0

Configure Feed

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

fixes

+130 -9
+117 -8
src/app/api/bluesky/stats/route.ts
··· 16 16 '1️⃣', '2️⃣', '🟡', '🟤' 17 17 ]; 18 18 19 + // Function to fetch true count from Bluesky API for a specific DID 20 + async function fetchTrueCountFromBluesky(did: string): Promise<number> { 21 + try { 22 + console.log(`Fetching true count for DID: ${did}`); 23 + 24 + // Step 1: Get PDS endpoint from PLC directory 25 + const plcResponse = await fetch(`https://plc.directory/${did}/data`); 26 + 27 + if (!plcResponse.ok) { 28 + console.warn(`Failed to get PLC data for ${did}: ${plcResponse.statusText}`); 29 + return 0; 30 + } 31 + 32 + const plcData = await plcResponse.json(); 33 + const pdsService = plcData.services?.atproto_pds; 34 + 35 + if (!pdsService?.endpoint) { 36 + console.warn(`No PDS service endpoint found for ${did}`); 37 + return 0; 38 + } 39 + 40 + // Extract the hostname from the PDS endpoint 41 + const serviceUrl = new URL(pdsService.endpoint); 42 + const servicePds = serviceUrl.hostname; 43 + const serviceEndpoint = `https://${servicePds}`; 44 + 45 + console.log(`Using PDS endpoint for ${did}: ${serviceEndpoint}`); 46 + 47 + // Step 2: Fetch all records from PDS 48 + let allRecords: any[] = []; 49 + let cursor: string | undefined; 50 + let hasMore = true; 51 + let pageCount = 0; 52 + const maxPages = 100; // Safety limit to prevent infinite loops 53 + 54 + while (hasMore && pageCount < maxPages) { 55 + const listRecordsUrl = `${serviceEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=im.flushing.right.now&limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`; 56 + 57 + try { 58 + const recordsResponse = await fetch(listRecordsUrl, { 59 + headers: { 60 + 'Accept': 'application/json' 61 + }, 62 + // Add timeout to prevent hanging 63 + signal: AbortSignal.timeout(5000) 64 + }); 65 + 66 + if (!recordsResponse.ok) { 67 + // If we get a 404, the collection might be empty 68 + if (recordsResponse.status === 404) { 69 + console.log(`Empty collection for ${did}`); 70 + break; 71 + } 72 + 73 + console.warn(`Failed to fetch records for ${did}: ${recordsResponse.statusText}`); 74 + break; 75 + } 76 + 77 + const recordsData = await recordsResponse.json(); 78 + allRecords = [...allRecords, ...recordsData.records]; 79 + 80 + cursor = recordsData.cursor; 81 + hasMore = !!cursor && recordsData.records.length === 100; 82 + pageCount++; 83 + 84 + console.log(`Fetched page ${pageCount} for ${did}: ${recordsData.records.length} records, total: ${allRecords.length}`); 85 + 86 + } catch (fetchError) { 87 + console.error(`Error fetching records for ${did}:`, fetchError); 88 + break; 89 + } 90 + } 91 + 92 + if (pageCount >= maxPages) { 93 + console.warn(`Reached maximum page limit for ${did}, may have incomplete data`); 94 + } 95 + 96 + console.log(`Total records fetched for ${did}: ${allRecords.length}`); 97 + return allRecords.length; 98 + 99 + } catch (error) { 100 + console.error(`Error fetching true count for ${did}:`, error); 101 + return 0; 102 + } 103 + } 104 + 19 105 export async function GET(request: NextRequest) { 20 106 try { 21 107 // Define the plumber's DID - this is the official plumber account DID 22 - const PLUMBER_DID = 'did:plc:fouf3svmcxzn6bpiw3lgwz22'; 108 + const PLUMBER_DID = 'did:plc:fouf3svmcxzn6bpiw6iyxxg2o3rljw'; 23 109 24 110 // List of DIDs to exclude from leaderboard 25 111 const excludedDids = [ 26 112 PLUMBER_DID, // plumber.flushes.app (formerly plumber.flushing.im) 27 - 'did:plc:fnhrjbkwjiw6iyxxg2o3rljw' // testing.dame.is 113 + 'did:plc:fouf3svmcxzn6bpiw3lgwz22' // testing.dame.is 28 114 ]; 29 115 30 116 // List of handles to exclude from leaderboard (as fallback) ··· 241 327 throw new Error(`Failed to get leaderboard data: ${leaderboardError.message}`); 242 328 } 243 329 244 - // Count flushes by DID 330 + // Count flushes by DID from Supabase (for ranking only) 245 331 const didCounts = new Map<string, number>(); 246 332 247 333 // Special count for the plumber account ··· 261 347 } 262 348 }); 263 349 264 - // Convert to array and sort by count 265 - const leaderboard = Array.from(didCounts.entries()) 266 - .map(([did, count]): {did: string, count: number} => ({ did, count })) 267 - .sort((a, b) => b.count - a.count) 268 - .slice(0, 10); // Get top 10 350 + // Convert to array and sort by count to get top 10 DIDs 351 + const top10Dids = Array.from(didCounts.entries()) 352 + .sort((a, b) => b[1] - a[1]) 353 + .slice(0, 10) 354 + .map(([did]) => did); 355 + 356 + console.log(`Top 10 DIDs from Supabase ranking: ${top10Dids.join(', ')}`); 357 + 358 + // Now fetch true counts from Bluesky API for these top 10 DIDs 359 + console.log('Fetching true counts from Bluesky API for top 10 users...'); 360 + const leaderboardWithTrueCounts = await Promise.all( 361 + top10Dids.map(async (did) => { 362 + const trueCount = await fetchTrueCountFromBluesky(did); 363 + const supabaseCount = didCounts.get(did) || 0; 364 + 365 + console.log(`DID ${did}: Supabase count = ${supabaseCount}, True count = ${trueCount}`); 366 + 367 + return { 368 + did, 369 + count: trueCount, // Use the true count from Bluesky API 370 + supabaseCount // Include for comparison/debugging 371 + }; 372 + }) 373 + ); 374 + 375 + // Sort by true count (descending) in case the order changed 376 + const leaderboard = leaderboardWithTrueCounts 377 + .sort((a, b) => b.count - a.count); 269 378 270 379 // Calculate total unique flushers (count of unique DIDs) 271 380 const totalFlushers = didCounts.size;
+4 -1
src/app/stats/page.tsx
··· 10 10 totalCount: number; 11 11 flushesPerDay: number; 12 12 chartData: { date: string; count: number }[]; 13 - leaderboard: { did: string; count: number; handle?: string }[]; 13 + leaderboard: { did: string; count: number; handle?: string; supabaseCount?: number }[]; 14 14 plumberFlushCount: number; 15 15 totalFlushers: number; 16 16 monthlyActiveFlushers: number; ··· 211 211 {/* Leaderboard */} 212 212 <section className={styles.leaderboardSection}> 213 213 <h2>Top Flushers</h2> 214 + <p className={styles.leaderboardNote}> 215 + 📊 Counts are fetched directly from Bluesky API for accuracy 216 + </p> 214 217 {statsData.leaderboard.length > 0 ? ( 215 218 <div className={styles.leaderboard}> 216 219 <div className={styles.leaderboardHeader}>
+9
src/app/stats/stats.module.css
··· 233 233 text-align: left; 234 234 } 235 235 236 + .leaderboardNote { 237 + color: var(--timestamp-color); 238 + font-size: 0.9rem; 239 + margin-top: -1rem; 240 + margin-bottom: 1.5rem; 241 + text-align: center; 242 + font-style: italic; 243 + } 244 + 236 245 .plumberProfileLink { 237 246 display: inline-block; 238 247 color: var(--primary-color);