From 4693fa495f94a26356d7d95310eb989c82456bd3 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Tue, 23 Sep 2025 08:50:17 -0400 Subject: [PATCH] Replace urllib with requests for MoneroClient HTTP calls Switch from urllib to requests library for all MoneroClient HTTP requests. The requests library handles Digest authentication correctly while urllib has compatibility issues with Monero wallet RPC server implementation. - Simplified code by removing complex urllib Digest auth workarounds - Uses requests.post() with HTTPDigestAuth for authenticated requests - Maintains backward compatibility with no-auth configurations - Adds requests as a runtime dependency for Monero functionality Fixes Digest authentication issues that caused '401 digest auth failed' errors. --- make_post_sell/lib/crypto_clients.py | 67 ++++++++++++---------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/make_post_sell/lib/crypto_clients.py b/make_post_sell/lib/crypto_clients.py index 6fe9f14..217e20b 100644 --- a/make_post_sell/lib/crypto_clients.py +++ b/make_post_sell/lib/crypto_clients.py @@ -6,6 +6,14 @@ import urllib.request import urllib.error import base64 +# Try to import requests for better Digest auth support +try: + import requests + from requests.auth import HTTPDigestAuth + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + class MoneroClient: """ @@ -38,48 +46,29 @@ class MoneroClient: } if params is not None: payload["params"] = params - data = json.dumps(payload).encode() + + # Use requests library for all HTTP calls - much better Digest auth support + if not HAS_REQUESTS: + raise RuntimeError("requests library required for Monero RPC. Install with: pip install requests") - # Monero uses Digest authentication, not Basic - # We need to handle this differently from the headers approach - if self.rpc_user and self.rpc_pass: - # Create a password manager for Digest auth - password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() - # Add password for both the full URL and just the base URL - password_mgr.add_password(None, self.rpc_url, self.rpc_user, self.rpc_pass) - from urllib.parse import urlparse - parsed = urlparse(self.rpc_url) - base_url = f"{parsed.scheme}://{parsed.netloc}" - password_mgr.add_password(None, base_url, self.rpc_user, self.rpc_pass) + auth = HTTPDigestAuth(self.rpc_user, self.rpc_pass) if self.rpc_user and self.rpc_pass else None + + try: + response = requests.post( + self.rpc_url, + json=payload, + auth=auth, + timeout=self.timeout + ) + response.raise_for_status() - # Create the Digest auth handler - auth_handler = urllib.request.HTTPDigestAuthHandler(password_mgr) - opener = urllib.request.build_opener(auth_handler) + obj = response.json() + if "error" in obj and obj["error"]: + raise RuntimeError(obj["error"]) # bubble up rpc error + return obj.get("result") - # Make the request with Digest auth - req = urllib.request.Request(self.rpc_url, data=data, - headers={"Content-Type": "application/json"}) - try: - with opener.open(req, timeout=self.timeout) as resp: - body = resp.read() - obj = json.loads(body) - if "error" in obj and obj["error"]: - raise RuntimeError(obj["error"]) # bubble up rpc error - return obj.get("result") - except urllib.error.URLError as e: - raise RuntimeError(f"Monero RPC connection error: {e}") - else: - # No auth needed - req = urllib.request.Request(self.rpc_url, data=data, headers=self._headers()) - try: - with urllib.request.urlopen(req, timeout=self.timeout) as resp: - body = resp.read() - obj = json.loads(body) - if "error" in obj and obj["error"]: - raise RuntimeError(obj["error"]) # bubble up rpc error - return obj.get("result") - except urllib.error.URLError as e: - raise RuntimeError(f"Monero RPC connection error: {e}") + except requests.exceptions.RequestException as e: + raise RuntimeError(f"Monero RPC connection error: {e}") # High-level helpers