From 6ff8ee2483dfbbc9f91354d0898bb2d623876497 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Mon, 11 Aug 2025 00:18:14 -0700 Subject: [PATCH] rate limiting to avoid sousleek ban --- .../soulseek_client.cpython-312.pyc | Bin 63439 -> 65881 bytes core/soulseek_client.py | 43 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/__pycache__/soulseek_client.cpython-312.pyc b/core/__pycache__/soulseek_client.cpython-312.pyc index 4025bf6de04fc87daf69ddd69ec1bc2f9bfa8005..c805d5f8879c7f55b6b66ae2daa1810afe3d44fd 100644 GIT binary patch delta 7060 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 still_active + + # Rate limiting for searches + self.search_timestamps: List[float] = [] # Track search timestamps + self.max_searches_per_window = 45 # Conservative limit (vs 34 mentioned online) + self.rate_limit_window = 220 # seconds (3 minutes 40 seconds) + self._setup_client() def _setup_client(self): @@ -220,6 +226,40 @@ class SoulseekClient: logger.info(f"Soulseek client configured with slskd at {self.base_url}") + def _clean_old_timestamps(self): + """Remove timestamps older than the rate limit window""" + current_time = time.time() + cutoff_time = current_time - self.rate_limit_window + self.search_timestamps = [ts for ts in self.search_timestamps if ts > cutoff_time] + + async def _wait_for_rate_limit(self): + """Wait if necessary to respect rate limiting""" + self._clean_old_timestamps() + + if len(self.search_timestamps) >= self.max_searches_per_window: + # Calculate how long to wait + oldest_timestamp = self.search_timestamps[0] + wait_time = oldest_timestamp + self.rate_limit_window - time.time() + + if wait_time > 0: + logger.info(f"Rate limit reached ({len(self.search_timestamps)}/{self.max_searches_per_window} searches). Waiting {wait_time:.1f} seconds...") + await asyncio.sleep(wait_time) + # Clean up again after waiting + self._clean_old_timestamps() + + # Record this search attempt + self.search_timestamps.append(time.time()) + + def get_rate_limit_status(self) -> Dict[str, Any]: + """Get current rate limiting status""" + self._clean_old_timestamps() + return { + 'searches_in_window': len(self.search_timestamps), + 'max_searches_per_window': self.max_searches_per_window, + 'window_seconds': self.rate_limit_window, + 'searches_remaining': max(0, self.max_searches_per_window - len(self.search_timestamps)) + } + def _get_headers(self) -> Dict[str, str]: headers = {'Content-Type': 'application/json'} if self.api_key: @@ -540,6 +580,9 @@ class SoulseekClient: logger.error("Soulseek client not configured") return [], [] + # Apply rate limiting before search + await self._wait_for_rate_limit() + try: logger.info(f"Starting search for: '{query}'")