web: add giphy provider for gif search
All checks were successful
CI / test (push) Successful in 21s
All checks were successful
CI / test (push) Successful in 21s
This commit is contained in:
@@ -3,3 +3,4 @@ VITE_WS_URL=ws://localhost:8000/api/v1/realtime/ws
|
|||||||
VITE_GIF_PROVIDER=
|
VITE_GIF_PROVIDER=
|
||||||
VITE_TENOR_API_KEY=
|
VITE_TENOR_API_KEY=
|
||||||
VITE_TENOR_CLIENT_KEY=benya_messenger_web
|
VITE_TENOR_CLIENT_KEY=benya_messenger_web
|
||||||
|
VITE_GIPHY_API_KEY=
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ const GIF_FAVORITES_KEY = "bm_gif_favorites_v1";
|
|||||||
const GIF_PROVIDER = (import.meta.env.VITE_GIF_PROVIDER ?? "").toLowerCase();
|
const GIF_PROVIDER = (import.meta.env.VITE_GIF_PROVIDER ?? "").toLowerCase();
|
||||||
const TENOR_API_KEY = (import.meta.env.VITE_TENOR_API_KEY ?? "").trim();
|
const TENOR_API_KEY = (import.meta.env.VITE_TENOR_API_KEY ?? "").trim();
|
||||||
const TENOR_CLIENT_KEY = (import.meta.env.VITE_TENOR_CLIENT_KEY ?? "benya_messenger_web").trim();
|
const TENOR_CLIENT_KEY = (import.meta.env.VITE_TENOR_CLIENT_KEY ?? "benya_messenger_web").trim();
|
||||||
const GIF_SEARCH_ENABLED = GIF_PROVIDER === "tenor" && TENOR_API_KEY.length > 0;
|
const GIPHY_API_KEY = (import.meta.env.VITE_GIPHY_API_KEY ?? "").trim();
|
||||||
|
const GIF_SEARCH_ENABLED =
|
||||||
|
(GIF_PROVIDER === "tenor" && TENOR_API_KEY.length > 0) ||
|
||||||
|
(GIF_PROVIDER === "giphy" && GIPHY_API_KEY.length > 0);
|
||||||
|
|
||||||
function loadFavorites(key: string): Set<string> {
|
function loadFavorites(key: string): Set<string> {
|
||||||
try {
|
try {
|
||||||
@@ -124,6 +127,67 @@ export function MessageComposer() {
|
|||||||
(activeChat.type !== "channel" || activeChat.my_role === "owner" || activeChat.my_role === "admin" || activeChat.is_saved)
|
(activeChat.type !== "channel" || activeChat.my_role === "owner" || activeChat.my_role === "admin" || activeChat.is_saved)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function searchGifs(term: string): Promise<Array<{ name: string; url: string }>> {
|
||||||
|
if (GIF_PROVIDER === "tenor") {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
q: term,
|
||||||
|
key: TENOR_API_KEY,
|
||||||
|
client_key: TENOR_CLIENT_KEY,
|
||||||
|
limit: "24",
|
||||||
|
media_filter: "gif",
|
||||||
|
contentfilter: "medium",
|
||||||
|
});
|
||||||
|
const response = await fetch(`https://tenor.googleapis.com/v2/search?${params.toString()}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("tenor search failed");
|
||||||
|
}
|
||||||
|
const data = (await response.json()) as {
|
||||||
|
results?: Array<{ content_description?: string; media_formats?: { gif?: { url?: string } } }>;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
data.results
|
||||||
|
?.map((item) => ({
|
||||||
|
name: item.content_description || "GIF",
|
||||||
|
url: item.media_formats?.gif?.url || "",
|
||||||
|
}))
|
||||||
|
.filter((item) => item.url.length > 0) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GIF_PROVIDER === "giphy") {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
api_key: GIPHY_API_KEY,
|
||||||
|
q: term,
|
||||||
|
limit: "24",
|
||||||
|
rating: "pg-13",
|
||||||
|
lang: "en",
|
||||||
|
});
|
||||||
|
const response = await fetch(`https://api.giphy.com/v1/gifs/search?${params.toString()}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("giphy search failed");
|
||||||
|
}
|
||||||
|
const data = (await response.json()) as {
|
||||||
|
data?: Array<{
|
||||||
|
title?: string;
|
||||||
|
images?: {
|
||||||
|
fixed_height?: { url?: string };
|
||||||
|
original?: { url?: string };
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
data.data
|
||||||
|
?.map((item) => ({
|
||||||
|
name: item.title || "GIF",
|
||||||
|
url: item.images?.fixed_height?.url || item.images?.original?.url || "",
|
||||||
|
}))
|
||||||
|
.filter((item) => item.url.length > 0) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
recordingStateRef.current = recordingState;
|
recordingStateRef.current = recordingState;
|
||||||
}, [recordingState]);
|
}, [recordingState]);
|
||||||
@@ -339,32 +403,8 @@ export function MessageComposer() {
|
|||||||
setGifSearchError(null);
|
setGifSearchError(null);
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams({
|
const rows = await searchGifs(term);
|
||||||
q: term,
|
|
||||||
key: TENOR_API_KEY,
|
|
||||||
client_key: TENOR_CLIENT_KEY,
|
|
||||||
limit: "24",
|
|
||||||
media_filter: "gif",
|
|
||||||
contentfilter: "medium",
|
|
||||||
});
|
|
||||||
const response = await fetch(`https://tenor.googleapis.com/v2/search?${params.toString()}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 400) {
|
|
||||||
throw new Error("GIF provider rejected the request. Configure your own API key.");
|
|
||||||
}
|
|
||||||
throw new Error("gif search failed");
|
|
||||||
}
|
|
||||||
const data = (await response.json()) as {
|
|
||||||
results?: Array<{ content_description?: string; media_formats?: { gif?: { url?: string } } }>;
|
|
||||||
};
|
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const rows =
|
|
||||||
data.results
|
|
||||||
?.map((item) => ({
|
|
||||||
name: item.content_description || "GIF",
|
|
||||||
url: item.media_formats?.gif?.url || "",
|
|
||||||
}))
|
|
||||||
.filter((item) => item.url.length > 0) ?? [];
|
|
||||||
setGifResults(rows);
|
setGifResults(rows);
|
||||||
} catch {
|
} catch {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
|
|||||||
Reference in New Issue
Block a user