diff --git a/package-lock.json b/package-lock.json index 2ae3772..125916f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sky-follower-bridge", - "version": "1.3.0", + "version": "1.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sky-follower-bridge", - "version": "1.3.0", + "version": "1.4.1", "dependencies": { "@atproto/api": "^0.13.12", "@changesets/cli": "^2.27.1", diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 68df44f..5767eeb 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -110,3 +110,5 @@ export const BSKY_DOMAIN = export const BSKY_PROFILE_LABEL = { IMPERSONATION: "impersonation", } as const; + +export const DEFAULT_LIST_NAME = "Imported List from X"; diff --git a/src/lib/domHelpers.ts b/src/lib/domHelpers.ts index dd107d7..ab1bcf0 100644 --- a/src/lib/domHelpers.ts +++ b/src/lib/domHelpers.ts @@ -1,6 +1,3 @@ -import type { CrawledUserInfo } from "~types"; -import { BSKY_DOMAIN } from "./constants"; - export const getUserCells = ({ queryParam, filterInsertedElement, @@ -22,27 +19,12 @@ export const getUserCells = ({ return Array.from(userCells); }; -export const extractUserData = (userCell: Element): CrawledUserInfo => { - const anchors = Array.from(userCell.querySelectorAll("a")); - const [avatarEl, displayNameEl] = anchors; - const accountName = avatarEl?.getAttribute("href")?.replace("/", ""); - const accountNameRemoveUnderscore = accountName.replaceAll("_", ""); // bsky does not allow underscores in handle, so remove them. - const accountNameReplaceUnderscore = accountName.replaceAll("_", "-"); - const displayName = displayNameEl?.textContent; - const bskyHandle = - userCell.textContent?.match( - new RegExp(`([^/\\s]+\\.${BSKY_DOMAIN})`), - )?.[1] ?? - userCell.textContent - ?.match(/bsky\.app\/profile\/([^/\s]+)…?/)?.[1] - ?.replace("…", "") ?? - ""; - - return { - accountName, - displayName, - accountNameRemoveUnderscore, - accountNameReplaceUnderscore, - bskyHandle, - }; +export const scrapeListNameFromPage = (): string => { + const listNameElement = document.querySelector( + 'div[aria-label="Timeline: List"] span', + ); + if (listNameElement) { + return listNameElement.textContent.trim(); + } + return "Imported List from X"; }; diff --git a/src/lib/hooks/useBskyUserManager.ts b/src/lib/hooks/useBskyUserManager.ts index ff19a43..ff2cecc 100644 --- a/src/lib/hooks/useBskyUserManager.ts +++ b/src/lib/hooks/useBskyUserManager.ts @@ -5,6 +5,7 @@ import { BskyServiceWorkerClient } from "~lib/bskyServiceWorkerClient"; import { ACTION_MODE, BSKY_USER_MATCH_TYPE, + DEFAULT_LIST_NAME, MESSAGE_NAME_TO_ACTION_MODE_MAP, STORAGE_KEYS, } from "~lib/constants"; @@ -21,13 +22,6 @@ export const useBskyUserManager = () => { }, (v) => (v === undefined ? [] : v), ); - const [listName, setListName] = React.useState(""); - React.useEffect(() => { - chrome.storage.local.get("listName", (result) => { - const name = result.listName || "Imported List from X"; - setListName(name); - }); - }, []); const bskyClient = React.useRef(null); const [actionMode, setActionMode] = React.useState< @@ -128,8 +122,12 @@ export const useBskyUserManager = () => { // Import list const importList = React.useCallback(async () => { if (!bskyClient.current) return; + const storage = new Storage({ + area: "local", + }); + const listName = await storage.get(STORAGE_KEYS.LIST_NAME); const listUri = await bskyClient.current.createListAndAddUsers({ - name: listName, + name: listName || DEFAULT_LIST_NAME, description: "List imported via Sky Follower Bridge", userDids: filteredUsers.map((user) => user.did), }); @@ -137,7 +135,7 @@ export const useBskyUserManager = () => { // const myProfile = await bskyClient.current.getMyProfile(); // return `https://bsky.app/profile/${myProfile.handle}/lists/${listUri}`; return "https://bsky.app/lists"; - }, [filteredUsers, listName]); + }, [filteredUsers]); // Follow All const followAll = React.useCallback(async () => { @@ -247,7 +245,6 @@ export const useBskyUserManager = () => { return { handleClickAction, users, - listName, actionMode, matchTypeFilter, changeMatchTypeFilter, diff --git a/src/lib/hooks/useRetrieveBskyUsers.ts b/src/lib/hooks/useRetrieveBskyUsers.ts index e1a0519..960bf3c 100644 --- a/src/lib/hooks/useRetrieveBskyUsers.ts +++ b/src/lib/hooks/useRetrieveBskyUsers.ts @@ -10,7 +10,7 @@ import type { AbstractService } from "~lib/services/abstractService"; import { XService } from "~lib/services/xService"; import type { BskyUser, CrawledUserInfo, MessageName } from "~types"; -const getService = (messageName: string): AbstractService => { +const getService = (messageName: MessageName): AbstractService => { return match(messageName) .with( P.when((name) => @@ -25,16 +25,6 @@ const getService = (messageName: string): AbstractService => { .otherwise(() => new XService(messageName)); }; -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 = () => { const bskyClient = React.useRef(null); const [users, setUsers] = useStorage( @@ -46,15 +36,6 @@ export const useRetrieveBskyUsers = () => { }, (v) => (v === undefined ? [] : v), ); - const [listName, setListName] = useStorage( - { - key: STORAGE_KEYS.LIST_NAME, - instance: new Storage({ - area: "local", - }), - }, - (v) => (v === undefined ? "" : v), - ); const [loading, setLoading] = React.useState(true); const [errorMessage, setErrorMessage] = React.useState(""); const [isBottomReached, setIsBottomReached] = React.useState(false); @@ -104,7 +85,7 @@ export const useRetrieveBskyUsers = () => { const abortControllerRef = React.useRef(null); const startRetrieveLoop = React.useCallback( - async (messageName: string) => { + async (messageName: MessageName) => { abortControllerRef.current = new AbortController(); const signal = abortControllerRef.current.signal; @@ -139,13 +120,6 @@ export const useRetrieveBskyUsers = () => { [retrieveBskyUsers, isBottomReached], ); - React.useEffect(() => { - chrome.storage.local.set({ - users: JSON.stringify(users), - listName: listName, - }); - }, [users, listName]); - const stopRetrieveLoop = React.useCallback(() => { if (abortControllerRef.current) { abortControllerRef.current.abort(); @@ -169,8 +143,6 @@ export const useRetrieveBskyUsers = () => { bskyClient.current = new BskyServiceWorkerClient(session); - setListName(scrapeListNameFromPage()); - startRetrieveLoop(messageName).catch((e) => { console.error(e); setErrorMessage(e.message); @@ -201,7 +173,6 @@ export const useRetrieveBskyUsers = () => { return { initialize, users, - listName, loading, errorMessage, isRateLimitError, diff --git a/src/lib/services/xService.ts b/src/lib/services/xService.ts index f127324..2567a12 100644 --- a/src/lib/services/xService.ts +++ b/src/lib/services/xService.ts @@ -1,10 +1,24 @@ +import { Storage } from "@plasmohq/storage"; import { MESSAGE_NAMES } from "~lib/constants"; import { BSKY_DOMAIN } from "~lib/constants"; +import { STORAGE_KEYS } from "~lib/constants"; +import { scrapeListNameFromPage } from "~lib/domHelpers"; import { wait } from "~lib/utils"; -import type { CrawledUserInfo } from "~types"; +import type { CrawledUserInfo, MessageName } from "~types"; import { AbstractService } from "./abstractService"; export class XService extends AbstractService { + constructor(messageName: MessageName) { + // Set the list name in the storage if it's a list members page + if (messageName === MESSAGE_NAMES.SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE) { + const listName = scrapeListNameFromPage(); + new Storage({ + area: "local", + }).set(STORAGE_KEYS.LIST_NAME, listName); + } + super(messageName); + } + extractUserData(userCell: Element): CrawledUserInfo { const anchors = Array.from(userCell.querySelectorAll("a")); const [avatarEl, displayNameEl] = anchors;