diff --git a/fpaste b/fpaste index 61a16cf..a141220 100755 --- a/fpaste +++ b/fpaste @@ -10,6 +10,7 @@ import json import os import ssl import sys +import time import urllib.error import urllib.request from datetime import UTC, datetime, timedelta @@ -336,6 +337,28 @@ def format_timestamp(ts: int | float) -> str: return dt.strftime("%Y-%m-%d %H:%M") +def format_time_remaining(expires_at: int | float | None) -> str: + """Format time remaining until expiry.""" + if not expires_at: + return "" + now = time.time() + remaining = expires_at - now + if remaining <= 0: + return "expired" + if remaining < 60: + return f"{int(remaining)}s" + if remaining < 3600: + return f"{int(remaining / 60)}m" + if remaining < 86400: + hours = int(remaining / 3600) + return f"{hours}h" + days = int(remaining / 86400) + if days >= 365: + years = days // 365 + return f"{years}y" + return f"{days}d" + + def parse_date(date_str: str) -> int: """Parse date string to Unix timestamp.""" if not date_str: @@ -359,22 +382,27 @@ def get_extension_for_mime(mime_type: str) -> str: return MIME_EXTENSIONS.get(mime_type, ".bin") -def format_paste_row(paste: dict[str, Any]) -> str: +def format_paste_row(paste: dict[str, Any], show_owner: bool = False) -> str: """Format a paste as a table row.""" paste_id = paste["id"] mime_type = paste.get("mime_type", "unknown")[:16] size = format_size(paste.get("size", 0)) created = format_timestamp(paste.get("created_at", 0)) + # Time remaining until expiry + expires = format_time_remaining(paste.get("expires_at")) + flags = [] if paste.get("burn_after_read"): flags.append("burn") if paste.get("password_protected"): flags.append("pass") - if paste.get("expires_at"): - flags.append("exp") - return f"{paste_id:<12} {mime_type:<16} {size:>6} {created:<16} {' '.join(flags)}" + flags_str = " ".join(flags) + row = f"{paste_id:<12} {mime_type:<16} {size:>6} {created:<16} {expires:<8} {flags_str}" + if show_owner and paste.get("owner"): + row += f" {paste['owner'][:12]}" + return row def print_paste_list( @@ -392,9 +420,14 @@ def print_paste_list( print("no pastes found") return - print(f"{'ID':<12} {'TYPE':<16} {'SIZE':>6} {'CREATED':<16} FLAGS") + # Check if owner data is present (admin view) + show_owner = any(paste.get("owner") for paste in pastes) + header = f"{'ID':<12} {'TYPE':<16} {'SIZE':>6} {'CREATED':<16} {'EXPIRES':<8} FLAGS" + if show_owner: + header += " OWNER" + print(header) for paste in pastes: - print(format_paste_row(paste)) + print(format_paste_row(paste, show_owner=show_owner)) print(f"\n{summary}") @@ -653,6 +686,8 @@ def cmd_list(args: argparse.Namespace, config: dict[str, Any]) -> None: require_auth(config) params = [] + if getattr(args, "all", False): + params.append("all=1") if args.limit: params.append(f"limit={args.limit}") if args.offset: @@ -1258,6 +1293,7 @@ def build_parser() -> argparse.ArgumentParser: # list p_list = subparsers.add_parser("list", aliases=["ls"], help="list your pastes") + p_list.add_argument("-a", "--all", action="store_true", help="list all pastes (admin only)") p_list.add_argument("-l", "--limit", type=int, metavar="N", help="max pastes (default: 50)") p_list.add_argument("-o", "--offset", type=int, metavar="N", help="skip first N pastes") p_list.add_argument("--json", action="store_true", help="output as JSON")