from pathlib import Path import json import secrets from urllib.parse import unquote from db import get_db, return_db, get_setting, set_setting, get_site_name, index_url, clean_url from templates import esc from ._helpers import ( _csrf_field, _respond, _redirect, _error, _paginate, _page_nav, _get_page_tags, _set_page_tags, _cleanup_orphaned_tags, _get_bookmark_token, _text_response, BROWSE_PER_PAGE, ) def handle_add_form(msg="", action_type="index", prefill_url=""): if action_type == "subscribe": return _respond( f"
Subscribe to a friend's TinyWeb instance to sync their index
" f'" f"" f"{msg}
" f'back' ) url_value = f'value="{esc(prefill_url)}" ' if prefill_url else "" return _respond( f"Add a site to your index
" 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(">", "") note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() 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://") else: if not reticulum_dest: return handle_add_form("Reticulum 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 reticulum destination hash. Must be 32 hex characters.") url = f"reticulum:{reticulum_dest}" try: title = index_url(url, note, reticulum_dest if reticulum_dest else "") 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 "block" in error_msg or "cloudflare" in error_msg or "403" in error_msg: return _respond( f"{esc(url)} blocks automated access. " f"You can still save it manually:
" 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): url = clean_url(body.get("url", [""])[0].strip()) note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() manual_title = body.get("manual_title", [""])[0].strip() manual_desc = body.get("manual_description", [""])[0].strip() if not url: return handle_add_form("URL is required.") if not manual_title: return handle_add_form("Title is required for manual entry.") db = get_db() try: now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S") db.execute( "INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) " "ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, " "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary", (url, manual_title, manual_desc, note, now, manual_desc[:200]), ) page_id = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()[0] if tags: _set_page_tags(page_id, tags, db) db.commit() if get_setting("semantic_search", "0") == "1": try: from embeddings import store_embeddings store_embeddings(page_id, manual_title, manual_desc, db) db.commit() except Exception as e: print(f"Error generating embeddings: {e}") return handle_add_form(f'Added manually: {esc(manual_title)}') 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 "" page = _paginate(query or {}) offset = (page - 1) * BROWSE_PER_PAGE db = get_db() try: total = db.execute("SELECT count(*) FROM pages").fetchone()[0] rows = db.execute( "SELECT id, url, title, note FROM pages ORDER BY id DESC LIMIT ? OFFSET ?", (BROWSE_PER_PAGE, offset), ).fetchall() items = "" for r in rows: note_html = f' — {esc(r["note"])}' if r["note"] else "" tags = _get_page_tags(r["id"], db) tags_html = "" if tags: tag_links = " ".join(f'[{esc(t)}]' for t in tags) tags_html = f' {tag_links}' items += ( f'Remove the following {n} page{'' if n == 1 else 's'}?
" f"{esc(row['title'])}
"
f"{esc(row['url'])}
{msg}
" f'back' ) def handle_edit_submit(page_id, body): title = body.get("title", [""])[0].strip() summary = body.get("summary", [""])[0].strip() note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() db = get_db() try: db.execute( "UPDATE pages SET title = ?, summary = ?, note = ? WHERE id = ?", (title, summary, note, page_id) ) _set_page_tags(page_id, tags, db) _cleanup_orphaned_tags(db) db.commit() finally: return_db(db) return _redirect("/pages") def handle_delete_confirm(page_id): db = get_db() try: row = db.execute("SELECT id, url, title FROM pages WHERE id = ?", (page_id,)).fetchone() finally: return_db(db) if not row: return _error(404) return _respond( f"Remove {esc(row['title'])}
"
f"{esc(row['url'])}