BotDiscord/src/event/schedule_job.rs
2024-06-30 20:11:19 +02:00

500 lines
22 KiB
Rust

use poise::serenity_prelude::{
Color, CreateEmbed, CreateMessage, Http, Mentionable, MessagePagination, RoleId,
};
use std::{
collections::HashMap,
fmt::{self, Display},
sync::Arc,
};
use tokio::sync::RwLock;
use tokio_cron_scheduler::{Job, JobScheduler};
use tracing::{error, info, info_span, instrument, Instrument};
use uuid::Uuid;
use crate::db::concour::{Concour, ConcourStatus, ConcourWinner};
#[derive(Debug)]
pub enum StopScheduleJob {
JobNotFound,
RemoveFailed,
}
impl Display for StopScheduleJob {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct ScheduleJob {
pub job_id: Arc<RwLock<HashMap<(u64, u64), Uuid>>>,
pub scheduler: JobScheduler,
}
impl Clone for ScheduleJob {
fn clone(&self) -> Self {
ScheduleJob {
job_id: self.job_id.clone(),
scheduler: self.scheduler.clone(),
}
}
}
impl ScheduleJob {
#[instrument(level = "info")]
pub async fn start_cron_scheduler() -> Result<Self, ()> {
let scheduler = JobScheduler::new().await;
let mut future_self = match scheduler {
Ok(scheduler) => ScheduleJob {
job_id: Default::default(),
scheduler,
},
Err(e) => {
error!("Error starting cron scheduler: {:?}", e);
return Err(());
}
};
future_self.scheduler.set_shutdown_handler(Box::new(|| {
Box::pin(async {
info!("Cron scheduler stopped");
})
}));
future_self.scheduler.shutdown_on_ctrl_c();
match future_self.scheduler.start().await {
Ok(_) => {
info!("Cron scheduler started");
Ok(future_self)
}
Err(e) => {
error!("Error starting cron scheduler: {:?}", e);
Err(())
}
}
}
#[instrument(skip(self, http), level = "info")]
pub async fn load_all_concour_cron_job(&mut self, http: &Http) -> Result<(), bool> {
let concours = match Concour::find_by_status(&ConcourStatus::OnGoing).await {
Ok(concour) => concour,
Err(e) => {
error!("Error getting concour: {:?}", e);
return Err(true);
}
};
for concour in concours {
match self
.add_concour_cron_job(concour.server_id, concour.channel_id, concour.periode, http)
.await
{
Ok(_) => {
info!("Concour cron job added");
}
Err(_) => {
error!("Error adding concour cron job");
return Err(true);
}
}
}
Ok(())
}
#[instrument(skip(self), level = "info")]
pub async fn stop_cron_scheduler(&mut self) -> &mut Self {
let job_id = self.job_id.write().await;
for (server_id, channel_id) in job_id.keys() {
match self
.scheduler
.remove(job_id.get(&(*server_id, *channel_id)).unwrap())
.await
{
Ok(_) => {
info!("Cron job removed");
}
Err(e) => {
error!("Error removing cron job: {:?}", e);
}
}
}
drop(job_id);
match self.scheduler.shutdown().await {
Ok(_) => {
info!("Cron scheduler stopped");
self
}
Err(e) => {
error!("Error stopping cron scheduler: {:?}", e);
self
}
}
}
#[instrument(skip(self, http), level = "info")]
pub async fn add_concour_cron_job(
&mut self,
server_id: u64,
channel_id: u64,
cron_expression: String,
http: &Http,
) -> Result<Uuid, ()> {
let http = Arc::new(Http::new(http.token()));
let job = match Job::new_cron_job_async_tz(
cron_expression.as_str(),
chrono::Local,
move |uuid, _l| {
Box::pin(
{
let http = http.clone();
Box::pin(async move {
info!(id = uuid.to_string(), "Cron job fired");
// Get concour data
let concour = match Concour::find_by_server_id_channel_id(
&server_id,
&channel_id,
)
.await
{
Ok(concour) => concour,
Err(e) => {
error!("Error getting concour: {:?}", e);
// Disable the concour ?
return;
}
};
if concour.is_none() {
error!("Concour not found");
return;
}
let mut concour = concour.unwrap();
// Send the message to announce the end of the concour
let current_keyword = concour
.keywords
.get(concour.index_keyword as usize)
.unwrap();
let embed = CreateEmbed::default()
.title(format!(
"Concour has ended Day {} has ended",
concour.index_keyword + 1
))
.description("Processing the results...")
.field("Title", concour.title.clone(), false)
.field("Description", concour.description.clone(), false)
.field("Word of the day", current_keyword, false)
.color(Color::DARK_GREEN);
let reply = CreateMessage::default().embed(embed);
let last_id =
match http.send_message(channel_id.into(), vec![], &reply).await {
Ok(message) => message.id,
Err(e) => {
error!("Error sending message: {:?}", e);
return;
}
};
// Check if the concour is still enabledl
if concour.status != ConcourStatus::OnGoing
|| concour.last_message_id.is_none()
{
info!("Concour is not enabled");
return;
}
// Get All message since the announcement
let message_pagination =
MessagePagination::After(concour.last_message_id.unwrap().into());
let mut messages = match http
.get_messages(channel_id.into(), Some(message_pagination), None)
.await
{
Ok(messages) => messages,
Err(e) => {
error!("Error getting messages: {:?}", e);
return;
}
};
if messages.is_empty() {
error!("No message found");
let embed: CreateEmbed = CreateEmbed::default()
.title("An error has occured while fetching the messages")
.color(Color::DARK_RED);
let reply = CreateMessage::default().embed(embed);
if let Err(err) =
http.send_message(channel_id.into(), vec![], &reply).await
{
error!("Error sending message: {:?}", err);
}
return;
}
if messages.last().unwrap().id != last_id {
info!("Fetching more messages because last one is not the last id");
loop {
let message_pagination =
MessagePagination::After(messages.last().unwrap().id);
let mut new_messages = match http
.get_messages(
channel_id.into(),
Some(message_pagination),
None,
)
.await
{
Ok(messages) => messages,
Err(e) => {
error!("Error getting messages: {:?}", e);
return;
}
};
if new_messages.is_empty() {
info!("No more messages found");
break;
}
messages.append(&mut new_messages);
if messages.last().unwrap().id == last_id {
info!("Last message found");
break;
}
}
}
// filter out the bot's message
messages.retain(|message| !message.author.bot);
info!(
nbr_message = messages.len(),
"{} messages found",
messages.len()
);
// count the number of reactions per message
let mut max_reaction = 0;
let mut max_winner = None;
messages.into_iter().for_each(|msg| {
// test
let count = msg
.reactions
.into_iter()
.fold(0, |acc, reaction| acc + reaction.count);
if count > max_reaction {
max_reaction = count;
max_winner = Some(msg.author);
}
});
// announce the winner
let winner = match max_winner {
Some(winner) => winner,
None => {
let embed = CreateEmbed::default()
.title("No winner found, What happened ?")
.color(Color::DARK_RED);
let reply = CreateMessage::default().embed(embed);
if let Err(err) =
http.send_message(channel_id.into(), vec![], &reply).await
{
error!("Error sending message: {:?}", err);
}
error!("No winner found");
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 (add, previous) = match concour.winner.last() {
Some(previous_winner) => (
previous_winner.user_id != winner.id.get(),
previous_winner.user_id,
),
None => (true, 0),
};
// Remove the role from the previous winner
// Give the winner the role reward
if add {
if previous != 0 {
match http
.remove_member_role(
server_id.into(),
previous.into(),
concour.role_recompense.into(),
None,
)
.await
{
Ok(_) => {
info!("Role removed from the previous winner");
}
Err(e) => {
error!(
"Error removing role from the previous winner: {:?}",
e
);
}
}
}
match http
.add_member_role(
server_id.into(),
winner.id,
concour.role_recompense.into(),
None,
)
.await
{
Ok(_) => {
info!("Role added to the winner");
}
Err(e) => {
error!("Error adding role to the winner: {:?}", e);
}
}
}
// update concour with the winner and increment the index
concour.winner.push(ConcourWinner {
user_id: winner.id.get(),
date: chrono::Utc::now(),
keyword: current_keyword.to_string(),
});
concour.index_keyword += 1;
if concour.index_keyword as usize >= concour.keywords.len() {
concour.status = ConcourStatus::Finished;
let embed = CreateEmbed::default()
.title("Concour has ended")
.description("The concour has ended, no more keyword")
.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);
}
info!("Concour has ended, no more keyword");
return;
}
let next_keyword = concour
.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 : {}",
concour.title.clone(),
concour.index_keyword + 1
))
.description(concour.description.clone())
.field("Word of the day ", next_keyword.to_string(), false)
.field("Good luck !", "", false)
.field(
"Please see when the concour end in the concour get command",
"",
false,
)
.color(Color::DARK_GREEN);
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,
Err(e) => {
error!("Error sending message: {:?}", e);
return;
}
};
concour.last_message_id = Some(last_id.get());
match concour.update().await {
Ok(_) => {
info!("Concour updated");
}
Err(e) => {
error!("Error updating concour: {:?}", e);
}
}
})
}
.instrument(info_span!("ConcourJob", id = uuid.to_string())),
)
},
) {
Ok(job) => job,
Err(e) => {
error!("Error creating cron job: {:?}", e);
return Err(());
}
};
match self.scheduler.add(job).await {
Ok(job_uid) => {
info!("Cron job added");
{
let mut job_id = self.job_id.write().await;
job_id.insert((server_id, channel_id), job_uid);
}
Ok(job_uid)
}
Err(e) => {
error!("Error adding cron job: {:?}", e);
Err(())
}
}
}
#[instrument(skip(self), level = "info")]
pub async fn stop_scheduled_job(
&mut self,
server_id: u64,
channel_id: u64,
) -> Result<(), StopScheduleJob> {
let remove_job = {
let mut job_id = self.job_id.write().await;
job_id.remove(&(server_id, channel_id))
};
match remove_job {
Some(job_uid) => match self.scheduler.remove(&job_uid).await {
Ok(_) => {
info!("Cron job removed");
Ok(())
}
Err(e) => {
error!("Error removing cron job: {:?}", e);
Err(StopScheduleJob::RemoveFailed)
}
},
None => {
error!("Cron job not found");
Err(StopScheduleJob::JobNotFound)
}
}
}
}