use anyhow::Result;
use crate::entities::{allowed_channel, rate_limit::{self, RateLimitScope}};
use robson_slack::events::InnerEvent;
use sea_orm::DatabaseConnection;
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub enum SanitizedInput {
Message {
text: String,
channel_id: String,
user_id: String,
thread_ts: Option<String>,
ts: String,
},
}
pub struct Sensorium {
db: DatabaseConnection,
seen_envelopes: Arc<Mutex<HashSet<String>>>,
max_requests_per_user: i32,
max_requests_per_channel: i32,
rate_limit_window_secs: i64,
}
impl Sensorium {
pub fn new(db: DatabaseConnection) -> Self {
Self {
db,
seen_envelopes: Arc::new(Mutex::new(HashSet::new())),
max_requests_per_user: 20,
max_requests_per_channel: 100,
rate_limit_window_secs: 3600,
}
}
pub fn new_with_limits(
db: DatabaseConnection,
max_requests_per_user: i32,
max_requests_per_channel: i32,
rate_limit_window_secs: i64,
) -> Self {
Self {
db,
seen_envelopes: Arc::new(Mutex::new(HashSet::new())),
max_requests_per_user,
max_requests_per_channel,
rate_limit_window_secs,
}
}
pub async fn process_event(
&self,
envelope_id: &str,
event: InnerEvent,
channel_id: &str,
) -> Result<Option<SanitizedInput>> {
{
let mut seen = self.seen_envelopes.lock().unwrap();
if seen.contains(envelope_id) {
return Ok(None);
}
seen.insert(envelope_id.to_string());
}
if !allowed_channel::Model::is_allowed(&self.db, channel_id).await? {
return Ok(None);
}
let (user_id, text, ts, thread_ts) = match &event {
InnerEvent::AppMention(m) => (
m.user.clone(),
m.text.clone(),
m.ts.clone(),
m.thread_ts.clone(),
),
InnerEvent::Message(m) => (
m.user.clone().unwrap_or_default(),
m.text.clone().unwrap_or_default(),
m.ts.clone(),
m.thread_ts.clone(),
),
};
if let InnerEvent::Message(m) = &event {
if m.bot_id.is_some() {
return Ok(None);
}
}
let user_allowed = rate_limit::Model::check_and_increment(
&self.db,
RateLimitScope::User,
&user_id,
self.rate_limit_window_secs,
self.max_requests_per_user,
)
.await?;
if !user_allowed {
return Ok(None);
}
let channel_allowed = rate_limit::Model::check_and_increment(
&self.db,
RateLimitScope::Channel,
channel_id,
self.rate_limit_window_secs,
self.max_requests_per_channel,
)
.await?;
if !channel_allowed {
return Ok(None);
}
let sanitized_text = ::robson_slack::events::sanitize_text(&text);
Ok(Some(SanitizedInput::Message {
text: sanitized_text,
channel_id: channel_id.to_string(),
user_id,
thread_ts,
ts,
}))
}
}