commit 38e8972b990c303e561b3faad74f104fce7baf82 Author: Michał Rudowicz Date: Mon Oct 6 21:48:01 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..28a3c8b --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# HSWro's NomadNet page + +Made entirely for fun of making it. + +Requires Redis/Valkey running on localhost on its default port. diff --git a/pages/chat.mu b/pages/chat.mu new file mode 100755 index 0000000..311bf5c --- /dev/null +++ b/pages/chat.mu @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +import hswro +from os import environ +from redis import Redis +from datetime import datetime +import json +from uuid import uuid4 +from typing import Optional + +CHAT_NAME = "chat_test" + +toast: Optional[str] = None +l = hswro.get_login_info() + +r = Redis(host='localhost', port=6379, db=0) + +msg_to_post = environ.get('field_message', None) + +if msg_to_post is not None and msg_to_post.strip() != "" and l[0] is not None: + msg_to_post = hswro.sanitize_input(msg_to_post[0:255].strip()) + r.rpush(CHAT_NAME, str(json.dumps({ + "when": datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + "who": l[0], + "msg": msg_to_post, + "id": uuid4().hex + }))) + r.ltrim(CHAT_NAME, -20, -1) + +#print(environ) +msg_to_del = environ.get('var_delete', None) +if msg_to_del is not None and hswro.is_admin(): + for msg in r.lrange(CHAT_NAME, 0, -1): + m = json.loads(msg.decode('utf-8')) + if m['id'] == msg_to_del: + toast = f"\nDeleted a post by {m['who']}" + r.lrem(CHAT_NAME, 0, msg) + break + +hswro.header("Shoutbox") +if toast is not None: + print(f"`c{toast}") + print("`l") + +if l[0] is not None: + print(f"{l[0]}: `BFFF`F222`{hswro.BGCOLOR} [`[Send`:/page/chat.mu`*]]") + +for msg in reversed(r.lrange(CHAT_NAME, 0, -1)): + m = json.loads(msg.decode('utf-8')) + delbtn = "" + if hswro.is_admin(): + delbtn = f"`B400`FAAA`!`[DEL`:/page/chat.mu`delete={m['id']}]`!" + print(f"{delbtn}`BCCC`F333{m['when']}`BAAA`F444{'<'+m['who']+'>':>16}`B333`FCCC{m['msg']}") +hswro.reset_colors() +hswro.footer() diff --git a/pages/hswro.py b/pages/hswro.py new file mode 100755 index 0000000..683e719 --- /dev/null +++ b/pages/hswro.py @@ -0,0 +1,117 @@ + +#!/usr/bin/env python3 + +from os import environ +from typing import Optional, Tuple +from datetime import datetime +from redis import Redis +from hashlib import sha256 +from functools import reduce + +s = datetime.now() +NO_REMOTE_IDENTITY_MSG = """User identity hash not available. +In MeshChat: use the fingerprint button in the top right to send the identification information. +In NomadNet: Save node, click on it, go to and select "Identify when connecting".""" + +FORBIDDEN_INPUT = [ + "[", + "`", + "\n" +] + +ADMIN_IDENTITIES = [ + "e6c72573bb91d48338dbcc57d0223b81", + "70c9608c9a0f4ae895f7fab406554e1c" +] +BGCOLOR = "`F333`BDDD" + +def is_admin() -> bool: + remote_identity = environ.get("remote_identity", None) + if remote_identity is None: + return False + return remote_identity in ADMIN_IDENTITIES + +def sanitize_input(text: str) -> str: + return reduce(lambda a, b: a.replace(b, ""), FORBIDDEN_INPUT, text) + +def check_password(password: str) -> bool: + expected = '34a77e61000b2ba1f56201332ef64d93f9cdc60e63ab48bc255102586ef7e592' + gotten = sha256(f"hswrosalt{password}".encode('utf-8'), usedforsecurity=True).hexdigest() + return expected == gotten + +def check_name(username: str) -> bool: + if len(username) >= 16: + return False + return all(map(lambda c: c.isalnum(), username)) + +def get_login_info() -> Tuple[Optional[str], Optional[str]]: + """Returns [login_name, error]""" + remote_identity = environ.get("remote_identity", None) + if remote_identity is None: + return (None, NO_REMOTE_IDENTITY_MSG) + r = Redis(host='localhost', port=6379, db=0) + login = r.get(f'login_info_{remote_identity}') + if login is None: + return (None, None) + return (login.decode('utf-8'), None) + +def login(username: str) -> Optional[str]: + """Returns err""" + remote_identity = environ.get("remote_identity", None) + if remote_identity is None: + return NO_REMOTE_IDENTITY_MSG + if not check_name(username): + return "Username should only contain alphanumeric characters and have at most 16 characters." + r = Redis(host='localhost', port=6379, db=0) + r.set(f'login_info_{remote_identity}', username) + return None + +def logout(): + remote_identity = environ.get("remote_identity", None) + if remote_identity is None: + return + r = Redis(host='localhost', port=6379, db=0) + r.delete(f'login_info_{remote_identity}') + + +def login_button() -> str: + l = get_login_info() + if l[0] is None: + return "[`[Identify`:/page/login.mu`]]" + else: + return f"Welcome, {l[0]} | [`[Forget`:/page/logout.mu`]]" + +def header(title: Optional[str] = None): + if title is None: + title = "" + else: + title = ": `i" + title + print(f"""`F000`BFB1 +`c +-*abc +`r {login_button()} +`l `!Hackerspace`!Wrocław{title} +`a + +-_ +`a +`` +`FEEE`B333 +`c + +`l `[Home`:/page/index.mu]` | `[Status`:/page/status.mu]` | `[Shoutbox`:/page/chat.mu]` + +`a +`` +{BGCOLOR} +""") + +def reset_colors() -> str: + print(BGCOLOR) + +def footer(): + print(f"{BGCOLOR}`r Rendered in: {(datetime.now()-s).total_seconds()}s") + print(f"{datetime.now()}") + +if __name__ == "__main__": + pass diff --git a/pages/index.mu b/pages/index.mu new file mode 100755 index 0000000..2e378fa --- /dev/null +++ b/pages/index.mu @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import hswro + +hswro.header() +print("Nothing to see here... yet") +hswro.footer() diff --git a/pages/login.mu b/pages/login.mu new file mode 100755 index 0000000..a271c92 --- /dev/null +++ b/pages/login.mu @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import hswro +from os import environ + +def login_form(): + print("`c `!Login:`") + print("Username: `<16|username`>") + print("Password: `") + print("[`[Submit`:/page/login.mu`*]]") + +l = hswro.get_login_info() +form_login = environ.get('field_username', None) +form_pass = environ.get('field_pass', None) + +hswro.header() +if l[1] is not None: + print(f"`c`!Warning:`! {l[1]}") +if form_pass is not None and not hswro.check_password(form_pass): + print(f"`c`!Incorrect password.`!") +if hswro.check_password(form_pass) and form_login is not None: + e = hswro.login(form_login) + if e is not None: + print(f"`c`!Error:`! {e}") +if l[0] is None: + login_form() +else: + print(f"Welcome, {l[0]}.") +#print("`l\n\n\n\n\n") +#print(environ) +hswro.footer() diff --git a/pages/logout.mu b/pages/logout.mu new file mode 100755 index 0000000..2b5ae81 --- /dev/null +++ b/pages/logout.mu @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +import hswro +from os import environ + +hswro.logout() + +hswro.header() +print("`cYou have been logged out.") +hswro.footer() diff --git a/pages/status.mu b/pages/status.mu new file mode 100755 index 0000000..fa7f654 --- /dev/null +++ b/pages/status.mu @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import os +import time +import subprocess +import json +import hswro + +def sizeof_fmt(num, suffix="B"): + for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"): + if abs(num) < 1024.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1024.0 + return f"{num:.1f}Yi{suffix}" + + +status_raw = subprocess.run(["/home/reticulum/venv/bin/rnstatus", "-j"], capture_output=True) +status = json.loads(status_raw.stdout) + +hswro.header("Node Status") +print("> Interfaces") + +for i in status['interfaces']: + print(">> ", i['short_name']) + print("`!Clients:`! ", i['clients']) + print("`!Current RX:`! ", i['rxs']) + print("`!Current TX:`! ", i['txs']) + print("`!Total RX:`! ", sizeof_fmt(i['rxb'])) + print("`!Total TX:`! ", sizeof_fmt(i['txb'])) + +hswro.footer()