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

View File

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

View File

@ -10,7 +10,7 @@ Main lib used : [serenity-rs/serenity](https://github.com/serenity-rs/serenity)
- [DONE] ImageWareHouse (like TestDiscord)
- 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)
- Notification (read an event topic and send the message to the expected chan)
- Some Administration command
@ -24,9 +24,9 @@ Main lib used : [serenity-rs/serenity](https://github.com/serenity-rs/serenity)
- Monitor the bot
- Authentification Discord
- Detecter controle
- [WIP] Integrate with the Opentelemetry project
- 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] Integrate with the Opentelemetry project
- [DONE] The bot has to be able to be deployed on a k8s cluster
- [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
## previous project

View File

@ -1,11 +1,14 @@
use std::str::FromStr;
use crate::botv2::{
domain::concour::{
check_if_allowed::check_if_allowed,
set_periode::{set_periode, SetPeriodeConcourError},
use crate::{
botv2::{
domain::concour::{
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 cron::Schedule;
@ -66,7 +69,8 @@ pub async fn period(
};
let schedule = match Schedule::from_str(&cron_schedule) {
Ok(schedule) => schedule,
Err(_) => {
Err(err) => {
warn!(err = err.to_string(), "Cron format is invalid");
let embed = CreateEmbed::new()
.title("Invalid CRON format")
.color(colour::Color::RED)
@ -118,16 +122,55 @@ pub async fn period(
.color(colour::Color::RED)
} else {
let concour = concour.unwrap();
CreateEmbed::new()
let mut output = CreateEmbed::new()
.title(concour.title)
.description(concour.description)
.field("Start date", concour.start_date.to_string(), false)
.field("Periode", concour.periode.to_string(), false)
.field(
.field("Periode", concour.periode.to_string(), false);
if concour.role_recompense == 0 {
output = output.field("Role récompense", "Aucun", false);
} else {
output = output.field(
"Role récompense",
RoleId::new(concour.role_recompense).mention().to_string(),
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 {

View File

@ -83,6 +83,11 @@ pub async fn start(ctx: Context<'_>) -> Result<(), Error> {
Some(role) => RoleId::new(role).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 text = format!("
Bonsoir !
@ -90,14 +95,14 @@ Bonsoir !
📜 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.
Ceux qui votent pour leur propre photo, cela ne sera pas pris en compte, une photo par personne.
À vos photos !
{}
", keyword.to_string(),ping_concour);
", keyword,role_recompense,ping_concour);
let output = CreateEmbed::new()
.title(format!(
"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 enable;
pub mod list;
pub mod main;

View File

@ -1,121 +1,18 @@
use super::init::{Data, Error};
use crate::{db::user_image::User, img::config_file::KeyWordItem};
use poise::serenity_prelude as serenity;
use rand::Rng;
use serenity::all::{CreateAttachment, CreateMessage};
use tokio::fs::File;
use tracing::{info, instrument};
#[instrument(skip(ctx, _framework, data), err, level = "trace")]
#[allow(clippy::single_match)]
#[instrument(skip(_ctx, _framework, _data), err, level = "trace")]
pub async fn event_handler(
ctx: &serenity::Context,
_ctx: &serenity::Context,
event: &serenity::FullEvent,
_framework: poise::FrameworkContext<'_, Data, Error>,
data: &Data,
_data: &Data,
) -> Result<(), Error> {
match event {
serenity::FullEvent::Ready { data_about_bot, .. } => {
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(())

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::{help::help, ping::ping};
use crate::config::Config;
@ -9,7 +9,7 @@ use serenity::GatewayIntents;
use std::fs;
use std::sync::Arc;
use tokio::sync::oneshot;
use tracing::{info, instrument};
use tracing::info;
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 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(
config: Config,
rx: oneshot::Receiver<()>,
@ -67,16 +55,7 @@ pub async fn start_bot(
let prefix = config.prefix.clone();
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![
age(),
ping(),
help(),
list(),
enable(),
answer(),
server(),
concour(),
],
commands: vec![ping(), help(), meme(), server(), concour()],
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some(prefix),
..Default::default()

View File

@ -1,5 +1,5 @@
use poise::serenity_prelude::{
Color, CreateEmbed, CreateMessage, Http, Mentionable, MessagePagination,
Color, CreateEmbed, CreateMessage, Http, Mentionable, MessagePagination, RoleId,
};
use std::{
collections::HashMap,
@ -291,16 +291,16 @@ impl ScheduleJob {
return;
}
};
let embed = CreateEmbed::default()
.title("Winner")
.description(format!("The winner is {}", winner.mention()))
.color(Color::DARK_GREEN);
let reply = CreateMessage::default().embed(embed);
if let Err(err) =
http.send_message(channel_id.into(), vec![], &reply).await
{
error!("Error sending message: {:?}", err);
}
// let embed = CreateEmbed::default()
// .title("Winner")
// .description(format!("The winner is {}", winner.mention()))
// .color(Color::DARK_GREEN);
// let reply = CreateMessage::default().embed(embed);
// if let Err(err) =
// http.send_message(channel_id.into(), vec![], &reply).await
// {
// error!("Error sending message: {:?}", err);
// }
let (add, previous) = match concour.winner.last() {
Some(previous_winner) => (
previous_winner.user_id != winner.id.get(),
@ -376,6 +376,33 @@ impl ScheduleJob {
.keywords
.get(concour.index_keyword as usize)
.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()
.title(format!(
"Concour: {} Jour : {}",
@ -391,7 +418,12 @@ impl ScheduleJob {
false,
)
.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 =
match http.send_message(channel_id.into(), vec![], &reply).await {
Ok(message) => message.id,