pub fn build_session_title(
is_dm: bool,
user_name: &str,
user_id: i64,
chat_title: &str,
chat_id: i64,
topic_id: Option<i32>,
topic_name: Option<&str>,
) -> String {
let suffix = chat_id_suffix(chat_id, topic_id);
if is_dm {
format!("Telegram: DM {user_name} ({user_id}) {suffix}")
} else {
match topic_name.map(str::trim).filter(|n| !n.is_empty()) {
Some(name) if topic_id.is_some() => {
format!("Telegram: {chat_title} / {name} {suffix}")
}
_ => format!("Telegram: {chat_title} {suffix}"),
}
}
}
pub fn build_legacy_session_title(
is_dm: bool,
user_name: &str,
user_id: i64,
chat_title: &str,
) -> String {
if is_dm {
format!("Telegram: DM {user_name} ({user_id})")
} else {
format!("Telegram: {chat_title}")
}
}
pub fn chat_id_suffix(chat_id: i64, topic_id: Option<i32>) -> String {
match topic_id {
Some(tid) => format!("[chat:{chat_id}:topic:{tid}]"),
None => format!("[chat:{chat_id}]"),
}
}
pub fn topic_session_id(is_topic_message: bool, thread_id: Option<i32>) -> Option<i32> {
if is_topic_message { thread_id } else { None }
}
pub fn session_idle_expired(
updated_at: chrono::DateTime<chrono::Utc>,
idle_hours: Option<f64>,
) -> bool {
idle_hours.is_some_and(|h| {
let elapsed = (chrono::Utc::now() - updated_at).num_seconds();
elapsed > (h * 3600.0) as i64
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolveSource {
ChatBound,
Suffix,
Create,
}
pub fn choose_resolve_source(
chat_bound: Option<uuid::Uuid>,
bound_archived: bool,
suffix_match: Option<uuid::Uuid>,
) -> ResolveSource {
if chat_bound.is_some() && !bound_archived {
ResolveSource::ChatBound
} else if suffix_match.is_some() {
ResolveSource::Suffix
} else {
ResolveSource::Create
}
}
pub fn should_refresh_label(stored: &str, template: &str) -> bool {
if stored == template {
return false;
}
if crate::brain::agent::service::AgentService::is_default_channel_title(stored) {
return crate::brain::agent::service::AgentService::is_default_channel_title(template)
&& stored != template;
}
if is_telegram_group_session_title(stored) && is_telegram_group_session_title(template) {
return telegram_middle_label(stored) != telegram_middle_label(template);
}
false
}
fn is_telegram_group_session_title(title: &str) -> bool {
let Some(rest) = title.strip_prefix("Telegram: ") else {
return false;
};
!rest.starts_with("DM ") && title.contains("[chat:")
}
fn telegram_middle_label(title: &str) -> String {
let body = title.strip_prefix("Telegram: ").unwrap_or(title).trim();
let suffix = crate::brain::agent::service::AgentService::extract_chat_id_suffix(title);
if suffix.is_empty() {
return body.to_string();
}
body.strip_suffix(suffix).unwrap_or(body).trim().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dm_template_format() {
let t = build_session_title(true, "Alice", 123, "", 456, None, None);
assert_eq!(t, "Telegram: DM Alice (123) [chat:456]");
}
#[test]
fn should_not_clobber_auto_titled_dm() {
let auto = "Telegram: Fix deploy [chat:133526395]";
let template = build_session_title(true, "Alexey", 133526395, "", 133526395, None, None);
assert!(!should_refresh_label(auto, &template));
}
#[test]
fn should_refresh_group_rename() {
let old = "Telegram: Old Group [chat:-1]";
let new = "Telegram: New Group [chat:-1]";
assert!(should_refresh_label(old, new));
}
#[test]
fn default_dm_still_refreshes_on_name_change() {
let old = build_session_title(true, "Alice", 1, "", 99, None, None);
let new = build_session_title(true, "Bob", 1, "", 99, None, None);
assert!(should_refresh_label(&old, &new));
}
#[test]
fn chat_bound_wins_over_suffix_candidate() {
let a = uuid::Uuid::new_v4();
let b = uuid::Uuid::new_v4();
assert_eq!(
choose_resolve_source(Some(a), false, Some(b)),
ResolveSource::ChatBound
);
}
#[test]
fn archived_bound_falls_through_to_suffix() {
let a = uuid::Uuid::new_v4();
let b = uuid::Uuid::new_v4();
assert_eq!(
choose_resolve_source(Some(a), true, Some(b)),
ResolveSource::Suffix
);
}
#[test]
fn session_idle_expired_within_and_past_window() {
let recent = chrono::Utc::now() - chrono::Duration::minutes(30);
assert!(!session_idle_expired(recent, Some(1.0)));
let stale = chrono::Utc::now() - chrono::Duration::hours(2);
assert!(session_idle_expired(stale, Some(1.0)));
assert!(!session_idle_expired(stale, None));
}
#[test]
fn session_idle_expired_boundary_not_yet_expired() {
let at_limit = chrono::Utc::now() - chrono::Duration::seconds(3600);
assert!(!session_idle_expired(at_limit, Some(1.0)));
let past_limit = chrono::Utc::now() - chrono::Duration::seconds(3601);
assert!(session_idle_expired(past_limit, Some(1.0)));
}
}