mirror of
https://github.com/snachodog/tok-to-insta-follower-bridge.git
synced 2025-09-12 23:23:31 -06:00
💫 add biome and format src files.
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -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)
|
||||
: "",
|
||||
),
|
||||
))
|
||||
}
|
||||
),
|
||||
);
|
||||
};
|
||||
|
@@ -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.",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@@ -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",
|
||||
),
|
||||
);
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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,
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@@ -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);
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user