From b0f932445c94df031d19843d21ff2c8b621978f4 Mon Sep 17 00:00:00 2001
From: jude
Date: Sun, 31 Mar 2024 12:49:52 +0100
Subject: [PATCH] Add server emoji picker
---
reminder-dashboard/src/api.ts | 14 ++++
.../src/components/App/Mentions.tsx | 12 ++-
.../src/components/Guild/GuildError.tsx | 1 +
src/web/mod.rs | 1 +
src/web/routes/dashboard/api/guild/emojis.rs | 84 +++++++++++++++++++
src/web/routes/dashboard/api/guild/mod.rs | 6 +-
6 files changed, 114 insertions(+), 4 deletions(-)
create mode 100644 src/web/routes/dashboard/api/guild/emojis.rs
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},