view who was fronting when a record was made

feat: show fronters on reposts and likes stats

ptr.pet 39a13a6f ae26612b

verified
Changed files
+154
src
+111
src/entrypoints/background.ts
··· 421 421 }); 422 422 // console.log("sent thread fronters", results); 423 423 }; 424 + const handleInteractions = async ( 425 + data: any, 426 + sender: globalThis.Browser.runtime.MessageSender, 427 + collection: string, 428 + actors: { did: AtprotoDid; displayName: string }[], 429 + ) => { 430 + const postUri = data.uri as ResourceUri; 431 + const fetchInteractions = async (cursor?: string) => { 432 + const resp = await fetch( 433 + `https://constellation.microcosm.blue/links?target=${postUri}&collection=${collection}&path=.subject.uri&limit=100${cursor ? `&cursor=${cursor}` : ""}`, 434 + ); 435 + if (!resp.ok) return; 436 + const data = await resp.json(); 437 + return { 438 + total: data.total as number, 439 + records: data.linking_records.map( 440 + (record: any) => 441 + `at://${record.did}/${record.collection}/${record.rkey}` as ResourceUri, 442 + ) as ResourceUri[], 443 + cursor: data.cursor as string, 444 + }; 445 + }; 446 + let interactions = await fetchInteractions(); 447 + if (!interactions) return; 448 + let allRecords: (typeof interactions)["records"] = []; 449 + while (allRecords.length < interactions.total) { 450 + allRecords.push(...interactions.records); 451 + if (!interactions.cursor) break; 452 + interactions = await fetchInteractions(interactions.cursor); 453 + if (!interactions) break; 454 + } 455 + 456 + const actorMap = new Map( 457 + actors.map((actor) => [actor.did, actor.displayName]), 458 + ); 459 + const allPromises = allRecords.map( 460 + async (recordUri): Promise<FronterView | undefined> => { 461 + const cachedFronter = await frontersCache.get(recordUri); 462 + let fronter = 463 + (cachedFronter ?? null) || 464 + (await getFronter(recordUri).then((fronter) => { 465 + if (!fronter.ok) { 466 + frontersCache.set(recordUri, null); 467 + return null; 468 + } 469 + return fronter.value; 470 + })); 471 + if (!fronter) return; 472 + const parsedUri = await cacheFronter(recordUri, fronter); 473 + const displayName = 474 + actorMap.get(fronter.did) ?? 475 + (await displayNameCache.get(fronter.did)); 476 + if (!displayName) return; 477 + return { 478 + type: 479 + collection === "app.bsky.feed.repost" 480 + ? "post_repost_entry" 481 + : "post_like_entry", 482 + rkey: parsedUri.rkey!, 483 + displayName, 484 + ...fronter, 485 + }; 486 + }, 487 + ); 488 + 489 + const results = new Map( 490 + (await Promise.allSettled(allPromises)) 491 + .filter((result) => result.status === "fulfilled") 492 + .flatMap((result) => result.value ?? []) 493 + .flatMap((fronter) => 494 + fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]), 495 + ), 496 + ); 497 + if (results.size === 0) return; 498 + browser.tabs.sendMessage(sender.tab?.id!, { 499 + type: "APPLY_FRONTERS", 500 + results: Object.fromEntries(results), 501 + }); 502 + }; 503 + const handleReposts = async ( 504 + data: any, 505 + sender: globalThis.Browser.runtime.MessageSender, 506 + ) => 507 + handleInteractions( 508 + data, 509 + sender, 510 + "app.bsky.feed.repost", 511 + data.repostedBy.map((by: any) => ({ 512 + did: by.did, 513 + displayName: by.displayName, 514 + })), 515 + ); 516 + const handleLikes = async ( 517 + data: any, 518 + sender: globalThis.Browser.runtime.MessageSender, 519 + ) => 520 + handleInteractions( 521 + data, 522 + sender, 523 + "app.bsky.feed.like", 524 + data.likes.map((by: any) => ({ 525 + did: by.actor.did, 526 + displayName: by.actor.displayName, 527 + })), 528 + ); 424 529 425 530 browser.runtime.onMessage.addListener(async (message, sender) => { 426 531 if (message.type !== "RESPONSE_CAPTURED") return; ··· 462 567 break; 463 568 case "notifications": 464 569 await handleNotifications(JSON.parse(message.data.body), sender); 570 + break; 571 + case "reposts": 572 + await handleReposts(JSON.parse(message.data.body), sender); 573 + break; 574 + case "likes": 575 + await handleLikes(JSON.parse(message.data.body), sender); 465 576 break; 466 577 } 467 578 });
+27
src/entrypoints/content.ts
··· 125 125 type: "notifications", 126 126 body, 127 127 }; 128 + } else if (response.url.includes("/xrpc/app.bsky.feed.getLikes")) { 129 + detail = { 130 + type: "likes", 131 + body, 132 + }; 133 + } else if (response.url.includes("/xrpc/app.bsky.feed.getRepostedBy")) { 134 + detail = { 135 + type: "reposts", 136 + body, 137 + }; 128 138 } 129 139 if (detail) { 130 140 sendEvent(detail); ··· 277 287 fronter.did !== actorIdentifier; 278 288 if (isUser) displayNameElement = null; 279 289 } else displayNameElement = null; 290 + } else if ( 291 + fronter.type === "post_repost_entry" || 292 + fronter.type === "post_like_entry" 293 + ) { 294 + // HACK: evil ass way to do this 295 + if (el.ariaLabel !== `View ${fronter.displayName}'s profile`) return; 296 + displayNameElement = 297 + el.firstElementChild?.firstElementChild?.firstElementChild 298 + ?.nextElementSibling?.firstElementChild?.firstElementChild ?? 299 + null; 300 + if (displayNameElement?.tagName !== "DIV") { 301 + console.log( 302 + `invalid display element tag ${displayNameElement?.tagName}, expected div:`, 303 + displayNameElement, 304 + ); 305 + return; 306 + } 280 307 } 281 308 if (!displayNameElement) return; 282 309 return applyFronterName(displayNameElement, fronter.members);
+16
src/lib/utils.ts
··· 68 68 type: "notification"; 69 69 reason: InferOutput<AppBskyNotificationListNotifications.notificationSchema>["reason"]; 70 70 } 71 + | { 72 + type: "post_repost_entry"; 73 + displayName: string; 74 + } 75 + | { 76 + type: "post_like_entry"; 77 + displayName: string; 78 + } 71 79 ); 72 80 export type FronterType = FronterView["type"]; 73 81 ··· 407 415 return [ 408 416 handle ? [`${fronterGetSocialAppHref(handle, subject.rkey)}`] : [], 409 417 `${fronterGetSocialAppHref(subject.did, subject.rkey)}`, 418 + ].flat(); 419 + } else if ( 420 + view.type === "post_repost_entry" || 421 + view.type === "post_like_entry" 422 + ) { 423 + return [ 424 + view.handle ? [`/profile/${view.handle}`] : [], 425 + `/profile/${view.did}`, 410 426 ].flat(); 411 427 } 412 428 const depth = view.type === "thread_post" ? view.depth : undefined;