feat: embeds

This commit is contained in:
oSumAtrIX 2022-07-07 02:55:55 +02:00
parent 57693d39ed
commit 4293dd5518
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
3 changed files with 247 additions and 51 deletions

View File

@ -38,9 +38,9 @@
"$ref": "#/$defs/channels", "$ref": "#/$defs/channels",
"description": "A list of channel ids. The bot will only introduce in threads under these channels." "description": "A list of channel ids. The bot will only introduce in threads under these channels."
}, },
"message": { "response": {
"$ref": "#/$defs/message", "$ref": "#/$defs/response",
"description": "The message to send when the thread has been created." "description": "The response to send when the thread has been created."
} }
} }
}, },
@ -91,9 +91,9 @@
}, },
"description": "The conditions to respond to the message." "description": "The conditions to respond to the message."
}, },
"message": { "response": {
"$ref": "#/$defs/message", "$ref": "#/$defs/response",
"description": "The message to send when the message is responded to." "description": "The response to send when the message is responded to."
} }
}, },
"description": "The conditions to respond to a message." "description": "The conditions to respond to a message."
@ -128,8 +128,110 @@
"uniqueItems": true, "uniqueItems": true,
"minItems": 1 "minItems": 1
}, },
"embed": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the embed."
},
"description": {
"type": "string",
"description": "The description of the embed."
},
"color": {
"type": "integer",
"description": "The color of the embed."
},
"fields": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the field."
},
"value": {
"type": "string",
"description": "The value of the field."
},
"inline": {
"type": "boolean",
"description": "Whether the field is inline."
}
},
"description": "The field to add to the embed."
},
"description": "The fields to add to the embed."
},
"image": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The url of the image."
}
},
"description": "The image to add to the embed."
},
"thumbnail": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The url of the thumbnail."
}
},
"description": "The thumbnail to add to the embed."
},
"author": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the author."
},
"url": {
"type": "string",
"description": "The url of the author."
},
"icon_url": {
"type": "string",
"description": "The url of the author's icon."
}
},
"description": "The author to add to the embed."
},
"footer": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The text of the footer."
},
"icon_url": {
"type": "string",
"description": "The url of the footer's icon."
}
},
"description": "The footer to add to the embed."
}
}
},
"response": {
"type": "object",
"properties": {
"message": { "message": {
"type": "string" "type": "string",
"description": "The message. Can be empty if the embed is not empty"
},
"embed": {
"$ref": "#/$defs/embed",
"description": "The embed to send."
}
},
}
} }
} }
} }

View File

@ -26,7 +26,7 @@ pub struct Administrators {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Introduction { pub struct Introduction {
pub channels: Vec<u64>, pub channels: Vec<u64>,
pub message: String, pub response: Response,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -35,7 +35,64 @@ pub struct MessageResponder {
pub includes: Includes, pub includes: Includes,
pub excludes: Excludes, pub excludes: Excludes,
pub condition: Condition, pub condition: Condition,
pub message: String, pub response: Response,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Response {
pub message: Option<String>,
pub embed: Option<Embed>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Embed {
pub title: String,
pub description: String,
pub color: i32,
pub fields: Vec<Field>,
pub footer: Footer,
pub image: Image,
pub thumbnail: Thumbnail,
pub author: Author,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Field {
pub name: String,
pub value: String,
pub inline: bool,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Footer {
pub text: String,
#[serde(rename = "icon_url")]
pub icon_url: String,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Image {
pub url: String,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Thumbnail {
pub url: String,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Author {
pub name: String,
#[serde(rename = "icon_url")]
pub icon_url: String,
pub url: String,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]

View File

@ -2,13 +2,13 @@ use std::sync::Arc;
use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use configuration::BotConfiguration; use configuration::BotConfiguration;
use log::{error, info, trace, warn, LevelFilter}; use log::{error, info, trace, LevelFilter};
use logger::logging::SimpleLogger; use logger::logging::SimpleLogger;
use regex::Regex; use regex::Regex;
use serenity::client::{Context, EventHandler}; use serenity::client::{Context, EventHandler};
use serenity::model::application::command::Command;
use serenity::model::channel::{GuildChannel, Message}; use serenity::model::channel::{GuildChannel, Message};
use serenity::model::gateway::Ready; use serenity::model::gateway::Ready;
use serenity::model::prelude::command::Command;
use serenity::model::prelude::interaction::{Interaction, InteractionResponseType, MessageFlags}; use serenity::model::prelude::interaction::{Interaction, InteractionResponseType, MessageFlags};
use serenity::prelude::{GatewayIntents, RwLock, TypeMapKey}; use serenity::prelude::{GatewayIntents, RwLock, TypeMapKey};
use serenity::{async_trait, Client}; use serenity::{async_trait, Client};
@ -47,17 +47,14 @@ impl EventHandler for Handler {
async fn interaction_create(&self, ctx: Context, interaction: Interaction) { async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
trace!("Created an interaction: {:?}", interaction); trace!("Created an interaction: {:?}", interaction);
if let Interaction::ApplicationCommand(command) = interaction {
let configuration_lock = get_configuration_lock(&ctx).await; let configuration_lock = get_configuration_lock(&ctx).await;
let mut configuration = configuration_lock.write().await; let mut configuration = configuration_lock.write().await;
if let Interaction::ApplicationCommand(command) = interaction { let administrators = &configuration.administrators;
let content = match command.data.name.as_str() {
"reload" => {
let member = command.member.as_ref().unwrap(); let member = command.member.as_ref().unwrap();
let user_id = member.user.id.0; let user_id = member.user.id.0;
let mut stop_command = false;
let administrators = &configuration.administrators;
let mut permission_granted = false; let mut permission_granted = false;
// check if the user is an administrator // check if the user is an administrator
@ -66,15 +63,18 @@ impl EventHandler for Handler {
} }
// check if the user has an administrating role // check if the user has an administrating role
if !permission_granted if !permission_granted
&& administrators.roles.iter().any(|role_id| { && administrators
member.roles.iter().any(|member_role| member_role == role_id) .roles
}) { .iter()
.any(|role_id| member.roles.iter().any(|member_role| member_role == role_id))
{
permission_granted = true permission_granted = true
} }
// if permission is granted, reload the configuration let content = if permission_granted {
if permission_granted { match command.data.name.as_str() {
trace!("{:?} reloading configuration.", command.user); "reload" => {
trace!("{:?} reloaded the configuration.", command.user);
let new_config = load_configuration(); let new_config = load_configuration();
@ -83,12 +83,16 @@ impl EventHandler for Handler {
configuration.thread_introductions = new_config.thread_introductions; configuration.thread_introductions = new_config.thread_introductions;
"Successfully reloaded configuration.".to_string() "Successfully reloaded configuration.".to_string()
} else { },
// else return an error message "stop" => {
"You do not have permission to use this command.".to_string() trace!("{:?} stopped the bot.", command.user);
} stop_command = true;
"Stopped the bot.".to_string()
}, },
_ => "Unknown command.".to_string(), _ => "Unknown command.".to_string(),
}
} else {
"You do not have permission to use this command.".to_string()
}; };
// send the response // send the response
@ -104,6 +108,10 @@ impl EventHandler for Handler {
{ {
error!("Cannot respond to slash command: {}", why); error!("Cannot respond to slash command: {}", why);
} }
if stop_command {
std::process::exit(0);
}
} }
} }
@ -113,7 +121,7 @@ impl EventHandler for Handler {
return; return;
} }
if let Some(response) = if let Some(responder) =
get_configuration_lock(&ctx).await.read().await.message_responders.iter().find( get_configuration_lock(&ctx).await.read().await.message_responders.iter().find(
|&responder| { |&responder| {
// check if the message was sent in a channel that is included in the responder // check if the message was sent in a channel that is included in the responder
@ -126,7 +134,7 @@ impl EventHandler for Handler {
&& contains_match(&responder.includes.match_field, &msg.content) && contains_match(&responder.includes.match_field, &msg.content)
}, },
) { ) {
let min_age = response.condition.user.server_age; let min_age = responder.condition.user.server_age;
if min_age != 0 { if min_age != 0 {
let joined_at = ctx let joined_at = ctx
@ -146,7 +154,30 @@ impl EventHandler for Handler {
return; return;
} }
msg.reply_ping(&ctx.http, &response.message) msg.channel_id
.send_message(&ctx.http, |m| {
m.reference_message(&msg);
match &responder.response.embed {
Some(embed) => m.embed(|e| {
e.title(&embed.title)
.description(&embed.description)
.color(embed.color)
.fields(embed.fields.iter().map(|field| {
(field.name.clone(), field.value.clone(), field.inline)
}))
.footer(|f| {
f.text(&embed.footer.text);
f.icon_url(&embed.footer.icon_url)
})
.thumbnail(&embed.thumbnail.url)
.image(&embed.image.url)
.author(|a| {
a.name(&embed.author.name).icon_url(&embed.author.icon_url)
})
}),
None => m.content(responder.response.message.as_ref().unwrap()),
}
})
.await .await
.expect("Could not reply to message author."); .expect("Could not reply to message author.");
} }
@ -162,7 +193,9 @@ impl EventHandler for Handler {
if let Some(introducer) = &configuration.thread_introductions.iter().find(|introducer| { if let Some(introducer) = &configuration.thread_introductions.iter().find(|introducer| {
introducer.channels.iter().any(|channel_id| *channel_id == thread.parent_id.unwrap().0) introducer.channels.iter().any(|channel_id| *channel_id == thread.parent_id.unwrap().0)
}) { }) {
if let Err(why) = thread.say(&ctx.http, &introducer.message).await { if let Err(why) =
thread.say(&ctx.http, &introducer.response.message.as_ref().unwrap()).await
{
error!("Error sending message: {:?}", why); error!("Error sending message: {:?}", why);
} }
} }
@ -171,13 +204,17 @@ impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) { async fn ready(&self, ctx: Context, ready: Ready) {
info!("Connected as {}", ready.user.name); info!("Connected as {}", ready.user.name);
for (cmd, description) in
[("repload", "Reloads the configuration."), ("stop", "Stop the Discord bot.")]
{
Command::create_global_application_command(&ctx.http, |command| { Command::create_global_application_command(&ctx.http, |command| {
command.name("reload").description("Reloads the configuration.") command.name(cmd).description(description)
}) })
.await .await
.expect("Could not create command."); .expect("Could not create command.");
} }
} }
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {