Skip to content

Commit

Permalink
feat: added a comparison filter for avatar images
Browse files Browse the repository at this point in the history
  • Loading branch information
kawamataryo committed Feb 2, 2025
1 parent e4329f4 commit 39e76a3
Show file tree
Hide file tree
Showing 22 changed files with 471 additions and 132 deletions.
12 changes: 12 additions & 0 deletions locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "Erneut suchen",
"description": "Tooltip-Text für den Erneut suchen Button"
},
"text_avatar_is_similar": {
"message": "✅ Ähnlicher Avatar",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "Unähnliche Avatare einschließen",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "Avatar unähnlich",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "Search again",
"description": "Tooltip text for the search again button"
},
"text_avatar_is_similar": {
"message": "✅ Similar avatar",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "Include non-similar avatars",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "Avatar not similar",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/es/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "Buscar de nuevo",
"description": "Texto de la descripción del botón buscar de nuevo"
},
"text_avatar_is_similar": {
"message": "✅ Avatar similar",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "Incluir avatares no similares",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "Avatar no similar",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/fr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "Rechercher à nouveau",
"description": "Texte de l'info-bulle pour le bouton de recherche à nouveau"
},
"text_avatar_is_similar": {
"message": "✅ Avatar similaire",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "Inclure avatars non similaires",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "Avatar non similaire",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/it/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "Cerca di nuovo",
"description": "Testo della descrizione del pulsante cerca di nuovo"
},
"text_avatar_is_similar": {
"message": "✅ Avatar simile",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "Includi avatar non simili",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "Avatar non simile",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/ja/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "再検索",
"description": "再検索ボタンのツールチップテキスト"
},
"text_avatar_is_similar": {
"message": "✅ アバター類似",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "アバターが異なるユーザーも含める",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "アバターが異なる",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/ko/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "다시 검색",
"description": "다시 검색 버튼의 도구 설명 텍스트"
},
"text_avatar_is_similar": {
"message": "✅ 아바타 유사",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "비유사 아바타 포함",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "아바타 비유사",
"description": "Label for the filter type when avatar is not similar"
}
}
12 changes: 12 additions & 0 deletions locales/pt_BR/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "Pesquisar novamente",
"description": "Texto da dica de ferramenta para o botão de pesquisar novamente"
},
"text_avatar_is_similar": {
"message": "✅ Avatar semelhante",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "Incluir avatares não semelhantes",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "Avatar não semelhante",
"description": "Label for the filter type when avatar is not similar"
}
}
4 changes: 4 additions & 0 deletions locales/pt_PT/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,9 @@
"share_on_bluesky": {
"message": "Partilhar no Bluesky",
"description": "Text for the share on Bluesky button"
},
"text_avatar_is_similar": {
"message": "✅ Avatar semelhante",
"description": "Text shown when the avatar is similar to the source user"
}
}
12 changes: 12 additions & 0 deletions locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,17 @@
"search_again": {
"message": "再次搜索",
"description": "再次搜索按钮的工具提示文本"
},
"text_avatar_is_similar": {
"message": "✅ 头像相似",
"description": "Text shown when the avatar is similar to the source user"
},
"include_non_avatar_similar_users": {
"message": "包含非相似头像",
"description": "Label for the checkbox to include users whose avatars are not similar"
},
"filter_type_avatar_not_similar": {
"message": "头像不相似",
"description": "Label for the filter type when avatar is not similar"
}
}
6 changes: 3 additions & 3 deletions server/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { googleFont } from "./lib/getFonts";
import { OG_FONT } from "./constants";

const app = new Hono();
const cacheSeconds = 60 * 60 * 24;
const cacheSeconds = 60 * 60 * 24 * 7;

app.use(
"*",
Expand Down Expand Up @@ -65,12 +65,12 @@ app.on(["GET", "OPTIONS"], "/proxy", async (c) => {

try {
const response = await fetch(targetUrl);
const data = await response.text();
const data = await response.arrayBuffer();

return new Response(data, {
status: response.status,
headers: {
"Content-Type": response.headers.get("Content-Type") || "text/plain",
"Content-Type": response.headers.get("Content-Type") || "application/octet-stream",
"Access-Control-Allow-Origin": "*",
"Cache-Control": "public, max-age=300"
}
Expand Down
20 changes: 20 additions & 0 deletions src/background/messages/getImageSimilarityScore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { PlasmoMessaging } from "@plasmohq/messaging";
import { getImageSimilarityScore } from "~lib/getImageSimilarityScore";

const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
const { url1, url2 } = req.body;
try {
const result = await getImageSimilarityScore(url1, url2);
res.send({
result,
});
} catch (e) {
res.send({
error: {
message: e.message,
},
});
}
};

export default handler;
31 changes: 25 additions & 6 deletions src/components/DetectedUserListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React from "react";
import { match } from "ts-pattern";
import { ACTION_MODE, MATCH_TYPE_LABEL_AND_COLOR } from "~lib/constants";
import {
ACTION_MODE,
AVATAR_SIMILARITY_SCORE_THRESHOLD,
FILTER_TYPE_LABEL_AND_COLOR,
} from "~lib/constants";
import type { BskyUser } from "~types";
import DetectedUserSource from "./DetectedUserSource";
import UserCard from "./UserCard";
Expand Down Expand Up @@ -105,17 +109,25 @@ const DetectedUserListItem = ({
deleteUser(user.did);
};

const matchTypeColor = MATCH_TYPE_LABEL_AND_COLOR[user.matchType].color;
const matchTypeColor = FILTER_TYPE_LABEL_AND_COLOR[user.matchType].color;

return (
<div>
<div className={`w-full border-l-8 border-${matchTypeColor}`}>
<div
className={`w-full border-t border-gray-500 text-${matchTypeColor} grid grid-cols-[22%_1fr] text-xs`}
>
<div className="px-3 bg-slate-100 dark:bg-slate-800" />
<div className="px-3">
{MATCH_TYPE_LABEL_AND_COLOR[user.matchType].label}
<div className="px-3 py-1 bg-slate-100 dark:bg-slate-800">
{user.avatarSimilarityScore > AVATAR_SIMILARITY_SCORE_THRESHOLD ? (
<div className="text-base-content">
{chrome.i18n.getMessage("text_avatar_is_similar")}
</div>
) : (
""
)}
</div>
<div className="px-3 py-1">
{FILTER_TYPE_LABEL_AND_COLOR[user.matchType].label}
</div>
</div>
<div className="bg-base-100 w-full relative grid grid-cols-[22%_1fr] gap-5">
Expand All @@ -136,4 +148,11 @@ const DetectedUserListItem = ({
);
};

export default DetectedUserListItem;
export default React.memo(DetectedUserListItem, (prevProps, nextProps) => {
return (
prevProps.user.did === nextProps.user.did &&
prevProps.actionMode === nextProps.actionMode &&
prevProps.user.isFollowing === nextProps.user.isFollowing &&
prevProps.user.isBlocking === nextProps.user.isBlocking
);
});
59 changes: 44 additions & 15 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import { match } from "ts-pattern";
import {
ACTION_MODE,
BSKY_USER_MATCH_TYPE,
MATCH_TYPE_LABEL_AND_COLOR,
FILTER_TYPE,
FILTER_TYPE_LABEL_AND_COLOR,
} from "~lib/constants";
import { getMessageWithLink } from "~lib/utils";
import type { MatchType, MatchTypeFilterValue } from "~types";
import type { FilterType, FilterValue, MatchType } from "~types";
import AsyncButton from "./AsyncButton";
import { ShareButton } from "./ShareButton";
import SocialLinks from "./SocialLinks";

type Props = {
detectedCount: number;
filterValue: MatchTypeFilterValue;
onChangeFilter: (key: MatchType) => void;
filterValue: FilterValue;
onChangeFilter: (key: FilterType) => void;
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
matchTypeStats: Record<Exclude<MatchType, "none">, number>;
importList: () => Promise<void>;
followAll: () => Promise<void>;
blockAll: () => Promise<void>;
importList: ({
includeNonAvatarSimilarUsers,
}: { includeNonAvatarSimilarUsers: boolean }) => Promise<void>;
followAll: ({
includeNonAvatarSimilarUsers,
}: { includeNonAvatarSimilarUsers: boolean }) => Promise<void>;
blockAll: ({
includeNonAvatarSimilarUsers,
}: { includeNonAvatarSimilarUsers: boolean }) => Promise<void>;
};

const Sidebar = ({
Expand All @@ -35,6 +42,8 @@ const Sidebar = ({
const shareText = chrome.i18n.getMessage("share_text", [
detectedCount.toString(),
]);
const [includeNonAvatarSimilarUsers, setIncludeNonAvatarSimilarUsers] =
React.useState(false);

return (
<aside className="bg-base-300 w-80 min-h-screen p-4 border-r border-base-300 flex flex-col">
Expand Down Expand Up @@ -122,14 +131,17 @@ const Sidebar = ({
</svg>
<p className="text-xl font-bold">Filter</p>
</div>
{Object.keys(filterValue).map((key: MatchType) => (
{Object.keys(filterValue).map((key: FilterType) => (
<div className="form-control" key={key}>
<label htmlFor={key} className="label cursor-pointer">
<span className="text-sm">
{key === BSKY_USER_MATCH_TYPE.FOLLOWING &&
{key === FILTER_TYPE.FOLLOWING &&
actionMode === ACTION_MODE.BLOCK
? chrome.i18n.getMessage("blocked_user")
: MATCH_TYPE_LABEL_AND_COLOR[key].label}
: FILTER_TYPE_LABEL_AND_COLOR[key].label}
{key === FILTER_TYPE.AVATAR_NOT_SIMILAR && (
<span className="ml-2 badge badge-sm badge-accent">New!</span>
)}{" "}
</span>
<input
type="checkbox"
Expand Down Expand Up @@ -159,30 +171,47 @@ const Sidebar = ({
</svg>
<p className="text-xl font-bold">Action</p>
</div>
<label
htmlFor="include_non_avatar_similar_users"
className="label cursor-pointer mb-1 px-5"
>
<span className="text-xs">
{chrome.i18n.getMessage("include_non_avatar_similar_users")}
</span>
<input
type="checkbox"
id="include_non_avatar_similar_users"
checked={includeNonAvatarSimilarUsers}
onChange={() =>
setIncludeNonAvatarSimilarUsers(!includeNonAvatarSimilarUsers)
}
className="checkbox checkbox-primary checkbox-xs"
/>
</label>
<div className="flex flex-col gap-2 items-center">
{match(actionMode)
.with(ACTION_MODE.FOLLOW, () => (
<AsyncButton
onClick={followAll}
onClick={() => followAll({ includeNonAvatarSimilarUsers })}
label={chrome.i18n.getMessage("follow_all")}
/>
))
.with(ACTION_MODE.BLOCK, () => (
<AsyncButton
onClick={blockAll}
onClick={() => blockAll({ includeNonAvatarSimilarUsers })}
label={chrome.i18n.getMessage("block_all")}
/>
))
.with(ACTION_MODE.IMPORT_LIST, () => (
<>
<AsyncButton
onClick={importList}
onClick={() => importList({ includeNonAvatarSimilarUsers })}
label={chrome.i18n.getMessage("import_list")}
/>
<AsyncButton
onClick={followAll}
onClick={() => importList({ includeNonAvatarSimilarUsers })}
className="btn-primary"
label={chrome.i18n.getMessage("follow_all")}
label={chrome.i18n.getMessage("import_list")}
/>
</>
))
Expand Down
Loading

0 comments on commit 39e76a3

Please sign in to comment.