babble/src/main.rs
2025-04-20 23:57:15 +01:00

134 lines
4.3 KiB
Rust

use axum::{Router, extract::Path, response::Html, routing::get};
use axum_server::{bind_rustls, tls_rustls::RustlsConfig};
use hashbrown::HashMap;
use itertools::Itertools;
use rand::{prelude::*, random_range};
use rand_chacha::{ChaCha8Rng, rand_core::SeedableRng};
use std::{
path::PathBuf,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
#[tokio::main]
async fn main() {
println!("Starting...");
let corpus = include_str!("/usr/share/dict/words")
.split_whitespace()
.collect::<Vec<_>>();
let mut freq = HashMap::<String, HashMap<String, usize>>::default();
include_str!("../wap.txt")
.split_whitespace()
.map(|s| s.trim_matches(|c: char| !c.is_alphabetic()))
.tuple_windows()
.for_each(|(a, b)| {
*freq
.entry(a.to_string())
.or_default()
.entry(b.to_string())
.or_default() += 1
});
let mut freq = freq
.into_iter()
.map(|(k, v)| (k, v.into_iter().collect::<Vec<_>>()))
.collect::<HashMap<_, _>>();
println!("Generated frequencies.");
let mut counter = Arc::new(AtomicU64::new(0));
// build our application with a single route
let app = {
let counter = counter.clone();
Router::new().route(
"/{id}",
get(|Path(id): Path<String>| async move {
tokio::time::sleep(Duration::from_millis(random_range(200..1000))).await;
let mut seed = [0; 32];
for (i, b) in id
.bytes()
.chain(core::iter::repeat(0))
.take(seed.len())
.enumerate()
{
seed[i] = b;
}
let mut rng = &mut ChaCha8Rng::from_seed(seed);
fn choose_word<'a, 'b>(corpus: &'b [&'b str], rng: &'a mut ChaCha8Rng) -> &'b str {
corpus.choose(rng).unwrap()
};
let title = (0..rng.gen_range(2..10))
.map(|_| choose_word(&corpus, rng))
.join(" ");
let mut word = freq.keys().choose(rng).unwrap();
let mut content = word.clone();
for _ in 0..rng.gen_range(50..5_000) {
// let word = choose_word(&corpus, rng).to_string();
word = freq
.get(&*word)
.and_then(|rhs| rhs.choose_weighted(rng, |(_, n)| *n).ok())
.map(|(rhs, _)| rhs)
.unwrap_or(word);
if rng.gen_bool(0.05) {
let url = (0..3).map(|_| choose_word(&corpus, rng)).join("-");
content += &format!(" <a href=\"{}\">{}</a>", url, word);
} else if rng.gen_bool(0.01) {
content += ".<br>";
} else {
content += " ";
content += &word;
};
}
counter.fetch_add(1, Ordering::Relaxed);
Html(format!(
"<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<p>{content}</p>
</body>
</html>"
))
}),
)
};
// run our app with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("Started server.");
let mut interval = tokio::time::interval(Duration::from_secs(20));
tokio::spawn(async move {
loop {
interval.tick().await;
println!(
"Served bollocks to {} clients!",
counter.load(Ordering::Relaxed)
);
}
});
let config = RustlsConfig::from_pem_file(
PathBuf::from(std::env::var("PEM_DIR").expect("PEM_DIR not set")).join("cert.pem"),
PathBuf::from(std::env::var("PEM_DIR").expect("PEM_DIR not set")).join("key.pem"),
)
.await
.unwrap();
bind_rustls("127.0.0.1:3000".parse().unwrap(), config)
.serve(app.into_make_service())
.await
.unwrap();
}