use super::AppState;
pub(super) fn metadata_str(meta: Option<&serde_json::Value>, ptr: &str) -> Option<String> {
meta.and_then(|m| m.pointer(ptr)).and_then(|v| {
v.as_str()
.map(|s| s.to_string())
.or_else(|| v.as_i64().map(|n| n.to_string()))
.or_else(|| v.as_u64().map(|n| n.to_string()))
})
}
pub(super) fn resolve_channel_chat_id(inbound: &roboticus_channels::InboundMessage) -> String {
let meta = inbound.metadata.as_ref();
metadata_str(meta, "/chat_id")
.or_else(|| metadata_str(meta, "/channel_id"))
.or_else(|| metadata_str(meta, "/thread_id"))
.or_else(|| metadata_str(meta, "/conversation_id"))
.or_else(|| metadata_str(meta, "/group_id"))
.or_else(|| metadata_str(meta, "/message/chat/id"))
.or_else(|| metadata_str(meta, "/messages/0/chat/id"))
.or_else(|| metadata_str(meta, "/messages/0/channel_id"))
.unwrap_or_else(|| inbound.sender_id.clone())
}
pub(crate) fn channel_chat_id_for_inbound(inbound: &roboticus_channels::InboundMessage) -> String {
resolve_channel_chat_id(inbound)
}
pub(super) fn resolve_channel_is_group(inbound: &roboticus_channels::InboundMessage) -> bool {
let meta = inbound.metadata.as_ref();
if let Some(v) = meta
.and_then(|m| m.get("is_group"))
.and_then(|v| v.as_bool())
{
return v;
}
if let Some(kind) = metadata_str(meta, "/message/chat/type") {
return matches!(kind.as_str(), "group" | "supergroup");
}
false
}
pub(super) fn resolve_channel_scope(
cfg: &roboticus_core::RoboticusConfig,
inbound: &roboticus_channels::InboundMessage,
chat_id: &str,
) -> roboticus_db::sessions::SessionScope {
let mode = cfg.session.scope_mode.as_str();
let channel = inbound.platform.to_lowercase();
if mode == "group" && resolve_channel_is_group(inbound) {
return roboticus_db::sessions::SessionScope::Group {
group_id: chat_id.to_string(),
channel,
};
}
if mode == "peer" || mode == "group" {
return roboticus_db::sessions::SessionScope::Peer {
peer_id: inbound.sender_id.clone(),
channel,
};
}
if mode == "agent" {
return roboticus_db::sessions::SessionScope::Agent;
}
tracing::warn!(
scope_mode = %mode,
platform = %channel,
"session scope degraded to Agent (global) — unrecognized scope_mode"
);
roboticus_db::sessions::SessionScope::Agent
}
pub(super) fn parse_skills_json(skills_json: Option<&str>) -> Vec<String> {
skills_json
.and_then(|s| {
serde_json::from_str::<Vec<String>>(s)
.inspect_err(|e| tracing::warn!(error = %e, "failed to parse skills JSON"))
.ok()
})
.unwrap_or_default()
}
pub(super) async fn send_typing_indicator(
state: &AppState,
platform: &str,
chat_id: &str,
metadata: Option<&serde_json::Value>,
) {
match platform {
"telegram" => {
if let Some(ref tg) = state.telegram {
tg.send_typing(chat_id).await;
}
}
"whatsapp" => {
if let Some(ref wa) = state.whatsapp {
let msg_id = metadata
.and_then(|m| m.pointer("/messages/0/id"))
.or_else(|| metadata.and_then(|m| m.get("id")))
.and_then(|v| v.as_str());
wa.send_typing(chat_id, msg_id).await;
}
}
"discord" => {
if let Some(ref dc) = state.discord {
dc.send_typing(chat_id).await;
}
}
"signal" => {
if let Some(ref sig) = state.signal {
sig.send_typing(chat_id).await;
}
}
_ => {}
}
}
pub(super) fn resolve_allowlist_status(
config: &roboticus_core::RoboticusConfig,
platform: &str,
chat_id: &str,
sender_id: &str,
) -> (bool, bool) {
match platform {
"telegram" => {
if let Some(ref tg) = config.channels.telegram {
let in_list = tg
.allowed_chat_ids
.iter()
.any(|id| id.to_string() == chat_id);
(
!tg.allowed_chat_ids.is_empty() && in_list,
!tg.allowed_chat_ids.is_empty(),
)
} else {
(false, true)
}
}
"whatsapp" => {
if let Some(ref wa) = config.channels.whatsapp {
let in_list = wa.allowed_numbers.iter().any(|n| n == sender_id);
(
!wa.allowed_numbers.is_empty() && in_list,
!wa.allowed_numbers.is_empty(),
)
} else {
(false, true)
}
}
"discord" => {
if let Some(ref dc) = config.channels.discord {
let in_list = dc.allowed_guild_ids.iter().any(|g| g == chat_id);
(
!dc.allowed_guild_ids.is_empty() && in_list,
!dc.allowed_guild_ids.is_empty(),
)
} else {
(false, true)
}
}
"signal" => {
if let Some(ref sig) = config.channels.signal {
let in_list = sig.allowed_numbers.iter().any(|n| n == sender_id);
(
!sig.allowed_numbers.is_empty() && in_list,
!sig.allowed_numbers.is_empty(),
)
} else {
(false, true)
}
}
"email" => {
let sender_lc = sender_id.to_lowercase();
let in_list = config
.channels
.email
.allowed_senders
.iter()
.any(|s| s.eq_ignore_ascii_case(&sender_lc));
(
!config.channels.email.allowed_senders.is_empty() && in_list,
!config.channels.email.allowed_senders.is_empty(),
)
}
_ => (false, true),
}
}