diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index edd5b9e..59f6b5a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: "Publish to Chrome Web Store" +name: "Publish to Extension Store" on: push: branches: @@ -64,12 +64,12 @@ jobs: run: npx changeset tag - name: Build the extension - run: npm run build + run: npm run build && npm run build:firefox - name: Package the extension into a zip artifact - run: npm run package + run: npm run package && npm run package:firefox - - name: Publish to Chrome Web Store + - name: Publish to Extension Store uses: PlasmoHQ/bpp@v3 with: keys: ${{ secrets.PUBLISH_KEYS }} diff --git a/README.md b/README.md index cefcfa0..ed5aba1 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ https://github.com/kawamataryo/sky-follower-bridge/assets/11070996/0c87f9b9-573f ## 📦 Installation - [Chrome Web Store](https://chrome.google.com/webstore/detail/sky-follower-bridge/behhbpbpmailcnfbjagknjngnfdojpko) +- [Firefox Add-ons](https://addons.mozilla.org/ja/firefox/addon/sky-follower-bridge/) ## 🚀 How to use 1. Open your Twitter [Follower](https://twitter.com/following) or [Follow](https://twitter.com/followers) or [list members](), [blocking](https://twitter.com/settings/blocked/all) page. diff --git a/src/background/messages/block.ts b/src/background/messages/block.ts new file mode 100644 index 0000000..9294b03 --- /dev/null +++ b/src/background/messages/block.ts @@ -0,0 +1,13 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" +import { BskyClient } from "~lib/bskyClient"; + +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { session, subjectDid } = req.body + const client = BskyClient.createAgentFromSession(session) + + res.send({ + result: await client.block(subjectDid) + }) +} + +export default handler diff --git a/src/background/messages/follow.ts b/src/background/messages/follow.ts new file mode 100644 index 0000000..cd291e1 --- /dev/null +++ b/src/background/messages/follow.ts @@ -0,0 +1,13 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" +import { BskyClient } from "~lib/bskyClient"; + +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { session, subjectDid } = req.body + const client = BskyClient.createAgentFromSession(session) + + res.send({ + result: await client.follow(subjectDid) + }) +} + +export default handler diff --git a/src/background/messages/login.ts b/src/background/messages/login.ts new file mode 100644 index 0000000..3833700 --- /dev/null +++ b/src/background/messages/login.ts @@ -0,0 +1,17 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" +import { BskyClient } from "../../lib/bskyClient"; + +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { identifier, password } = req.body + + const agent = await BskyClient.createAgent({ + identifier, + password, + }) + + res.send({ + session: agent.session, + }) +} + +export default handler diff --git a/src/background/messages/searchUser.ts b/src/background/messages/searchUser.ts new file mode 100644 index 0000000..40315b0 --- /dev/null +++ b/src/background/messages/searchUser.ts @@ -0,0 +1,16 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" +import { BskyClient } from "~lib/bskyClient"; + +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { session, term, limit } = req.body + const client = BskyClient.createAgentFromSession(session) + + res.send({ + actors: await client.searchUser({ + term, + limit, + }) + }) +} + +export default handler diff --git a/src/background/messages/unblock.ts b/src/background/messages/unblock.ts new file mode 100644 index 0000000..166176a --- /dev/null +++ b/src/background/messages/unblock.ts @@ -0,0 +1,13 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" +import { BskyClient } from "~lib/bskyClient"; + +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { session, blockUri } = req.body + const client = BskyClient.createAgentFromSession(session) + + res.send({ + result: await client.unblock(blockUri) + }) +} + +export default handler diff --git a/src/background/messages/unfollow.ts b/src/background/messages/unfollow.ts new file mode 100644 index 0000000..b876ed9 --- /dev/null +++ b/src/background/messages/unfollow.ts @@ -0,0 +1,13 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" +import { BskyClient } from "~lib/bskyClient"; + +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { session, followUri } = req.body + const client = BskyClient.createAgentFromSession(session) + + res.send({ + result: await client.unfollow(followUri) + }) +} + +export default handler diff --git a/src/content.ts b/src/content.ts index ad71305..55b3cd3 100644 --- a/src/content.ts +++ b/src/content.ts @@ -1,8 +1,9 @@ -import { BskyClient, type BskyLoginParams } from "./lib/bskyClient"; +import { type BskyLoginParams } from "./lib/bskyClient"; import type { PlasmoCSConfig } from "plasmo" import { MESSAGE_NAMES, VIEWER_STATE } from "~lib/constants"; import "./style.content.css" import { searchAndInsertBskyUsers } from '~lib/searchAndInsertBskyUsers'; +import { BskyServiceWorkerClient } from "~lib/bskyServiceWorkerClient"; export const config: PlasmoCSConfig = { matches: ["https://twitter.com/*", "https://x.com/*"], @@ -15,10 +16,11 @@ const searchAndShowBskyUsers = async ({ messageName, }: BskyLoginParams & { messageName: string }) => { - const agent = await BskyClient.createAgent({ + const agent = await BskyServiceWorkerClient.createAgent({ identifier, password, - }) + }); + switch (messageName) { case MESSAGE_NAMES.SEARCH_BSKY_USER_ON_FOLLOW_PAGE: await searchAndInsertBskyUsers({ diff --git a/src/lib/bskyClient.ts b/src/lib/bskyClient.ts index f7f5a29..c9fa788 100644 --- a/src/lib/bskyClient.ts +++ b/src/lib/bskyClient.ts @@ -1,4 +1,4 @@ -import { AtUri, BskyAgent } from "@atproto/api"; +import { AtUri, BskyAgent, type AtpSessionData } from "@atproto/api"; export type BskyLoginParams = { identifier: string; @@ -13,9 +13,24 @@ export class BskyClient { email: string; }; agent: BskyAgent; + session = {} private constructor() { - this.agent = new BskyAgent({ service: this.service }); + this.agent = new BskyAgent({ service: this.service, persistSession: (evt, session) => { + this.session = session + } }); + } + + public static createAgentFromSession(session: AtpSessionData): BskyClient { + const client = new BskyClient(); + client.agent.resumeSession(session); + client.me = { + did: session.did, + handle: session.handle, + email: session.email, + } + + return client; } public static async createAgent({ diff --git a/src/lib/bskyServiceWorkerClient.ts b/src/lib/bskyServiceWorkerClient.ts new file mode 100644 index 0000000..b825982 --- /dev/null +++ b/src/lib/bskyServiceWorkerClient.ts @@ -0,0 +1,92 @@ +import { sendToBackground } from "@plasmohq/messaging"; + +export type BskyLoginParams = { + identifier: string; + password: string; +} + +export class BskyServiceWorkerClient { + private session = {} + + private constructor() { + } + + public static async createAgent({ + identifier, + password, + }: BskyLoginParams): Promise { + const client = new BskyServiceWorkerClient(); + const { session } = await sendToBackground({ + name: "login", + body: { + identifier, + password, + } + }) + client.session = session + return client; + } + + public searchUser = async ({ + term, + limit, + }: { + term: string; + limit: number; + }) => { + const { actors } = await sendToBackground({ + name: "searchUser", + body: { + session: this.session, + term, + limit, + } + }) + return actors; + }; + + public follow = async (subjectDid: string) => { + const { result } = await sendToBackground({ + name: "follow", + body: { + session: this.session, + subjectDid + } + }) + return result; + } + + public unfollow = async (followUri: string) => { + const { result } = await sendToBackground({ + name: "unfollow", + body: { + session: this.session, + followUri + } + }) + return result; + } + + public block = async (subjectDid: string) => { + const { result } = await sendToBackground({ + name: "block", + body: { + session: this.session, + subjectDid + } + }) + return result; + } + + public unblock = async (blockUri: string) => { + // TODO: unblock is not working. Need to fix it. + const { result } = await sendToBackground({ + name: "unblock", + body: { + session: this.session, + blockUri + } + }) + return result; + } +} diff --git a/src/lib/searchAndInsertBskyUsers.ts b/src/lib/searchAndInsertBskyUsers.ts index 8e330c3..e985797 100644 --- a/src/lib/searchAndInsertBskyUsers.ts +++ b/src/lib/searchAndInsertBskyUsers.ts @@ -5,6 +5,7 @@ import { debugLog } 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'; const notFoundUserCache = new Set() @@ -20,7 +21,7 @@ export const searchAndInsertBskyUsers = async ( addQuery, removeQuery, }: { - agent: BskyClient, + agent: BskyServiceWorkerClient | BskyClient, userCellQueryParam: string, btnLabel: UserCellBtnLabel, statusKey: keyof ViewerState,