Compare commits

...

10 Commits

10 changed files with 149 additions and 163 deletions

17
Cargo.lock generated
View File

@ -3535,6 +3535,15 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde_cow"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e84ce5596a72f0c4c60759a10ff8c22d5eaf227b0dc2789c8746193309058b"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.203" version = "1.0.203"
@ -3635,13 +3644,12 @@ dependencies = [
[[package]] [[package]]
name = "serenity" name = "serenity"
version = "0.12.1" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/serenity-rs/serenity?branch=current#060ee3281b44f7e532f3ea5863c5df57340e1ec9"
checksum = "c64da29158bb55d70677cacd4f4f8eab1acef005fb830d9c3bea411b090e96a9"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"async-trait", "async-trait",
"base64 0.21.7", "base64 0.22.1",
"bitflags 2.5.0", "bitflags 2.5.0",
"bytes", "bytes",
"chrono", "chrono",
@ -3655,6 +3663,7 @@ dependencies = [
"reqwest", "reqwest",
"secrecy", "secrecy",
"serde", "serde",
"serde_cow",
"serde_json", "serde_json",
"time", "time",
"tokio", "tokio",

View File

@ -51,6 +51,8 @@ serde_with = "3.8.1"
tokio-cron = "0.1.3" tokio-cron = "0.1.3"
cron = "0.12.1" cron = "0.12.1"
[patch.crates-io]
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "current" }
[[bin]] [[bin]]
name = "botdiscord" name = "botdiscord"

View File

@ -10,7 +10,7 @@ Main lib used : [serenity-rs/serenity](https://github.com/serenity-rs/serenity)
- [DONE] ImageWareHouse (like TestDiscord) - [DONE] ImageWareHouse (like TestDiscord)
- ImageWareHouse V2 - ImageWareHouse V2
- Mise en place d'autre source de gif OPT - Mise en place d'autre source de gif comme [Teno](https://developers.google.com/tenor/guides/quickstart?hl=fr)
- SoundBoard (like UnlabeledBot) - SoundBoard (like UnlabeledBot)
- Notification (read an event topic and send the message to the expected chan) - Notification (read an event topic and send the message to the expected chan)
- Some Administration command - Some Administration command
@ -24,9 +24,9 @@ Main lib used : [serenity-rs/serenity](https://github.com/serenity-rs/serenity)
- Monitor the bot - Monitor the bot
- Authentification Discord - Authentification Discord
- Detecter controle - Detecter controle
- [WIP] Integrate with the Opentelemetry project - [DONE] Integrate with the Opentelemetry project
- The bot has to be able to be deployed on a k8s cluster - [DONE] The bot has to be able to be deployed on a k8s cluster
- The bot has to be OPT-IN (the user has to enable the bot on his server with a command) - [DONE] The bot has to be OPT-IN (the user has to enable the bot on his server with a command)
- Mise en place de metric OpenTelemetry - Mise en place de metric OpenTelemetry
## previous project ## previous project

View File

@ -1,11 +1,14 @@
use std::str::FromStr; use std::str::FromStr;
use crate::botv2::{ use crate::{
domain::concour::{ botv2::{
check_if_allowed::check_if_allowed, domain::concour::{
set_periode::{set_periode, SetPeriodeConcourError}, check_if_allowed::check_if_allowed,
set_periode::{set_periode, SetPeriodeConcourError},
},
init::{Context, Error},
}, },
init::{Context, Error}, db::concour::ConcourStatus,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use cron::Schedule; use cron::Schedule;
@ -66,7 +69,8 @@ pub async fn period(
}; };
let schedule = match Schedule::from_str(&cron_schedule) { let schedule = match Schedule::from_str(&cron_schedule) {
Ok(schedule) => schedule, Ok(schedule) => schedule,
Err(_) => { Err(err) => {
warn!(err = err.to_string(), "Cron format is invalid");
let embed = CreateEmbed::new() let embed = CreateEmbed::new()
.title("Invalid CRON format") .title("Invalid CRON format")
.color(colour::Color::RED) .color(colour::Color::RED)
@ -118,16 +122,55 @@ pub async fn period(
.color(colour::Color::RED) .color(colour::Color::RED)
} else { } else {
let concour = concour.unwrap(); let concour = concour.unwrap();
CreateEmbed::new() let mut output = CreateEmbed::new()
.title(concour.title) .title(concour.title)
.description(concour.description) .description(concour.description)
.field("Start date", concour.start_date.to_string(), false) .field("Start date", concour.start_date.to_string(), false)
.field("Periode", concour.periode.to_string(), false) .field("Periode", concour.periode.to_string(), false);
.field(
if concour.role_recompense == 0 {
output = output.field("Role récompense", "Aucun", false);
} else {
output = output.field(
"Role récompense", "Role récompense",
RoleId::new(concour.role_recompense).mention().to_string(), RoleId::new(concour.role_recompense).mention().to_string(),
false, false,
) );
}
// Restart the cronjob if concour is ongoing
if concour.status == ConcourStatus::OnGoing {
{
let mut scheduler = ctx.data().scheduler.clone();
match scheduler
.stop_scheduled_job(concour.server_id, concour.channel_id)
.await
{
Ok(_) => {
info!("Cronjob stopped");
match scheduler
.add_concour_cron_job(
concour.server_id,
concour.channel_id,
concour.periode,
ctx.http(),
)
.await
{
Ok(_) => {
info!("Cronjob restarted");
}
Err(_) => {
warn!("Error restarting cronjob");
}
};
}
Err(err) => {
warn!(err = err.to_string(), "Error stopping cronjob");
}
}
}
}
output
} }
} }
Err(err) => match err { Err(err) => match err {

View File

@ -83,6 +83,11 @@ pub async fn start(ctx: Context<'_>) -> Result<(), Error> {
Some(role) => RoleId::new(role).mention().to_string(), Some(role) => RoleId::new(role).mention().to_string(),
None => "".to_string(), None => "".to_string(),
}; };
let role_recompense = if concour.role_recompense != 0 {
RoleId::new(concour.role_recompense).mention().to_string()
} else {
"(Pas encore définis)".to_string()
};
let text = format!(" let text = format!("
Bonsoir ! Bonsoir !
@ -90,14 +95,14 @@ Bonsoir !
📜 Les règles : Pas de loli, ni de shoota, ni de irl, ni de zoo. 📜 Les règles : Pas de loli, ni de shoota, ni de irl, ni de zoo.
Celui ou celle qui a le plus de votes gagne, comme récompense elle aura le rôle @ROLE pour une durée de 48h. Celui ou celle qui a le plus de votes gagne, comme récompense elle aura le rôle {} pour une durée de 48h.
Le concours ce termine dans deux jours. Le concours ce termine dans deux jours.
Ceux qui votent pour leur propre photo, cela ne sera pas pris en compte, une photo par personne. Ceux qui votent pour leur propre photo, cela ne sera pas pris en compte, une photo par personne.
À vos photos ! À vos photos !
{} {}
", keyword.to_string(),ping_concour); ", keyword,role_recompense,ping_concour);
let output = CreateEmbed::new() let output = CreateEmbed::new()
.title(format!( .title(format!(
"Concour: {} Jour : {}", "Concour: {} Jour : {}",

View File

@ -0,0 +1,18 @@
use crate::botv2::{
cmd::meme::{answer::answer, enable::enable, list::list},
init::{Context, Error},
};
use tracing::instrument;
/// Handle meme command
#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))]
#[poise::command(
slash_command,
prefix_command,
category = "mem",
subcommands("answer", "enable", "list"),
guild_only = true
)]
pub async fn meme(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}

View File

@ -1,3 +1,4 @@
pub mod answer; pub mod answer;
pub mod enable; pub mod enable;
pub mod list; pub mod list;
pub mod main;

View File

@ -1,121 +1,18 @@
use super::init::{Data, Error}; use super::init::{Data, Error};
use crate::{db::user_image::User, img::config_file::KeyWordItem};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use rand::Rng;
use serenity::all::{CreateAttachment, CreateMessage};
use tokio::fs::File;
use tracing::{info, instrument}; use tracing::{info, instrument};
#[allow(clippy::single_match)]
#[instrument(skip(ctx, _framework, data), err, level = "trace")] #[instrument(skip(_ctx, _framework, _data), err, level = "trace")]
pub async fn event_handler( pub async fn event_handler(
ctx: &serenity::Context, _ctx: &serenity::Context,
event: &serenity::FullEvent, event: &serenity::FullEvent,
_framework: poise::FrameworkContext<'_, Data, Error>, _framework: poise::FrameworkContext<'_, Data, Error>,
data: &Data, _data: &Data,
) -> Result<(), Error> { ) -> Result<(), Error> {
match event { match event {
serenity::FullEvent::Ready { data_about_bot, .. } => { serenity::FullEvent::Ready { data_about_bot, .. } => {
info!("{} is connected !", data_about_bot.user.name); info!("{} is connected !", data_about_bot.user.name);
} }
serenity::FullEvent::Message { new_message } => {
if new_message.author.bot
|| new_message.content.starts_with(&data.config.prefix.clone())
|| new_message.content.is_empty()
{
return Ok(());
}
let config_img = data.config_img.clone();
let config = data.config.clone();
let guild = match new_message.guild_id {
Some(guild) => guild,
None => return Ok(()),
};
let user_in_db =
match User::find_by_server_id_user_id(&guild.get(), &new_message.author.id.get())
.await
{
Ok(Some(user_in_db)) => user_in_db.clone(),
Ok(None) => {
let user_in_db =
User::new(guild.get(), new_message.author.id.get(), false).unwrap();
match user_in_db.create().await {
Ok(_) => user_in_db,
Err(e) => {
println!("Error saving user image: {:?}", e);
return Ok(());
}
}
}
Err(e) => {
println!("Error finding user image: {:?}", e);
return Ok(());
}
};
if !user_in_db.enable {
return Ok(());
}
if config_img.keyword.is_empty() || new_message.content.len() > 50 {
return Ok(());
}
let folder_container = match config_img
.keyword
.iter()
.find(|keyword| keyword.does_value_match(new_message.content.clone()))
{
Some(keyword_matching) => {
println!("{} match {:?}", new_message.content, keyword_matching);
let keyword_path = match keyword_matching.path.len() {
0 => keyword_matching.path[0].clone(),
_ => {
let id: usize = {
let mut rng = rand::thread_rng();
rng.gen_range(0..keyword_matching.path.len())
};
keyword_matching.path[id].clone()
}
};
keyword_path.clone()
}
None => return Ok(()),
};
let path = format!("{}/{}", config.image.path.clone(), folder_container);
let file_folder = KeyWordItem::output_folder_content(path.clone());
let id_rand: usize = {
let mut rng = rand::thread_rng();
rng.gen_range(0..file_folder.len())
};
let filename = match file_folder.get(id_rand) {
Some(file) => file.file_name().to_str().unwrap(),
None => return Ok(()),
};
let file_path = format!("{}/{}", path, filename);
let file = match File::open(file_path).await {
Ok(file) => file,
Err(why) => {
println!("Error opening file: {:?}", why);
return Ok(());
}
};
let attachment = match CreateAttachment::file(&file, filename).await {
Ok(attachment) => attachment,
Err(why) => {
println!("Error creating attachment: {:?}", why);
return Ok(());
}
};
let builder = CreateMessage::new().add_file(attachment);
if let Err(why) = new_message
.channel_id
.send_message(&ctx.http, builder)
.await
{
println!("Error sending message: {:?}", why);
}
}
_ => {} _ => {}
} }
Ok(()) Ok(())

View File

@ -1,4 +1,4 @@
use crate::botv2::cmd::meme::{answer::answer, enable::enable, list::list}; use crate::botv2::cmd::meme::main::meme;
use crate::botv2::cmd::server_config::server::server; use crate::botv2::cmd::server_config::server::server;
use crate::botv2::cmd::{help::help, ping::ping}; use crate::botv2::cmd::{help::help, ping::ping};
use crate::config::Config; use crate::config::Config;
@ -9,7 +9,7 @@ use serenity::GatewayIntents;
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use tracing::{info, instrument}; use tracing::info;
use super::cmd::concour::main::concour; use super::cmd::concour::main::concour;
@ -24,18 +24,6 @@ pub struct Data {
pub type Error = Box<dyn std::error::Error + Send + Sync>; pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Context<'a> = poise::Context<'a, Data, Error>; pub type Context<'a> = poise::Context<'a, Data, Error>;
#[instrument(skip(ctx), level = "info")]
#[poise::command(slash_command, prefix_command)]
async fn age(
ctx: Context<'_>,
#[description = "Selected user"] user: Option<serenity::User>,
) -> Result<(), Error> {
let u = user.as_ref().unwrap_or_else(|| ctx.author());
let response = format!("{}'s account was created at {}", u.name, u.created_at());
ctx.say(response).await?;
Ok(())
}
pub async fn start_bot( pub async fn start_bot(
config: Config, config: Config,
rx: oneshot::Receiver<()>, rx: oneshot::Receiver<()>,
@ -67,16 +55,7 @@ pub async fn start_bot(
let prefix = config.prefix.clone(); let prefix = config.prefix.clone();
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
commands: vec![ commands: vec![ping(), help(), meme(), server(), concour()],
age(),
ping(),
help(),
list(),
enable(),
answer(),
server(),
concour(),
],
prefix_options: poise::PrefixFrameworkOptions { prefix_options: poise::PrefixFrameworkOptions {
prefix: Some(prefix), prefix: Some(prefix),
..Default::default() ..Default::default()

View File

@ -1,5 +1,5 @@
use poise::serenity_prelude::{ use poise::serenity_prelude::{
Color, CreateEmbed, CreateMessage, Http, Mentionable, MessagePagination, Color, CreateEmbed, CreateMessage, Http, Mentionable, MessagePagination, RoleId,
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -291,16 +291,16 @@ impl ScheduleJob {
return; return;
} }
}; };
let embed = CreateEmbed::default() // let embed = CreateEmbed::default()
.title("Winner") // .title("Winner")
.description(format!("The winner is {}", winner.mention())) // .description(format!("The winner is {}", winner.mention()))
.color(Color::DARK_GREEN); // .color(Color::DARK_GREEN);
let reply = CreateMessage::default().embed(embed); // let reply = CreateMessage::default().embed(embed);
if let Err(err) = // if let Err(err) =
http.send_message(channel_id.into(), vec![], &reply).await // http.send_message(channel_id.into(), vec![], &reply).await
{ // {
error!("Error sending message: {:?}", err); // error!("Error sending message: {:?}", err);
} // }
let (add, previous) = match concour.winner.last() { let (add, previous) = match concour.winner.last() {
Some(previous_winner) => ( Some(previous_winner) => (
previous_winner.user_id != winner.id.get(), previous_winner.user_id != winner.id.get(),
@ -376,6 +376,33 @@ impl ScheduleJob {
.keywords .keywords
.get(concour.index_keyword as usize) .get(concour.index_keyword as usize)
.unwrap(); .unwrap();
let ping_role = match concour.ping_concour {
Some(role_id) => RoleId::new(role_id).mention().to_string(),
None => "".to_string(),
};
let role_recompense = if concour.role_recompense != 0 {
RoleId::new(concour.role_recompense).mention().to_string()
} else {
"(Pas encore définis)".to_string()
};
let answer = format!(
"Bonsoir !
🏆 Bravo à {} pour ses réactions sous son image.
👹 Le thème de ce soir est : {}
📜 Les règles : Pas de loli, ni de shoota, ni de irl, ni de zoo.
Celui ou celle qui a le plus de votes gagne, comme récompense elle aura le rôle {} pour une durée de 48h.
Le concours ce termine dans deux jours.
Ceux qui votent pour leur propre photo, cela ne sera pas pris en compte, une photo par personne.
À vos photos !
{}",
winner.id.mention(), next_keyword,role_recompense, ping_role
);
let output = CreateEmbed::new() let output = CreateEmbed::new()
.title(format!( .title(format!(
"Concour: {} Jour : {}", "Concour: {} Jour : {}",
@ -391,7 +418,12 @@ impl ScheduleJob {
false, false,
) )
.color(Color::DARK_GREEN); .color(Color::DARK_GREEN);
let reply = CreateMessage::default().embed(output); let mut reply = CreateMessage::default();
if !answer.is_empty() {
reply = reply.content(answer);
}else{
reply = reply.embed(output);
}
let last_id = let last_id =
match http.send_message(channel_id.into(), vec![], &reply).await { match http.send_message(channel_id.into(), vec![], &reply).await {
Ok(message) => message.id, Ok(message) => message.id,