use std::collections::HashMap;
use uuid::Uuid;
use crate::config::schema::DmScope;
#[derive(Debug, Clone)]
pub enum MessageKind {
DirectMessage { account_id: Option<String> },
GroupMessage {
group_id: String,
thread_id: Option<String>, },
Webhook { custom_key: Option<String> },
Cron {
job_id: String,
mode: CronSessionMode,
},
}
#[derive(Debug, Clone)]
pub enum CronSessionMode {
Isolated,
Persistent(String),
}
#[derive(Debug, Clone)]
pub struct SessionKeyParams {
pub agent_id: String,
pub channel: String,
pub peer_id: String,
pub kind: MessageKind,
pub dm_scope: DmScope,
}
pub fn derive_session_key(params: &SessionKeyParams) -> String {
match ¶ms.kind {
MessageKind::DirectMessage { account_id } => derive_dm_key(params, account_id.as_deref()),
MessageKind::GroupMessage {
group_id,
thread_id,
} => derive_group_key(
¶ms.agent_id,
¶ms.channel,
group_id,
thread_id.as_deref(),
),
MessageKind::Webhook { custom_key } => custom_key
.clone()
.unwrap_or_else(|| format!("hook:{}", Uuid::new_v4())),
MessageKind::Cron { job_id, mode } => match mode {
CronSessionMode::Isolated => format!("cron:{job_id}"),
CronSessionMode::Persistent(key) => format!("session:{key}"),
},
}
}
fn derive_dm_key(params: &SessionKeyParams, account_id: Option<&str>) -> String {
let a = ¶ms.agent_id;
let c = ¶ms.channel;
let p = ¶ms.peer_id;
match params.dm_scope {
DmScope::Main => {
format!("agent:{a}:main")
}
DmScope::PerPeer => {
format!("agent:{a}:direct:{p}")
}
DmScope::PerChannelPeer => {
format!("agent:{a}:{c}:direct:{p}")
}
DmScope::PerAccountChannelPeer => {
let acc = account_id.unwrap_or("default");
format!("agent:{a}:{c}:{acc}:direct:{p}")
}
}
}
fn derive_group_key(
agent_id: &str,
channel: &str,
group_id: &str,
thread_id: Option<&str>,
) -> String {
match thread_id {
Some(tid) => {
format!("agent:{agent_id}:{channel}:group:{group_id}:topic:{tid}")
}
None => {
format!("agent:{agent_id}:{channel}:group:{group_id}")
}
}
}
pub fn resolve_identity(
channel: &str,
peer_id: &str,
identity_links: &HashMap<String, Vec<String>>,
) -> Option<String> {
let needle = format!("{channel}:{peer_id}");
for (identity, peers) in identity_links {
if peers.iter().any(|p| p == &needle) {
return Some(identity.clone());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn params(scope: DmScope, channel: &str, peer: &str) -> SessionKeyParams {
SessionKeyParams {
agent_id: "main".to_owned(),
channel: channel.to_owned(),
peer_id: peer.to_owned(),
kind: MessageKind::DirectMessage { account_id: None },
dm_scope: scope,
}
}
#[test]
fn dm_scope_main() {
let key = derive_session_key(¶ms(DmScope::Main, "telegram", "u1"));
assert_eq!(key, "agent:main:main");
}
#[test]
fn dm_scope_per_peer() {
let key = derive_session_key(¶ms(DmScope::PerPeer, "telegram", "u1"));
assert_eq!(key, "agent:main:direct:u1");
}
#[test]
fn dm_scope_per_channel_peer() {
let key = derive_session_key(¶ms(DmScope::PerChannelPeer, "telegram", "u1"));
assert_eq!(key, "agent:main:telegram:direct:u1");
}
#[test]
fn dm_scope_per_account_channel_peer() {
let mut p = params(DmScope::PerAccountChannelPeer, "telegram", "u1");
p.kind = MessageKind::DirectMessage {
account_id: Some("acc42".to_owned()),
};
let key = derive_session_key(&p);
assert_eq!(key, "agent:main:telegram:acc42:direct:u1");
}
#[test]
fn group_message_key() {
let p = SessionKeyParams {
agent_id: "main".to_owned(),
channel: "telegram".to_owned(),
peer_id: "u1".to_owned(),
kind: MessageKind::GroupMessage {
group_id: "g100".to_owned(),
thread_id: None,
},
dm_scope: DmScope::PerChannelPeer,
};
assert_eq!(derive_session_key(&p), "agent:main:telegram:group:g100");
}
#[test]
fn telegram_topic_key() {
let p = SessionKeyParams {
agent_id: "main".to_owned(),
channel: "telegram".to_owned(),
peer_id: String::new(),
kind: MessageKind::GroupMessage {
group_id: "g100".to_owned(),
thread_id: Some("t42".to_owned()),
},
dm_scope: DmScope::PerChannelPeer,
};
assert_eq!(
derive_session_key(&p),
"agent:main:telegram:group:g100:topic:t42"
);
}
#[test]
fn cron_isolated_key() {
let p = SessionKeyParams {
agent_id: "main".to_owned(),
channel: String::new(),
peer_id: String::new(),
kind: MessageKind::Cron {
job_id: "morning-briefing".to_owned(),
mode: CronSessionMode::Isolated,
},
dm_scope: DmScope::Main,
};
assert_eq!(derive_session_key(&p), "cron:morning-briefing");
}
#[test]
fn identity_link_resolution() {
let mut links = HashMap::new();
links.insert(
"alice".to_owned(),
vec!["telegram:12345".to_owned(), "discord:67890".to_owned()],
);
assert_eq!(
resolve_identity("telegram", "12345", &links),
Some("alice".to_owned())
);
assert_eq!(
resolve_identity("discord", "67890", &links),
Some("alice".to_owned())
);
assert_eq!(resolve_identity("slack", "99999", &links), None);
}
}