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

11
Cargo.lock generated
View File

@@ -184,6 +184,7 @@ dependencies = [
"futures", "futures",
"governor", "governor",
"http 1.3.1", "http 1.3.1",
"num-format",
"once_cell", "once_cell",
"poise", "poise",
"rand 0.9.2", "rand 0.9.2",
@@ -1699,6 +1700,16 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-format"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
dependencies = [
"arrayvec",
"itoa",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.46" version = "0.1.46"

View File

@@ -44,5 +44,6 @@ url = "2.5"
governor = "0.10.1" governor = "0.10.1"
once_cell = "1.21.3" once_cell = "1.21.3"
serde_path_to_error = "0.1.17" serde_path_to_error = "0.1.17"
num-format = "0.4.4"
[dev-dependencies] [dev-dependencies]

View File

@@ -1,5 +1,6 @@
use figment::value::UncasedStr; use figment::value::UncasedStr;
use serenity::all::{ClientBuilder, GatewayIntents}; use num_format::{Locale, ToFormattedString};
use serenity::all::{ActivityData, ClientBuilder, GatewayIntents};
use tokio::signal; use tokio::signal;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use tracing_subscriber::{EnvFilter, FmtSubscriber}; use tracing_subscriber::{EnvFilter, FmtSubscriber};
@@ -27,6 +28,21 @@ mod services;
mod state; mod state;
mod web; 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] #[tokio::main]
async fn main() { async fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
@@ -102,7 +118,7 @@ async fn main() {
.expect("Failed to create BannerApi"); .expect("Failed to create BannerApi");
let banner_api_arc = Arc::new(banner_api); 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"); .expect("Failed to create AppState");
// Create BannerState for web service // Create BannerState for web service
@@ -174,6 +190,27 @@ async fn main() {
) )
.await?; .await?;
poise::builtins::register_globally(ctx, &framework.options().commands).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 }) Ok(Data { app_state })
}) })
}) })

View File

@@ -86,7 +86,7 @@ impl SubjectJob {
.execute(db_pool) .execute(db_pool)
.await .await
.map(|result| { .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}")) .map_err(|e| anyhow::anyhow!("Failed to upsert course: {e}"))
} }

View File

@@ -5,24 +5,28 @@ use crate::banner::Course;
use anyhow::Result; use anyhow::Result;
use redis::AsyncCommands; use redis::AsyncCommands;
use redis::Client; use redis::Client;
use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
pub banner_api: Arc<BannerApi>, pub banner_api: Arc<BannerApi>,
pub redis: Arc<Client>, pub redis: Arc<Client>,
pub db_pool: PgPool,
} }
impl AppState { impl AppState {
pub fn new( pub fn new(
banner_api: Arc<BannerApi>, banner_api: Arc<BannerApi>,
redis_url: &str, redis_url: &str,
db_pool: PgPool,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let redis_client = Client::open(redis_url)?; let redis_client = Client::open(redis_url)?;
Ok(Self { Ok(Self {
banner_api, banner_api,
redis: Arc::new(redis_client), redis: Arc::new(redis_client),
db_pool,
}) })
} }
@@ -45,4 +49,12 @@ impl AppState {
Err(anyhow::anyhow!("Course not found for CRN {crn}")) 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)
}
} }