mirror of
https://github.com/snachodog/tok-to-insta-follower-bridge.git
synced 2025-04-23 20:12:22 -06:00
add mvp of import list action for lists
This commit is contained in:
parent
7c5c65f0ed
commit
4807e07d95
21
src/background/messages/addUserToList.ts
Normal file
21
src/background/messages/addUserToList.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { PlasmoMessaging } from "@plasmohq/messaging";
|
||||||
|
import { BskyClient } from "~lib/bskyClient";
|
||||||
|
|
||||||
|
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
|
||||||
|
const { session, userDid, listUri } = req.body;
|
||||||
|
const client = await BskyClient.createAgentFromSession(session);
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.send({
|
||||||
|
result: await client.addUserToList({ userDid, listUri }),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handler;
|
21
src/background/messages/createList.ts
Normal file
21
src/background/messages/createList.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { PlasmoMessaging } from "@plasmohq/messaging";
|
||||||
|
import { BskyClient } from "~lib/bskyClient";
|
||||||
|
|
||||||
|
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
|
||||||
|
const { session, name, description } = req.body;
|
||||||
|
const client = await BskyClient.createAgentFromSession(session);
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.send({
|
||||||
|
uri: await client.createList({ name, description }),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handler;
|
33
src/background/messages/createListAndAddUsers.ts
Normal file
33
src/background/messages/createListAndAddUsers.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { PlasmoMessaging } from "@plasmohq/messaging";
|
||||||
|
import { BskyClient } from "~lib/bskyClient";
|
||||||
|
import { STORAGE_KEYS } from "~lib/constants";
|
||||||
|
|
||||||
|
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
|
||||||
|
const { name, description, userDids } = req.body;
|
||||||
|
|
||||||
|
const storage = await chrome.storage.local.get(STORAGE_KEYS.BSKY_CLIENT_SESSION);
|
||||||
|
const session = storage[STORAGE_KEYS.BSKY_CLIENT_SESSION];
|
||||||
|
|
||||||
|
if (!session || !session.did) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
message: "Invalid session data",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = await BskyClient.createAgentFromSession(session);
|
||||||
|
await client.createListAndAddUsers({ name, description, userDids });
|
||||||
|
res.send({ success: true });
|
||||||
|
} catch (e) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handler;
|
@ -30,6 +30,7 @@ const App = () => {
|
|||||||
restart,
|
restart,
|
||||||
isBottomReached,
|
isBottomReached,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
listName,
|
||||||
} = useRetrieveBskyUsers();
|
} = useRetrieveBskyUsers();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -66,7 +67,7 @@ const App = () => {
|
|||||||
|
|
||||||
const stopAndShowDetectedUsers = async () => {
|
const stopAndShowDetectedUsers = async () => {
|
||||||
stopRetrieveLoop();
|
stopRetrieveLoop();
|
||||||
await chrome.storage.local.set({ users: JSON.stringify(users) });
|
await chrome.storage.local.set({ users: JSON.stringify(users), listName: listName });
|
||||||
openOptionPage();
|
openOptionPage();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,4 +114,59 @@ export class BskyClient {
|
|||||||
rkey,
|
rkey,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public createList = async ({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}) => {
|
||||||
|
const result = await this.agent.com.atproto.repo.createRecord({
|
||||||
|
repo: this.me.did,
|
||||||
|
collection: 'app.bsky.graph.list',
|
||||||
|
record: {
|
||||||
|
$type: 'app.bsky.graph.list',
|
||||||
|
purpose: 'app.bsky.graph.defs#curatelist',
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result.data.uri;
|
||||||
|
};
|
||||||
|
|
||||||
|
public addUserToList = async ({
|
||||||
|
userDid,
|
||||||
|
listUri,
|
||||||
|
}: {
|
||||||
|
userDid: string;
|
||||||
|
listUri: string;
|
||||||
|
}) => {
|
||||||
|
return await this.agent.com.atproto.repo.createRecord({
|
||||||
|
repo: this.me.did,
|
||||||
|
collection: 'app.bsky.graph.listitem',
|
||||||
|
record: {
|
||||||
|
$type: 'app.bsky.graph.listitem',
|
||||||
|
subject: userDid,
|
||||||
|
list: listUri,
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public createListAndAddUsers = async ({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
userDids,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
userDids: string[];
|
||||||
|
}) => {
|
||||||
|
const listUri = await this.createList({ name, description });
|
||||||
|
for (const userDid of userDids) {
|
||||||
|
await this.addUserToList({ userDid, listUri });
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
@ -110,4 +110,59 @@ export class BskyServiceWorkerClient {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public createList = async ({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}) => {
|
||||||
|
const { uri, error } = await sendToBackground({
|
||||||
|
name: "createList",
|
||||||
|
body: {
|
||||||
|
session: this.session,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
};
|
||||||
|
|
||||||
|
public addUserToList = async ({
|
||||||
|
userDid,
|
||||||
|
listUri,
|
||||||
|
}: {
|
||||||
|
userDid: string;
|
||||||
|
listUri: string;
|
||||||
|
}) => {
|
||||||
|
const { result, error } = await sendToBackground({
|
||||||
|
name: "addUserToList",
|
||||||
|
body: {
|
||||||
|
session: this.session,
|
||||||
|
userDid,
|
||||||
|
listUri,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
public createListAndAddUsers = async ({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
userDids,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
userDids: string[];
|
||||||
|
}) => {
|
||||||
|
const listUri = await this.createList({ name, description });
|
||||||
|
for (const userDid of userDids) {
|
||||||
|
await this.addUserToList({ userDid, listUri });
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,19 @@ const Sidebar = ({
|
|||||||
actionMode,
|
actionMode,
|
||||||
matchTypeStats,
|
matchTypeStats,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const getActionLabel = () => {
|
||||||
|
switch (actionMode) {
|
||||||
|
case ACTION_MODE.FOLLOW:
|
||||||
|
return "Follow All";
|
||||||
|
case ACTION_MODE.BLOCK:
|
||||||
|
return "Block All";
|
||||||
|
case ACTION_MODE.IMPORT_LIST:
|
||||||
|
return "Import List";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="bg-base-300 w-80 min-h-screen p-4 border-r border-base-300 flex flex-col">
|
<aside className="bg-base-300 w-80 min-h-screen p-4 border-r border-base-300 flex flex-col">
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
@ -146,7 +159,7 @@ const Sidebar = ({
|
|||||||
</div>
|
</div>
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
onClick={actionAll}
|
onClick={actionAll}
|
||||||
label={actionMode === ACTION_MODE.FOLLOW ? "Follow All" : "Block All"}
|
label={getActionLabel()}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
⚠️ User detection is not perfect and may include false positives.
|
⚠️ User detection is not perfect and may include false positives.
|
||||||
|
@ -16,7 +16,7 @@ const UserCard = ({ user, actionMode, clickAction }: Props) => {
|
|||||||
const actionBtnLabelAndClass = React.useMemo(
|
const actionBtnLabelAndClass = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
match(actionMode)
|
match(actionMode)
|
||||||
.with(ACTION_MODE.FOLLOW, () => {
|
.with(ACTION_MODE.FOLLOW, ACTION_MODE.IMPORT_LIST, () => {
|
||||||
const follow = {
|
const follow = {
|
||||||
label: "Follow on Bluesky",
|
label: "Follow on Bluesky",
|
||||||
class: "btn-primary",
|
class: "btn-primary",
|
||||||
@ -41,7 +41,7 @@ const UserCard = ({ user, actionMode, clickAction }: Props) => {
|
|||||||
})
|
})
|
||||||
.with(ACTION_MODE.BLOCK, () => {
|
.with(ACTION_MODE.BLOCK, () => {
|
||||||
const block = {
|
const block = {
|
||||||
label: "block on Bluesky",
|
label: "Block on Bluesky",
|
||||||
class: "btn-primary",
|
class: "btn-primary",
|
||||||
};
|
};
|
||||||
const blocking = {
|
const blocking = {
|
||||||
|
@ -20,11 +20,12 @@ export const MESSAGE_NAME_TO_QUERY_PARAM_MAP = {
|
|||||||
export const ACTION_MODE = {
|
export const ACTION_MODE = {
|
||||||
FOLLOW: "follow",
|
FOLLOW: "follow",
|
||||||
BLOCK: "block",
|
BLOCK: "block",
|
||||||
|
IMPORT_LIST: "import_list",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MESSAGE_NAME_TO_ACTION_MODE_MAP = {
|
export const MESSAGE_NAME_TO_ACTION_MODE_MAP = {
|
||||||
[MESSAGE_NAMES.SEARCH_BSKY_USER_ON_FOLLOW_PAGE]: ACTION_MODE.FOLLOW,
|
[MESSAGE_NAMES.SEARCH_BSKY_USER_ON_FOLLOW_PAGE]: ACTION_MODE.FOLLOW,
|
||||||
[MESSAGE_NAMES.SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE]: ACTION_MODE.FOLLOW,
|
[MESSAGE_NAMES.SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE]: ACTION_MODE.IMPORT_LIST,
|
||||||
[MESSAGE_NAMES.SEARCH_BSKY_USER_ON_BLOCK_PAGE]: ACTION_MODE.BLOCK,
|
[MESSAGE_NAMES.SEARCH_BSKY_USER_ON_BLOCK_PAGE]: ACTION_MODE.BLOCK,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,14 @@ export const useBskyUserManager = () => {
|
|||||||
},
|
},
|
||||||
(v) => (v === undefined ? [] : v),
|
(v) => (v === undefined ? [] : v),
|
||||||
);
|
);
|
||||||
|
const [listName, setListName] = React.useState<string>("");
|
||||||
|
React.useEffect(() => {
|
||||||
|
chrome.storage.local.get("listName", (result) => {
|
||||||
|
const name = result.listName || "Imported List from X";
|
||||||
|
setListName(name);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const bskyClient = React.useRef<BskyServiceWorkerClient | null>(null);
|
const bskyClient = React.useRef<BskyServiceWorkerClient | null>(null);
|
||||||
const [actionMode, setActionMode] = React.useState<
|
const [actionMode, setActionMode] = React.useState<
|
||||||
(typeof ACTION_MODE)[keyof typeof ACTION_MODE]
|
(typeof ACTION_MODE)[keyof typeof ACTION_MODE]
|
||||||
@ -121,6 +129,25 @@ export const useBskyUserManager = () => {
|
|||||||
if (!bskyClient.current) return;
|
if (!bskyClient.current) return;
|
||||||
let actionCount = 0;
|
let actionCount = 0;
|
||||||
|
|
||||||
|
if (actionMode === ACTION_MODE.IMPORT_LIST) {
|
||||||
|
const userDids = filteredUsers.map(user => user.did);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await chrome.runtime.sendMessage({
|
||||||
|
name: "createListAndAddUsers",
|
||||||
|
body: {
|
||||||
|
name: listName,
|
||||||
|
description: "List imported from Sky Follower Bridge",
|
||||||
|
userDids,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log("List created and users added successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating list and adding users:", error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const user of filteredUsers) {
|
for (const user of filteredUsers) {
|
||||||
let resultUri: string | null = null;
|
let resultUri: string | null = null;
|
||||||
// follow
|
// follow
|
||||||
@ -153,24 +180,24 @@ export const useBskyUserManager = () => {
|
|||||||
}
|
}
|
||||||
const result = await bskyClient.current.block(user.did);
|
const result = await bskyClient.current.block(user.did);
|
||||||
resultUri = result.uri;
|
resultUri = result.uri;
|
||||||
|
await setUsers((prev) =>
|
||||||
|
prev.map((prevUser) => {
|
||||||
|
if (prevUser.did === user.did) {
|
||||||
|
return {
|
||||||
|
...prevUser,
|
||||||
|
isBlocking: !prevUser.isBlocking,
|
||||||
|
blockingUri: resultUri ?? prevUser.blockingUri,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return prevUser;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await wait(300);
|
||||||
|
actionCount++;
|
||||||
}
|
}
|
||||||
await setUsers((prev) =>
|
|
||||||
prev.map((prevUser) => {
|
|
||||||
if (prevUser.did === user.did) {
|
|
||||||
return {
|
|
||||||
...prevUser,
|
|
||||||
isBlocking: !prevUser.isBlocking,
|
|
||||||
blockingUri: resultUri ?? prevUser.blockingUri,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return prevUser;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
await wait(300);
|
|
||||||
actionCount++;
|
|
||||||
}
|
}
|
||||||
return actionCount;
|
return actionCount;
|
||||||
}, [filteredUsers, actionMode, setUsers]);
|
}, [filteredUsers, actionMode, setUsers, listName]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
chrome.storage.local.get(
|
chrome.storage.local.get(
|
||||||
@ -204,6 +231,7 @@ export const useBskyUserManager = () => {
|
|||||||
return {
|
return {
|
||||||
handleClickAction,
|
handleClickAction,
|
||||||
users,
|
users,
|
||||||
|
listName,
|
||||||
actionMode,
|
actionMode,
|
||||||
matchTypeFilter,
|
matchTypeFilter,
|
||||||
changeMatchTypeFilter,
|
changeMatchTypeFilter,
|
||||||
|
@ -8,6 +8,14 @@ import { searchBskyUser } from "~lib/searchBskyUsers";
|
|||||||
import { XService } from "~lib/services/x";
|
import { XService } from "~lib/services/x";
|
||||||
import type { BskyUser, CrawledUserInfo } from "~types";
|
import type { BskyUser, CrawledUserInfo } from "~types";
|
||||||
|
|
||||||
|
const scrapeListNameFromPage = (): string => {
|
||||||
|
const listNameElement = document.querySelector('div[aria-label="Timeline: List"] span');
|
||||||
|
if (listNameElement) {
|
||||||
|
return listNameElement.textContent.trim();
|
||||||
|
}
|
||||||
|
return "Imported List from X";
|
||||||
|
};
|
||||||
|
|
||||||
export const useRetrieveBskyUsers = () => {
|
export const useRetrieveBskyUsers = () => {
|
||||||
const bskyClient = React.useRef<BskyServiceWorkerClient | null>(null);
|
const bskyClient = React.useRef<BskyServiceWorkerClient | null>(null);
|
||||||
const [users, setUsers] = useStorage<BskyUser[]>(
|
const [users, setUsers] = useStorage<BskyUser[]>(
|
||||||
@ -27,6 +35,7 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
session: AtpSessionData;
|
session: AtpSessionData;
|
||||||
messageName: (typeof MESSAGE_NAMES)[keyof typeof MESSAGE_NAMES];
|
messageName: (typeof MESSAGE_NAMES)[keyof typeof MESSAGE_NAMES];
|
||||||
}>(null);
|
}>(null);
|
||||||
|
const [listName, setListName] = React.useState<string>("");
|
||||||
|
|
||||||
const modalRef = React.useRef<HTMLDialogElement>(null);
|
const modalRef = React.useRef<HTMLDialogElement>(null);
|
||||||
const showModal = () => {
|
const showModal = () => {
|
||||||
@ -104,6 +113,10 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
[retrieveBskyUsers, isBottomReached],
|
[retrieveBskyUsers, isBottomReached],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
chrome.storage.local.set({ users: JSON.stringify(users), listName: listName });
|
||||||
|
}, [users, listName]);
|
||||||
|
|
||||||
const stopRetrieveLoop = React.useCallback(() => {
|
const stopRetrieveLoop = React.useCallback(() => {
|
||||||
if (abortControllerRef.current) {
|
if (abortControllerRef.current) {
|
||||||
abortControllerRef.current.abort();
|
abortControllerRef.current.abort();
|
||||||
@ -127,6 +140,9 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
|
|
||||||
bskyClient.current = new BskyServiceWorkerClient(session);
|
bskyClient.current = new BskyServiceWorkerClient(session);
|
||||||
|
|
||||||
|
const listName = scrapeListNameFromPage();
|
||||||
|
setListName(listName);
|
||||||
|
|
||||||
startRetrieveLoop(messageName).catch((e) => {
|
startRetrieveLoop(messageName).catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setErrorMessage(e.message);
|
setErrorMessage(e.message);
|
||||||
@ -160,6 +176,7 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
showModal,
|
showModal,
|
||||||
initialize,
|
initialize,
|
||||||
users,
|
users,
|
||||||
|
listName,
|
||||||
loading,
|
loading,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
isRateLimitError,
|
isRateLimitError,
|
||||||
|
@ -7,6 +7,7 @@ const Option = () => {
|
|||||||
const {
|
const {
|
||||||
users,
|
users,
|
||||||
filteredUsers,
|
filteredUsers,
|
||||||
|
listName,
|
||||||
matchTypeFilter,
|
matchTypeFilter,
|
||||||
changeMatchTypeFilter,
|
changeMatchTypeFilter,
|
||||||
handleClickAction,
|
handleClickAction,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user