feat: add /set_welcome command
This commit is contained in:
parent
239928a22a
commit
59bf42998b
7 changed files with 286 additions and 51 deletions
|
@ -1,17 +1,8 @@
|
|||
mod help;
|
||||
mod joke;
|
||||
mod members;
|
||||
mod ping;
|
||||
mod rory;
|
||||
mod say;
|
||||
mod stars;
|
||||
mod tag;
|
||||
|
||||
pub use help::help;
|
||||
pub use joke::joke;
|
||||
pub use members::members;
|
||||
pub use ping::ping;
|
||||
pub use rory::rory;
|
||||
pub use say::say;
|
||||
pub use stars::stars;
|
||||
pub use tag::tag;
|
||||
pub mod help;
|
||||
pub mod joke;
|
||||
pub mod members;
|
||||
pub mod ping;
|
||||
pub mod rory;
|
||||
pub mod say;
|
||||
pub mod stars;
|
||||
pub mod tag;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::Context;
|
||||
use crate::{utils, Context};
|
||||
|
||||
use eyre::{OptionExt, Result};
|
||||
use log::trace;
|
||||
use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage};
|
||||
use poise::serenity_prelude::{CreateEmbed, CreateMessage};
|
||||
|
||||
/// Say something through the bot
|
||||
#[poise::command(
|
||||
|
@ -17,7 +17,6 @@ pub async fn say(
|
|||
ctx: Context<'_>,
|
||||
#[description = "the message content"] content: String,
|
||||
) -> Result<()> {
|
||||
let guild = ctx.guild().ok_or_eyre("Couldn't get guild!")?.to_owned();
|
||||
let channel = ctx
|
||||
.guild_channel()
|
||||
.await
|
||||
|
@ -41,19 +40,9 @@ pub async fn say(
|
|||
.clone()
|
||||
.discord_config()
|
||||
.channels()
|
||||
.say_log_channel_id()
|
||||
.log_channel_id()
|
||||
{
|
||||
let log_channel = guild
|
||||
.channels
|
||||
.iter()
|
||||
.find(|c| c.0 == &channel_id)
|
||||
.ok_or_eyre("Couldn't get log channel from guild!")?;
|
||||
|
||||
let author = CreateEmbedAuthor::new(ctx.author().tag()).icon_url(
|
||||
ctx.author()
|
||||
.avatar_url()
|
||||
.unwrap_or_else(|| ctx.author().default_avatar_url()),
|
||||
);
|
||||
let author = utils::embed_author_from_user(ctx.author());
|
||||
|
||||
let embed = CreateEmbed::default()
|
||||
.title("Say command used!")
|
||||
|
@ -61,7 +50,7 @@ pub async fn say(
|
|||
.author(author);
|
||||
|
||||
let message = CreateMessage::new().embed(embed);
|
||||
log_channel.1.send_message(ctx, message).await?;
|
||||
channel_id.send_message(ctx, message).await?;
|
||||
} else {
|
||||
trace!("Not sending /say log as no channel is set");
|
||||
}
|
||||
|
|
|
@ -3,18 +3,47 @@ use crate::Data;
|
|||
use eyre::Report;
|
||||
|
||||
mod general;
|
||||
mod moderation;
|
||||
|
||||
macro_rules! command {
|
||||
($module: ident, $name: ident) => {
|
||||
$module::$name::$name()
|
||||
};
|
||||
|
||||
($module: ident, $name: ident, $func: ident) => {
|
||||
$module::$name::$func()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! module_macro {
|
||||
($module: ident) => {
|
||||
macro_rules! $module {
|
||||
($name: ident) => {
|
||||
command!($module, $name)
|
||||
};
|
||||
|
||||
($name: ident, $func: ident) => {
|
||||
command!($module, $name, $func)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module_macro!(general);
|
||||
module_macro!(moderation);
|
||||
|
||||
pub type Command = poise::Command<Data, Report>;
|
||||
|
||||
pub fn get() -> Vec<Command> {
|
||||
vec![
|
||||
general::joke(),
|
||||
general::members(),
|
||||
general::ping(),
|
||||
general::rory(),
|
||||
general::say(),
|
||||
general::stars(),
|
||||
general::tag(),
|
||||
general::help(),
|
||||
general!(help),
|
||||
general!(joke),
|
||||
general!(members),
|
||||
general!(ping),
|
||||
general!(rory),
|
||||
general!(say),
|
||||
general!(stars),
|
||||
general!(tag),
|
||||
moderation!(set_welcome),
|
||||
]
|
||||
}
|
||||
|
|
1
src/commands/moderation/mod.rs
Normal file
1
src/commands/moderation/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod set_welcome;
|
203
src/commands/moderation/set_welcome.rs
Normal file
203
src/commands/moderation/set_welcome.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use std::{fmt::Write, str::FromStr};
|
||||
|
||||
use crate::{utils, Context};
|
||||
|
||||
use eyre::{bail, Result};
|
||||
use log::trace;
|
||||
use poise::serenity_prelude::{
|
||||
futures::StreamExt, Attachment, CreateActionRow, CreateButton, CreateEmbed, CreateMessage,
|
||||
Mentionable, ReactionType,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WelcomeEmbed {
|
||||
title: String,
|
||||
description: Option<String>,
|
||||
url: Option<Url>,
|
||||
hex_color: Option<String>,
|
||||
image: Option<Url>,
|
||||
}
|
||||
|
||||
impl From<WelcomeEmbed> for CreateMessage {
|
||||
fn from(val: WelcomeEmbed) -> Self {
|
||||
let mut embed = CreateEmbed::new();
|
||||
|
||||
embed = embed.title(val.title);
|
||||
if let Some(description) = val.description {
|
||||
embed = embed.description(description);
|
||||
}
|
||||
|
||||
if let Some(url) = val.url {
|
||||
embed = embed.url(url);
|
||||
}
|
||||
|
||||
if let Some(color) = val.hex_color {
|
||||
let hex = i32::from_str_radix(&color, 16).unwrap();
|
||||
embed = embed.color(hex);
|
||||
}
|
||||
|
||||
if let Some(image) = val.image {
|
||||
embed = embed.image(image);
|
||||
}
|
||||
|
||||
Self::new().embed(embed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WelcomeRole {
|
||||
title: String,
|
||||
id: u64,
|
||||
emoji: Option<String>,
|
||||
}
|
||||
|
||||
impl From<WelcomeRole> for CreateButton {
|
||||
fn from(value: WelcomeRole) -> Self {
|
||||
let mut button = Self::new(value.id.to_string()).label(value.title);
|
||||
if let Some(emoji) = value.emoji {
|
||||
button = button.emoji(ReactionType::from_str(&emoji).unwrap());
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WelcomeRoleCategory {
|
||||
title: String,
|
||||
description: Option<String>,
|
||||
roles: Vec<WelcomeRole>,
|
||||
}
|
||||
|
||||
impl From<WelcomeRoleCategory> for CreateMessage {
|
||||
fn from(value: WelcomeRoleCategory) -> Self {
|
||||
let mut content = format!("**{}**", value.title);
|
||||
if let Some(description) = value.description {
|
||||
write!(content, "\n{description}").ok();
|
||||
}
|
||||
|
||||
let buttons: Vec<CreateButton> = value
|
||||
.roles
|
||||
.iter()
|
||||
.map(|role| CreateButton::from(role.clone()))
|
||||
.collect();
|
||||
|
||||
let components = vec![CreateActionRow::Buttons(buttons)];
|
||||
Self::new().content(content).components(components)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WelcomeLayout {
|
||||
embeds: Vec<WelcomeEmbed>,
|
||||
messages: Vec<String>,
|
||||
roles: Vec<WelcomeRoleCategory>,
|
||||
}
|
||||
|
||||
/// Sets your welcome channel info
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
guild_only,
|
||||
ephemeral,
|
||||
default_member_permissions = "MANAGE_GUILD",
|
||||
required_permissions = "MANAGE_GUILD"
|
||||
)]
|
||||
pub async fn set_welcome(
|
||||
ctx: Context<'_>,
|
||||
#[description = "A file to use"] file: Option<Attachment>,
|
||||
#[description = "A URL for a file to use"] url: Option<String>,
|
||||
) -> Result<()> {
|
||||
trace!("Running set_welcome command!");
|
||||
|
||||
let configured_channels = ctx.data().config.clone().discord_config().channels();
|
||||
let Some(channel_id) = configured_channels.welcome_channel_id() else {
|
||||
ctx.say("You don't have a welcome channel ID set, so I can't do anything :(")
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
ctx.defer_ephemeral().await?;
|
||||
|
||||
// download attachment from discord or URL
|
||||
let file = if let Some(attachment) = file {
|
||||
let Some(content_type) = &attachment.content_type else {
|
||||
bail!("Welcome channel attachment was sent without a content type!");
|
||||
};
|
||||
|
||||
if !content_type.starts_with("application/json;") {
|
||||
trace!("Not attempting to read non-json content type {content_type}");
|
||||
ctx.say("Invalid file! Please only send json").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let downloaded = attachment.download().await?;
|
||||
String::from_utf8(downloaded)?
|
||||
} else if let Some(url) = url {
|
||||
if Url::parse(&url).is_err() {
|
||||
ctx.say("Invalid url!").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
utils::text_from_url(&url).await?
|
||||
} else {
|
||||
ctx.say("A text file or URL must be provided!").await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// parse and create messages from file
|
||||
let welcome_layout: WelcomeLayout = serde_json::from_str(&file)?;
|
||||
let embed_messages: Vec<CreateMessage> = welcome_layout
|
||||
.embeds
|
||||
.iter()
|
||||
.map(|e| CreateMessage::from(e.clone()))
|
||||
.collect();
|
||||
let roles_messages: Vec<CreateMessage> = welcome_layout
|
||||
.roles
|
||||
.iter()
|
||||
.map(|c| CreateMessage::from(c.clone()))
|
||||
.collect();
|
||||
|
||||
// clear previous messages
|
||||
let mut prev_messages = channel_id.messages_iter(ctx).boxed();
|
||||
while let Some(prev_message) = prev_messages.next().await {
|
||||
if let Ok(message) = prev_message {
|
||||
message.delete(ctx).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// send our new ones
|
||||
for embed in embed_messages {
|
||||
channel_id.send_message(ctx, embed).await?;
|
||||
}
|
||||
|
||||
for message in roles_messages {
|
||||
channel_id.send_message(ctx, message).await?;
|
||||
}
|
||||
|
||||
for message in welcome_layout.messages {
|
||||
channel_id.say(ctx, message).await?;
|
||||
}
|
||||
|
||||
if let Some(log_channel) = configured_channels.log_channel_id() {
|
||||
let author = utils::embed_author_from_user(ctx.author());
|
||||
let embed = CreateEmbed::new()
|
||||
.title("set_welcome command used!")
|
||||
.author(author);
|
||||
let message = CreateMessage::new().embed(embed);
|
||||
|
||||
log_channel.send_message(ctx, message).await?;
|
||||
} else {
|
||||
trace!("Not sending /set_welcome log as no channel is set");
|
||||
}
|
||||
|
||||
ctx.reply(format!("Updated {}!", channel_id.mention()))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -5,7 +5,8 @@ use poise::serenity_prelude::ChannelId;
|
|||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct RefractionChannels {
|
||||
say_log_channel_id: Option<ChannelId>,
|
||||
log_channel_id: Option<ChannelId>,
|
||||
welcome_channel_id: Option<ChannelId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -14,20 +15,29 @@ pub struct Config {
|
|||
}
|
||||
|
||||
impl RefractionChannels {
|
||||
pub fn new(say_log_channel_id: Option<ChannelId>) -> Self {
|
||||
Self { say_log_channel_id }
|
||||
pub fn new(log_channel_id: Option<ChannelId>, welcome_channel_id: Option<ChannelId>) -> Self {
|
||||
Self {
|
||||
log_channel_id,
|
||||
welcome_channel_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_env() -> Self {
|
||||
let say_log_channel_id = Self::get_channel_from_env("DISCORD_SAY_LOG_CHANNELID");
|
||||
|
||||
if let Some(channel_id) = say_log_channel_id {
|
||||
let log_channel_id = Self::get_channel_from_env("DISCORD_LOG_CHANNEL_ID");
|
||||
if let Some(channel_id) = log_channel_id {
|
||||
info!("Log channel is {channel_id}");
|
||||
} else {
|
||||
warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server.");
|
||||
warn!("DISCORD_LOG_CHANNEL_ID is empty; this will disable logging in your server.");
|
||||
}
|
||||
|
||||
Self::new(say_log_channel_id)
|
||||
let welcome_channel_id = Self::get_channel_from_env("DISCORD_WELCOME_CHANNEL_ID");
|
||||
if let Some(channel_id) = welcome_channel_id {
|
||||
info!("Welcome channel is {channel_id}");
|
||||
} else {
|
||||
warn!("DISCORD_WELCOME_CHANNEL_ID is empty; this will disable welcome channel features in your server");
|
||||
}
|
||||
|
||||
Self::new(log_channel_id, welcome_channel_id)
|
||||
}
|
||||
|
||||
fn get_channel_from_env(var: &str) -> Option<ChannelId> {
|
||||
|
@ -36,8 +46,12 @@ impl RefractionChannels {
|
|||
.and_then(|env_var| ChannelId::from_str(&env_var).ok())
|
||||
}
|
||||
|
||||
pub fn say_log_channel_id(self) -> Option<ChannelId> {
|
||||
self.say_log_channel_id
|
||||
pub fn log_channel_id(self) -> Option<ChannelId> {
|
||||
self.log_channel_id
|
||||
}
|
||||
|
||||
pub fn welcome_channel_id(self) -> Option<ChannelId> {
|
||||
self.welcome_channel_id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::api::REQWEST_CLIENT;
|
|||
|
||||
use eyre::Result;
|
||||
use log::debug;
|
||||
use poise::serenity_prelude::{CreateEmbedAuthor, User};
|
||||
use reqwest::Response;
|
||||
|
||||
pub mod resolve_message;
|
||||
|
@ -27,3 +28,10 @@ pub async fn bytes_from_url(url: &str) -> Result<Vec<u8>> {
|
|||
let bytes = resp.bytes().await?;
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
|
||||
pub fn embed_author_from_user(user: &User) -> CreateEmbedAuthor {
|
||||
CreateEmbedAuthor::new(user.tag()).icon_url(
|
||||
user.avatar_url()
|
||||
.unwrap_or_else(|| user.default_avatar_url()),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue