diff --git a/src/tinyweb/handlers/__init__.py b/src/tinyweb/handlers/__init__.py index 3fde8be..14a37fd 100644 --- a/src/tinyweb/handlers/__init__.py +++ b/src/tinyweb/handlers/__init__.py @@ -18,7 +18,6 @@ from ._helpers import ( from .search import handle_search from .pages import ( handle_add_form, handle_add_submit, handle_add_manual_submit, - handle_page_view, handle_pages, _render_bulk_delete_confirm, handle_bulk_action, handle_edit_form, handle_edit_submit, handle_delete_confirm, handle_delete, @@ -66,9 +65,6 @@ def _dispatch_inner(data): elif path == "/add": prefill_url = query.get("url", [""])[0].strip() return handle_add_form(prefill_url=prefill_url) - elif path.startswith("/pages/"): - pid = extract_id("/pages/") - return handle_page_view(pid) if pid is not None else _error(400) elif path == "/pages": return handle_pages(query) elif path.startswith("/edit/"): diff --git a/src/tinyweb/handlers/pages.py b/src/tinyweb/handlers/pages.py index be68e18..9d7f96c 100644 --- a/src/tinyweb/handlers/pages.py +++ b/src/tinyweb/handlers/pages.py @@ -29,93 +29,86 @@ def handle_add_form(msg="", action_type="index", prefill_url=""): ) url_value = f'value="{esc(prefill_url)}" ' if prefill_url else "" return _respond( - f"

add url

" - f"

Add a site to your index

" + f"

add site

" + f"

Add a site to your index — URL or RNS destination hash

" f'
' f'{_csrf_field()}' - f'' - f'

' + f'

' f'

' f'
' f'tag: private to exclude from sharing

' f'' f"
" - f"


" - f"

add mesh site

" - f"

Browse a remote TinyWeb instance over Reticulum

" - f'
' - f'{_csrf_field()}' - f'' - f'

' - f'

' - f'' - f"
" f"

{msg}

" f'back' ) def handle_add_submit(body): - input_type = body.get("input_type", ["url"])[0] - url = body.get("url", [""])[0].strip() - reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "") - name = body.get("name", [""])[0].strip() + raw = body.get("url", [""])[0].strip().replace("<", "").replace(">", "") note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() - if input_type == "rns": - if not reticulum_dest: - return handle_add_form("RNS destination hash is required.") - if len(reticulum_dest) != 32 or not all(c in "0123456789abcdefABCDEF" for c in reticulum_dest): - return handle_add_form("Invalid RNS destination hash. Must be 32 hex characters.") + if not raw: + return handle_add_form("URL or RNS hash is required.") + + is_rns = ( + len(raw) == 32 + and all(c in "0123456789abcdefABCDEF" for c in raw) + ) + if raw.startswith("rns:") or raw.startswith("RNS:"): + raw = raw[4:] + is_rns = ( + len(raw) == 32 + and all(c in "0123456789abcdefABCDEF" for c in raw) + ) + + if is_rns: from .rns import handle_rns_add_hash - handle_rns_add_hash(reticulum_dest, name) + errs = handle_rns_add_hash(raw) + if errs: + return handle_add_form(f"Hash saved but indexing failed: {'; '.join(errs)}") return _redirect("/") - if input_type == "url": - if not url: - return handle_add_form("URL is required.") - url = clean_url(url) - if not url.startswith(("http://", "https://")): - return handle_add_form("URL must start with http:// or https://") + url = clean_url(raw) + if not url.startswith(("http://", "https://")): + return handle_add_form("Enter a URL (http:// or https://) or a 32-char RNS destination hash.") - try: - title = index_url(url, note) - if tags: - db = get_db() - try: - row = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone() - if row: - _set_page_tags(row["id"], tags, db) - db.commit() - finally: - return_db(db) - return handle_add_form(f'Indexed: {esc(url)}') - except ValueError as e: - return handle_add_form(f"Error: {esc(str(e))}") - except Exception as e: - error_msg = str(e).lower() - if any(x in error_msg for x in ("block", "cloudflare", "403", "ssl", "handshake", "max retries", "timeout", "connection")): - return _respond( - f"

add url (manual entry)

" - f"

{esc(url)} blocks automated access. " - f"You can still save it manually:

" - f'
' - f'{_csrf_field()}' - f'' - f'' - f'' - f'
' - f'

' - f'
' - f'

' - f'' - f"
" - f'back' - ) - return handle_add_form(f"Error: could not fetch or index that URL. {esc(str(e)[:100])}") - else: - return handle_add_form("Invalid input type.") + try: + title = index_url(url, note) + if tags: + db = get_db() + try: + row = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone() + if row: + _set_page_tags(row["id"], tags, db) + db.commit() + finally: + return_db(db) + return handle_add_form(f'Indexed: {esc(url)}') + except ValueError as e: + return handle_add_form(f"Error: {esc(str(e))}") + except Exception as e: + error_msg = str(e).lower() + if any(x in error_msg for x in ("block", "cloudflare", "403", "ssl", "handshake", "max retries", "timeout", "connection")): + return _respond( + f"

add url (manual entry)

" + f"

{esc(url)} blocks automated access. " + f"You can still save it manually:

" + f'
' + f'{_csrf_field()}' + f'' + f'' + f'' + f'
' + f'

' + f'
' + f'

' + f'' + f"
" + f'back' + ) + return handle_add_form(f"Error: could not fetch or index that URL. {esc(str(e)[:100])}") def handle_add_manual_submit(body): @@ -162,32 +155,6 @@ def handle_add_manual_submit(body): return_db(db) -def handle_page_view(page_id): - db = get_db() - try: - row = db.execute("SELECT id, url, title, body, note FROM pages WHERE id = ?", (page_id,)).fetchone() - if not row: - return _error(404) - tags = _get_page_tags(row["id"], db) - tag_links = " ".join(f'[{esc(t)}]' for t in tags) if tags else "" - note_html = f'

{esc(row["note"])}

' if row["note"] else "" - title = esc(row["title"] or "(untitled)") - body_html = row["body"] or "(no content)" - url_link = f'

source: {esc(row["url"])}

' - return _respond( - f"

{title}

" - f"{tag_links}" - f"{note_html}" - f"
" - f"{body_html}" - f"
" - f"{url_link}" - f'back to browse' - ) - finally: - return_db(db) - - def handle_pages(query=None): msg = query.get("msg", [""])[0] if query else "" msg_html = f'

{esc(msg)}

' if msg else "" @@ -208,10 +175,17 @@ def handle_pages(query=None): if tags: tag_links = " ".join(f'[{esc(t)}]' for t in tags) tags_html = f' {tag_links}' + url = r["url"] + if url.startswith("rns:"): + display_url = url + link_url = f"/rns/{esc(url[4:])}/" + else: + display_url = url + link_url = url items += ( f'
  • {note_html}{tags_html} ' - f'({esc(r["url"])}) ' + f'{esc(r["title"])}{note_html}{tags_html} ' + f'({esc(display_url)}) ' f'edit ' f'remove
  • ' ) diff --git a/src/tinyweb/handlers/rns.py b/src/tinyweb/handlers/rns.py index 7fe67f3..8941f99 100644 --- a/src/tinyweb/handlers/rns.py +++ b/src/tinyweb/handlers/rns.py @@ -1,4 +1,5 @@ import json +import traceback from tinyweb.db import get_db, return_db from tinyweb.rns_client import fetch_remote_page from tinyweb.templates import esc @@ -14,15 +15,49 @@ def _get_mesh_sites(): def handle_rns_add_hash(dest_hash, name=""): db = get_db() + errors = [] try: db.execute( "INSERT OR REPLACE INTO mesh_sites (hash, name) VALUES (?, ?)", (dest_hash, name or ""), ) + + try: + resp = fetch_remote_page(dest_hash, "/") + if resp.get("status") == 200: + body = resp.get("body", "") + title = name or dest_hash[:16] + import re + m = re.search(r"]*>(.*?)", body, re.IGNORECASE | re.DOTALL) + if m: + title = m.group(1).strip() + desc = "" + m = re.search(r']+>", " ", body) + text = re.sub(r"\s+", " ", text).strip() + desc = text[:200].strip() + url = f"rns:{dest_hash}" + import datetime + now = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + db.execute( + "INSERT OR REPLACE INTO pages (url, title, body, last_modified, summary) VALUES (?, ?, ?, ?, ?)", + (url, title, body, now, desc), + ) + else: + errors.append(f"Remote returned status {resp.get('status')}") + except Exception as e: + errors.append(str(e)) + traceback.print_exc() + db.commit() finally: return_db(db) + return errors if errors else None + def handle_rns_delete_hash(body): dest_hash = body.get("hash", [""])[0].strip() if isinstance(body, dict) else body diff --git a/src/tinyweb/handlers/search.py b/src/tinyweb/handlers/search.py index 9d7dbee..10b87fd 100644 --- a/src/tinyweb/handlers/search.py +++ b/src/tinyweb/handlers/search.py @@ -1,6 +1,6 @@ from tinyweb.db import get_db, return_db, get_setting, get_site_name, clean_url from tinyweb.templates import esc -from ._helpers import _sanitize_fts_query, _paginate, _get_page_tags, _respond, _page_nav, _csrf_field, PER_PAGE +from ._helpers import _sanitize_fts_query, _paginate, _get_page_tags, _respond, _page_nav, PER_PAGE def handle_search(query): @@ -83,10 +83,17 @@ def handle_search(query): tag_links = " ".join(f'[{esc(t)}]' for t in tags) tags_html = f'
    {tag_links}
    ' snip_html = f'
    {esc(r["summary"])}' if r["summary"] else "" + url = r["url"] + if url.startswith("rns:"): + display_url = url + link_url = f"/rns/{esc(url[4:])}/" + else: + display_url = url + link_url = url result_html += ( f'
    ' - f'{esc(r["title"])}
    ' - f'{esc(r["url"])}' + f'{esc(r["title"])}
    ' + f'{esc(display_url)}' f'{snip_html}' f'{note_html}{tags_html}' f'
    ' @@ -163,30 +170,6 @@ def handle_search(query): if q and remote_rows: sub_count = f" + {len(remote_rows)} from subscriptions" - mesh_html = "" - if not q: - from .rns import _get_mesh_sites - sites = _get_mesh_sites() - if sites: - rows_html = "" - for s in sites: - rows_html += ( - f'' - f'{esc(s["name"] or s["hash"][:16])}' - f'{esc(s["hash"])}' - f'
    ' - f'{_csrf_field()}' - f'' - f'' - f'
    ' - f'' - ) - mesh_html = ( - f"
    " - f"

    mesh sites

    " - f'{rows_html}
    ' - ) - welcome_html = "" if count == 0 and not q: welcome_html = ( @@ -209,5 +192,5 @@ def handle_search(query): f'{welcome_html}' f'{result_html}' f'{_page_nav(page, total_results, f"/?q={esc(q)}") if q else ""}' - f'{trusted_html}{remote_html}{mesh_html}' + f'{trusted_html}{remote_html}' ) diff --git a/src/tinyweb/handlers/tags.py b/src/tinyweb/handlers/tags.py index 5e50567..46189d6 100644 --- a/src/tinyweb/handlers/tags.py +++ b/src/tinyweb/handlers/tags.py @@ -45,7 +45,7 @@ def handle_tag_browse(tag_name, query=None): tags = _get_page_tags(r["id"], db) tag_links = " ".join(f'[{esc(t)}]' for t in tags) items += ( - f'
  • {esc(r["title"])}{note_html} {tag_links} ' + f'
  • {esc(r["title"])}{note_html} {tag_links} ' f'({esc(r["url"])})
  • ' ) finally: diff --git a/src/tinyweb/templates.py b/src/tinyweb/templates.py index 8cc3535..8208211 100644 --- a/src/tinyweb/templates.py +++ b/src/tinyweb/templates.py @@ -13,7 +13,7 @@ def _nav_html(): return ( f'

    {name}' ' | search | browse' - ' | tags | subscriptions | mesh' + ' | tags | subscriptions' f'{forum_link}' ' | customize | about

    \n' "
    \n"