💫 add biome and format src files.

This commit is contained in:
kawamataryo
2024-01-13 10:35:23 +09:00
parent 6deacda7bd
commit b9c51dd758
22 changed files with 1067 additions and 669 deletions

View File

@@ -1,9 +1,9 @@
import { AtUri, BskyAgent, type AtpSessionData } from "@atproto/api";
import { AtUri, type AtpSessionData, BskyAgent } from "@atproto/api";
export type BskyLoginParams = {
identifier: string;
password: string;
}
};
export class BskyClient {
private service = "https://bsky.social";
@@ -13,12 +13,15 @@ export class BskyClient {
email: string;
};
agent: BskyAgent;
session = {}
session = {};
private constructor() {
this.agent = new BskyAgent({ service: this.service, persistSession: (evt, session) => {
this.session = session
} });
this.agent = new BskyAgent({
service: this.service,
persistSession: (evt, session) => {
this.session = session;
},
});
}
public static createAgentFromSession(session: AtpSessionData): BskyClient {
@@ -28,7 +31,7 @@ export class BskyClient {
did: session.did,
handle: session.handle,
email: session.email,
}
};
return client;
}
@@ -38,15 +41,15 @@ export class BskyClient {
password,
}: BskyLoginParams): Promise<BskyClient> {
const client = new BskyClient();
const {data} = await client.agent.login({
const { data } = await client.agent.login({
identifier: identifier.replace(/^@/, ""), // if identifier is a handle name, @ is not required
password
password,
});
client.me = {
did: data.did,
handle: data.handle,
email: data.email,
}
};
return client;
}
@@ -66,30 +69,32 @@ export class BskyClient {
public follow = async (subjectDid: string) => {
return await this.agent.follow(subjectDid);
}
};
public unfollow = async (followUri: string) => {
return await this.agent.deleteFollow(followUri);
}
};
public block = async (subjectDid: string) => {
return await this.agent.app.bsky.graph.block.create({
repo: this.me.did,
collection: "app.bsky.graph.block",
},
{
subject: subjectDid,
createdAt: new Date().toISOString(),
})
}
return await this.agent.app.bsky.graph.block.create(
{
repo: this.me.did,
collection: "app.bsky.graph.block",
},
{
subject: subjectDid,
createdAt: new Date().toISOString(),
},
);
};
public unblock = async (blockUri: string) => {
// TODO: unblock is not working. Need to fix it.
const {rkey} = new AtUri(blockUri)
const { rkey } = new AtUri(blockUri);
return await this.agent.app.bsky.graph.block.delete({
repo: this.me.did,
collection: "app.bsky.graph.block",
rkey,
});
}
};
}

View File

@@ -1,53 +1,76 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs"
import { BSKY_USER_MATCH_TYPE } from "./constants"
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import { BSKY_USER_MATCH_TYPE } from "./constants";
type Names = {
accountName: string,
accountNameRemoveUnderscore: string,
displayName: string,
}
accountName: string;
accountNameRemoveUnderscore: string;
displayName: string;
};
export const isSimilarUser = (names: Names, bskyProfile: ProfileView | undefined): {
isSimilar: boolean,
type: typeof BSKY_USER_MATCH_TYPE[keyof typeof BSKY_USER_MATCH_TYPE],
export const isSimilarUser = (
names: Names,
bskyProfile: ProfileView | undefined,
): {
isSimilar: boolean;
type: (typeof BSKY_USER_MATCH_TYPE)[keyof typeof BSKY_USER_MATCH_TYPE];
} => {
if (!bskyProfile) {
return {
isSimilar: false,
type: BSKY_USER_MATCH_TYPE.NONE,
}
};
}
const lowerCaseNames = Object.entries(names).reduce<Names>((acc, [key, value]) => {
acc[key] = value.toLowerCase();
return acc;
}, {} as Names);
const lowerCaseNames = Object.entries(names).reduce<Names>(
(acc, [key, value]) => {
acc[key] = value.toLowerCase();
return acc;
},
{} as Names,
);
const bskyHandle = bskyProfile.handle.toLocaleLowerCase().replace("@", "").split('.')[0];
const bskyHandle = bskyProfile.handle
.toLocaleLowerCase()
.replace("@", "")
.split(".")[0];
if (lowerCaseNames.accountName === bskyHandle || lowerCaseNames.accountNameRemoveUnderscore === bskyHandle) {
if (
lowerCaseNames.accountName === bskyHandle ||
lowerCaseNames.accountNameRemoveUnderscore === bskyHandle
) {
return {
isSimilar: true,
type: BSKY_USER_MATCH_TYPE.HANDLE,
}
};
}
if (lowerCaseNames.displayName === bskyProfile.displayName?.toLocaleLowerCase()) {
if (
lowerCaseNames.displayName === bskyProfile.displayName?.toLocaleLowerCase()
) {
return {
isSimilar: true,
type: BSKY_USER_MATCH_TYPE.DISPLAY_NAME,
}
};
}
if (bskyProfile.description?.toLocaleLowerCase().includes(`@${lowerCaseNames.accountName}`) && !['pfp ', 'pfp: ', 'pfp by '].some(t => bskyProfile.description.toLocaleLowerCase().includes(`${t}@${lowerCaseNames.accountName}`))) {
if (
bskyProfile.description
?.toLocaleLowerCase()
.includes(`@${lowerCaseNames.accountName}`) &&
!["pfp ", "pfp: ", "pfp by "].some((t) =>
bskyProfile.description
.toLocaleLowerCase()
.includes(`${t}@${lowerCaseNames.accountName}`),
)
) {
return {
isSimilar: true,
type: BSKY_USER_MATCH_TYPE.DESCRIPTION,
}
};
}
return {
isSimilar: false,
type: BSKY_USER_MATCH_TYPE.NONE,
}
}
};
};

View File

@@ -3,13 +3,12 @@ import { sendToBackground } from "@plasmohq/messaging";
export type BskyLoginParams = {
identifier: string;
password: string;
}
};
export class BskyServiceWorkerClient {
private session = {}
private session = {};
private constructor() {
}
private constructor() {}
public static async createAgent({
identifier,
@@ -21,11 +20,11 @@ export class BskyServiceWorkerClient {
body: {
identifier,
password,
}
})
if(error) throw new Error(error.message)
},
});
if (error) throw new Error(error.message);
client.session = session
client.session = session;
return client;
}
@@ -42,9 +41,9 @@ export class BskyServiceWorkerClient {
session: this.session,
term,
limit,
}
})
if(error) throw new Error(error.message)
},
});
if (error) throw new Error(error.message);
return actors;
};
@@ -54,39 +53,39 @@ export class BskyServiceWorkerClient {
name: "follow",
body: {
session: this.session,
subjectDid
}
})
if(error) throw new Error(error.message)
subjectDid,
},
});
if (error) throw new Error(error.message);
return result;
}
};
public unfollow = async (followUri: string) => {
const { result, error } = await sendToBackground({
name: "unfollow",
body: {
session: this.session,
followUri
}
})
if(error) throw new Error(error.message)
followUri,
},
});
if (error) throw new Error(error.message);
return result;
}
};
public block = async (subjectDid: string) => {
const { result, error } = await sendToBackground({
name: "block",
body: {
session: this.session,
subjectDid
}
})
if(error) throw new Error(error.message)
subjectDid,
},
});
if (error) throw new Error(error.message);
return result;
}
};
public unblock = async (blockUri: string) => {
// TODO: unblock is not working. Need to fix it.
@@ -94,11 +93,11 @@ export class BskyServiceWorkerClient {
name: "unblock",
body: {
session: this.session,
blockUri
}
})
if(error) throw new Error(error.message)
blockUri,
},
});
if (error) throw new Error(error.message);
return result;
}
};
}

View File

@@ -1,110 +1,145 @@
import type { ProfileView, ViewerState } from "@atproto/api/dist/client/types/app/bsky/actor/defs"
import { P, match } from "ts-pattern"
import van from 'vanjs-core'
import { BSKY_USER_MATCH_TYPE } from "~lib/constants"
import type {
ProfileView,
ViewerState,
} from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import { P, match } from "ts-pattern";
import van from "vanjs-core";
import { BSKY_USER_MATCH_TYPE } from "~lib/constants";
const { a, div, p, img, button, span } = van.tags
const { svg, path } = van.tagsNS("http://www.w3.org/2000/svg")
const { a, div, p, img, button, span } = van.tags;
const { svg, path } = van.tagsNS("http://www.w3.org/2000/svg");
export type UserCellBtnLabel = {
add: string,
remove: string,
progressive: string,
}
add: string;
remove: string;
progressive: string;
};
const ActionButton = ({ statusKey, profile, btnLabel, addAction, removeAction }: {
profile: ProfileView,
statusKey: keyof ViewerState,
btnLabel: UserCellBtnLabel,
addAction: () => Promise<void>,
removeAction: () => Promise<void>
const ActionButton = ({
statusKey,
profile,
btnLabel,
addAction,
removeAction,
}: {
profile: ProfileView;
statusKey: keyof ViewerState;
btnLabel: UserCellBtnLabel;
addAction: () => Promise<void>;
removeAction: () => Promise<void>;
}) => {
const label = van.state(`${profile.viewer[statusKey] ? btnLabel.progressive : btnLabel.add} on Bluesky`)
const label = van.state(
`${
profile.viewer[statusKey] ? btnLabel.progressive : btnLabel.add
} on Bluesky`,
);
const isStateOfBeing = van.state(profile.viewer[statusKey])
const isProcessing = van.state(false)
const isJustApplied = van.state(false)
const isStateOfBeing = van.state(profile.viewer[statusKey]);
const isProcessing = van.state(false);
const isJustApplied = van.state(false);
const beingClass = van.derive(() => isStateOfBeing.val ? "action-button__being" : "")
const processingClass = van.derive(() => isProcessing.val ? "action-button__processing" : "")
const justAppliedClass = van.derive(() => isJustApplied.val ? "action-button__just-applied" : "")
const beingClass = van.derive(() =>
isStateOfBeing.val ? "action-button__being" : "",
);
const processingClass = van.derive(() =>
isProcessing.val ? "action-button__processing" : "",
);
const justAppliedClass = van.derive(() =>
isJustApplied.val ? "action-button__just-applied" : "",
);
const onClick = async () => {
if (isProcessing.val) return
isProcessing.val = true
label.val = "Processing..."
if (isProcessing.val) return;
isProcessing.val = true;
label.val = "Processing...";
if (isStateOfBeing.val) {
await removeAction()
label.val = `${btnLabel.add} on Bluesky`
isStateOfBeing.val = false
await removeAction();
label.val = `${btnLabel.add} on Bluesky`;
isStateOfBeing.val = false;
} else {
await addAction()
label.val = `${btnLabel.progressive} on Bluesky`
isStateOfBeing.val = true
isJustApplied.val = true
await addAction();
label.val = `${btnLabel.progressive} on Bluesky`;
isStateOfBeing.val = true;
isJustApplied.val = true;
}
isProcessing.val = false
}
isProcessing.val = false;
};
const onMouseover = () => {
if (
isProcessing.val ||
isJustApplied.val ||
!isStateOfBeing.val
) return
if (isProcessing.val || isJustApplied.val || !isStateOfBeing.val) return;
label.val = `${btnLabel.remove} on Bluesky`
}
label.val = `${btnLabel.remove} on Bluesky`;
};
const onMouseout = () => {
if (isJustApplied.val) {
isJustApplied.val = false
isJustApplied.val = false;
}
if (!isStateOfBeing.val) return
if (!isStateOfBeing.val) return;
label.val = `${btnLabel.progressive} on Bluesky`
}
label.val = `${btnLabel.progressive} on Bluesky`;
};
return button({
class: () => `action-button ${beingClass.val} ${processingClass.val} ${justAppliedClass.val}`,
onclick: onClick,
onmouseover: onMouseover,
onmouseout: onMouseout,
},
return button(
{
class: () =>
`action-button ${beingClass.val} ${processingClass.val} ${justAppliedClass.val}`,
onclick: onClick,
onmouseover: onMouseover,
onmouseout: onMouseout,
},
() => label.val,
)
}
);
};
const Avatar = ({ avatar }: { avatar?: string }) => {
return avatar ? img({ src: avatar, width: "40" }) : div({ class: "no-avatar" })
}
return avatar
? img({ src: avatar, width: "40" })
: div({ class: "no-avatar" });
};
const MatchTypeLabel = ({ matchType }: { matchType: typeof BSKY_USER_MATCH_TYPE[keyof typeof BSKY_USER_MATCH_TYPE] }) => {
const MatchTypeLabel = ({
matchType,
}: {
matchType: (typeof BSKY_USER_MATCH_TYPE)[keyof typeof BSKY_USER_MATCH_TYPE];
}) => {
const [text, labelClass] = match(matchType)
.with(
BSKY_USER_MATCH_TYPE.HANDLE,
() => ["Same handle name", "match-type__handle"]
)
.with(
BSKY_USER_MATCH_TYPE.DISPLAY_NAME,
() => ["Same display name", "match-type__display-name"]
)
.with(
BSKY_USER_MATCH_TYPE.DESCRIPTION,
() => ["Included handle name in description", "match-type__description"]
)
.run()
.with(BSKY_USER_MATCH_TYPE.HANDLE, () => [
"Same handle name",
"match-type__handle",
])
.with(BSKY_USER_MATCH_TYPE.DISPLAY_NAME, () => [
"Same display name",
"match-type__display-name",
])
.with(BSKY_USER_MATCH_TYPE.DESCRIPTION, () => [
"Included handle name in description",
"match-type__description",
])
.run();
return div({ class: `match-type ${labelClass}` },
svg({ fill: "none", width: "12", viewBox: "0 0 24 24", "stroke-width": "3", stroke: "currentColor", class: "w-6 h-6" },
path({ "stroke-linecap": "round", "stroke-linejoin": "round", "d": "M4.5 12.75l6 6 9-13.5" }),
return div(
{ class: `match-type ${labelClass}` },
svg(
{
fill: "none",
width: "12",
viewBox: "0 0 24 24",
"stroke-width": "3",
stroke: "currentColor",
class: "w-6 h-6",
},
path({
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M4.5 12.75l6 6 9-13.5",
}),
),
text
)
}
text,
);
};
export const BskyUserCell = ({
profile,
@@ -114,32 +149,46 @@ export const BskyUserCell = ({
addAction,
removeAction,
}: {
profile: ProfileView,
statusKey: keyof ViewerState,
btnLabel: UserCellBtnLabel,
matchType: typeof BSKY_USER_MATCH_TYPE[keyof typeof BSKY_USER_MATCH_TYPE],
addAction: () => Promise<void>,
removeAction: () => Promise<void>
profile: ProfileView;
statusKey: keyof ViewerState;
btnLabel: UserCellBtnLabel;
matchType: (typeof BSKY_USER_MATCH_TYPE)[keyof typeof BSKY_USER_MATCH_TYPE];
addAction: () => Promise<void>;
removeAction: () => Promise<void>;
}) => {
return div({ class: "bsky-user-content-wrapper" },
return div(
{ class: "bsky-user-content-wrapper" },
MatchTypeLabel({ matchType }),
div({ class: "bsky-user-content bsky-fade-in" },
div({ class: "icon-section" },
a({ href: `https://bsky.app/profile/${profile.handle}`, target: "_blank", rel: "noopener" },
div(
{ class: "bsky-user-content bsky-fade-in" },
div(
{ class: "icon-section" },
a(
{
href: `https://bsky.app/profile/${profile.handle}`,
target: "_blank",
rel: "noopener",
},
Avatar({ avatar: profile.avatar }),
),
),
div({ class: "content" },
div({ class: "name-and-controller" },
div(
{ class: "content" },
div(
{ class: "name-and-controller" },
div(
p({ class: "display-name" },
a({ href: `https://bsky.app/profile/${profile.handle}`, target: "_blank", rel: "noopener" },
p(
{ class: "display-name" },
a(
{
href: `https://bsky.app/profile/${profile.handle}`,
target: "_blank",
rel: "noopener",
},
profile.displayName ?? profile.handle,
),
),
p({ class: "handle" },
`@${profile.handle}`,
),
p({ class: "handle" }, `@${profile.handle}`),
),
div(
ActionButton({
@@ -148,10 +197,13 @@ export const BskyUserCell = ({
btnLabel,
addAction,
removeAction,
})
}),
),
),
profile.description ? p({ class: "description" }, profile.description) : "",
profile.description
? p({ class: "description" }, profile.description)
: "",
),
))
}
),
);
};

View File

@@ -1,30 +1,35 @@
import van from "vanjs-core";
import van from "vanjs-core"
const { div, p } = van.tags;
const { svg, path } = van.tagsNS("http://www.w3.org/2000/svg");
const { div, p } = van.tags
const { svg, path } = van.tagsNS("http://www.w3.org/2000/svg")
const WarningIcon = () => svg(
{
fill: "none",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-6 h-6",
viewBox: "0 0 24 24"
},
path({
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
}),
)
export const NotFoundCell = () => div({ class: "bsky-user-content-wrapper" },
div({ class: "bsky-user-content bsky-user-content__not-found bsky-fade-in" },
WarningIcon(),
p({
class: "not-found"
const WarningIcon = () =>
svg(
{
fill: "none",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-6 h-6",
viewBox: "0 0 24 24",
},
"No similar users found."
)
))
path({
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z",
}),
);
export const NotFoundCell = () =>
div(
{ class: "bsky-user-content-wrapper" },
div(
{ class: "bsky-user-content bsky-user-content__not-found bsky-fade-in" },
WarningIcon(),
p(
{
class: "not-found",
},
"No similar users found.",
),
),
);

View File

@@ -1,19 +1,24 @@
import van from "vanjs-core"
import van from "vanjs-core";
const { button, div } = van.tags
const { button, div } = van.tags;
export const ReloadButton = ({clickAction}: {clickAction: () => void}) => {
const deleted = van.state(false)
export const ReloadButton = ({ clickAction }: { clickAction: () => void }) => {
const deleted = van.state(false);
return () => deleted.val ? null : div({ class: "bsky-reload-btn-wrapper" },
button(
{
class: "bsky-reload-btn bsky-fade-in",
onclick: () => {
clickAction()
deleted.val = true
}
},
"Find More"
))
}
return () =>
deleted.val
? null
: div(
{ class: "bsky-reload-btn-wrapper" },
button(
{
class: "bsky-reload-btn bsky-fade-in",
onclick: () => {
clickAction();
deleted.val = true;
},
},
"Find More",
),
);
};

View File

@@ -1,37 +1,37 @@
export const MESSAGE_NAMES = {
SEARCH_BSKY_USER_ON_FOLLOW_PAGE: "search_bsky_user_on_follow_page",
SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE: "search_bsky_user_on_list_members_page",
SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE:
"search_bsky_user_on_list_members_page",
SEARCH_BSKY_USER_ON_BLOCK_PAGE: "search_bsky_user_on_block_page",
}
};
const STORAGE_PREFIX = "sky_follower_bridge_storage"
const STORAGE_PREFIX = "sky_follower_bridge_storage";
export const STORAGE_KEYS = {
BSKY_USER_ID: `${STORAGE_PREFIX}_bsky_password`,
BSKY_PASSWORD: `${STORAGE_PREFIX}_bsky_user`,
} as const
} as const;
export const TARGET_URLS_REGEX = {
FOLLOW: /https:\/\/(twitter|x)\.com\/[^/]+\/(verified_follow|follow)/,
LIST: /^https:\/\/(twitter|x)\.com\/[^/]+\/lists\/[^/]+\/members/,
BLOCK: /^https:\/\/(twitter|x)\.com\/settings\/blocked/,
} as const
} as const;
export const MESSAGE_TYPE = {
ERROR: "error",
SUCCESS: "success",
} as const
} as const;
export const VIEWER_STATE = {
BLOCKING: "blocking",
FOLLOWING: "following",
} as const
} as const;
export const BSKY_USER_MATCH_TYPE = {
HANDLE: "handle",
DISPLAY_NAME: "display_name",
DESCRIPTION: "description",
NONE: "none",
} as const
} as const;
export const MAX_RELOAD_COUNT = 1
export const MAX_RELOAD_COUNT = 1;

View File

@@ -1,67 +1,91 @@
import type { ProfileView, ViewerState } from "@atproto/api/dist/client/types/app/bsky/actor/defs"
import van from "vanjs-core"
import { ReloadButton } from "./components/ReloadBtn"
import { NotFoundCell } from "./components/NotFoundCell"
import { BskyUserCell, type UserCellBtnLabel } from "./components/BskyUserCell"
import type { BSKY_USER_MATCH_TYPE } from "./constants"
import type {
ProfileView,
ViewerState,
} from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import van from "vanjs-core";
import { BskyUserCell, type UserCellBtnLabel } from "./components/BskyUserCell";
import { NotFoundCell } from "./components/NotFoundCell";
import { ReloadButton } from "./components/ReloadBtn";
import type { BSKY_USER_MATCH_TYPE } from "./constants";
export const getUserCells = ({ queryParam, filterInsertedElement }: { queryParam: string, filterInsertedElement: boolean }) => {
export const getUserCells = ({
queryParam,
filterInsertedElement,
}: { queryParam: string; filterInsertedElement: boolean }) => {
const userCells = document.querySelectorAll(queryParam);
// filter out already inserted elements
if (filterInsertedElement) {
return Array.from(userCells).filter((userCell) => {
const nextElement = userCell.nextElementSibling
if (!nextElement) { return true }
return nextElement.classList.contains("bsky-user-content-wrapper") === false
})
} else {
return Array.from(userCells)
const nextElement = userCell.nextElementSibling;
if (!nextElement) {
return true;
}
return (
nextElement.classList.contains("bsky-user-content-wrapper") === false
);
});
}
}
return Array.from(userCells);
};
export const insertReloadEl = (clickAction: () => void) => {
const lastInsertedEl = Array.from(document.querySelectorAll('.bsky-user-content')).at(-1)
van.add(lastInsertedEl.parentElement, ReloadButton({clickAction}))
}
const lastInsertedEl = Array.from(
document.querySelectorAll(".bsky-user-content"),
).at(-1);
van.add(lastInsertedEl.parentElement, ReloadButton({ clickAction }));
};
export const removeReloadEl = () => {
const reloadEl = document.querySelectorAll('.bsky-reload-btn-wrapper')
reloadEl.forEach(el => el.remove())
}
const reloadEl = document.querySelectorAll(".bsky-reload-btn-wrapper");
for (const el of reloadEl) {
el.remove();
}
};
export const getAccountNameAndDisplayName = (userCell: Element) => {
const [avatarEl, displayNameEl] = userCell?.querySelectorAll("a")
const twAccountName = avatarEl?.getAttribute("href")?.replace("/", "")
const twAccountNameRemoveUnderscore = twAccountName.replaceAll("_", "") // bsky does not allow underscores in handle, so remove them.
const twDisplayName = displayNameEl?.textContent
return { twAccountName, twDisplayName, twAccountNameRemoveUnderscore }
}
const [avatarEl, displayNameEl] = userCell.querySelectorAll("a");
const twAccountName = avatarEl?.getAttribute("href")?.replace("/", "");
const twAccountNameRemoveUnderscore = twAccountName.replaceAll("_", ""); // bsky does not allow underscores in handle, so remove them.
const twDisplayName = displayNameEl?.textContent;
return { twAccountName, twDisplayName, twAccountNameRemoveUnderscore };
};
export const insertBskyProfileEl = ({ dom, profile, statusKey, btnLabel, matchType, addAction, removeAction }: {
dom: Element,
profile: ProfileView,
statusKey: keyof ViewerState,
btnLabel: UserCellBtnLabel,
matchType: typeof BSKY_USER_MATCH_TYPE[keyof typeof BSKY_USER_MATCH_TYPE],
addAction: () => Promise<void>,
removeAction: () => Promise<void>
export const insertBskyProfileEl = ({
dom,
profile,
statusKey,
btnLabel,
matchType,
addAction,
removeAction,
}: {
dom: Element;
profile: ProfileView;
statusKey: keyof ViewerState;
btnLabel: UserCellBtnLabel;
matchType: (typeof BSKY_USER_MATCH_TYPE)[keyof typeof BSKY_USER_MATCH_TYPE];
addAction: () => Promise<void>;
removeAction: () => Promise<void>;
}) => {
van.add(dom.parentElement, BskyUserCell({
profile,
statusKey,
btnLabel,
matchType,
addAction,
removeAction,
}))
}
van.add(
dom.parentElement,
BskyUserCell({
profile,
statusKey,
btnLabel,
matchType,
addAction,
removeAction,
}),
);
};
export const insertNotFoundEl = (dom: Element) => {
van.add(dom.parentElement, NotFoundCell())
}
van.add(dom.parentElement, NotFoundCell());
};
export const isOutOfTopViewport = (el: Element) => {
const rect = el.getBoundingClientRect();
return rect.top < 0
}
return rect.top < 0;
};

View File

@@ -1,92 +1,98 @@
import { isOutOfTopViewport, removeReloadEl } from './domHelpers';
import { getAccountNameAndDisplayName, getUserCells, insertBskyProfileEl, insertNotFoundEl, insertReloadEl } from "~lib/domHelpers";
import type { ViewerState } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import { isSimilarUser } from "~lib/bskyHelpers";
import {
getAccountNameAndDisplayName,
getUserCells,
insertBskyProfileEl,
insertNotFoundEl,
insertReloadEl,
} from "~lib/domHelpers";
import { debugLog, isOneSymbol } from "~lib/utils";
import type { BskyClient } from './bskyClient';
import type { ViewerState } from '@atproto/api/dist/client/types/app/bsky/actor/defs';
import type { UserCellBtnLabel } from './components/BskyUserCell';
import type { BskyServiceWorkerClient } from './bskyServiceWorkerClient';
import type { BskyClient } from "./bskyClient";
import type { BskyServiceWorkerClient } from "./bskyServiceWorkerClient";
import type { UserCellBtnLabel } from "./components/BskyUserCell";
import { isOutOfTopViewport, removeReloadEl } from "./domHelpers";
const notFoundUserCache = new Set<string>();
const notFoundUserCache = new Set<string>()
const bskyUserUrlMap = new Map<string, string>();
const bskyUserUrlMap = new Map<string, string>()
export const searchAndInsertBskyUsers = async (
{
agent,
btnLabel,
userCellQueryParam,
statusKey,
addQuery,
removeQuery,
}: {
agent: BskyServiceWorkerClient | BskyClient,
userCellQueryParam: string,
btnLabel: UserCellBtnLabel,
statusKey: keyof ViewerState,
addQuery: (arg: string) => Promise<any>,
removeQuery: (arg: string) => Promise<any>,
}) => {
removeReloadEl()
export const searchAndInsertBskyUsers = async ({
agent,
btnLabel,
userCellQueryParam,
statusKey,
addQuery,
removeQuery,
}: {
agent: BskyServiceWorkerClient | BskyClient;
userCellQueryParam: string;
btnLabel: UserCellBtnLabel;
statusKey: keyof ViewerState;
// biome-ignore lint:
addQuery: (arg: string) => Promise<any>;
// biome-ignore lint:
removeQuery: (arg: string) => Promise<any>;
}) => {
removeReloadEl();
const userCells = getUserCells({
queryParam: userCellQueryParam,
filterInsertedElement: true,
})
debugLog(`userCells length: ${userCells.length}`)
});
debugLog(`userCells length: ${userCells.length}`);
let index = 0
let index = 0;
// loop over twitter user profile cells and search and insert bsky user
for (const userCell of userCells) {
if (isOutOfTopViewport(userCell)) {
continue
continue;
}
const { twAccountName, twDisplayName, twAccountNameRemoveUnderscore } = getAccountNameAndDisplayName(userCell)
const { twAccountName, twDisplayName, twAccountNameRemoveUnderscore } =
getAccountNameAndDisplayName(userCell);
if (notFoundUserCache.has(twAccountName)) {
insertNotFoundEl(userCell)
continue
insertNotFoundEl(userCell);
continue;
}
const searchTerms = [
twAccountNameRemoveUnderscore,
twDisplayName,
]
const searchTerms = [twAccountNameRemoveUnderscore, twDisplayName];
let targetAccount = null
let matchType = null
let targetAccount = null;
let matchType = null;
// Loop over search parameters and break if a user is found
searchLoop: for (const term of searchTerms) {
// one symbol is not a valid search term for bsky
if (!term || isOneSymbol(term)) {
continue
continue;
}
try {
const searchResults = await agent.searchUser({
term: term,
limit: 3,
})
});
for (const searchResult of searchResults) {
const { isSimilar: isUserFound, type } = isSimilarUser({
accountName: twAccountName,
accountNameRemoveUnderscore: twAccountNameRemoveUnderscore,
displayName: twDisplayName,
}, searchResult)
const { isSimilar: isUserFound, type } = isSimilarUser(
{
accountName: twAccountName,
accountNameRemoveUnderscore: twAccountNameRemoveUnderscore,
displayName: twDisplayName,
},
searchResult,
);
if (isUserFound) {
targetAccount = searchResult
matchType = type
targetAccount = searchResult;
matchType = type;
break searchLoop; // Stop searching when a user is found
}
}
} catch (e) {
console.error(e)
console.error(e);
}
}
@@ -100,7 +106,7 @@ export const searchAndInsertBskyUsers = async (
matchType,
addAction: async () => {
const result = await addQuery(targetAccount.did);
bskyUserUrlMap.set(targetAccount.did, result.uri)
bskyUserUrlMap.set(targetAccount.did, result.uri);
},
removeAction: async () => {
if (targetAccount?.viewer?.following) {
@@ -109,16 +115,16 @@ export const searchAndInsertBskyUsers = async (
await removeQuery(bskyUserUrlMap.get(targetAccount.did));
}
},
})
});
} else {
insertNotFoundEl(userCell)
notFoundUserCache.add(twAccountName)
insertNotFoundEl(userCell);
notFoundUserCache.add(twAccountName);
}
index++
index++;
if (process.env.NODE_ENV === "development" && index > 5) {
break
break;
}
}
@@ -131,6 +137,6 @@ export const searchAndInsertBskyUsers = async (
statusKey,
addQuery,
removeQuery,
})
})
}
});
});
};

View File

@@ -1,9 +1,9 @@
export const debugLog = (message: string) => {
if(process.env.NODE_ENV === "development") {
console.log(`🔷 [Sky Follower Bridge] ${message}`)
if (process.env.NODE_ENV === "development") {
console.log(`🔷 [Sky Follower Bridge] ${message}`);
}
}
};
export const isOneSymbol = (str: string) => {
return /^[^\w\s]$/.test(str);
}
};