🌱 create projects

This commit is contained in:
kawamataryo
2023-05-15 16:20:51 +09:00
commit 4d46a620e4
22 changed files with 15524 additions and 0 deletions

39
src/lib/bskyClient.ts Normal file
View File

@@ -0,0 +1,39 @@
import { AppBskyFeedPost, AppBskyRichtextFacet, BskyAgent } from "@atproto/api";
export class BskyClient {
private service = "https://bsky.social";
agent: BskyAgent;
private constructor() {
this.agent = new BskyAgent({ service: this.service });
}
public static async createAgent({
identifier,
password,
}: {
identifier: string;
password: string;
}): Promise<BskyClient> {
const client = new BskyClient();
await client.agent.login({ identifier, password });
return client;
}
public searchUser = async ({
term,
limit,
}: {
term: string;
limit: number;
}) => {
const result = await this.agent.searchActors({
term,
limit,
});
return result.data.actors;
};
public follow = async (subjectDid: string) => {
await this.agent.follow(subjectDid);
}
}

17
src/lib/bskyHelpers.ts Normal file
View File

@@ -0,0 +1,17 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs"
export const isSimilarUser = (name: string, bskyProfile: ProfileView | undefined) => {
if(!bskyProfile) { return false }
const lowerCaseName = name.toLocaleLowerCase()
if(lowerCaseName === bskyProfile?.handle.toLocaleLowerCase().replace("@", "").split('.')[0]) {
return true
}
if(lowerCaseName === bskyProfile.displayName?.toLocaleLowerCase()) {
return true
}
if(bskyProfile.description?.toLocaleLowerCase().includes(lowerCaseName)) {
return true
}
return false
}

19
src/lib/constants.ts Normal file
View File

@@ -0,0 +1,19 @@
export const MESSAGE_NAMES = {
SEARCH_BSKY_USER: "search_bsky_user"
}
const STORAGE_PREFIX = "sky_follower_bridge_storage"
export const STORAGE_KEYS = {
BSKY_USER_ID: `${STORAGE_PREFIX}_bsky_password`,
BSKY_PASSWORD: `${STORAGE_PREFIX}_bsky_user`,
}
export const TARGET_URLS_REGEX = [
/^https:\/\/twitter\.com\/[^/]+\/following$/,
/^https:\/\/twitter\.com\/[^/]+\/followers$/,
]
export const MESSAGE_TYPE = {
ERROR: "error",
SUCCESS: "success",
} as const

107
src/lib/domHelpers.ts Normal file
View File

@@ -0,0 +1,107 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs"
export const getUserCells = ({ filterInsertedElement }: { filterInsertedElement: boolean } = { filterInsertedElement: true }) => {
const userCells = document.querySelectorAll('[data-testid="primaryColumn"] [data-testid="UserCell"]');
// 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") === false
})
} else {
return Array.from(userCells)
}
}
export const insertReloadEl = (clickAction: () => void) => {
const lastInsertedEl = Array.from(document.querySelectorAll('.bsky-user-content')).at(-1)
lastInsertedEl.insertAdjacentHTML('afterend', `
<div class="bsky-reload-btn-wrapper">
<button class="bsky-reload-btn">
Find More
</button>
</div>
`)
const reloadBtn = document.querySelector(".bsky-reload-btn") as HTMLElement
reloadBtn.addEventListener("click", async (e) => {
const target = e.target as HTMLButtonElement
if (target.classList.contains('bsky-reload-btn__processing')) {
return
}
await clickAction()
})
}
export const removeReloadElIfExists = () => {
const reloadBtnWrapper = document.querySelector(".bsky-reload-btn-wrapper") as HTMLElement
reloadBtnWrapper?.remove()
}
export const getAccountNameAndDisplayName = (userCell: Element) => {
const [avatarEl, displayNameEl] = userCell?.querySelectorAll("a")
const twAccountName = avatarEl?.getAttribute("href")?.replace("/", "")
const twDisplayName = displayNameEl?.textContent
return { twAccountName, twDisplayName }
}
export const insertBskyProfileEl = ({ dom, profile, abortController, clickAction }: { dom: Element, profile: ProfileView, abortController: AbortController, clickAction: () => void }) => {
const avatarEl = profile.avatar ? `<img src="${profile.avatar}" width="48" />` : "<div class='no-avatar'></div>"
const followButtonEl = profile.viewer?.following ? "<button class='follow-button follow-button__following'>Following on Bluesky</button>" : "<button class='follow-button'>Follow on Bluesky</button>"
dom.insertAdjacentHTML('afterend', `
<div class="bsky-user-content">
<div class="icon-section">
<a href="https://bsky.app/profile/${profile.handle}" target="_blank" rel="noopener">
${avatarEl}
</a>
</div>
<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">${profile.displayName ?? profile.handle}</a></p>
<p class="handle">@${profile.handle}</p>
</div>
<div>
${followButtonEl}
</div>
</div>
${profile.description ? `<p class="description">${profile.description}</p>` : ""}
</div>
<simple-greeting></simple-greeting>
</div>
`)
dom.nextElementSibling?.addEventListener('click', async (e) => {
// TODO: Add unfollow action
const target = e.target as Element
const classList = target.classList
if (classList.contains('follow-button') && !classList.contains('follow-button__following')) {
target.textContent = "processing..."
target.classList.add('follow-button__processing')
await clickAction()
target.textContent = "Following on Bluesky"
target.classList.remove('follow-button__processing')
target.classList.add('follow-button__following')
}
}, {
signal: abortController.signal
})
}
export const insertNotFoundEl = (dom: Element) => {
dom.insertAdjacentHTML('afterend', `
<div class="bsky-user-content bsky-user-content__not-found">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><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" /></svg>
<p class="not-found">No similar users found.</p>
</div>
`)
}
export const cleanBskyUserElements = () => {
const bskyUserContent = document.querySelectorAll('.bsky-user-content');
if (bskyUserContent.length > 0) {
bskyUserContent.forEach((el) => {
el.remove()
})
}
}

5
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,5 @@
export const debugLog = (message: string) => {
if(process.env.NODE_ENV === "development") {
console.log(`🔷 [Sky Follower Bridge] ${message}`)
}
}