"""A group's content: its files, its video matches, its chat.""" import sys from meshbay_common import MNP_VERSION from meshbay_node.cli.api import _daemon_api, _resolve_group from meshbay_node.config import DEFAULT_CONFIG_PATH, load_config def file(args) -> None: cfg = load_config(args.config or DEFAULT_CONFIG_PATH) sub = args.subcommand or "list" group_id = _resolve_group(cfg, args.group) if sub == "list": out = _daemon_api(cfg, f"/api/groups/{group_id}/files") files = sorted(out.get("files", []), key=lambda f: (f["path"], f["name"])) if not files: print("no files indexed") return for f in files: print(f" {f['id'][:12]} {f['size']:>12} {f['path']}/{f['name']}") print(f"\n{len(files)} file(s). Remove one with: " f"meshbay-node file rm ") return if sub == "rm": # Milestone 14.11 — the last operator action that needed a browser. if not args.target: print("usage: meshbay-node file rm [--group NAME]") sys.exit(1) out = _daemon_api(cfg, f"/api/groups/{group_id}/files",) matches = [f for f in out.get("files", []) if f["id"].startswith(args.target)] if not matches: print(f"no file whose id starts with {args.target!r}") sys.exit(1) if len(matches) > 1: print(f"{args.target!r} matches {len(matches)} files — be more specific:") for f in matches[:10]: print(f" {f['id'][:16]} {f['path']}/{f['name']}") sys.exit(1) target = matches[0] if not args.yes: print(f"Delete {target['path']}/{target['name']} " f"({target['size']} bytes) from disk?") print("This removes the file itself, not just the listing.") if input("delete? [y/N] ").strip().lower() not in ("y", "yes"): print("cancelled") return _daemon_api(cfg, f"/api/groups/{group_id}/files/{target['id']}", method="DELETE") print(f"deleted {target['path']}/{target['name']}") return print("usage: meshbay-node file list|rm [--group NAME] [--yes]") sys.exit(1) def video(args) -> None: cfg = load_config(args.config or DEFAULT_CONFIG_PATH) if (args.subcommand or "") != "rematch": print("usage: meshbay-node video rematch [--group NAME] [--yes]") sys.exit(1) group_id = _resolve_group(cfg, args.group) if not args.yes: print("Re-resolve every automatic TMDB match for this group's videos?") print("Manual 'Fix match' corrections are kept. Re-resolution is lazy —") print("each poster re-queries TMDB the next time it is opened.") if input("proceed? [y/N] ").strip().lower() not in ("y", "yes"): print("cancelled") return out = _daemon_api(cfg, f"/api/groups/{group_id}/video/rematch", method="POST") print(f"cleared {out.get('removed', 0)} automatic match(es) " f"across {out.get('videos', 0)} video file(s)") return def chat(args) -> None: cfg = load_config(args.config or DEFAULT_CONFIG_PATH) sub = args.subcommand or "status" group_id = _resolve_group(cfg, args.group) if sub == "status": out = _daemon_api(cfg, f"/api/groups/{group_id}/chat") print(f"encryption always on (MNP {MNP_VERSION})") print(f"epoch {out.get('epoch', 0)}") print(f"messages {out.get('encrypted_messages', 0)} encrypted, " f"{out.get('plaintext_messages', 0)} in the clear") if out.get("plaintext_messages"): print("\nThose messages were written before this node spoke MNP " "2.0 and are\nstill readable off this disk. " "`chat encrypt-history` converts them.") return if sub == "rotate": out = _daemon_api(cfg, f"/api/groups/{group_id}/chat/epoch", method="POST") print(f"chat epoch {out['epoch']} opened") print("Everyone still in the group keeps reading the history; " "whoever left\ncannot read what is written from now on.") return if sub == "encrypt-history": if not args.yes: print("This rewrites the only copy of this group's older " "messages.") print("A backup of chat.db is taken first, beside it.") if input("re-encrypt now? [y/N] ").strip().lower() not in ("y", "yes"): print("cancelled") return out = _daemon_api(cfg, f"/api/groups/{group_id}/chat/encrypt-history", method="POST", timeout=300) print(f"re-encrypted {out['converted']} message(s) under epoch " f"{out['epoch']}") print(f"backup {out['backup']}") return if sub == "prune": days = int(args.target or 0) if days < 1: print("usage: meshbay-node chat prune [--group G]") sys.exit(1) out = _daemon_api( cfg, f"/api/groups/{group_id}/chat/prune?max_age_days={days}", method="POST") print(f"removed {out['removed']} message(s) older than {days} day(s)") return print("usage: meshbay-node chat " "status|rotate|encrypt-history|prune [--group G]") sys.exit(1)