diff --git a/src/args.rs b/src/args.rs index 737e645..25b24a6 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,6 +3,7 @@ use lazy_static::lazy_static; use std::convert::Infallible; use std::fmt; use std::net::IpAddr; +use std::path::PathBuf; use std::str::FromStr; lazy_static! { @@ -124,6 +125,11 @@ pub struct Args { #[clap(long, env = "MICROBIN_CUSTOM_CSS")] pub custom_css: Option, + /// Replace built-in animal names file with custom names file for pasta links. + /// The file must be newline seperated. + #[clap(long, env = "MICROBIN_CUSTOM_NAMES")] + pub custom_names: Option, + /// Enable the use of Hash IDs for shorter URLs instead of animal names. #[clap(long, env = "MICROBIN_HASH_IDS")] pub hash_ids: bool, diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs index fdf91e7..d5fb8c6 100644 --- a/src/endpoints/create.rs +++ b/src/endpoints/create.rs @@ -1,8 +1,8 @@ use crate::dbio::save_to_file; use crate::pasta::PastaFile; -use crate::util::animalnumbers::to_animal_names; use crate::util::hashids::to_hashids; use crate::util::misc::is_valid_url; +use crate::util::pasta_id_converter::CONVERTER; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; use actix_web::{get, web, Error, HttpResponse, Responder}; @@ -194,7 +194,7 @@ pub async fn create( let slug = if ARGS.hash_ids { to_hashids(id) } else { - to_animal_names(id) + CONVERTER.to_names(id) }; Ok(HttpResponse::Found() .append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, slug))) diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs index ba2cfe7..bebf25c 100644 --- a/src/endpoints/edit.rs +++ b/src/endpoints/edit.rs @@ -1,9 +1,9 @@ use crate::args::Args; use crate::dbio::save_to_file; use crate::endpoints::errors::ErrorTemplate; -use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; +use crate::util::pasta_id_converter::CONVERTER; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; use actix_web::{get, post, web, Error, HttpResponse}; @@ -24,7 +24,7 @@ pub async fn get_edit(data: web::Data, id: web::Path) -> HttpR let id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id.into_inner()).unwrap_or(0) + CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) }; remove_expired(&mut pastas); @@ -62,7 +62,7 @@ pub async fn post_edit( let id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id.into_inner()).unwrap_or(0) + CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) }; let mut pastas = data.pastas.lock().await; diff --git a/src/endpoints/pasta.rs b/src/endpoints/pasta.rs index 28978f0..2b1651f 100644 --- a/src/endpoints/pasta.rs +++ b/src/endpoints/pasta.rs @@ -2,10 +2,10 @@ use crate::args::{Args, ARGS}; use crate::dbio::save_to_file; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::Pasta; -use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::AppState; +use crate::util::pasta_id_converter::CONVERTER; use actix_web::{get, web, HttpResponse}; use askama::Template; @@ -27,7 +27,7 @@ pub async fn getpasta(data: web::Data, id: web::Path) -> HttpR let id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id.into_inner()).unwrap_or(0) + CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) }; // remove expired pastas (including this one if needed) @@ -92,7 +92,7 @@ pub async fn redirecturl(data: web::Data, id: web::Path) -> Ht let id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id.into_inner()).unwrap_or(0) + CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) }; // remove expired pastas (including this one if needed) @@ -160,7 +160,7 @@ pub async fn getrawpasta(data: web::Data, id: web::Path) -> St let id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id.into_inner()).unwrap_or(0) + CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) }; // remove expired pastas (including this one if needed) diff --git a/src/endpoints/qr.rs b/src/endpoints/qr.rs index 4fe799b..6639470 100644 --- a/src/endpoints/qr.rs +++ b/src/endpoints/qr.rs @@ -1,10 +1,10 @@ use crate::args::{Args, ARGS}; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::Pasta; -use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::{self, remove_expired}; use crate::AppState; +use crate::util::pasta_id_converter::CONVERTER; use actix_web::{get, web, HttpResponse}; use askama::Template; @@ -25,7 +25,7 @@ pub async fn getqr(data: web::Data, id: web::Path) -> HttpResp let u64_id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id).unwrap_or(0) + CONVERTER.to_u64(&id).unwrap_or(0) }; // remove expired pastas (including this one if needed) diff --git a/src/endpoints/remove.rs b/src/endpoints/remove.rs index fc99c0f..ce0c407 100644 --- a/src/endpoints/remove.rs +++ b/src/endpoints/remove.rs @@ -3,10 +3,10 @@ use actix_web::{get, web, HttpResponse}; use crate::args::ARGS; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::PastaFile; -use crate::util::animalnumbers::to_u64; use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::AppState; +use crate::util::pasta_id_converter::CONVERTER; use askama::Template; use std::fs; @@ -24,7 +24,7 @@ pub async fn remove(data: web::Data, id: web::Path) -> HttpRes let id = if ARGS.hash_ids { hashid_to_u64(&id).unwrap_or(0) } else { - to_u64(&id.into_inner()).unwrap_or(0) + CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) }; for (i, pasta) in pastas.iter().enumerate() { diff --git a/src/main.rs b/src/main.rs index b2c4fef..958e768 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ pub mod args; pub mod pasta; pub mod util { - pub mod animalnumbers; + pub mod pasta_id_converter; pub mod auth; pub mod dbio; pub mod hashids; diff --git a/src/pasta.rs b/src/pasta.rs index 428bf8c..5ac74d5 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -6,8 +6,8 @@ use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; use crate::args::ARGS; -use crate::util::animalnumbers::to_animal_names; use crate::util::hashids::to_hashids; +use crate::util::pasta_id_converter::CONVERTER; use crate::util::syntaxhighlighter::html_highlight; #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -55,7 +55,7 @@ impl Pasta { if ARGS.hash_ids { to_hashids(self.id) } else { - to_animal_names(self.id) + CONVERTER.to_names(self.id) } } diff --git a/src/util/animalnumbers.rs b/src/util/animalnumbers.rs deleted file mode 100644 index eb003f1..0000000 --- a/src/util/animalnumbers.rs +++ /dev/null @@ -1,53 +0,0 @@ -const ANIMAL_NAMES: &[&str] = &[ - "ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse", - "snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox", - "panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat", - "goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper", - "deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal", - "wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra", -]; - -pub fn to_animal_names(mut number: u64) -> String { - let mut result: Vec<&str> = Vec::new(); - - if number == 0 { - return ANIMAL_NAMES[0].parse().unwrap(); - } - - let mut power = 6; - - loop { - let digit = number / ANIMAL_NAMES.len().pow(power) as u64; - if !(result.is_empty() && digit == 0) { - result.push(ANIMAL_NAMES[digit as usize]); - } - number -= digit * ANIMAL_NAMES.len().pow(power) as u64; - if power > 0 { - power -= 1; - } else if power == 0 || number == 0 { - break; - } - } - - result.join("-") -} - -pub fn to_u64(animal_names: &str) -> Result { - let mut result: u64 = 0; - - let animals: Vec<&str> = animal_names.split('-').collect(); - - let mut pow = animals.len(); - for animal in animals { - pow -= 1; - let animal_index = ANIMAL_NAMES.iter().position(|&r| r == animal); - match animal_index { - None => return Err("Failed to convert animal name to u64!"), - Some(_) => { - result += (animal_index.unwrap() * ANIMAL_NAMES.len().pow(pow as u32)) as u64 - } - } - } - - Ok(result) -} diff --git a/src/util/pasta_id_converter.rs b/src/util/pasta_id_converter.rs new file mode 100644 index 0000000..3d45d68 --- /dev/null +++ b/src/util/pasta_id_converter.rs @@ -0,0 +1,98 @@ +use std::fs; + +use lazy_static::lazy_static; + +use crate::args::ARGS; + +const ANIMAL_NAMES: &[&str] = &[ + "ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse", + "snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox", + "panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat", + "goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper", + "deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal", + "wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra", +]; + +lazy_static!{ + pub static ref CONVERTER: PastaIdConverter = PastaIdConverter::new(); +} + +/// Convert pasta IDs to names and vice versa +pub struct PastaIdConverter { + names: Vec +} + +impl PastaIdConverter { + pub fn new() -> Self { + let names; + if let Some(names_path) = &ARGS.custom_names { + let names_data = fs::read_to_string(names_path) + .expect("path for the names file should contain a names file"); + names = names_data + .split('\n') + .map(ToOwned::to_owned) + .collect::>(); + } else { + names = ANIMAL_NAMES + .iter() + .copied() + .map(ToOwned::to_owned) + .collect(); + } + + Self { names } + } + + pub fn to_names(&self, mut number: u64) -> String { + let mut result: Vec<&str> = Vec::new(); + + if number == 0 { + return self.names[0].parse().unwrap(); + } + + let mut power = 6; + + loop { + let digit = number / self.names.len().pow(power) as u64; + if !(result.is_empty() && digit == 0) { + result.push(&self.names[digit as usize]); + } + number -= digit * self.names.len().pow(power) as u64; + if power > 0 { + power -= 1; + } else if power == 0 || number == 0 { + break; + } + } + + result.join("-") + } + + pub fn to_u64(&self, pasta_id: &str) -> Result { + let mut result: u64 = 0; + + let names: Vec<&str> = pasta_id.split('-').collect(); + + let mut pow = names.len(); + for name in names { + pow -= 1; + let name_index = self.names.iter().position(|r| r == name); + match name_index { + None => return Err("Failed to convert animal name to u64!"), + Some(_) => { + result += (name_index.unwrap() * self.names.len().pow(pow as u32)) as u64 + } + } + } + + Ok(result) + } +} + +impl Default for PastaIdConverter { + fn default() -> Self { + Self::new() + } +} + +