diff --git a/core/__pycache__/soulseek_client.cpython-312.pyc b/core/__pycache__/soulseek_client.cpython-312.pyc index 4025bf6..c805d5f 100644 Binary files a/core/__pycache__/soulseek_client.cpython-312.pyc and b/core/__pycache__/soulseek_client.cpython-312.pyc differ diff --git a/core/soulseek_client.py b/core/soulseek_client.py index 7ff216e..016a7b8 100644 --- a/core/soulseek_client.py +++ b/core/soulseek_client.py @@ -204,6 +204,12 @@ class SoulseekClient: self.api_key: Optional[str] = None self.download_path: Path = Path("./downloads") self.active_searches: Dict[str, bool] = {} # search_id -> 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}'")