refacotr: create service

This commit is contained in:
kawamataryo 2024-11-19 22:07:08 +09:00
parent e07fbf91c3
commit fb0b439137
4 changed files with 94 additions and 76 deletions

View File

@ -1,6 +1,6 @@
import React from "react";
import { BSKY_USER_MATCH_TYPE, MATCH_TYPE_LABEL_AND_COLOR } from "../constants";
import type { MatchType } from "../../types";
import { BSKY_USER_MATCH_TYPE, MATCH_TYPE_LABEL_AND_COLOR } from "../constants";
export type MatchTypeFilterValue = {
[BSKY_USER_MATCH_TYPE.DESCRIPTION]: boolean;

View File

@ -3,14 +3,9 @@ import { Storage } from "@plasmohq/storage";
import { useStorage } from "@plasmohq/storage/hook";
import React from "react";
import { BskyServiceWorkerClient } from "~lib/bskyServiceWorkerClient";
import {
type MESSAGE_NAMES,
MESSAGE_NAME_TO_QUERY_PARAM_MAP,
STORAGE_KEYS,
} from "~lib/constants";
import { extractUserData, getUserCells } from "~lib/domHelpers";
import { type MESSAGE_NAMES, STORAGE_KEYS } from "~lib/constants";
import { searchBskyUser } from "~lib/searchBskyUsers";
import { wait } from "~lib/utils";
import { XService } from "~lib/services/x";
import type { CrawledUserInfo, MatchType } from "~types";
export type BskyUser = {
@ -26,21 +21,8 @@ export type BskyUser = {
blockingUri: string | null;
};
const detectXUsers = (userCellQueryParam: string) => {
const userCells = getUserCells({
queryParam: userCellQueryParam,
filterInsertedElement: true,
});
return userCells.map((userCell) => {
return extractUserData(userCell);
});
};
export const useRetrieveBskyUsers = () => {
const bskyClient = React.useRef<BskyServiceWorkerClient | null>(null);
const [detectedXUsers, setDetectedXUsers] = React.useState<
ReturnType<typeof detectXUsers>
>([]);
const [users, setUsers] = useStorage<BskyUser[]>(
{
key: STORAGE_KEYS.DETECTED_BSKY_USERS,
@ -106,22 +88,7 @@ export const useRetrieveBskyUsers = () => {
let index = 0;
const queryParam = MESSAGE_NAME_TO_QUERY_PARAM_MAP[messageName];
let scrollElement: HTMLElement | Window;
let modalScrollInterval: number;
if (messageName === "search_bsky_user_on_list_members_page") {
// select the modal wrapper using viewport selector to avoid conflation with feed in the background
scrollElement = document.querySelector(
'div[data-viewportview="true"]',
) as HTMLElement;
// base interval off of intitial scroll height
modalScrollInterval = scrollElement.scrollHeight;
} else {
// for other cases, use the window, no need to cache a scroll interval due to different window scroll logic
scrollElement = window;
}
const xService = new XService(messageName);
// loop until we get to the bottom
while (!isBottomReached) {
@ -129,42 +96,15 @@ export const useRetrieveBskyUsers = () => {
break;
}
const data = detectXUsers(queryParam).filter((u) => {
return !detectedXUsers.some((t) => t.accountName === u.accountName);
});
setDetectedXUsers((prev) => [...prev, ...data]);
const data = xService.getCrawledUsers();
await retrieveBskyUsers(data);
// handle scrolling pattern for both modal and window
if (scrollElement instanceof HTMLElement) {
scrollElement.scrollTop += modalScrollInterval;
} else {
window.scrollTo(0, document.body.scrollHeight);
}
const isEnd = await xService.performScrollAndCheckEnd();
// wait for fetching data by x
await wait(3000);
// break if bottom is reached
if (scrollElement instanceof HTMLElement) {
if (
scrollElement.scrollTop + scrollElement.clientHeight >=
scrollElement.scrollHeight
) {
setIsBottomReached(true);
setLoading(false);
break;
}
} else {
const documentElement = document.documentElement;
if (
documentElement.scrollTop + documentElement.clientHeight >=
documentElement.scrollHeight
) {
setIsBottomReached(true);
setLoading(false);
break;
}
if (isEnd) {
setIsBottomReached(true);
setLoading(false);
break;
}
index++;
@ -174,13 +114,9 @@ export const useRetrieveBskyUsers = () => {
}
}
},
[retrieveBskyUsers, detectedXUsers, isBottomReached],
[retrieveBskyUsers, isBottomReached],
);
React.useEffect(() => {
chrome.storage.local.set({ users: JSON.stringify(users) });
}, [users]);
const stopRetrieveLoop = React.useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();

View File

@ -0,0 +1,80 @@
import { MESSAGE_NAMES } from "~lib/constants";
import { BSKY_DOMAIN, MESSAGE_NAME_TO_QUERY_PARAM_MAP } from "~lib/constants";
import { wait } from "~lib/utils";
import type { CrawledUserInfo, MessageName } from "~types";
export class XService {
// 対象のdomを取得する処理
messageName: MessageName;
crawledUsers: Set<string>;
constructor(messageName: string) {
// TODO: add type check
this.messageName = messageName as MessageName;
this.crawledUsers = new Set();
}
private 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,
};
}
getCrawledUsers(): CrawledUserInfo[] {
const userCells = Array.from(
document.querySelectorAll(
MESSAGE_NAME_TO_QUERY_PARAM_MAP[this.messageName],
),
);
const users = userCells
.map((userCell) => this.extractUserData(userCell))
.filter((user) => !this.crawledUsers.has(user.accountName));
this.crawledUsers = new Set([
...this.crawledUsers,
...users.map((user) => user.accountName),
]);
return users;
}
async performScrollAndCheckEnd(): Promise<boolean> {
const isListMembersPage =
this.messageName === MESSAGE_NAMES.SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE;
const scrollTarget = isListMembersPage
? (document.querySelector('div[data-viewportview="true"]') as HTMLElement)
: document.documentElement;
const initialScrollHeight = scrollTarget.scrollHeight;
scrollTarget.scrollTop += initialScrollHeight;
await wait(3000);
const hasReachedEnd =
scrollTarget.scrollTop + scrollTarget.clientHeight >=
scrollTarget.scrollHeight;
return hasReachedEnd;
}
}

View File

@ -1,8 +1,10 @@
import type { BSKY_USER_MATCH_TYPE } from "~lib/constants";
import type { BSKY_USER_MATCH_TYPE, MESSAGE_NAMES } from "~lib/constants";
export type MatchType =
(typeof BSKY_USER_MATCH_TYPE)[keyof typeof BSKY_USER_MATCH_TYPE];
export type MESSAGE_NAME = (typeof MESSAGE_NAMES)[keyof typeof MESSAGE_NAMES];
export type BskyUser = {
did: string;
avatar: string;