🚧 port to rust
This commit is contained in:
parent
e5aa247f11
commit
af3a052acb
24 changed files with 3125 additions and 564 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -136,5 +136,6 @@ cython_debug/
|
||||||
.vscode
|
.vscode
|
||||||
/data
|
/data
|
||||||
|
|
||||||
# Cached Files
|
# Added by cargo
|
||||||
/src/static
|
|
||||||
|
/target
|
||||||
|
|
2556
Cargo.lock
generated
Normal file
2556
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "me-site"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-files = "0.6.2"
|
||||||
|
actix-web = "4.2.1"
|
||||||
|
chrono = "0.4.22"
|
||||||
|
env_logger = "0.9.3"
|
||||||
|
log = "0.4.17"
|
||||||
|
pgp = "0.9.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
reqwest = "0.11.12"
|
||||||
|
serde = {version = "1.0.147", features = ["derive"] }
|
||||||
|
serde_json = "1.0.87"
|
16
Dockerfile
16
Dockerfile
|
@ -1,13 +1,13 @@
|
||||||
FROM python:3
|
FROM rust as builder
|
||||||
|
|
||||||
COPY requirements.txt /
|
COPY . /app
|
||||||
|
|
||||||
RUN pip3 install -r /requirements.txt
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM archlinux
|
||||||
|
|
||||||
|
COPY --from=builder /app/target/release/me-site /bin/me-site
|
||||||
|
|
||||||
VOLUME /config
|
VOLUME /config
|
||||||
|
|
||||||
COPY src /app
|
CMD [ "./bin/me-site" ]
|
||||||
|
|
||||||
RUN sed -i "s/debug=True/debug=False/" /app/main.py
|
|
||||||
|
|
||||||
CMD [ "python3", "/app/main.py" ]
|
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
fmt:
|
|
||||||
black .
|
|
||||||
|
|
||||||
clean:
|
|
||||||
fd pycache -I -x rm -rv {}
|
|
||||||
|
|
||||||
debug:
|
|
||||||
docker-compose -f docker-compose-debug.yml up -d
|
|
||||||
|
|
||||||
debug-stop:
|
|
||||||
docker-compose -f docker-compose-debug.yml down
|
|
|
@ -1,13 +0,0 @@
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
build: "."
|
|
||||||
environment:
|
|
||||||
TZ: Europe/Berlin
|
|
||||||
ports:
|
|
||||||
- 1030:1030
|
|
||||||
volumes:
|
|
||||||
- ./config:/config
|
|
||||||
- ./data:/data
|
|
||||||
- ./src:/app
|
|
|
@ -1,4 +0,0 @@
|
||||||
Flask
|
|
||||||
gnupg
|
|
||||||
requests
|
|
||||||
git+https://github.com/jmarya/htmlpy#egg=htmlpy
|
|
|
@ -1,28 +0,0 @@
|
||||||
from flask import request, session, Blueprint, Response, send_from_directory
|
|
||||||
from htmlpy import *
|
|
||||||
from config import CONFIG
|
|
||||||
import os
|
|
||||||
|
|
||||||
asset_pages = Blueprint("assets", __name__)
|
|
||||||
|
|
||||||
###################
|
|
||||||
### Site Assets ###
|
|
||||||
###################
|
|
||||||
|
|
||||||
|
|
||||||
def filesend(path):
|
|
||||||
filename = os.path.basename(path)
|
|
||||||
dirpath = os.path.dirname(path)
|
|
||||||
return send_from_directory(dirpath, filename)
|
|
||||||
|
|
||||||
|
|
||||||
# Profile Picture Asset
|
|
||||||
@asset_pages.route("/me", methods=["GET"])
|
|
||||||
def me_picture():
|
|
||||||
return filesend("/config/me.avif")
|
|
||||||
|
|
||||||
|
|
||||||
# Background Image
|
|
||||||
@asset_pages.route("/wall")
|
|
||||||
def wall_bg():
|
|
||||||
return filesend("/config/wall.avif")
|
|
|
@ -1,39 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
#####################
|
|
||||||
### Configuration ###
|
|
||||||
#####################
|
|
||||||
|
|
||||||
CONFIG = json.loads(open("/config/config.json").read())
|
|
||||||
|
|
||||||
|
|
||||||
# Colors Array
|
|
||||||
def colors():
|
|
||||||
try:
|
|
||||||
return json.loads(open("/config/colors.json").read())
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Foreground Color
|
|
||||||
def fg_color():
|
|
||||||
if colors() is not None:
|
|
||||||
fg = colors()["special"]["foreground"]
|
|
||||||
if "colors" in CONFIG:
|
|
||||||
if "fg" in CONFIG["colors"]:
|
|
||||||
i = CONFIG["colors"]["fg"] - 1
|
|
||||||
fg = colors()["colors"][f"color{i}"]
|
|
||||||
return fg
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Background Color
|
|
||||||
def bg_color():
|
|
||||||
if colors() is not None:
|
|
||||||
bg = colors()["special"]["background"]
|
|
||||||
if "colors" in CONFIG:
|
|
||||||
if "bg" in CONFIG["colors"]:
|
|
||||||
i = CONFIG["colors"]["bg"] - 1
|
|
||||||
bg = colors()["colors"][f"color{i}"]
|
|
||||||
return bg
|
|
||||||
return None
|
|
81
src/config.rs
Normal file
81
src/config.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::fmt::format;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GotifySettings {
|
||||||
|
pub host: String,
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
root: Value,
|
||||||
|
color: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_json_file(f: &str) -> Option<Value> {
|
||||||
|
return serde_json::from_str(&std::fs::read_to_string(f).ok()?).ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new() -> Config {
|
||||||
|
let v = read_json_file("/config/config.json").expect("could not read config file");
|
||||||
|
let c = read_json_file("/config/colors.json");
|
||||||
|
Config { root: v, color: c }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<String> {
|
||||||
|
return Option::from(self.root.get("name")?.as_str()?.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email(&self) -> Option<String> {
|
||||||
|
return Option::from(self.root.get("email")?.as_str()?.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn xmr_address(&self) -> Option<String> {
|
||||||
|
return Option::from(self.root.get("xmr_address")?.as_str()?.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_n_fg(&self) -> Option<i64> {
|
||||||
|
return Some(self.root.get("colors")?.get("fg")?.as_i64()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_n_bg(&self) -> Option<i64> {
|
||||||
|
return Some(self.root.get("colors")?.get("bg")?.as_i64()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fg_color(&self) -> Option<String> {
|
||||||
|
if let Some(col) = &self.color {
|
||||||
|
let fg = col.get("special")?.get("foreground")?.as_str()?;
|
||||||
|
if let Some(fg_n) = self.color_n_fg() {
|
||||||
|
let n = fg_n - 1;
|
||||||
|
let fg = col.get("colors")?.get(format!("color{}", n))?.as_str()?;
|
||||||
|
return Some(fg.to_string());
|
||||||
|
}
|
||||||
|
return Some(fg.to_string());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bg_color(&self) -> Option<String> {
|
||||||
|
if let Some(col) = &self.color {
|
||||||
|
let fg = col.get("special")?.get("background")?.as_str()?;
|
||||||
|
if let Some(bg_n) = self.color_n_bg() {
|
||||||
|
let n = bg_n - 1;
|
||||||
|
let fg = col.get("colors")?.get(format!("color{}", n))?.as_str()?;
|
||||||
|
return Some(fg.to_string());
|
||||||
|
}
|
||||||
|
return Some(fg.to_string());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gotify_config(&self) -> Option<GotifySettings> {
|
||||||
|
let settings = self.root.get("notify")?.get("gotify")?;
|
||||||
|
let host = settings.get("host")?.as_str()?.to_string();
|
||||||
|
let token = settings.get("token")?.as_str()?.to_string();
|
||||||
|
return Some(GotifySettings { host, token });
|
||||||
|
}
|
||||||
|
}
|
35
src/fn.py
35
src/fn.py
|
@ -1,35 +0,0 @@
|
||||||
import flask
|
|
||||||
from htmlpy import Link
|
|
||||||
|
|
||||||
######################
|
|
||||||
### Site Functions ###
|
|
||||||
######################
|
|
||||||
|
|
||||||
|
|
||||||
# Check if request is from onion
|
|
||||||
def is_onion(req: flask.globals.request) -> bool:
|
|
||||||
return req.host.endswith("onion")
|
|
||||||
|
|
||||||
|
|
||||||
# Check if request is from i2p
|
|
||||||
def is_i2p(req: flask.globals.request) -> bool:
|
|
||||||
return req.host.endswith("i2p")
|
|
||||||
|
|
||||||
|
|
||||||
# Return dynamic link depending on request origin
|
|
||||||
def dynamic_link(
|
|
||||||
inner, normal: str, onion: str, i2p: str, req: flask.globals.request
|
|
||||||
) -> Link:
|
|
||||||
if is_onion(req):
|
|
||||||
return Link(onion, inner)
|
|
||||||
if is_i2p(req):
|
|
||||||
return Link(i2p, inner)
|
|
||||||
return Link(normal, inner)
|
|
||||||
|
|
||||||
|
|
||||||
# Check if request is from common browsers
|
|
||||||
def is_browser(req: flask.globals.request) -> bool:
|
|
||||||
ua = req.user_agent.string.lower()
|
|
||||||
if "chrome" in ua or "safari" in ua or "firefox" in ua:
|
|
||||||
return True
|
|
||||||
return False
|
|
|
@ -1,99 +0,0 @@
|
||||||
import requests
|
|
||||||
import os
|
|
||||||
from htmlpy import *
|
|
||||||
from config import colors, CONFIG, fg_color, bg_color
|
|
||||||
|
|
||||||
############################
|
|
||||||
### HTML Generation Code ###
|
|
||||||
############################
|
|
||||||
|
|
||||||
# Bootstrap Icon
|
|
||||||
def Icon(code, middle_align=False):
|
|
||||||
style = ""
|
|
||||||
if middle_align:
|
|
||||||
style = "vertical-align: middle;"
|
|
||||||
return Span(
|
|
||||||
"", global_attr=GlobalAttributes(css_class=f"bi bi-{code}", style=style)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Wrapper for Base HTML
|
|
||||||
def buildSite(content, title=None, disable_color=False, shadow=True):
|
|
||||||
c_class = "bg-dark text-white justify-content-center text-center"
|
|
||||||
c_style = ""
|
|
||||||
g_style = "a {text-decoration: none; font-weight: bold; color: white}"
|
|
||||||
if not disable_color:
|
|
||||||
if colors() is not None:
|
|
||||||
c_class = "justify-content-center text-center"
|
|
||||||
fg = fg_color()
|
|
||||||
bg = bg_color()
|
|
||||||
c_style = f"background: {bg}; color: {fg};"
|
|
||||||
g_style = f"a {{text-decoration: none; font-weight: bold; color: {fg}}}"
|
|
||||||
|
|
||||||
if os.path.exists("/config/wall.avif"):
|
|
||||||
c_style += "background-image: url('assets/wall');background-size:cover;"
|
|
||||||
|
|
||||||
if shadow:
|
|
||||||
c_style += "text-shadow: 1px 1px 3px black;"
|
|
||||||
|
|
||||||
return Document(
|
|
||||||
head=Head(
|
|
||||||
[
|
|
||||||
Title(title),
|
|
||||||
Meta(
|
|
||||||
name="viewport",
|
|
||||||
content="user-scalable=no, width=device-width, initial-scale=1.0",
|
|
||||||
),
|
|
||||||
BOOTSTRAP,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
body=Body(
|
|
||||||
[Style(g_style), content],
|
|
||||||
global_attr=GlobalAttributes(css_class=c_class, style=c_style),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def download_file(url, file):
|
|
||||||
os.makedirs("/app/static/", exist_ok=True)
|
|
||||||
r = requests.get(url, allow_redirects=True)
|
|
||||||
open(file, "wb").write(r.content)
|
|
||||||
|
|
||||||
|
|
||||||
# Downloads all bootstrap files to flasks static directory
|
|
||||||
def cache_bootstrap():
|
|
||||||
download_file(
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css",
|
|
||||||
"/app/static/bootstrap.min.css",
|
|
||||||
)
|
|
||||||
download_file(
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css",
|
|
||||||
"/app/static/bootstrap-icons.css",
|
|
||||||
)
|
|
||||||
download_file(
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js",
|
|
||||||
"/app/static/bootstrap.bundle.min.js",
|
|
||||||
)
|
|
||||||
os.makedirs("/app/static/fonts", exist_ok=True)
|
|
||||||
download_file(
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/fonts/bootstrap-icons.woff2?8d200481aa7f02a2d63a331fc782cfaf",
|
|
||||||
"/app/static/fonts/bootstrap-icons.woff2",
|
|
||||||
)
|
|
||||||
download_file(
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/fonts/bootstrap-icons.woff?8d200481aa7f02a2d63a331fc782cfaf",
|
|
||||||
"/app/static/fonts/bootstrap-icons.woff",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Bootstrap CSS
|
|
||||||
BOOTSTRAP = [
|
|
||||||
Reference(
|
|
||||||
"/static/bootstrap.min.css",
|
|
||||||
"stylesheet",
|
|
||||||
),
|
|
||||||
Reference(
|
|
||||||
"/static/bootstrap-icons.css",
|
|
||||||
"stylesheet",
|
|
||||||
),
|
|
||||||
Script(src="/static/bootstrap.bundle.min.js"),
|
|
||||||
]
|
|
262
src/index.py
262
src/index.py
|
@ -1,262 +0,0 @@
|
||||||
from os.path import exists
|
|
||||||
|
|
||||||
import gnupg
|
|
||||||
from flask import request, Blueprint, Response, redirect
|
|
||||||
from htmlpy import *
|
|
||||||
|
|
||||||
from config import CONFIG, colors, fg_color, bg_color
|
|
||||||
from html_fn import buildSite, Icon
|
|
||||||
from msg import encrypt, save_message
|
|
||||||
from notification import notify
|
|
||||||
from fn import is_browser
|
|
||||||
|
|
||||||
main_pages = Blueprint("main", __name__)
|
|
||||||
|
|
||||||
# Color Scheme
|
|
||||||
@main_pages.route("/color")
|
|
||||||
def color():
|
|
||||||
if colors() is None:
|
|
||||||
return ""
|
|
||||||
colors_p = [Heading(1, "Color 0")]
|
|
||||||
for i in range(0, 16):
|
|
||||||
c = colors()["colors"][f"color{i}"]
|
|
||||||
colors_p.append(
|
|
||||||
Heading(
|
|
||||||
1, f"Color {i+1}", global_attr=GlobalAttributes(style=f"color: {c}")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
site = buildSite([], "Colors")
|
|
||||||
|
|
||||||
bg = colors()["special"]["background"]
|
|
||||||
fg = colors()["special"]["foreground"]
|
|
||||||
cur = colors()["special"]["cursor"]
|
|
||||||
site.body = Body(
|
|
||||||
[Div([colors_p], global_attr=GlobalAttributes(css_class="container"))],
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
style=f"background: {bg}; color: {fg}",
|
|
||||||
css_class="justify-content-center text-center",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return site.to_code()
|
|
||||||
|
|
||||||
|
|
||||||
# Mirrors
|
|
||||||
@main_pages.route("/mirrors.txt", methods=["GET"])
|
|
||||||
def mirrors():
|
|
||||||
# TODO : Autogenerate GPG Sign Message
|
|
||||||
if exists("/config/mirrors.txt"):
|
|
||||||
txt = open("/config/mirrors.txt").read()
|
|
||||||
if is_browser(request):
|
|
||||||
site = buildSite(
|
|
||||||
Div(
|
|
||||||
f"<pre>{txt}</pre>",
|
|
||||||
global_attr=GlobalAttributes(style="margin: 25px;"),
|
|
||||||
),
|
|
||||||
"Mirrors",
|
|
||||||
)
|
|
||||||
|
|
||||||
return site.to_code()
|
|
||||||
|
|
||||||
return txt
|
|
||||||
else:
|
|
||||||
return Response(response="", status=404)
|
|
||||||
|
|
||||||
|
|
||||||
# Message Sending Page
|
|
||||||
@main_pages.route("/message", methods=["GET", "POST"])
|
|
||||||
def send_message():
|
|
||||||
if request.method == "POST":
|
|
||||||
# If POST handle message
|
|
||||||
msg = request.form["message"]
|
|
||||||
name = request.form["msg_name"]
|
|
||||||
if msg is not None:
|
|
||||||
cipher = encrypt(msg)
|
|
||||||
save_message(cipher, name)
|
|
||||||
notify(f"New Message from {name}")
|
|
||||||
return redirect(request.url_root)
|
|
||||||
else:
|
|
||||||
# Return Message Form
|
|
||||||
return buildSite(
|
|
||||||
[
|
|
||||||
Div(
|
|
||||||
[
|
|
||||||
Heading(1, "Message"),
|
|
||||||
LineBreak(),
|
|
||||||
Form(
|
|
||||||
destination=request.url,
|
|
||||||
inner=[
|
|
||||||
Input(
|
|
||||||
"",
|
|
||||||
placeholder="Name",
|
|
||||||
name="msg_name",
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="form-control bg-dark text-white",
|
|
||||||
style="margin-bottom: 15px",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextArea(
|
|
||||||
inner="",
|
|
||||||
placeholder="Message",
|
|
||||||
name="message",
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="form-control bg-dark text-white",
|
|
||||||
style="margin-bottom: 15px;",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Input(
|
|
||||||
type=InputType.Submit,
|
|
||||||
value="Send Message",
|
|
||||||
name="submit",
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="btn btn-danger text-white text-decoration-none"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="container", style="margin-top: 25px"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"Send a message",
|
|
||||||
).to_code()
|
|
||||||
|
|
||||||
|
|
||||||
# Public Key Page
|
|
||||||
@main_pages.route("/public_key", methods=["GET"])
|
|
||||||
def public_key():
|
|
||||||
try:
|
|
||||||
ret = open("/config/pub.key").read()
|
|
||||||
|
|
||||||
pgp = gnupg.GPG()
|
|
||||||
pgp.import_keys(open("/config/pub.key").read())
|
|
||||||
key_id = (
|
|
||||||
str(pgp.list_keys()[0]["uids"][0]).replace("<", "[ ").replace(">", " ]")
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_browser(request):
|
|
||||||
# If request is from common browser return stylized html
|
|
||||||
return buildSite(
|
|
||||||
[
|
|
||||||
Div(
|
|
||||||
Div(
|
|
||||||
[
|
|
||||||
Bold("To Import: "),
|
|
||||||
Span(
|
|
||||||
f'curl -sL "{request.base_url}"|gpg --import',
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
style="display: block;font-family: monospace,monospace;margin-top: 10px; font-size: 20px;overflow-wrap: break-word;"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
global_attr=GlobalAttributes(css_class="alert alert-info"),
|
|
||||||
),
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="container", style="margin-top: 25px"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Heading(
|
|
||||||
4,
|
|
||||||
key_id,
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="container card",
|
|
||||||
style="padding-top: 10px; padding-bottom: 10px; background: black; margin-bottom: 15px;",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Div(
|
|
||||||
Paragraph(ret.replace("\n", "<br>")),
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="container card bg-primary"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
"Public Key",
|
|
||||||
disable_color=True,
|
|
||||||
shadow=False,
|
|
||||||
).to_code()
|
|
||||||
|
|
||||||
# Return raw key
|
|
||||||
resp = Response(response=ret, status=200, mimetype="application/pgp-keys")
|
|
||||||
return resp
|
|
||||||
except:
|
|
||||||
# If key does not exist return error
|
|
||||||
return Response(response="", status=502)
|
|
||||||
|
|
||||||
|
|
||||||
# Contact
|
|
||||||
def build_contact_block():
|
|
||||||
return Div(
|
|
||||||
[
|
|
||||||
Heading(1, [Icon("person-lines-fill"), "Contact"]),
|
|
||||||
ThematicBreak(),
|
|
||||||
[
|
|
||||||
Link("/public_key", "My PGP Key"),
|
|
||||||
LineBreak(),
|
|
||||||
Link("/message", "Write a message"),
|
|
||||||
LineBreak(),
|
|
||||||
LineBreak(),
|
|
||||||
]
|
|
||||||
if exists("/config/pub.key")
|
|
||||||
else None,
|
|
||||||
Link(f"mailto:{CONFIG['email']}", CONFIG["email"]),
|
|
||||||
LineBreak(),
|
|
||||||
],
|
|
||||||
global_attr=GlobalAttributes(css_class="container border-dark"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Donations
|
|
||||||
def build_donation_block():
|
|
||||||
return Div(
|
|
||||||
[
|
|
||||||
Heading(1, [Icon("cash-coin", True), "Donation"]),
|
|
||||||
ThematicBreak(),
|
|
||||||
Paragraph(
|
|
||||||
[
|
|
||||||
Bold("Monero: "),
|
|
||||||
Span(
|
|
||||||
CONFIG["xmr_address"],
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
style="color: orange;overflow-wrap: break-word;"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if "xmr_address" in CONFIG
|
|
||||||
else None,
|
|
||||||
],
|
|
||||||
global_attr=GlobalAttributes(css_class="container", style="margin-top: 20px"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Basic Information
|
|
||||||
def build_information_block():
|
|
||||||
return Div(
|
|
||||||
[
|
|
||||||
Image(
|
|
||||||
"/assets/me",
|
|
||||||
200,
|
|
||||||
200,
|
|
||||||
"Me",
|
|
||||||
global_attr=GlobalAttributes(css_class="rounded"),
|
|
||||||
),
|
|
||||||
LineBreak(),
|
|
||||||
LineBreak(),
|
|
||||||
Heading(1, CONFIG["name"]),
|
|
||||||
ThematicBreak(),
|
|
||||||
],
|
|
||||||
global_attr=GlobalAttributes(
|
|
||||||
css_class="container border-dark", style="margin-top: 20px"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Main
|
|
||||||
@main_pages.route("/", methods=["GET"])
|
|
||||||
def index():
|
|
||||||
return buildSite(
|
|
||||||
[build_information_block(), build_contact_block(), build_donation_block()],
|
|
||||||
"About Me",
|
|
||||||
).to_code()
|
|
18
src/main.py
18
src/main.py
|
@ -1,18 +0,0 @@
|
||||||
from flask import Flask, session, request
|
|
||||||
from config import CONFIG
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.secret_key = CONFIG["secret_key"]
|
|
||||||
|
|
||||||
from index import main_pages
|
|
||||||
from assets import asset_pages
|
|
||||||
|
|
||||||
app.register_blueprint(main_pages)
|
|
||||||
app.register_blueprint(asset_pages, url_prefix="/assets")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from html_fn import cache_bootstrap
|
|
||||||
|
|
||||||
cache_bootstrap()
|
|
||||||
app.run(host="0.0.0.0", port=1030, debug=True, threaded=True)
|
|
40
src/main.rs
Normal file
40
src/main.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
mod config;
|
||||||
|
mod msg;
|
||||||
|
mod notification;
|
||||||
|
mod pages;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
std::env::set_var("RUST_LOG", "info");
|
||||||
|
std::env::set_var("RUST_BACKTRACE", "1");
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let conf = config::Config::new();
|
||||||
|
|
||||||
|
pages::assets::cache_bootstrap().await;
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
let logger = actix_web::middleware::Logger::default();
|
||||||
|
App::new()
|
||||||
|
.wrap(logger)
|
||||||
|
.app_data(web::Data::new(conf.clone()))
|
||||||
|
.service(pages::index::index)
|
||||||
|
// Assets
|
||||||
|
.service(pages::assets::bootstrap_js)
|
||||||
|
.service(pages::assets::bootstrap_css)
|
||||||
|
.service(pages::assets::bootstrap_icons)
|
||||||
|
.service(pages::assets::bootstrap_font1)
|
||||||
|
.service(pages::assets::bootstrap_font2)
|
||||||
|
.service(pages::assets::wallpaper)
|
||||||
|
.service(pages::assets::me_img)
|
||||||
|
.service(pages::index::public_key)
|
||||||
|
.service(pages::index::mirrors)
|
||||||
|
.service(pages::index::message_page)
|
||||||
|
.service(pages::index::message_post)
|
||||||
|
})
|
||||||
|
.bind(("0.0.0.0", 8080))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
21
src/msg.py
21
src/msg.py
|
@ -1,21 +0,0 @@
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import gnupg
|
|
||||||
|
|
||||||
################
|
|
||||||
### Messages ###
|
|
||||||
################
|
|
||||||
|
|
||||||
# Encrypt msg with GPG
|
|
||||||
def encrypt(msg):
|
|
||||||
pgp = gnupg.GPG()
|
|
||||||
pgp.import_keys(open("/config/pub.key").read())
|
|
||||||
return str(pgp.encrypt(msg, pgp.list_keys()[0]["fingerprint"]))
|
|
||||||
|
|
||||||
|
|
||||||
# Save msg in `/data/messages`
|
|
||||||
def save_message(msg, name=""):
|
|
||||||
os.makedirs("/data/messages", exist_ok=True)
|
|
||||||
dt = datetime.datetime.now().strftime("%Y-%m-%d.%H-%M")
|
|
||||||
f = open(f"/data/messages/{name}-{dt}.asc", "w")
|
|
||||||
f.write(msg)
|
|
26
src/msg.rs
Normal file
26
src/msg.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use pgp::crypto::SymmetricKeyAlgorithm;
|
||||||
|
use pgp::{Deserializable, Message, SignedPublicKey};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
pub fn encrypt(msg: String) -> String {
|
||||||
|
// err: Encryption is done twice
|
||||||
|
let pub_key =
|
||||||
|
SignedPublicKey::from_string(&*std::fs::read_to_string("/config/pub.key").unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let c = Message::new_literal("msg", &msg)
|
||||||
|
.encrypt_to_keys(&mut rng, SymmetricKeyAlgorithm::AES128, &[&pub_key])
|
||||||
|
.unwrap()
|
||||||
|
.to_armored_string(None)
|
||||||
|
.unwrap();
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_msg(msg: String, name: &str) {
|
||||||
|
std::fs::create_dir_all("/data/messages").expect("couldn't create msg dir");
|
||||||
|
let time = chrono::offset::Utc::now();
|
||||||
|
let time = time.format("%Y-%m-%d.%H-%M").to_string();
|
||||||
|
let mut f = std::fs::File::create(format!("/data/messages/{name}-{time}.asc")).unwrap();
|
||||||
|
f.write_all(encrypt(msg).as_bytes()).unwrap();
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
from config import CONFIG
|
|
||||||
import requests
|
|
||||||
|
|
||||||
#####################
|
|
||||||
### Notifications ###
|
|
||||||
#####################
|
|
||||||
|
|
||||||
# Send Notification to all handlers
|
|
||||||
def notify(msg, title=None):
|
|
||||||
try:
|
|
||||||
gotify_notification(msg, title)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Gotify Notification Handler
|
|
||||||
def gotify_notification(msg, title=None):
|
|
||||||
token = CONFIG["notify"]["gotify"]["token"]
|
|
||||||
url = CONFIG["notify"]["gotify"]["host"]
|
|
||||||
requests.post(
|
|
||||||
f"https://{url}/message?token={token}",
|
|
||||||
{"title": title, "message": msg, "priority": "5"},
|
|
||||||
)
|
|
25
src/notification.rs
Normal file
25
src/notification.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::config;
|
||||||
|
use crate::config::Config;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
pub async fn notify(msg: &str, title: &str, config: Data<Config>) {
|
||||||
|
if let Some(gotify) = config.gotify_config() {
|
||||||
|
gotify_notification(msg, title, gotify).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn gotify_notification(msg: &str, title: &str, config: config::GotifySettings) {
|
||||||
|
info!("Sending gotify notification");
|
||||||
|
let c = reqwest::Client::new();
|
||||||
|
let res = c
|
||||||
|
.post(format!(
|
||||||
|
"https://{}/message?token={}",
|
||||||
|
config.host, config.token
|
||||||
|
))
|
||||||
|
.header("title", title)
|
||||||
|
.header("message", msg)
|
||||||
|
.header("priority", 5)
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
}
|
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
Reference in a new issue