500 lines
22 KiB
Rust
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)
|
|
}
|
|
}
|
|
}
|
|
}
|