feat: basic activity status

This commit is contained in:
2025-09-13 01:04:46 -05:00
parent 430e2a255b
commit 624247ee14
5 changed files with 64 additions and 3 deletions
+39 -2
View File
@@ -1,5 +1,6 @@
use figment::value::UncasedStr;
use serenity::all::{ClientBuilder, GatewayIntents};
use num_format::{Locale, ToFormattedString};
use serenity::all::{ActivityData, ClientBuilder, GatewayIntents};
use tokio::signal;
use tracing::{error, info, warn};
use tracing_subscriber::{EnvFilter, FmtSubscriber};
@@ -27,6 +28,21 @@ mod services;
mod state;
mod web;
async fn update_bot_status(
ctx: &serenity::all::Context,
app_state: &AppState,
) -> Result<(), anyhow::Error> {
let course_count = app_state.get_course_count().await?;
ctx.set_activity(Some(ActivityData::playing(format!(
"Querying {:} classes",
course_count.to_formatted_string(&Locale::en)
))));
tracing::info!(course_count = course_count, "Updated bot status");
Ok(())
}
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
@@ -102,7 +118,7 @@ async fn main() {
.expect("Failed to create BannerApi");
let banner_api_arc = Arc::new(banner_api);
let app_state = AppState::new(banner_api_arc.clone(), &config.redis_url)
let app_state = AppState::new(banner_api_arc.clone(), &config.redis_url, db_pool.clone())
.expect("Failed to create AppState");
// Create BannerState for web service
@@ -174,6 +190,27 @@ async fn main() {
)
.await?;
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
// Start status update task
let status_app_state = app_state.clone();
let status_ctx = ctx.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(30));
// Update status immediately on startup
if let Err(e) = update_bot_status(&status_ctx, &status_app_state).await {
tracing::error!(error = %e, "Failed to update status on startup");
}
loop {
interval.tick().await;
if let Err(e) = update_bot_status(&status_ctx, &status_app_state).await {
tracing::error!(error = %e, "Failed to update bot status");
}
}
});
Ok(Data { app_state })
})
})
+1 -1
View File
@@ -86,7 +86,7 @@ impl SubjectJob {
.execute(db_pool)
.await
.map(|result| {
trace!(result = ?result, "Course upserted");
trace!(subject = course.subject, crn = course.course_reference_number, result = ?result, "Course upserted");
})
.map_err(|e| anyhow::anyhow!("Failed to upsert course: {e}"))
}
+12
View File
@@ -5,24 +5,28 @@ use crate::banner::Course;
use anyhow::Result;
use redis::AsyncCommands;
use redis::Client;
use sqlx::PgPool;
use std::sync::Arc;
#[derive(Clone)]
pub struct AppState {
pub banner_api: Arc<BannerApi>,
pub redis: Arc<Client>,
pub db_pool: PgPool,
}
impl AppState {
pub fn new(
banner_api: Arc<BannerApi>,
redis_url: &str,
db_pool: PgPool,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let redis_client = Client::open(redis_url)?;
Ok(Self {
banner_api,
redis: Arc::new(redis_client),
db_pool,
})
}
@@ -45,4 +49,12 @@ impl AppState {
Err(anyhow::anyhow!("Course not found for CRN {crn}"))
}
/// Get the total number of courses in the database
pub async fn get_course_count(&self) -> Result<i64> {
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM courses")
.fetch_one(&self.db_pool)
.await?;
Ok(count.0)
}
}