integrated the forum plugin

This commit is contained in:
lichenblankie 2026-06-05 00:32:29 +00:00
parent 4a0214f020
commit 46cd28ba54
5 changed files with 122 additions and 4 deletions

View file

@ -6,9 +6,11 @@ from datetime import datetime
from urllib.parse import unquote
from db import get_db, return_db, get_setting, set_setting, get_site_name, index_url, clean_url
import templates as templates_mod
from templates import esc, wrap_page, DEFAULT_TEMPLATE
from rns_client import fetch_remote_sites
forum_plugin = None
_request_local = threading.local()
@ -360,7 +362,7 @@ def handle_search(query):
)
def handle_add_form(msg="", action_type="index"):
def handle_add_form(msg="", action_type="index", prefill_url=""):
if action_type == "subscribe":
return _respond(
f"<h1>subscribe</h1>"
@ -374,12 +376,13 @@ def handle_add_form(msg="", action_type="index"):
f"<p>{msg}</p>"
f'<a href="/">back</a>'
)
url_value = f'value="{esc(prefill_url)}" ' if prefill_url else ""
return _respond(
f"<h1>add url</h1>"
f"<p>Add a site to your index</p>"
f'<form method="post" action="/add">'
f'{_csrf_field()}'
f'<input name="url" placeholder="https://example.com" size="50"><br><br>'
f'<input name="url" placeholder="https://example.com" size="50" {url_value}><br><br>'
f'<input name="note" placeholder="why are you saving this? (optional)" size="50"><br><br>'
f'<input name="tags" placeholder="tags (comma-separated, e.g. solarpunk, mesh)" size="50"><br>'
f'<small>tag: private to exclude from sharing</small><br><br>'
@ -814,6 +817,8 @@ def handle_style_form(msg=""):
sharing = get_setting("sharing_enabled", "0")
checked = " checked" if sharing == "1" else ""
sharing_mode = get_setting("sharing_mode", "exclude_private")
forum = get_setting("forum_enabled", "0")
forum_checked = " checked" if forum == "1" else ""
exclude_checked = " checked" if sharing_mode != "require_public" else ""
require_checked = " checked" if sharing_mode == "require_public" else ""
shared_count = _count_shared_pages()
@ -840,6 +845,17 @@ def handle_style_form(msg=""):
lora_txpower = get_setting("lora_txpower", "7")
lora_sf = get_setting("lora_sf", "8")
lora_cr = get_setting("lora_cr", "5")
if forum_plugin is not None:
forum_section = (
f"<h2>forum</h2>"
f'<label><input type="checkbox" name="forum_enabled" value="1"{forum_checked}>'
f" enable forum (shared URL discussion board)</label><br>"
f"<small>Share URLs and discuss them with other TinyWeb instances. "
f"Requires <code>tinyweb-forum</code> — "
f'<a href="https://git.derickphan.com/lichenblankie/tinyweb-forum">more info</a>.</small><br><br>'
)
else:
forum_section = ""
return _respond(
f"<h1>customize</h1>"
f"<h2>name your search engine</h2>"
@ -915,6 +931,7 @@ def handle_style_form(msg=""):
f"<small>Saves ~50% on storage for embeddings. Slight quality reduction at large scale.</small><br><br>"
f'<a href="/reindex">manage semantic index</a><br><br>'
f"</div>"
{forum_section}
f"<h2>custom html</h2>"
f"<p>Edit the full page template. Use <code>{esc('{{content}}')}</code> "
f"where page content should appear.</p>"
@ -974,6 +991,27 @@ def handle_style_submit(body):
set_setting("lora_txpower", body.get("lora_txpower", ["7"])[0].strip())
set_setting("lora_sf", body.get("lora_sf", ["8"])[0].strip())
set_setting("lora_cr", body.get("lora_cr", ["5"])[0].strip())
forum_enabled = "1" if body.get("forum_enabled") else "0"
current_forum = get_setting("forum_enabled", "0")
if forum_enabled != current_forum:
if forum_enabled == "1" and forum_plugin is None:
return handle_style_form(
"Forum plugin not installed. Run: pip install tinyweb-forum"
)
if forum_enabled == "1":
forum_plugin.enable()
try:
forum_plugin.fdb.set_setting("forum_enabled", "1")
except Exception:
pass
else:
forum_plugin.disable()
try:
forum_plugin.fdb.set_setting("forum_enabled", "0")
except Exception:
pass
set_setting("forum_enabled", forum_enabled)
templates_mod.FORUM_ENABLED = (forum_enabled == "1")
return handle_style_form("Saved. Restart TinyWeb for mesh network changes to take effect.")
@ -1654,7 +1692,11 @@ def _dispatch_inner(data):
return handle_search(query)
elif path == "/add":
action_type = query.get("type", ["index"])[0]
return handle_add_form(action_type=action_type if action_type == "subscribe" else "index")
prefill_url = query.get("url", [""])[0].strip()
return handle_add_form(
action_type=action_type if action_type == "subscribe" else "index",
prefill_url=prefill_url,
)
elif path == "/pages":
return handle_pages(query)
elif path.startswith("/edit/"):
@ -1689,7 +1731,15 @@ def _dispatch_inner(data):
elif path.startswith("/subscriptions/browse/"):
sid = extract_id("/subscriptions/browse/")
return handle_subscription_browse(sid) if sid is not None else _error(400)
elif path.startswith("/forum"):
if forum_plugin and forum_plugin.is_enabled():
return forum_plugin.handle(method, path, query, {}, data.get("cookies", {}))
return _error(404)
elif method == "POST":
if path.startswith("/forum"):
if forum_plugin and forum_plugin.is_enabled():
return forum_plugin.handle(method, path, query, body, data.get("cookies", {}))
return _error(404)
if not _check_csrf(body):
return _respond("<h1>403 Forbidden</h1><p>Invalid or missing CSRF token.</p>", status=403)
if path == "/add":
@ -1737,7 +1787,28 @@ def _dispatch_inner(data):
def dispatch_request(data):
path = data.get("path", "/")
cookies = data.get("cookies", {})
# Forum handles its own CSRF — skip main CSRF to avoid cookie conflicts
if path.startswith("/forum") and forum_plugin and forum_plugin.is_enabled():
resp = _dispatch_inner(data)
resp.setdefault("headers", {})
resp["headers"]["X-Frame-Options"] = "DENY"
resp["headers"]["X-Content-Type-Options"] = "nosniff"
if resp.get("content_type", "").startswith("text/html"):
resp["body"] = wrap_page(resp.get("body", ""))
resp["headers"]["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline'; "
"style-src 'self' 'unsafe-inline'; "
"img-src * data:; "
"frame-ancestors 'none'; "
"form-action 'self'; "
"base-uri 'self'"
)
return resp
csrf_token = cookies.get("_csrf", "")
if not csrf_token:
csrf_token = secrets.token_hex(32)