Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 70 additions & 7 deletions src/bot/version_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,42 @@ pub async fn check_and_notify_version_update(
let current_version = dotenvy::var("BOT_VERSION").unwrap_or_else(|_| "0.1.1".to_string());
let user_id = command.user.id.get();

tracing::info!(
"[VERSION] Checking version for user {}. Current version: {}",
user_id,
current_version
);

match handler.database.get_user_last_seen_version(user_id).await {
Ok(last_seen_version) => {
tracing::info!(
"[VERSION] User {} last seen version: {}",
user_id,
last_seen_version
);

if last_seen_version != current_version {
tracing::info!(
"[VERSION] Version mismatch. Sending update notification to user {}",
user_id
);

let embed = commands::update_message::create_update_embed(&current_version);
let response = CreateInteractionResponseFollowup::new()
.embed(embed)
.ephemeral(true);

if let Err(e) = command.create_followup(&context.http, response).await {
tracing::warn!("[VERSION] Failed to send update notification: {}", e);
tracing::warn!(
"[VERSION] Failed to send update notification to user {}: {}",
user_id,
e
);
} else {
tracing::info!(
"[VERSION] Successfully sent update notification to user {}",
user_id
);
}

if let Err(e) = handler
Expand All @@ -32,20 +58,57 @@ pub async fn check_and_notify_version_update(
tracing::error!("[VERSION] Failed to update last seen version: {}", e);
} else {
tracing::info!(
"[VERSION] User {} notified of update from {} to {}",
"[VERSION] User {} version updated from {} to {}",
user_id,
last_seen_version,
current_version
);
}
} else {
tracing::debug!(
"[VERSION] User {} already on current version {}",
user_id,
current_version
);
}
}
Err(e) => {
tracing::debug!(
"[VERSION] Could not get last seen version for user {}: {}",
user_id,
e
Err(_) => {
tracing::info!(
"[VERSION] No last seen version for user {}. Sending initial notification",
user_id
);

let embed = commands::update_message::create_update_embed(&current_version);
let response = CreateInteractionResponseFollowup::new()
.embed(embed)
.ephemeral(true);

if let Err(e) = command.create_followup(&context.http, response).await {
tracing::warn!(
"[VERSION] Failed to send initial version notification to user {}: {}",
user_id,
e
);
} else {
tracing::info!(
"[VERSION] Successfully sent initial notification to user {}",
user_id
);
}

if let Err(e) = handler
.database
.update_user_last_seen_version(user_id, &current_version)
.await
{
tracing::error!("[VERSION] Failed to set initial version: {}", e);
} else {
tracing::info!(
"[VERSION] User {} initial version set to {}",
user_id,
current_version
);
}
}
}

Expand Down
133 changes: 124 additions & 9 deletions src/commands/bg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ pub async fn run(
}
};

let status = handler
.nightscout_client
.get_status(base_url, token)
.await
.ok();

let profile = match handler.nightscout_client.get_profile(base_url, token).await {
Ok(profile) => profile,
Err(e) => {
Expand All @@ -120,13 +126,38 @@ pub async fn run(
.ok()
.flatten();

let now_utc = chrono::Utc::now();
let thirty_min_ago = now_utc - chrono::Duration::minutes(30);
let start_time = thirty_min_ago.to_rfc3339();
let end_time = now_utc.to_rfc3339();

let recent_entries = handler
.nightscout_client
.get_entries_for_hours(base_url, 1, token)
.await
.unwrap_or_default();

let recent_treatments = handler
.nightscout_client
.fetch_treatments_between(base_url, &start_time, &end_time, token)
.await
.unwrap_or_default();

let default_profile_name = &profile.default_profile;
let profile_store = profile
.store
.get(default_profile_name)
.context("Default profile not found")?;

let thresholds = status
.as_ref()
.and_then(|s| s.settings.as_ref())
.and_then(|settings| settings.thresholds.as_ref());

let user_timezone = &profile_store.timezone;
let target_low_mg = profile_store.get_target_low_mg(thresholds);
let target_high_mg = profile_store.get_target_high_mg(thresholds);

let entry_time = entry.millis_to_user_timezone(user_timezone);
let now = chrono::Utc::now()
.with_timezone(&chrono_tz::Tz::from_str(user_timezone).unwrap_or(chrono_tz::UTC));
Expand All @@ -140,9 +171,9 @@ pub async fn run(
format!("{} days ago", duration.num_days())
};

let color = if entry.sgv > 180.0 {
let color = if entry.sgv > target_high_mg {
Colour::from_rgb(227, 177, 11)
} else if entry.sgv < 70.0 {
} else if entry.sgv < target_low_mg {
Colour::from_rgb(235, 47, 47)
} else {
Colour::from_rgb(87, 189, 79)
Expand All @@ -154,13 +185,23 @@ pub async fn run(
.and_then(|u| u.avatar_url())
.unwrap_or_default();

let title = format!(
"{}'s Nightscout data",
target_user
.as_ref()
.map(|u| u.display_name())
.unwrap_or_else(|| "User")
);
let custom_title = status
.as_ref()
.and_then(|s| s.settings.as_ref())
.and_then(|settings| settings.custom_title.as_deref())
.filter(|title| *title != "Nightscout");

let title = if let Some(custom) = custom_title {
custom.to_string()
} else {
format!(
"{}'s Nightscout data",
target_user
.as_ref()
.map(|u| u.display_name())
.unwrap_or_else(|| "User")
)
};

let icon_bytes = std::fs::read("assets/images/nightscout_icon.png")?;
let icon_attachment = CreateAttachment::bytes(icon_bytes, "nightscout_icon.png");
Expand Down Expand Up @@ -219,6 +260,80 @@ pub async fn run(
}
}

let mut fingerprick_value: Option<(f32, u64)> = None;
let thirty_min_ago_millis = thirty_min_ago.timestamp_millis() as u64;

for entry in recent_entries.iter() {
if entry.has_mbg()
&& let Some(entry_time_millis) = entry.date
&& entry_time_millis >= thirty_min_ago_millis
&& let Some(mbg) = entry.mbg
{
fingerprick_value = Some((mbg, entry_time_millis));
break;
}
}

if fingerprick_value.is_none() {
for treatment in recent_treatments.iter() {
if treatment.event_type.as_deref() == Some("BG Check")
&& let Some(glucose_str) = &treatment.glucose
&& let Ok(glucose) = glucose_str.parse::<f32>()
{
let treatment_time_millis = if let Some(time) = treatment.date.or(treatment.mills) {
time
} else if let Some(created_at) = &treatment.created_at {
if let Ok(parsed_time) = chrono::DateTime::parse_from_rfc3339(created_at) {
parsed_time.timestamp_millis() as u64
} else {
continue;
}
} else {
continue;
};

if treatment_time_millis >= thirty_min_ago_millis {
fingerprick_value = Some((glucose, treatment_time_millis));
break;
}
}
}
}

if let Some((fp_value, fp_timestamp)) = fingerprick_value {
let fp_mmol = fp_value / 18.0;

let now_millis = now_utc.timestamp_millis() as u64;

tracing::info!("[BG] Fingerprick timestamp: {}", fp_timestamp);
tracing::info!("[BG] Now timestamp: {}", now_millis);

let timestamp_millis = if fp_timestamp < 10000000000 {
tracing::info!("[BG] Converting seconds to milliseconds");
fp_timestamp * 1000
} else {
tracing::info!("[BG] Timestamp already in milliseconds");
fp_timestamp
};

tracing::info!("[BG] Normalized timestamp: {}", timestamp_millis);

let diff_millis = now_millis.saturating_sub(timestamp_millis);
tracing::info!("[BG] Difference in milliseconds: {}", diff_millis);

let fp_age_minutes = diff_millis / 1000 / 60;
tracing::info!("[BG] Age in minutes: {}", fp_age_minutes);

embed = embed.field(
"Fingerprick",
format!(
"{:.0} mg/dL ({:.1} mmol/L)\n-# {} min ago",
fp_value, fp_mmol, fp_age_minutes
),
false,
);
}

embed = embed.footer(
CreateEmbedFooter::new(format!("measured • {time_ago}"))
.icon_url("attachment://nightscout_icon.png"),
Expand Down
12 changes: 12 additions & 0 deletions src/commands/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ pub async fn run(
}
};

let status = handler
.nightscout_client
.get_status(base_url, token)
.await
.ok();

let now = chrono::Utc::now();
let hours_ago = now - chrono::Duration::hours(hours);
let start_time = hours_ago.to_rfc3339();
Expand All @@ -132,6 +138,11 @@ pub async fn run(
}
};

let thresholds = status
.as_ref()
.and_then(|s| s.settings.as_ref())
.and_then(|settings| settings.thresholds.as_ref());

let buffer = draw_graph(
&entries,
&treatments,
Expand All @@ -141,6 +152,7 @@ pub async fn run(
handler,
hours as u16,
None,
thresholds,
)
.await?;

Expand Down
Loading