From ae67a8dad652d68cd6142cd16e2d381e35e620b9 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 16:18:59 +0100 Subject: [PATCH] link shorting --- src/data/communication.rs | 6 +++ src/server/mod.rs | 1 + src/server/omikron_connection.rs | 16 +++++++ src/server/server.rs | 17 +++++++ src/server/short_link.rs | 77 ++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 src/server/short_link.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index fd57fe1..8cf9188 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -13,6 +13,9 @@ pub enum DataTypes { accepted_ids, uuid, register_id, + + link, + settings, settings_name, chat_partner_id, @@ -124,6 +127,9 @@ pub enum CommunicationType { error_no_call_id, error_invalid_call_id, success, + + shorten_link, + settings_save, settings_load, settings_list, diff --git a/src/server/mod.rs b/src/server/mod.rs index 39df0d8..72ff9f4 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,4 +2,5 @@ pub mod api; pub mod omikron_connection; pub mod omikron_manager; pub mod server; +pub mod short_link; pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index b2ee2c7..9e8f9d5 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,4 +1,5 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::server::short_link::add_short_link; use crate::sql::connection_status::ConnectionType; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; @@ -472,6 +473,21 @@ impl OmikronConnection { } } } + + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 6c5102b..09d41ed 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,5 +1,6 @@ use crate::log; use crate::server::api; +use crate::server::short_link::get_short_link; use crate::server::socket; use crate::util::file_util::load_file_buf; @@ -126,6 +127,22 @@ impl Service> for HttpService { }; Ok(api::handle(&path, headers.clone(), body_string).await) + } else if path.starts_with("/direct") { + let short = path.split('/').nth(2).unwrap_or_default(); + if let Ok(long) = get_short_link(short).await { + let response = HttpResponse::builder() + .status(StatusCode::FOUND) + .header("Location", long) + .body(Full::new(Bytes::from(""))) + .unwrap(); + Ok(response) + } else { + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("Short link not found"))) + .unwrap(); + Ok(response) + } } else { let response = HttpResponse::builder() .status(StatusCode::BAD_REQUEST) diff --git a/src/server/short_link.rs b/src/server/short_link.rs new file mode 100644 index 0000000..5e28cfa --- /dev/null +++ b/src/server/short_link.rs @@ -0,0 +1,77 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::{Rng, thread_rng}; + +static LINKS: Lazy> = Lazy::new(DashMap::new); + +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ#1234567890"; + +pub async fn add_short_link(long: &str) -> Result { + let raw = generate_unique_short_link().await; + LINKS.insert(raw.clone(), long.to_string()); + + Ok(format!( + "omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )) +} + +async fn generate_unique_short_link() -> String { + loop { + let short = generate_short_link().await; + if !LINKS.contains_key(&short) { + return short; + } + } +} + +pub async fn generate_short_link() -> String { + let len = short_length(); + + let mut rng = thread_rng(); + (0..len) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect() +} + +pub async fn get_short_link(short: &str) -> Result { + let normalized = normalize_short(short); + + LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) +} + +/* ---------------- helpers ---------------- */ + +fn short_length() -> usize { + let count = LINKS.len(); + + match count { + 0..=1_999 => 4, + 2_000..=999_999 => 8, + _ => 12, + } +} + +fn format_with_dashes(s: &str) -> String { + s.chars() + .collect::>() + .chunks(4) + .map(|c| c.iter().collect::()) + .collect::>() + .join("-") +} + +fn normalize_short(input: &str) -> String { + input + .chars() + .filter(|c| *c != '-') + .map(|c| match c { + 'Q' | 'O' => '0', + 'I' => 'l', + _ => c, + }) + .collect() +}