🚧 port to rust
This commit is contained in:
parent
e5aa247f11
commit
af3a052acb
24 changed files with 3125 additions and 564 deletions
71
src/pages/assets.rs
Normal file
71
src/pages/assets.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use actix_files::NamedFile;
|
||||
use actix_web::*;
|
||||
use std::io::Write;
|
||||
|
||||
// Bootstrap
|
||||
|
||||
async fn download_file(url: &str, file: &str) {
|
||||
let content = reqwest::get(url).await.expect("couldn't download file");
|
||||
std::fs::File::create(file)
|
||||
.unwrap()
|
||||
.write_all(&content.bytes().await.unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) async fn cache_bootstrap() {
|
||||
std::fs::create_dir_all("./cache/fonts").expect("couldn't create cache dir");
|
||||
download_file(
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css",
|
||||
"./cache/bootstrap.min.css",
|
||||
)
|
||||
.await;
|
||||
download_file(
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css",
|
||||
"./cache/bootstrap-icons.css",
|
||||
)
|
||||
.await;
|
||||
download_file(
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js",
|
||||
"./cache/bootstrap.bundle.min.js",
|
||||
)
|
||||
.await;
|
||||
download_file("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/fonts/bootstrap-icons.woff2?8d200481aa7f02a2d63a331fc782cfaf", "./cache/fonts/bootstrap-icons.woff2").await;
|
||||
download_file("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/fonts/bootstrap-icons.woff?8d200481aa7f02a2d63a331fc782cfaf", "./cache/fonts/bootstrap-icons.woff").await;
|
||||
}
|
||||
|
||||
#[get("/bootstrap.min.css")]
|
||||
pub(crate) async fn bootstrap_css() -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("./cache/bootstrap.min.css")?)
|
||||
}
|
||||
|
||||
#[get("/bootstrap-icons.css")]
|
||||
pub(crate) async fn bootstrap_icons() -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("./cache/bootstrap-icons.css")?)
|
||||
}
|
||||
|
||||
#[get("/bootstrap.bundle.min.js")]
|
||||
pub(crate) async fn bootstrap_js() -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("./cache/bootstrap.bundle.min.js")?)
|
||||
}
|
||||
|
||||
#[get("/fonts/bootstrap-icons.woff2")]
|
||||
pub(crate) async fn bootstrap_font1(_: HttpRequest) -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("./cache/fonts/bootstrap-icons.woff2")?)
|
||||
}
|
||||
|
||||
#[get("/fonts/bootstrap-icons.woff")]
|
||||
pub(crate) async fn bootstrap_font2(_: HttpRequest) -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("./cache/fonts/bootstrap-icons.woff")?)
|
||||
}
|
||||
|
||||
// Assets
|
||||
|
||||
#[get("/assets/wall")]
|
||||
pub(crate) async fn wallpaper() -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("/config/wall.avif")?)
|
||||
}
|
||||
|
||||
#[get("/assets/me")]
|
||||
pub(crate) async fn me_img() -> Result<NamedFile> {
|
||||
Ok(NamedFile::open("/config/me.avif")?)
|
||||
}
|
53
src/pages/func.rs
Normal file
53
src/pages/func.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use reqwest::get;
|
||||
|
||||
pub fn is_browser(req: &actix_web::HttpRequest) -> bool {
|
||||
let ua = req
|
||||
.headers()
|
||||
.get("user-agent")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_lowercase();
|
||||
if ua.contains("chrome") || ua.contains("safari") || ua.contains("firefox") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn get_host(r: &actix_web::HttpRequest) -> String {
|
||||
let res = r.headers().get("HOST").unwrap().to_str().unwrap();
|
||||
return res.to_string();
|
||||
}
|
||||
|
||||
pub fn get_host_address(r: &actix_web::HttpRequest) -> String {
|
||||
let res = r.headers().get("HOST").unwrap().to_str().unwrap();
|
||||
let res: Vec<&str> = res.split(":").collect();
|
||||
let res = res.first().unwrap();
|
||||
return res.to_string();
|
||||
}
|
||||
|
||||
pub fn is_onion(r: &actix_web::HttpRequest) -> bool {
|
||||
return get_host_address(r).ends_with("onion");
|
||||
}
|
||||
|
||||
pub fn is_i2p(r: &actix_web::HttpRequest) -> bool {
|
||||
return get_host_address(r).ends_with("i2p");
|
||||
}
|
||||
|
||||
pub fn dynamic_link(
|
||||
inner: &str,
|
||||
normal_link: &str,
|
||||
onion: Option<&str>,
|
||||
i2p: Option<&str>,
|
||||
req: &actix_web::HttpRequest,
|
||||
) -> String {
|
||||
if is_onion(req) {
|
||||
let href = onion.unwrap_or(normal_link);
|
||||
return format!("<a href={href}> {inner} </a>");
|
||||
}
|
||||
if is_i2p(req) {
|
||||
let href = i2p.unwrap_or(normal_link);
|
||||
return format!("<a href={href}> {inner} </a>");
|
||||
}
|
||||
return format!("<a href={normal_link}> {inner} </a>");
|
||||
}
|
56
src/pages/html_fn.rs
Normal file
56
src/pages/html_fn.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::*;
|
||||
|
||||
pub(crate) async fn build_site(
|
||||
content: String,
|
||||
title: &str,
|
||||
disable_color: bool,
|
||||
shadow: bool,
|
||||
config: &Data<Config>,
|
||||
) -> HttpResponse<String> {
|
||||
const BOOTSTRAP: &str = r#"
|
||||
<link href="/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/bootstrap-icons.css" rel="stylesheet">
|
||||
<link href="/bootstrap.bundle.min.js" rel="stylesheet">
|
||||
"#;
|
||||
|
||||
let mut c_class = "bg-dark text-white justify-content-center text-center".to_string();
|
||||
let mut c_style = "".to_string();
|
||||
let mut g_style = "a {text-decoration: none; font-weight: bold; color: white}".to_string();
|
||||
if !disable_color {
|
||||
if let (Some(fg), Some(bg)) = (config.fg_color(), config.bg_color()) {
|
||||
c_class = "justify-content-center text-center".to_string();
|
||||
c_style = format!("background: {bg}; color: {fg};");
|
||||
g_style = format!("a {{text-decoration: none; font-weight: bold; color: {fg}}}");
|
||||
}
|
||||
}
|
||||
if std::path::Path::new("/config/wall.avif").exists() {
|
||||
c_style.push_str("background-image: url('assets/wall');background-size:cover;");
|
||||
}
|
||||
if shadow {
|
||||
c_style.push_str("text-shadow: 1px 1px 3px black;");
|
||||
}
|
||||
|
||||
let r = format!(
|
||||
"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title> {title} </title>
|
||||
<meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1.0\">
|
||||
{BOOTSTRAP}
|
||||
</head>
|
||||
<body style=\"{c_style}\" class=\"{c_class}\">
|
||||
<style>
|
||||
{g_style}
|
||||
</style>
|
||||
{content}
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
);
|
||||
|
||||
return HttpResponse::Ok().message_body(r).unwrap();
|
||||
}
|
184
src/pages/index.rs
Normal file
184
src/pages/index.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
use crate::pages::html_fn::build_site;
|
||||
use crate::{config, pages};
|
||||
use actix_web::http::header;
|
||||
use actix_web::web::{Form, Json};
|
||||
use actix_web::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct MessageForm {
|
||||
msg_name: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[post("/message")]
|
||||
pub async fn message_post(r: HttpRequest, f: Form<MessageForm>) -> impl Responder {
|
||||
let config: &web::Data<config::Config> = r.app_data().unwrap();
|
||||
let cipher = crate::msg::encrypt(f.message.to_string());
|
||||
crate::msg::save_msg(cipher, &f.msg_name.to_string());
|
||||
crate::notification::notify(
|
||||
&format!("New Message from {}", f.msg_name.to_string()),
|
||||
"New Message",
|
||||
config.clone(),
|
||||
);
|
||||
return HttpResponse::Found()
|
||||
.header("Location", "/message")
|
||||
.finish();
|
||||
}
|
||||
|
||||
#[get("/message")]
|
||||
pub async fn message_page(r: HttpRequest) -> impl Responder {
|
||||
let config: &web::Data<config::Config> = r.app_data().unwrap();
|
||||
let host = pages::func::get_host(&r);
|
||||
let resp = format!(
|
||||
r#"
|
||||
<div class="container" style="margin-top: 25px"><h1>Message</h1>
|
||||
<br>
|
||||
<form action="http://{host}/message" method="post" autocomplete="off">
|
||||
<input value="" type="text" required name="msg_name" placeholder="Name" class="form-control bg-dark text-white" style="margin-bottom: 15px">
|
||||
<textarea placeholder="Message" required name="message" cols="10" rows="10" class="form-control bg-dark text-white" style="margin-bottom: 15px;">
|
||||
</textarea>
|
||||
<input value="Send Message" type="submit" required name="submit" class="btn btn-danger text-white text-decoration-none">
|
||||
</form>
|
||||
</div>
|
||||
"#
|
||||
);
|
||||
return pages::html_fn::build_site(resp, "Message", false, true, config).await;
|
||||
}
|
||||
|
||||
#[get("/mirrors.txt")]
|
||||
pub async fn mirrors(r: HttpRequest) -> impl Responder {
|
||||
let config: &web::Data<config::Config> = r.app_data().unwrap();
|
||||
if let Ok(mirror_file) = std::fs::File::open("/config/mirrors.txt") {
|
||||
let content = std::io::read_to_string(mirror_file).unwrap();
|
||||
if pages::func::is_browser(&r) {
|
||||
let resp = format!(
|
||||
r#"
|
||||
<div style="margin: 25px;">
|
||||
<pre> {content} </pre>
|
||||
</div>
|
||||
"#
|
||||
);
|
||||
return pages::html_fn::build_site(resp, "Mirrors", false, true, config).await;
|
||||
}
|
||||
let res: HttpResponse<String> = HttpResponse::Ok().message_body(content).unwrap();
|
||||
return res;
|
||||
}
|
||||
let res: HttpResponse<String> = HttpResponse::NotFound()
|
||||
.message_body("".to_string())
|
||||
.unwrap();
|
||||
return res;
|
||||
}
|
||||
|
||||
#[get("/public_key")]
|
||||
pub async fn public_key(r: HttpRequest) -> impl Responder {
|
||||
if pages::func::is_browser(&r) {
|
||||
let config: &web::Data<config::Config> = r.app_data().unwrap();
|
||||
let host = format!("http://{}", pages::func::get_host(&r));
|
||||
let key = std::io::read_to_string(std::fs::File::open("/config/pub.key").unwrap())
|
||||
.unwrap()
|
||||
.replace("\n", "<br>");
|
||||
let resp = format!(
|
||||
r#"
|
||||
<div class="container" style="margin-top: 25px">
|
||||
<div class="alert alert-info">
|
||||
<b>To Import: </b>
|
||||
<span style="display: block;font-family: monospace,monospace;margin-top: 10px; font-size: 20px;overflow-wrap: break-word;">
|
||||
curl -sL "{host}/public_key"|gpg --import</span>
|
||||
</div></div>
|
||||
<div class="container card bg-primary"><p> {key} </p></div>
|
||||
"#
|
||||
);
|
||||
return pages::html_fn::build_site(resp, "Public Key", true, false, config).await;
|
||||
}
|
||||
if let Ok(key_f) = std::fs::File::open("/config/pub.key") {
|
||||
if let Ok(key_data) = std::io::read_to_string(key_f) {
|
||||
let res: HttpResponse<String> = HttpResponse::Ok()
|
||||
.insert_header(header::ContentType::plaintext())
|
||||
.message_body(key_data)
|
||||
.unwrap();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
let res: HttpResponse<String> = HttpResponse::NotFound()
|
||||
.message_body("".to_string())
|
||||
.unwrap();
|
||||
return res;
|
||||
}
|
||||
|
||||
fn build_information_block(conf: &web::Data<config::Config>) -> String {
|
||||
let name = conf.name().unwrap();
|
||||
format!(
|
||||
r#"
|
||||
<div class="container border-dark" style="margin-top: 20px">
|
||||
<img src="/assets/me" height=200 width=200 alt="Me" class="rounded">
|
||||
<br><br>
|
||||
<h1> {name} </h1>
|
||||
<hr>
|
||||
</div>
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
fn build_contact_block(conf: &web::Data<config::Config>) -> String {
|
||||
if let Some(email) = conf.email() {
|
||||
let pgp_key_message = match std::path::Path::new("/config/pub.key").exists() {
|
||||
true => {
|
||||
r#"
|
||||
<a href="/public_key"> My PGP Key </a>
|
||||
<br>
|
||||
<a href="/message"> Write a message </a>
|
||||
<br><br>
|
||||
"#
|
||||
}
|
||||
false => "",
|
||||
};
|
||||
return format!(
|
||||
r#"
|
||||
<div class="container border-dark">
|
||||
<h1> <span class="bi bi-person-lines-fill" style="vertical-align: middle;"> </span> Contact </h1>
|
||||
<tr>
|
||||
{pgp_key_message}
|
||||
<a href="mailto:{email}"> {email} </a>
|
||||
<hr>
|
||||
</div>
|
||||
"#
|
||||
);
|
||||
} else {
|
||||
return "".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_donation_block(conf: &web::Data<config::Config>) -> String {
|
||||
if let Some(xmr_addr) = conf.xmr_address() {
|
||||
format!(
|
||||
r#"
|
||||
<div class="container" style="margin-top: 20px">
|
||||
<h1> <span class="bi bi-cash-coin"> </span> Donation </h1>
|
||||
<tr>
|
||||
<p> <b> Monero: </b> <span style="color: orange;overflow-wrap: break-word;"> {xmr_addr} </span> </p>
|
||||
</div>
|
||||
"#
|
||||
)
|
||||
} else {
|
||||
return "".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub(crate) async fn index(conf: web::Data<config::Config>) -> impl Responder {
|
||||
let information_block = build_information_block(&conf);
|
||||
let contact_block = build_contact_block(&conf);
|
||||
let donation_block = build_donation_block(&conf);
|
||||
let content = format!(
|
||||
"
|
||||
{information_block}
|
||||
{contact_block}
|
||||
{donation_block}
|
||||
"
|
||||
);
|
||||
let r = crate::pages::html_fn::build_site(content, "About Me", false, true, &conf).await;
|
||||
return r;
|
||||
}
|
4
src/pages/mod.rs
Normal file
4
src/pages/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod assets;
|
||||
pub mod func;
|
||||
pub mod html_fn;
|
||||
pub mod index;
|
Loading…
Add table
Add a link
Reference in a new issue