+111
src/entrypoints/background.ts
+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
+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
+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;