#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PermittedTarget {
pub channel: &'static str,
pub target_id: String,
}
tokio::task_local! {
static CRON_PERMITTED_TARGETS: Option<Vec<PermittedTarget>>;
}
pub async fn with_permitted_targets<F, T>(targets: Option<Vec<PermittedTarget>>, fut: F) -> T
where
F: std::future::Future<Output = T>,
{
CRON_PERMITTED_TARGETS.scope(targets, fut).await
}
pub async fn with_send_target<F, T>(target: Option<i64>, fut: F) -> T
where
F: std::future::Future<Output = T>,
{
let targets = match target {
Some(chat) => Some(vec![PermittedTarget {
channel: "telegram",
target_id: chat.to_string(),
}]),
None => Some(Vec::new()),
};
with_permitted_targets(targets, fut).await
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SendPermission {
Unscoped,
Permitted(Vec<PermittedTarget>),
Nowhere,
}
pub fn permission() -> SendPermission {
CRON_PERMITTED_TARGETS
.try_with(|targets| match targets {
Some(list) if list.is_empty() => SendPermission::Nowhere,
Some(list) => SendPermission::Permitted(list.clone()),
None => SendPermission::Unscoped,
})
.unwrap_or(SendPermission::Unscoped)
}
pub fn may_send(channel: &str, target_id: &str) -> bool {
match permission() {
SendPermission::Unscoped => true,
SendPermission::Permitted(list) => list
.iter()
.any(|p| p.channel == channel && p.target_id == target_id),
SendPermission::Nowhere => false,
}
}
pub fn may_send_to(chat_id: i64) -> bool {
may_send("telegram", &chat_id.to_string())
}
pub fn refusal_for(channel: &str, target_id: &str) -> String {
match permission() {
SendPermission::Permitted(list) => {
let allowed_str = list
.iter()
.map(|p| format!("{}:{}", p.channel, p.target_id))
.collect::<Vec<_>>()
.join(", ");
format!(
"Refused: this scheduled job may only send to [{allowed_str}], and this send \
targeted {channel}:{target_id}. If the report belongs in another channel, \
change the job's deliver_to; an address found in memory or in earlier context \
is not permission to post there."
)
}
_ => format!(
"Refused: this scheduled job has no deliver_to, so it may not send to any channel \
(attempted {channel}:{target_id}). Its output stays in its own session. Set \
deliver_to on the job if it should report to a channel."
),
}
}
pub fn refusal(chat_id: i64) -> String {
refusal_for("telegram", &chat_id.to_string())
}