import threading from datetime import datetime from db import get_db, return_db, get_setting, set_setting, get_site_name, index_url, clean_url from templates import esc from rns_client import fetch_remote_sites from ._helpers import ( _get_page_tags, _respond, _redirect, _json_response, _error, _csrf_field, ) _sync_threads = {} MAX_API_SITES = 5000 MAX_BROWSE = 5000 def _page_is_shared(tags, mode): if "private" in tags: return False if mode == "require_public" and "public" not in tags: return False return True def _shared_sites(db, since=""): mode = get_setting("sharing_mode", "exclude_private") if since: rows = db.execute( "SELECT id, url, title, note, last_modified FROM pages " "WHERE last_modified > ? ORDER BY id DESC LIMIT ?", (since, MAX_API_SITES), ).fetchall() else: rows = db.execute( "SELECT id, url, title, note, last_modified FROM pages ORDER BY id DESC LIMIT ?", (MAX_API_SITES,), ).fetchall() sites = [] for r in rows: tags = _get_page_tags(r["id"], db) if not _page_is_shared(tags, mode): continue sites.append({ "url": r["url"], "title": r["title"], "note": r["note"], "tags": tags, "last_modified": r["last_modified"] or "", }) return sites def _shared_all_urls(db): mode = get_setting("sharing_mode", "exclude_private") rows = db.execute( "SELECT id, url FROM pages ORDER BY id DESC LIMIT ?", (MAX_API_SITES,) ).fetchall() return [r["url"] for r in rows if _page_is_shared(_get_page_tags(r["id"], db), mode)] def _count_shared_pages(): db = get_db() try: return len(_shared_all_urls(db)) finally: return_db(db) def handle_share_preview(): mode = get_setting("sharing_mode", "exclude_private") mode_label = ( "only pages tagged public" if mode == "require_public" else "all pages except those tagged private" ) sharing_on = get_setting("sharing_enabled", "0") == "1" status = ( '

Sharing is enabled. Subscribers see the pages listed below.

' if sharing_on else '

Sharing is disabled. Nothing is actually being shared right now; ' 'this is the list that would be exposed if you enabled it.

' ) db = get_db() try: sites = _shared_sites(db) finally: return_db(db) if not sites: body = ( "

sharing preview

" f"

Rule: {mode_label}.

" f"{status}" "

No pages match the current rule.

" '

back to settings

' ) return _respond(body) rows = "" for s in sites: tags_html = "" if s["tags"]: tags_html = " " + " ".join(f"[{esc(t)}]" for t in s["tags"]) note_html = f' — {esc(s["note"])}' if s["note"] else "" rows += ( f'
  • ' f'{esc(s["title"] or s["url"])}' f'{note_html}{tags_html} ' f'
    {esc(s["url"])}' f'
  • ' ) body = ( "

    sharing preview

    " f"

    Rule: {mode_label}.

    " f"{status}" f"

    {len(sites)} page(s) visible to subscribers.

    " f"" '

    back to settings

    ' ) return _respond(body) def handle_api_sites(query=None): if get_setting("sharing_enabled", "0") != "1": return _json_response( {"error": "sharing disabled"}, status=403, headers={"Access-Control-Allow-Origin": "*"}, ) since = (query or {}).get("since", [""])[0].strip() db = get_db() try: sites = _shared_sites(db, since=since) all_urls = _shared_all_urls(db) if not since else None finally: return_db(db) data = {"name": get_site_name(), "sites": sites} if all_urls is not None: data["all_urls"] = all_urls return _json_response(data, headers={"Access-Control-Allow-Origin": "*"}) def handle_subscriptions(msg=""): db = get_db() try: subs = db.execute("SELECT * FROM subscriptions ORDER BY id DESC").fetchall() finally: return_db(db) cards = "" for s in subs: sub_id = s["id"] auto_label = "on" if s["auto_sync"] else "off" last = s["last_sync"] or "never" sync_status = get_setting(f"sync_status_{sub_id}", "") is_syncing = sub_id in _sync_threads and _sync_threads[sub_id].is_alive() if is_syncing: status_html = '
    syncing...
    ' elif sync_status.startswith("error:"): err_msg = sync_status[6:] status_html = f'
    {esc(err_msg)}
    ' else: status_html = "" if is_syncing: sync_btn = '' else: sync_btn = ( f'
    ' f'{_csrf_field()}
    ' ) cards += ( f'
    ' f'
    {esc(s["name"] or "unknown")}
    ' f'
    {esc(s["dest_hash"])}
    ' f'
    last sync: {esc(last)}
    ' f'{status_html}' f'
    ' f'browse' f'{sync_btn}' f'
    ' f'{_csrf_field()}
    ' f'
    ' f'{_csrf_field()}
    ' f'
    ' f'
    ' ) any_syncing = any( s["id"] in _sync_threads and _sync_threads[s["id"]].is_alive() for s in subs ) head_html = '' if any_syncing else "" listing = "" if subs: syncall_btn = '' if any_syncing else '' listing = ( f'{cards}' f'
    ' f'{_csrf_field()}{syncall_btn}
    ' ) return _respond( f"

    subscriptions

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

    or subscribe to an instance

    ' f'

    {msg}

    ' f'
    {listing}' f'
    back', head_html=head_html, ) def handle_subscription_add(body): dest_hash = body.get("dest_hash", [""])[0].strip().replace("<", "").replace(">", "") if not dest_hash or len(dest_hash) != 32: return handle_subscriptions("Enter a valid 32-character destination hash.") try: int(dest_hash, 16) except ValueError: return handle_subscriptions("Invalid destination hash (must be hex).") try: data = fetch_remote_sites(dest_hash) name = data.get("name", "") except PermissionError: return handle_subscriptions("That instance has sharing disabled.") except Exception: return handle_subscriptions("Could not reach that instance.") db = get_db() try: db.execute( "INSERT INTO subscriptions (dest_hash, name) VALUES (?, ?) " "ON CONFLICT(dest_hash) DO UPDATE SET name=excluded.name", (dest_hash, name), ) db.commit() finally: return_db(db) return handle_subscriptions(f"Subscribed to {esc(name or dest_hash)}.") def handle_subscription_browse(sub_id): db = get_db() try: sub = db.execute("SELECT * FROM subscriptions WHERE id = ?", (sub_id,)).fetchone() if not sub: return _error(404) local_urls = set(r["url"] for r in db.execute("SELECT url FROM pages LIMIT ?", (MAX_BROWSE,)).fetchall()) remote_rows = db.execute( "SELECT url, title, note, tags FROM remote_pages WHERE subscription_id = ? LIMIT ?", (sub_id, MAX_BROWSE), ).fetchall() finally: return_db(db) if remote_rows: sites = [] for r in remote_rows: tags = [t for t in r["tags"].split(",") if t] if r["tags"] else [] sites.append({"url": r["url"], "title": r["title"], "note": r["note"], "tags": tags}) else: try: data = fetch_remote_sites(sub["dest_hash"]) sites = data.get("sites", []) except PermissionError: return handle_subscriptions("That instance has sharing disabled.") except Exception: return handle_subscriptions("Could not fetch sites from that instance.") new_items = "" existing_items = "" new_count = 0 for s in sites: if s["url"] in local_urls: existing_items += ( f'
  • {esc(s["title"])} ' f'({esc(s["url"])}) — already indexed
  • ' ) else: new_count += 1 note_html = f' — {esc(s["note"])}' if s.get("note") else "" tags_html = "" if s.get("tags"): tags_html = " " + " ".join(f'[{esc(t)}]' for t in s["tags"]) new_items += ( f'
  • ' ) buttons = "" if new_count: buttons = ' ' return _respond( f'

    browsing: {esc(sub["name"] or sub["dest_hash"])}

    ' f'

    {len(sites)} site(s) available, {new_count} new

    ' f'
    ' f'{_csrf_field()}' f'' f'' f'{buttons}' f'
    ' f'

    already indexed

    ' f'back' ) def handle_subscription_pick(body): sub_id = body.get("sub_id", [""])[0] import_all = body.get("import_all", [""])[0] db = get_db() try: remote_rows = db.execute( "SELECT url, tags FROM remote_pages WHERE subscription_id = ?", (sub_id,) ).fetchall() remote_tags = {r["url"]: r["tags"] for r in remote_rows} if import_all: local_urls = set(r["url"] for r in db.execute("SELECT url FROM pages LIMIT ?", (MAX_BROWSE,)).fetchall()) urls = [r["url"] for r in remote_rows if r["url"] not in local_urls] else: urls = body.get("urls", []) finally: return_db(db) if not urls: return handle_subscriptions("No sites selected.") imported = 0 errors = 0 for url in urls: try: index_url(url) tags_str = remote_tags.get(url, "") if tags_str: db = get_db() try: row = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone() if row: _set_page_tags(row["id"], tags_str, db) db.commit() finally: return_db(db) imported += 1 except Exception: errors += 1 return handle_subscriptions(f"Imported {imported} page(s). {errors} error(s).") def _sync_subscription(sub_id): set_setting(f"sync_status_{sub_id}", "syncing") db = get_db() try: sub = db.execute("SELECT * FROM subscriptions WHERE id = ?", (sub_id,)).fetchone() if not sub: set_setting(f"sync_status_{sub_id}", "error:Subscription not found.") return since = sub["last_sync"].replace(" ", "T") if sub["last_sync"] else "" try: data = fetch_remote_sites(sub["dest_hash"], since=since) sites = data.get("sites", []) all_urls = data.get("all_urls") remote_name = data.get("name", sub["name"]) except PermissionError: set_setting(f"sync_status_{sub_id}", "error:That instance has sharing disabled.") return except Exception as e: set_setting(f"sync_status_{sub_id}", f"error:Could not sync \u2014 {e}") return if all_urls is not None: existing = db.execute( "SELECT id, url FROM remote_pages WHERE subscription_id = ?", (sub_id,) ).fetchall() remote_url_set = set(all_urls) for row in existing: if row["url"] not in remote_url_set: db.execute("DELETE FROM remote_pages WHERE id = ?", (row["id"],)) synced = 0 for s in sites: try: tags_str = ",".join(s.get("tags", [])) db.execute( "INSERT INTO remote_pages (subscription_id, url, title, note, tags) VALUES (?, ?, ?, ?, ?) " "ON CONFLICT(subscription_id, url) DO UPDATE SET title=excluded.title, note=excluded.note, tags=excluded.tags", (sub_id, s["url"], s["title"], s.get("note", ""), tags_str), ) if get_setting("semantic_search", "0") == "1": try: from embeddings import store_remote_embeddings rp_id = db.execute( "SELECT id FROM remote_pages WHERE subscription_id = ? AND url = ?", (sub_id, s["url"]), ).fetchone()["id"] store_remote_embeddings(rp_id, s["title"], s.get("note", ""), db) except Exception: pass synced += 1 except Exception: pass now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") db.execute("UPDATE subscriptions SET last_sync = ?, name = ? WHERE id = ?", (now, remote_name, sub_id)) db.commit() set_setting(f"sync_status_{sub_id}", f"done:{synced}") except Exception as e: set_setting(f"sync_status_{sub_id}", f"error:{e}") finally: return_db(db) def handle_subscription_sync(sub_id): if sub_id in _sync_threads and _sync_threads[sub_id].is_alive(): return _redirect("/subscriptions") set_setting(f"sync_status_{sub_id}", "syncing") t = threading.Thread(target=_sync_subscription, args=(sub_id,), daemon=True) _sync_threads[sub_id] = t t.start() return _redirect("/subscriptions") def handle_subscription_autosync(sub_id): db = get_db() try: db.execute("UPDATE subscriptions SET auto_sync = 1 - auto_sync WHERE id = ?", (sub_id,)) db.commit() finally: return_db(db) return _redirect("/subscriptions") def handle_subscription_delete(sub_id): db = get_db() try: db.execute("DELETE FROM remote_pages WHERE subscription_id = ?", (sub_id,)) db.execute("DELETE FROM subscriptions WHERE id = ?", (sub_id,)) db.commit() finally: return_db(db) return _redirect("/subscriptions") def handle_subscription_syncall(): db = get_db() try: subs = db.execute("SELECT * FROM subscriptions WHERE auto_sync = 1").fetchall() finally: return_db(db) if not subs: return handle_subscriptions("No subscriptions have auto-sync enabled.") for sub in subs: sub_id = sub["id"] if sub_id in _sync_threads and _sync_threads[sub_id].is_alive(): continue set_setting(f"sync_status_{sub_id}", "syncing") t = threading.Thread(target=_sync_subscription, args=(sub_id,), daemon=True) _sync_threads[sub_id] = t t.start() return _redirect("/subscriptions")