This commit is contained in:
Victor Mignot 2023-01-28 22:09:09 +01:00
parent 3a437db2b6
commit b34da70c8b
No known key found for this signature in database
GPG key ID: FFE4EF056FB5E0D0
12 changed files with 197 additions and 100 deletions

View file

@ -6,16 +6,18 @@ use crate::discord::event_handler::Handler;
pub struct Client { pub struct Client {
serenity_client: SerenityClient, serenity_client: SerenityClient,
database_client: DatabaseClient,
} }
impl<'a> Client { impl Client {
async fn new() -> Self { pub async fn new() -> Self {
let mut database_client = DatabaseClient::new();
database_client.connect().await;
let discord_token = get_env_variable("DISCORD_TOKEN"); let discord_token = get_env_variable("DISCORD_TOKEN");
let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
let database_client = DatabaseClient::new();
let event_handler = Handler { let event_handler = Handler {
database: &database_client, database: database_client,
}; };
let serenity_client = match SerenityClient::builder(discord_token, intents) let serenity_client = match SerenityClient::builder(discord_token, intents)
@ -26,15 +28,10 @@ impl<'a> Client {
Err(e) => panic!("Failed to instantiate Discord Client: {e}"), Err(e) => panic!("Failed to instantiate Discord Client: {e}"),
}; };
Client { Client { serenity_client }
serenity_client,
database_client,
}
} }
pub async fn start(&mut self) { pub async fn start(&mut self) {
self.database_client.connect().await;
if let Err(e) = self.serenity_client.start().await { if let Err(e) = self.serenity_client.start().await {
panic!("Could not connect the bot: {e}"); panic!("Could not connect the bot: {e}");
} }

View file

@ -1,6 +1,6 @@
//! Module for database interaction and wrapping //! Module for database interaction and wrapping
pub mod client; pub mod client;
mod models; pub mod models;
pub use client::Client; pub use client::Client;

View file

@ -116,7 +116,7 @@ impl Client {
pub async fn get_all<T: YorokobotModel + for<'de> Deserialize<'de>>( pub async fn get_all<T: YorokobotModel + for<'de> Deserialize<'de>>(
&self, &self,
filter: Option<Document>, filter: Option<Document>,
) { ) -> Vec<T> {
let mut result: Vec<T> = vec![]; let mut result: Vec<T> = vec![];
let mut cursor = match filter { let mut cursor = match filter {
@ -129,6 +129,8 @@ impl Client {
result result
.push(from_bson(Bson::Document(document)).expect("Could not deserialize document")); .push(from_bson(Bson::Document(document)).expect("Could not deserialize document"));
} }
return result;
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -5,46 +5,25 @@
use mongodb::bson::oid::ObjectId; use mongodb::bson::oid::ObjectId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub const COLLECTIONS_NAMES: [&str; 2] = ["guilds", "tags"]; pub const COLLECTIONS_NAMES: [&str; 1] = ["tags"];
pub trait YorokobotModel { pub trait YorokobotModel {
fn get_collection_name() -> String; fn get_collection_name() -> String;
} }
/// Settings for a server
#[derive(Debug, Serialize, Deserialize)]
pub struct GuildSettings {
admin_only_can_tag: bool,
server_ban_list: Vec<String>,
}
/// Server infos
#[derive(Debug, Serialize, Deserialize)]
pub struct Guild {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<ObjectId>,
discord_guild_id: String,
bot_settings: GuildSettings,
}
/// Tags /// Tags
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Tag { pub struct Tag {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")] #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<ObjectId>, pub id: Option<ObjectId>,
name: String, pub name: String,
guild: String, pub guild_id: String,
is_nsfw: bool, pub is_nsfw: bool,
subscribers: Vec<String>, pub subscribers: Vec<String>,
} }
impl YorokobotModel for Guild {
fn get_collection_name() -> String {
"guilds".to_string()
}
}
impl YorokobotModel for Tag { impl YorokobotModel for Tag {
fn get_collection_name() -> String { fn get_collection_name() -> String {
"traits".to_string() "tags".to_string()
} }
} }

View file

@ -1 +1,3 @@
pub mod event_handler; pub mod event_handler;
mod commands;

View file

@ -0,0 +1,5 @@
pub mod bulk_create_tag;
pub mod create_tag;
pub mod delete_tag;
pub mod list_tags;
pub mod source_code;

View file

@ -0,0 +1,22 @@
use serenity::{builder::CreateApplicationCommand, model::prelude::command::CommandOptionType};
pub fn register(
command: &mut CreateApplicationCommand,
max_args_number: u32,
) -> &mut CreateApplicationCommand {
command
.name("bulk_create_tag")
.description("Add multiples tags");
for i in 0..max_args_number {
command.create_option(|option| {
option
.name(format!("tag{}", i + 1))
.description("A new tag to add")
.kind(CommandOptionType::String)
.required(i == 0)
});
}
command
}

View file

@ -0,0 +1,72 @@
use mongodb::bson::doc;
use serenity::{
builder::{CreateApplicationCommand, CreateInteractionResponseData},
model::{
application::interaction::application_command::ApplicationCommandInteraction,
prelude::{
command::CommandOptionType, interaction::application_command::CommandDataOptionValue,
},
},
};
use crate::database::{models::Tag, Client as DatabaseClient};
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name("create_tag")
.description("Add a new tag")
.create_option(|option| {
option
.name("tag")
.description("The tag to create")
.kind(CommandOptionType::String)
.required(true)
})
}
pub async fn run<'a, 'b>(
response: &'a mut CreateInteractionResponseData<'b>,
command: &ApplicationCommandInteraction,
database: &DatabaseClient,
) -> &'a mut CreateInteractionResponseData<'b> {
let arg = command
.data
.options
.get(0)
.expect("Missing option")
.resolved
.as_ref()
.expect("Could not deserialize option");
let guild_id = command
.guild_id
.expect("Could not fetch guild id")
.to_string();
if let CommandDataOptionValue::String(tag_name) = arg {
let matching_tags = database
.get_all::<Tag>(Some(doc! {"name": tag_name, "guild_id": guild_id.as_str()}))
.await;
if !matching_tags.is_empty() {
response.content("This tag already exist for this server");
} else {
match database
.insert_one(Tag {
id: None,
name: tag_name.to_string(),
guild_id: guild_id.clone(),
is_nsfw: false,
subscribers: vec![],
})
.await
{
Ok(_) => response.content("Tag successfully created."),
Err(_) => response.content("Error creating the tag"),
};
}
}
response
}

View file

@ -0,0 +1,14 @@
use serenity::{builder::CreateApplicationCommand, model::prelude::command::CommandOptionType};
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name("delete_tag")
.description("Delete a tag")
.create_option(|option| {
option
.name("tag")
.description("The tag to delete")
.kind(CommandOptionType::String)
.required(true)
})
}

View file

@ -0,0 +1,5 @@
use serenity::builder::CreateApplicationCommand;
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command.name("list_tags").description("List your own tags")
}

View file

@ -0,0 +1,17 @@
use serenity::builder::{CreateApplicationCommand, CreateInteractionResponseData};
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name("source_code")
.description("Access to the bot source code")
}
pub fn run<'a, 'b>(
response: &'a mut CreateInteractionResponseData<'b>,
) -> &'a mut CreateInteractionResponseData<'b> {
response.embed(|embed| {
embed
.title("Yorokobot repository")
.description("https://sr.ht/~victormignot/yorokobot/")
})
}

View file

@ -1,87 +1,69 @@
use crate::database::Client as DatabaseClient;
use serenity::{ use serenity::{
async_trait, async_trait,
model::gateway::Ready, model::gateway::Ready,
model::{ model::{
application::command::{Command, CommandOptionType}, application::command::Command,
prelude::ResumedEvent, prelude::{
interaction::{Interaction, InteractionResponseType},
ResumedEvent,
},
}, },
prelude::{Context, EventHandler}, prelude::{Context, EventHandler},
}; };
use crate::database::Client as DatabaseClient; use super::commands::*;
const MAX_ARGS_NUMBER: u32 = 25; const MAX_ARGS_NUMBER: u32 = 25;
pub struct Handler<'a> { pub struct Handler {
pub database: &'a DatabaseClient, pub database: DatabaseClient,
} }
#[async_trait] #[async_trait]
impl EventHandler for Handler<'_> { impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) { async fn ready(&self, ctx: Context, ready: Ready) {
println!("Successfully connected as {}", ready.user.name); println!("Successfully connected as {}", ready.user.name);
match Command::create_global_application_command(&ctx.http, |command| { match Command::set_global_application_commands(&ctx.http, |commands| {
command commands
.name("create_tag") .create_application_command(|command| source_code::register(command))
.description("Add a new tag") .create_application_command(|command| create_tag::register(command))
.create_option(|option| { .create_application_command(|command| {
option bulk_create_tag::register(command, MAX_ARGS_NUMBER)
.name("tag")
.description("The tag to create")
.kind(CommandOptionType::String)
.required(true)
}) })
.create_application_command(|command| delete_tag::register(command))
.create_application_command(|command| list_tags::register(command))
}) })
.await .await
{ {
Ok(_) => println!("Successfully registered create_tag command"), Ok(_) => println!("Successfully registered application commands"),
Err(e) => println!("Failed to register create_tag command : {e}"), Err(e) => println!("Failed to register application commands: {e}"),
};
match Command::create_global_application_command(&ctx.http, |command| {
let command = command
.name("bulk_create_tag")
.description("Add multiples tags");
for i in 0..MAX_ARGS_NUMBER {
command.create_option(|option| {
option
.name(format!("tag{}", i + 1))
.description("A new tag to add")
.kind(CommandOptionType::String)
.required(i == 0)
});
}
command
})
.await
{
Ok(_) => println!("Successfully registered bulk_create_tag command"),
Err(e) => println!("Failed to register bulk_create_tag command: {e}"),
};
match Command::create_global_application_command(&ctx.http, |command| {
command
.name("delete_tag")
.description("Delete a tag")
.create_option(|option| {
option
.name("tag")
.description("The tag to delete")
.kind(CommandOptionType::String)
.required(true)
})
})
.await
{
Ok(_) => println!("Successfully registered delete_tag command"),
Err(e) => println!("Failed to register delete_tag command: {e}"),
}; };
} }
async fn resume(&self, _: Context, _: ResumedEvent) { async fn resume(&self, _: Context, _: ResumedEvent) {
println!("Successfully reconnected.") println!("Successfully reconnected.")
} }
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
if let Interaction::ApplicationCommand(command) = interaction {
println!("Received command {}", command.data.name);
if let Err(e) = command
.create_interaction_response(&ctx.http, |response| {
response
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|message| match command.data.name.as_str() {
"source_code" => source_code::run(message),
"create_tag" => create_tag::run(message, &command, &self.database),
_ => message.content("Not yet implemented"),
})
})
.await
{
println!("Failed to answer to command: {e}");
}
}
}
} }