Skip to content

Commit

Permalink
implement scheduler system and a prototype task to check status update
Browse files Browse the repository at this point in the history
  • Loading branch information
ivinjabraham committed Oct 17, 2024
1 parent c222bef commit 0960398
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 7 deletions.
16 changes: 10 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ edition = "2021"

[dependencies]
anyhow = "1.0.66"
async-trait = "0.1.83"
chrono = "0.4.38"
chrono-tz = "0.9.0"
poise = "0.6.1"
reqwest = { version = "0.12.5", features = ["blocking", "json"] }
chrono-tz = "0.10.0"
poise = { git = "https://github.com/serenity-rs/poise", branch = "current" }
reqwest = { version = "0.12.5", features = ["json"] }
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
serenity = { version = "0.12.0", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
shuttle-runtime = "0.47.0"
shuttle-serenity = "0.47.0"
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "current" }
shuttle-runtime = "0.48.0"
shuttle-serenity = "0.48.0"
tokio = "1.26.0"
tracing = "0.1.37"

[patch.crates-io]
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "current" }
47 changes: 47 additions & 0 deletions src/graphql.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
amFOSS Daemon: A discord bot for the amFOSS Discord server.
Copyright (C) 2024 amFOSS
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use serde_json::Value;

const REQUEST_URL: &str = "https://root.shuttleapp.rs/";

pub async fn fetch_members() -> Result<Vec<String>, reqwest::Error> {
let client = reqwest::Client::new();
let query = r#"
query {
getMember {
name
}
}"#;

let response = client
.post(REQUEST_URL)
.json(&serde_json::json!({"query": query}))
.send()
.await?;

let json: Value = response.json().await?;

let member_names: Vec<String> = json["data"]["getMember"]
.as_array()
.unwrap()
.iter()
.map(|member| member["name"].as_str().unwrap().to_string())
.collect();

Ok(member_names)
}
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
mod commands;
mod graphql;
mod scheduler;
mod tasks;
mod utils;

use anyhow::Context as _;
use std::collections::HashMap;
Expand Down Expand Up @@ -77,6 +81,8 @@ async fn main(
(ReactionType::Unicode("📁".to_string()), role_id),
);

crate::scheduler::run_scheduler(ctx.clone()).await;

Ok(data)
})
})
Expand Down Expand Up @@ -126,7 +132,8 @@ async fn event_handler(
if &removed_reaction.emoji == expected_reaction {
if let Some(guild_id) = removed_reaction.guild_id {
if let Ok(member) = guild_id
.member(ctx, removed_reaction.user_id.unwrap()).await
.member(ctx, removed_reaction.user_id.unwrap())
.await
{
if let Err(e) = member.remove_role(&ctx.http, *role_id).await {
eprintln!("Error: {:?}", e);
Expand Down
38 changes: 38 additions & 0 deletions src/scheduler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
amFOSS Daemon: A discord bot for the amFOSS Discord server.
Copyright (C) 2024 amFOSS
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::tasks::{get_tasks, Task};
use serenity::client::Context as SerenityContext;

use tokio::spawn;

pub async fn run_scheduler(ctx: SerenityContext) {
let tasks = get_tasks();

for task in tasks {
spawn(schedule_task(ctx.clone(), task));
}
}

async fn schedule_task(ctx: SerenityContext, task: Box<dyn Task>) {
loop {
let next_run_in = task.interval();
tokio::time::sleep(next_run_in).await;

task.run(ctx.clone()).await;
}
}
134 changes: 134 additions & 0 deletions src/tasks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
amFOSS Daemon: A discord bot for the amFOSS Discord server.
Copyright (C) 2024 amFOSS
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::{
graphql::fetch_members,
utils::{get_five_am_timestamp, time_until},
};
use async_trait::async_trait;
use serenity::{
all::{ChannelId, Message},
client::Context,
};

use tokio::time::Duration;

const GROUP_ONE_CHANNEL_ID: u64 = 1225098248293716008;
const GROUP_TWO_CHANNEL_ID: u64 = 1225098298935738489;
const GROUP_THREE_CHANNEL_ID: u64 = 1225098353378070710;
const GROUP_FOUR_CHANNEL_ID: u64 = 1225098407216156712;
const STATUS_UPDATE_CHANNEL_ID: u64 = 764575524127244318;

#[async_trait]
pub trait Task: Send + Sync {
fn name(&self) -> &'static str;
fn interval(&self) -> Duration;
async fn run(&self, ctx: Context);
}

pub struct StatusUpdateCheck;

#[async_trait]
impl Task for StatusUpdateCheck {
fn name(&self) -> &'static str {
"StatusUpdateCheck"
}

fn interval(&self) -> Duration {
time_until(5, 0)
}

async fn run(&self, ctx: Context) {
let members = fetch_members().await.expect("Root must be up.");

let channel_ids: Vec<ChannelId> = vec![
ChannelId::new(GROUP_ONE_CHANNEL_ID),
ChannelId::new(GROUP_TWO_CHANNEL_ID),
ChannelId::new(GROUP_THREE_CHANNEL_ID),
ChannelId::new(GROUP_FOUR_CHANNEL_ID),
];

let time = chrono::Local::now().with_timezone(&chrono_tz::Asia::Kolkata);
let today_five_am = get_five_am_timestamp(time);
let yesterday_five_am = today_five_am - chrono::Duration::hours(24);

let mut valid_updates: Vec<Message> = vec![];

for &channel_id in &channel_ids {
let builder = serenity::builder::GetMessages::new().limit(50);
match channel_id.messages(&ctx.http, builder).await {
Ok(messages) => {
let filtered_messages: Vec<Message> = messages
.into_iter()
.filter(|msg| {
msg.timestamp >= yesterday_five_am.into()
&& msg.timestamp < today_five_am.into()
&& msg.content.to_lowercase().contains("namah shivaya")
&& msg.content.to_lowercase().contains("regards")
})
.collect();

valid_updates.extend(filtered_messages);
}
Err(e) => println!("ERROR: {:?}", e),
}
}

let mut naughty_list: Vec<String> = vec![];

for member in &members {
let name_parts: Vec<&str> = member.split_whitespace().collect();
let first_name = name_parts.get(0).unwrap_or(&"");
let last_name = name_parts.get(1).unwrap_or(&"");
let has_sent_update = valid_updates
.iter()
.any(|msg| msg.content.contains(first_name) || msg.content.contains(last_name));

if !has_sent_update {
naughty_list.push(member.to_string());
}
}

let status_update_channel = ChannelId::new(STATUS_UPDATE_CHANNEL_ID);

if naughty_list.is_empty() {
status_update_channel
.say(ctx.http, "Everyone sent their update today!")
.await;
} else {
let formatted_list = naughty_list
.iter()
.enumerate()
.map(|(i, member)| format!("{}. {:?}", i + 1, member))
.collect::<Vec<String>>()
.join("\n");
status_update_channel
.say(
ctx.http,
format!(
"These members did not send their updates:\n{}",
formatted_list
),
)
.await;
}
}
}

pub fn get_tasks() -> Vec<Box<dyn Task>> {
vec![Box::new(StatusUpdateCheck)]
}
41 changes: 41 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
amFOSS Daemon: A discord bot for the amFOSS Discord server.
Copyright (C) 2024 amFOSS
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use chrono::{DateTime, Datelike, Local, TimeZone};
use chrono_tz::Tz;
use tokio::time::Duration;

pub fn time_until(hour: u32, minute: u32) -> Duration {
let now = chrono::Local::now().with_timezone(&chrono_tz::Asia::Kolkata);
let today_run = now.date().and_hms(hour, minute, 0);

let next_run = if now < today_run {
today_run
} else {
today_run + chrono::Duration::days(1)
};

let time_until = (next_run - now).to_std().unwrap();
Duration::from_secs(time_until.as_secs())
}

pub fn get_five_am_timestamp(now: DateTime<Tz>) -> DateTime<Local> {
chrono::Local
.ymd(now.year(), now.month(), now.day())
.and_hms_opt(5, 0, 0)
.expect("Chrono must work.")
}

0 comments on commit 0960398

Please sign in to comment.