BotDiscord/src/event/schedule_job.rs

495 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 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 @ROLE 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, 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)
}
}
}
}