From 916a09f595adfb4b8c5a2e07515fcd99b26b290d Mon Sep 17 00:00:00 2001 From: Username Date: Sun, 21 Dec 2025 22:06:53 +0100 Subject: [PATCH] fpaste: add batch delete and --all with confirmation --- fpaste | 87 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/fpaste b/fpaste index a141220..a72af1d 100755 --- a/fpaste +++ b/fpaste @@ -635,26 +635,73 @@ def cmd_get(args: argparse.Namespace, config: dict[str, Any]) -> None: def cmd_delete(args: argparse.Namespace, config: dict[str, Any]) -> None: - """Delete a paste.""" + """Delete paste(s).""" require_auth(config) - paste_id = args.id.split("/")[-1] - url = f"{config['server'].rstrip('/')}/{paste_id}" + delete_all = getattr(args, "all", False) + confirm_count = getattr(args, "confirm", None) + paste_ids = [pid.split("/")[-1] for pid in (args.ids or [])] - status, _, _ = request( - url, method="DELETE", headers=auth_headers(config), ssl_context=config.get("ssl_context") - ) + # Validate arguments + if delete_all and paste_ids: + die("cannot specify both --all and paste IDs") + if not delete_all and not paste_ids: + die("specify paste ID(s) or use --all") - if status == 200: - print(f"deleted: {paste_id}") - elif status == 404: - die(f"not found: {paste_id}") - elif status == 403: - die("permission denied (not owner)") - elif status == 401: - die("authentication failed") - else: - die(f"delete failed ({status})") + if delete_all: + # Fetch all pastes to get count and IDs + url = f"{config['server'].rstrip('/')}/pastes?all=1&limit=1000" + status, body, _ = request( + url, headers=auth_headers(config), ssl_context=config.get("ssl_context") + ) + if status == 401: + die("authentication failed") + if status != 200: + die(f"failed to list pastes ({status})") + + data = json.loads(body) + pastes = data.get("pastes", []) + total = len(pastes) + + if total == 0: + print("no pastes to delete") + return + + # Require confirmation with expected count + if confirm_count is None: + die(f"--all requires --confirm {total} (found {total} pastes)") + if confirm_count != total: + die(f"confirmation mismatch: expected {confirm_count}, found {total}") + + paste_ids = [p["id"] for p in pastes] + + # Delete pastes + deleted = 0 + failed = 0 + for paste_id in paste_ids: + url = f"{config['server'].rstrip('/')}/{paste_id}" + status, _, _ = request( + url, + method="DELETE", + headers=auth_headers(config), + ssl_context=config.get("ssl_context"), + ) + if status == 200: + print(f"deleted: {paste_id}") + deleted += 1 + elif status == 404: + print(f"not found: {paste_id}", file=sys.stderr) + failed += 1 + elif status == 403: + print(f"permission denied: {paste_id}", file=sys.stderr) + failed += 1 + else: + print(f"failed ({status}): {paste_id}", file=sys.stderr) + failed += 1 + + # Summary for batch operations + if len(paste_ids) > 1: + print(f"\n{deleted} deleted, {failed} failed") def cmd_info(args: argparse.Namespace, config: dict[str, Any]) -> None: @@ -1285,8 +1332,12 @@ def build_parser() -> argparse.ArgumentParser: p_get.add_argument("-m", "--meta", action="store_true", help="show metadata only") # delete - p_delete = subparsers.add_parser("delete", aliases=["d", "rm"], help="delete paste") - p_delete.add_argument("id", help="paste ID or URL") + p_delete = subparsers.add_parser("delete", aliases=["d", "rm"], help="delete paste(s)") + p_delete.add_argument("ids", nargs="*", metavar="ID", help="paste ID(s) or URL(s)") + p_delete.add_argument("-a", "--all", action="store_true", help="delete all pastes (admin)") + p_delete.add_argument( + "-c", "--confirm", type=int, metavar="N", help="confirm expected delete count" + ) # info subparsers.add_parser("info", aliases=["i"], help="show server info")