view who was fronting when a record was made

feat: show fronters on reposted by

ptr.pet 7073cc29 c81d81d5

verified
+1
env.d.ts
···
/// <reference types="@atcute/atproto" />
+
/// <reference types="@atcute/bluesky" />
+1
package.json
···
},
"dependencies": {
"@atcute/atproto": "^3.1.1",
+
"@atcute/bluesky": "^3.2.2",
"@atcute/client": "^4.0.3",
"@atcute/identity": "^1.0.3",
"@atcute/identity-resolver": "^1.1.3",
+11
pnpm-lock.yaml
···
'@atcute/atproto':
specifier: ^3.1.1
version: 3.1.3
+
'@atcute/bluesky':
+
specifier: ^3.2.2
+
version: 3.2.2
'@atcute/client':
specifier: ^4.0.3
version: 4.0.3
···
'@atcute/atproto@3.1.3':
resolution: {integrity: sha512-+5u0l+8E7h6wZO7MM1HLXIPoUEbdwRtr28ZRTgsURp+Md9gkoBj9e5iMx/xM8F2Exfyb65J5RchW/WlF2mw/RQ==}
+
+
'@atcute/bluesky@3.2.2':
+
resolution: {integrity: sha512-L8RrMNeRLGvSHMq2KDIAGXrpuNGA87YOXpXHY1yhmovVCjQ5n55FrR6JoQaxhprdXdKKQiefxNwQQQybDrfgFQ==}
'@atcute/client@4.0.3':
resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==}
···
'@atcute/atproto@3.1.3':
dependencies:
+
'@atcute/lexicons': 1.1.1
+
+
'@atcute/bluesky@3.2.2':
+
dependencies:
+
'@atcute/atproto': 3.1.3
'@atcute/lexicons': 1.1.1
'@atcute/client@4.0.3':
+109 -64
src/entrypoints/background.ts
···
import {
type Fronter,
fronterGetSocialAppHrefs,
-
fronterGetSocialAppHref,
getFronter,
getSpFronters,
-
memberUriString,
putFronter,
frontersCache,
parseSocialAppPostUrl,
displayNameCache,
deleteFronter,
getPkFronters,
+
FronterType,
+
FronterView,
} from "@/lib/utils";
import {
-
parseCanonicalResourceUri,
+
ComAtprotoRepoApplyWrites,
+
ComAtprotoRepoCreateRecord,
+
} from "@atcute/atproto";
+
import { createResultSchema } from "@atcute/atproto/types/repo/applyWrites";
+
import { feedViewPostSchema } from "@atcute/bluesky/types/app/feed/defs";
+
import {
+
InferOutput,
+
is,
parseResourceUri,
ResourceUri,
+
safeParse,
} from "@atcute/lexicons";
+
import { AtprotoDid, parseCanonicalResourceUri } from "@atcute/lexicons/syntax";
export default defineBackground({
persistent: true,
···
}
// dont write if no names is specified or no sp/pk fronters are fetched
if (members.length === 0) return;
-
const results = [];
+
const results: FronterView[] = [];
for (const result of items) {
const resp = await putFronter(result.uri, members, authToken);
if (resp.ok) {
const parsedUri = await cacheFronter(result.uri, resp.value);
results.push({
+
type:
+
parsedUri.collection === "app.bsky.feed.repost"
+
? "repost"
+
: parsedUri.collection === "app.bsky.feed.like"
+
? "like"
+
: "post",
rkey: parsedUri.rkey!,
...resp.value,
});
···
type: "TIMELINE_FRONTER",
results: Object.fromEntries(
results.flatMap((fronter) =>
-
fronterGetSocialAppHrefs(fronter, fronter.rkey).map((href) => [
-
href,
-
fronter,
-
]),
+
fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]),
),
),
});
···
feed: any[],
sender: globalThis.Browser.runtime.MessageSender,
) => {
-
const handlePost = async (post: any) => {
-
const cachedFronter = await frontersCache.get(post.uri);
-
if (cachedFronter === null) return;
-
const promise = cachedFronter
-
? Promise.resolve(cachedFronter)
-
: getFronter(post.uri).then(async (fronter) => {
-
if (!fronter.ok) {
-
await frontersCache.set(post.uri, null);
-
return;
-
}
-
return fronter.value;
-
});
-
return promise.then(async (fronter) => {
-
if (!fronter) return;
-
const parsedUri = await cacheFronter(post.uri, fronter);
-
return {
-
rkey: parsedUri.rkey!,
-
...fronter,
+
const allPromises = feed.flatMap(
+
(item): Promise<FronterView | undefined>[] => {
+
if (!is(feedViewPostSchema, item)) return [];
+
const handleUri = async (
+
uri: ResourceUri,
+
type: "repost" | "post",
+
) => {
+
const cachedFronter = await frontersCache.get(uri);
+
if (cachedFronter === null) return;
+
const promise = cachedFronter
+
? Promise.resolve(cachedFronter)
+
: getFronter(uri).then(async (fronter) => {
+
if (!fronter.ok) {
+
await frontersCache.set(uri, null);
+
return;
+
}
+
return fronter.value;
+
});
+
return await promise.then(
+
async (fronter): Promise<FronterView | undefined> => {
+
if (!fronter) return;
+
if (type === "repost") {
+
const parsedPostUri = expect(
+
parseCanonicalResourceUri(item.post.uri),
+
);
+
fronter = {
+
subject: {
+
did: parsedPostUri.repo as AtprotoDid,
+
rkey: parsedPostUri.rkey,
+
handle:
+
item.post.author.handle === "handle.invalid"
+
? undefined
+
: item.post.author.handle,
+
},
+
...fronter,
+
};
+
}
+
const parsedUri = await cacheFronter(uri, fronter);
+
return {
+
type,
+
rkey: parsedUri.rkey!,
+
...fronter,
+
};
+
},
+
);
};
-
});
-
};
-
const allPromises = feed.flatMap((item) => {
-
const promises = [handlePost(item.post)];
-
if (item.reply?.parent) {
-
promises.push(handlePost(item.reply.parent));
-
}
-
if (item.reply?.root) {
-
promises.push(handlePost(item.reply.root));
-
}
-
return promises;
-
});
+
const promises: ReturnType<typeof handleUri>[] = [];
+
promises.push(handleUri(item.post.uri, "post"));
+
if (item.reply?.parent) {
+
promises.push(handleUri(item.reply.parent.uri, "post"));
+
}
+
if (item.reply?.root) {
+
promises.push(handleUri(item.reply.root.uri, "post"));
+
}
+
if (
+
item.reason &&
+
item.reason.$type === "app.bsky.feed.defs#reasonRepost" &&
+
item.reason.uri
+
) {
+
promises.push(handleUri(item.reason.uri, "repost"));
+
}
+
return promises;
+
},
+
);
const results = new Map(
(await Promise.allSettled(allPromises))
.filter((result) => result.status === "fulfilled")
.flatMap((result) => result.value ?? [])
.flatMap((fronter) =>
-
fronterGetSocialAppHrefs(fronter, fronter.rkey).map((href) => [
-
href,
-
fronter,
-
]),
+
fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]),
),
);
if (results.size === 0) return;
···
}
return fronter.value;
});
-
return promise.then(async (fronter): Promise<any> => {
-
if (!fronter) return;
-
const parsedUri = await cacheFronter(item.uri, fronter);
-
if (isReplyThreadFetch)
+
return promise.then(
+
async (fronter): Promise<FronterView | undefined> => {
+
if (!fronter) return;
+
const parsedUri = await cacheFronter(item.uri, fronter);
+
if (isReplyThreadFetch)
+
return {
+
type: "thread_reply",
+
rkey: parsedUri.rkey!,
+
...fronter,
+
};
+
if (item.depth === 0) await setTabFronter(item.uri, fronter);
+
const displayName = item.value.post.author.displayName;
+
// cache display name for later use
+
if (fronter.handle)
+
await displayNameCache.set(fronter.handle, displayName);
+
await displayNameCache.set(fronter.did, displayName);
return {
+
type: "thread_post",
rkey: parsedUri.rkey!,
+
displayName,
+
depth: item.depth,
...fronter,
};
-
if (item.depth === 0) await setTabFronter(item.uri, fronter);
-
const displayName = item.value.post.author.displayName;
-
// cache display name for later use
-
if (fronter.handle)
-
await displayNameCache.set(fronter.handle, displayName);
-
await displayNameCache.set(fronter.did, displayName);
-
return {
-
rkey: parsedUri.rkey!,
-
displayName,
-
depth: item.depth,
-
...fronter,
-
};
-
});
+
},
+
);
});
});
const results = new Map(
···
.filter((result) => result.status === "fulfilled")
.flatMap((result) => result.value ?? [])
.flatMap((fronter) =>
-
fronterGetSocialAppHrefs(fronter, fronter.rkey, fronter.depth).map(
-
(href) => [href, fronter],
-
),
+
fronterGetSocialAppHrefs(fronter).map((href) => [href, fronter]),
),
);
if (results.size === 0) return;
···
break;
case "write":
await handleWrite(
-
JSON.parse(message.data.body).results,
+
JSON.parse(message.data.body),
message.data.authToken,
sender,
);
break;
-
case "writeOne":
+
case "writeOne": {
await handleWrite(
[JSON.parse(message.data.body)],
message.data.authToken,
sender,
);
break;
+
}
case "posts":
await handleTimeline(
(JSON.parse(message.data.body) as any[]).map((post) => ({ post })),
+72 -30
src/entrypoints/content.ts
···
-
import { decodeStorageKey } from "@/lib/cache";
-
import { expect } from "@/lib/result";
-
import {
-
Fronter,
-
fronterGetSocialAppHref,
-
fronterGetSocialAppHrefs,
-
parseSocialAppPostUrl,
-
} from "@/lib/utils";
-
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
+
import { FronterView, parseSocialAppPostUrl } from "@/lib/utils";
const getAuthHeader = (headers: any): string | null => {
if (headers instanceof Headers) {
···
});
respEventSetup.then((name) => (respEventName = name));
-
const applyFronterName = (el: Element, fronters: Fronter["members"]) => {
-
if (el.hasAttribute("data-fronter")) return;
+
const applyFronterName = (
+
el: Element,
+
fronters: FronterView["members"],
+
) => {
+
if (el.hasAttribute("data-fronter")) return false;
const s = fronters.map((f) => f.name).join(", ");
el.textContent += ` [f: ${s}]`;
el.setAttribute("data-fronter", s);
+
return true;
};
const applyFrontersToPage = (
-
fronters: Map<string, any>,
+
fronters: Map<string, FronterView | null>,
pageChange: boolean,
) => {
// console.log("applyFrontersToPage", fronters);
···
);
for (const el of document.querySelectorAll("[data-fronter]")) {
const previousFronter = el.getAttribute("data-fronter")!;
-
// remove fronter text
-
el.textContent = el.textContent.replace(
-
` [f: ${previousFronter}]`,
-
"",
-
);
+
if (previousFronter !== "__set__") {
+
// remove fronter text
+
el.textContent = el.textContent.replace(
+
` [f: ${previousFronter}]`,
+
"",
+
);
+
}
el.removeAttribute("data-fronter");
}
}
console.log("applyFrontersToPage", match, fronters);
if (fronters.size === 0) return;
+
const applyFronterToElement = (el: Element, fronter: FronterView) => {
+
let displayNameElement: Element | null = null;
+
if (fronter.type === "repost") {
+
displayNameElement =
+
el.parentElement?.parentElement?.parentElement?.parentElement
+
?.parentElement?.firstElementChild?.nextElementSibling
+
?.firstElementChild?.nextElementSibling?.firstElementChild
+
?.firstElementChild?.nextElementSibling?.firstElementChild
+
?.firstElementChild?.firstElementChild?.firstElementChild ?? null;
+
// sanity check
+
if (displayNameElement?.tagName !== "SPAN") {
+
console.log(
+
`invalid display element tag ${displayNameElement?.tagName}, expected span:`,
+
displayNameElement,
+
);
+
return;
+
}
+
} else {
+
if (fronter.type === "thread_post" && fronter.depth === 0) {
+
if (match && match.rkey !== fronter.rkey) return;
+
if (el.ariaLabel !== fronter.displayName) return;
+
displayNameElement =
+
el.firstElementChild?.firstElementChild?.firstElementChild
+
?.firstElementChild?.firstElementChild ?? null;
+
// sanity check
+
if (displayNameElement?.tagName !== "DIV") {
+
console.log(
+
`invalid display element tag ${displayNameElement?.tagName}, expected a:`,
+
displayNameElement,
+
);
+
return;
+
}
+
} else {
+
displayNameElement =
+
el.parentElement?.firstElementChild?.firstElementChild
+
?.firstElementChild?.firstElementChild ?? null;
+
// sanity check
+
if (displayNameElement?.tagName !== "A") {
+
console.log(
+
`invalid display element tag ${displayNameElement?.tagName}, expected a:`,
+
displayNameElement,
+
);
+
return;
+
}
+
}
+
}
+
if (!displayNameElement) return;
+
return applyFronterName(displayNameElement, fronter.members);
+
};
for (const el of document.getElementsByTagName("a")) {
+
if (el.getAttribute("data-fronter")) continue;
const path = `/${el.href.split("/").slice(3).join("/")}`;
-
const fronter = fronters.get(path);
-
if (!fronter || fronter.members?.length === 0) continue;
-
if (el.hasAttribute("data-fronter")) continue;
-
const isFocusedPost = fronter.depth === 0;
-
if (isFocusedPost) if (match && match.rkey !== fronter.rkey) continue;
-
if (isFocusedPost) if (el.ariaLabel !== fronter.displayName) continue;
-
const displayNameElement = isFocusedPost
-
? (el.firstElementChild?.firstElementChild?.firstElementChild
-
?.firstElementChild?.firstElementChild ?? null)
-
: (el.parentElement?.firstElementChild?.firstElementChild
-
?.firstElementChild?.firstElementChild ?? null);
-
if (!displayNameElement) continue;
-
applyFronterName(displayNameElement, fronter.members);
+
const elFronters = [fronters.get(path), fronters.get(`${path}#repost`)];
+
for (const fronter of elFronters) {
+
if (!fronter || fronter.members?.length === 0) continue;
+
if (applyFronterToElement(el, fronter)) {
+
el.setAttribute("data-fronter", "__set__");
+
}
+
}
}
};
let postTabObserver: MutationObserver | null = null;
+39 -18
src/entrypoints/isolated.content.ts
···
import {
displayNameCache,
Fronter,
+
fronterGetSocialAppHref,
fronterGetSocialAppHrefs,
frontersCache,
+
FronterView,
parseSocialAppPostUrl,
} from "@/lib/utils";
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
···
});
const updateOnUrlChange = async () => {
const fronters = await frontersCache.getAll();
-
const updated = new Map<string, any>(
-
fronters.entries().flatMap(([storageKey, fronter]) => {
-
if (!fronter) return [];
-
const uri = decodeStorageKey(storageKey);
-
const rkey = expect(parseResourceUri(uri)).rkey!;
-
return fronterGetSocialAppHrefs(fronter, rkey).map((href) => [
-
href,
-
fronter,
-
]);
-
}),
-
);
+
const updated = new Map<string, FronterView | null>();
+
for (const [storageKey, fronter] of fronters.entries()) {
+
const uri = decodeStorageKey(storageKey);
+
const parsedUri = expect(parseResourceUri(uri));
+
if (!fronter) {
+
updated.set(
+
fronterGetSocialAppHref(parsedUri.repo, parsedUri.rkey!),
+
null,
+
);
+
continue;
+
}
+
const view: FronterView = {
+
type:
+
parsedUri.collection === "app.bsky.feed.repost" ? "repost" : "post",
+
rkey: parsedUri.rkey!,
+
...fronter,
+
};
+
for (const href of fronterGetSocialAppHrefs(view)) {
+
updated.set(href, view);
+
}
+
}
// add entry for current page
const match = parseSocialAppPostUrl(document.location.href);
if (match && !updated.has(`/profile/${match.actorIdentifier}`)) {
const maybeFronter = updated.get(
`/profile/${match.actorIdentifier}/post/${match.rkey}`,
);
-
if (maybeFronter)
-
updated.set(`/profile/${match.actorIdentifier}`, {
-
depth: 0,
-
displayName: await displayNameCache.get(match.actorIdentifier),
-
rkey: match.rkey,
-
...maybeFronter,
-
});
+
if (maybeFronter) {
+
const displayName = await displayNameCache.get(match.actorIdentifier);
+
if (displayName) {
+
const view: FronterView = {
+
...maybeFronter,
+
type: "thread_post",
+
depth: 0,
+
displayName,
+
rkey: match.rkey,
+
};
+
updated.set(`/profile/${maybeFronter.did}`, view);
+
if (maybeFronter.handle) {
+
updated.set(`/profile/${maybeFronter.handle}`, view);
+
}
+
}
+
}
}
window.postMessage({
type: "APPLY_CACHED_FRONTERS",
+2 -2
src/entrypoints/popup/App.svelte
···
</div>
<div class="config-note">
<span class="note-text">
-
when set, pulls fronters from PluralKit (fronter
-
history must be public)
+
when set, pulls fronters from PluralKit (fronters
+
must be public)
</span>
</div>
</div>
+40 -9
src/lib/utils.ts
···
import { getAtprotoHandle, getPdsEndpoint } from "@atcute/identity";
import { PersistentCache } from "./cache";
+
export type Subject = {
+
handle?: Handle;
+
did: AtprotoDid;
+
rkey: RecordKey;
+
};
+
export type Fronter = {
members: {
uri?: MemberUri;
···
}[];
handle: Handle | null;
did: AtprotoDid;
+
subject?: Subject;
};
+
+
export type FronterView = Fronter & { rkey: RecordKey } & (
+
| {
+
type: "thread_reply";
+
}
+
| {
+
type: "thread_post";
+
displayName: string;
+
depth: number;
+
}
+
| {
+
type: "post";
+
}
+
| {
+
type: "like";
+
}
+
| {
+
type: "repost";
+
}
+
);
+
export type FronterType = FronterView["type"];
export const fronterSchema = v.record(
v.string(),
···
}));
};
-
export const fronterGetSocialAppHrefs = (
-
fronter: Fronter,
-
rkey: RecordKey,
-
depth?: number,
-
) => {
+
export const fronterGetSocialAppHrefs = (view: FronterView) => {
+
if (view.type === "repost" && view.subject) {
+
const subject = view.subject;
+
const handle = subject?.handle;
+
return [
+
handle ? [`${fronterGetSocialAppHref(handle, subject.rkey)}#repost`] : [],
+
`${fronterGetSocialAppHref(subject.did, subject.rkey)}#repost`,
+
].flat();
+
}
+
const depth = view.type === "thread_post" ? view.depth : undefined;
return [
-
fronter.handle
-
? [fronterGetSocialAppHref(fronter.handle, rkey, depth)]
-
: [],
-
fronterGetSocialAppHref(fronter.did, rkey, depth),
+
view.handle ? [fronterGetSocialAppHref(view.handle, view.rkey, depth)] : [],
+
fronterGetSocialAppHref(view.did, view.rkey, depth),
].flat();
};