diff --git a/reminder-dashboard/src/api.ts b/reminder-dashboard/src/api.ts index 4cae391..12141bb 100644 --- a/reminder-dashboard/src/api.ts +++ b/reminder-dashboard/src/api.ts @@ -59,6 +59,11 @@ type RoleInfo = { name: string; }; +type EmojiInfo = { + fmt: string; + name: string; +}; + type Template = { id: number; name: string; @@ -125,6 +130,15 @@ export const fetchGuildRoles = (guild: string) => ({ staleTime: GUILD_INFO_STALE_TIME, }); +export const fetchGuildEmojis = (guild: string) => ({ + queryKey: ["GUILD_EMOJIS", guild], + queryFn: () => + axios.get(`/dashboard/api/guild/${guild}/emojis`).then((resp) => resp.data) as Promise< + EmojiInfo[] + >, + staleTime: GUILD_INFO_STALE_TIME, +}); + export const fetchGuildReminders = (guild: string) => ({ queryKey: ["GUILD_REMINDERS", guild], queryFn: () => diff --git a/reminder-dashboard/src/components/App/Mentions.tsx b/reminder-dashboard/src/components/App/Mentions.tsx index 1c5e972..13de4c1 100644 --- a/reminder-dashboard/src/components/App/Mentions.tsx +++ b/reminder-dashboard/src/components/App/Mentions.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo } from "preact/hooks"; import { useQuery } from "react-query"; -import { fetchGuildChannels, fetchGuildRoles } from "../../api"; +import { fetchGuildChannels, fetchGuildRoles, fetchGuildEmojis } from "../../api"; import Tribute from "tributejs"; import { useGuild } from "./useGuild"; @@ -9,6 +9,7 @@ export const Mentions = ({ input }) => { const { data: roles } = useQuery(fetchGuildRoles(guild)); const { data: channels } = useQuery(fetchGuildChannels(guild)); + const { data: emojis } = useQuery(fetchGuildEmojis(guild)); const tribute = useMemo(() => { return new Tribute({ @@ -27,9 +28,16 @@ export const Mentions = ({ input }) => { selectTemplate: (item) => `<#${item.original.value}>`, menuItemTemplate: (item) => `#${item.original.key}`, }, + { + trigger: ":", + values: (emojis || []).map(({ fmt, name }) => ({ key: name, value: fmt })), + allowSpaces: true, + selectTemplate: (item) => item.original.value, + menuItemTemplate: (item) => `:${item.original.key}:`, + }, ], }); - }, [roles, channels]); + }, [roles, channels, emojis]); useEffect(() => { tribute.detach(input.current); diff --git a/reminder-dashboard/src/components/Guild/GuildError.tsx b/reminder-dashboard/src/components/Guild/GuildError.tsx index b90eb44..22fbaaf 100644 --- a/reminder-dashboard/src/components/Guild/GuildError.tsx +++ b/reminder-dashboard/src/components/Guild/GuildError.tsx @@ -8,6 +8,7 @@ export const GuildError = () => { The bot may have just been restarted, in which case please try again in a few minutes.
+
Otherwise, please check Reminder Bot is in the server, and has correct permissions.

diff --git a/src/web/mod.rs b/src/web/mod.rs index ea6991f..2e1ab86 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -138,6 +138,7 @@ pub async fn initialize( routes::dashboard::api::guild::get_guild_info, routes::dashboard::api::guild::get_guild_channels, routes::dashboard::api::guild::get_guild_roles, + routes::dashboard::api::guild::get_guild_emojis, routes::dashboard::api::guild::get_reminder_templates, routes::dashboard::api::guild::create_reminder_template, routes::dashboard::api::guild::delete_reminder_template, diff --git a/src/web/routes/dashboard/api/guild/emojis.rs b/src/web/routes/dashboard/api/guild/emojis.rs new file mode 100644 index 0000000..f63611a --- /dev/null +++ b/src/web/routes/dashboard/api/guild/emojis.rs @@ -0,0 +1,84 @@ +use std::{collections::HashMap, sync::OnceLock, time::Instant}; + +use log::{info, warn}; +use rocket::{get, http::CookieJar, serde::json::json, State}; +use serde::Serialize; +use serenity::{client::Context, model::id::GuildId}; +use tokio::sync::RwLock; + +use crate::web::{check_authorization, routes::JsonResult}; + +#[derive(Serialize, Clone)] +struct EmojiInfo { + fmt: String, + name: String, +} + +#[derive(Clone)] +struct EmojiCache { + emojis: Vec, + timestamp: Instant, +} + +const CACHE_LENGTH: u64 = 120; + +static EMOJI_CACHE: OnceLock>> = OnceLock::new(); + +#[get("/api/guild//emojis")] +pub async fn get_guild_emojis( + id: u64, + cookies: &CookieJar<'_>, + ctx: &State, +) -> JsonResult { + offline!(Ok(json!(vec![] as Vec))); + check_authorization(cookies, ctx.inner(), id).await?; + + let cache_value = { + let cache = EMOJI_CACHE.get_or_init(|| RwLock::new(HashMap::new())); + let read_lock = cache.read().await; + read_lock.get(&GuildId::new(id)).cloned() + }; + + if let Some(emojis) = cache_value + .map(|v| { + if Instant::now().duration_since(v.timestamp).as_secs() < CACHE_LENGTH { + Some(v.emojis) + } else { + None + } + }) + .flatten() + { + Ok(json!(emojis)) + } else { + let emojis_res = ctx.http.get_emojis(GuildId::new(id)).await; + + match emojis_res { + Ok(emojis) => { + let emojis = emojis + .iter() + .map(|emoji| EmojiInfo { + fmt: format!("{}", emoji), + name: emoji.name.to_string(), + }) + .collect::>(); + + { + let cache = EMOJI_CACHE.get_or_init(|| RwLock::new(HashMap::new())); + let mut write_lock = cache.write().await; + write_lock.insert( + GuildId::new(id), + EmojiCache { emojis: emojis.clone(), timestamp: Instant::now() }, + ); + } + + Ok(json!(emojis)) + } + Err(e) => { + warn!("Could not fetch emojis from {}: {:?}", id, e); + + json_err!("Could not get emojis") + } + } + } +} diff --git a/src/web/routes/dashboard/api/guild/mod.rs b/src/web/routes/dashboard/api/guild/mod.rs index 07fb133..f5c2914 100644 --- a/src/web/routes/dashboard/api/guild/mod.rs +++ b/src/web/routes/dashboard/api/guild/mod.rs @@ -1,14 +1,16 @@ mod channels; +mod emojis; mod reminders; mod roles; mod templates; use std::env; -pub use channels::*; +pub use channels::get_guild_channels; +pub use emojis::get_guild_emojis; pub use reminders::*; use rocket::{get, http::CookieJar, serde::json::json, State}; -pub use roles::*; +pub use roles::get_guild_roles; use serenity::{ client::Context, model::id::{GuildId, RoleId},