Private/Test-CMCRateLimit.ps1
function Test-CMCRateLimit { <# .SYNOPSIS Tests and manages API rate limiting for CoinMarketCap requests. .DESCRIPTION This private function tracks API usage and implements rate limiting to prevent exceeding CoinMarketCap API limits. It maintains request history and calculates appropriate delays between requests. .PARAMETER Reset Resets the rate limit tracking counters. .PARAMETER GetStatus Returns the current rate limit status without making changes. .EXAMPLE Test-CMCRateLimit Checks rate limits and delays if necessary before allowing a request. .EXAMPLE Test-CMCRateLimit -GetStatus Gets the current rate limiting status. .NOTES This is a private helper function for internal use only. Rate limits vary by subscription plan: - Basic: 10,000 calls/month, 333 calls/day, 10 calls/minute - Hobbyist: 100,000 calls/month, 3,333 calls/day, 30 calls/minute - Startup: 500,000 calls/month, 16,666 calls/day, 60 calls/minute - Standard: 1,500,000 calls/month, 50,000 calls/day, 100 calls/minute - Professional: 3,000,000 calls/month, 100,000 calls/day, 300 calls/minute #> [CmdletBinding()] param( [Parameter()] [switch]$Reset, [Parameter()] [switch]$GetStatus ) begin { # Initialize rate limit tracking if not exists if (-not $script:CMCRateLimitHistory) { $script:CMCRateLimitHistory = @{ MinuteHistory = [System.Collections.ArrayList]::new() DailyHistory = [System.Collections.ArrayList]::new() MonthlyTotal = 0 LastResetDate = [datetime]::Today LastResetMonth = [datetime]::Now.Month # Default limits (Basic plan) - these should be configurable MinuteLimit = 10 DailyLimit = 333 MonthlyLimit = 10000 } } } process { $now = [datetime]::Now $rateLimits = $script:CMCRateLimitHistory # Handle reset if ($Reset) { Write-Verbose "Resetting rate limit tracking" $rateLimits.MinuteHistory.Clear() $rateLimits.DailyHistory.Clear() $rateLimits.MonthlyTotal = 0 $rateLimits.LastResetDate = [datetime]::Today $rateLimits.LastResetMonth = $now.Month return } # Clean up old history entries CleanupRateLimitHistory -Now $now -RateLimits $rateLimits # Get current usage $minuteCount = $rateLimits.MinuteHistory.Count $dailyCount = $rateLimits.DailyHistory.Count $monthlyCount = $rateLimits.MonthlyTotal # Return status if requested if ($GetStatus) { return [PSCustomObject]@{ MinuteUsage = $minuteCount MinuteLimit = $rateLimits.MinuteLimit MinuteRemaining = [Math]::Max(0, $rateLimits.MinuteLimit - $minuteCount) DailyUsage = $dailyCount DailyLimit = $rateLimits.DailyLimit DailyRemaining = [Math]::Max(0, $rateLimits.DailyLimit - $dailyCount) MonthlyUsage = $monthlyCount MonthlyLimit = $rateLimits.MonthlyLimit MonthlyRemaining = [Math]::Max(0, $rateLimits.MonthlyLimit - $monthlyCount) Timestamp = $now } } # Check rate limits $delay = 0 $warnings = @() # Check minute limit if ($minuteCount -ge $rateLimits.MinuteLimit) { # Calculate time until oldest request expires $oldestMinuteRequest = $rateLimits.MinuteHistory[0] $timeUntilExpiry = 60 - ($now - $oldestMinuteRequest).TotalSeconds if ($timeUntilExpiry -gt 0) { $delay = [Math]::Ceiling($timeUntilExpiry * 1000) $warnings += "Minute rate limit reached ($minuteCount/$($rateLimits.MinuteLimit)). Waiting $([Math]::Round($timeUntilExpiry, 1)) seconds." } } # Check daily limit if ($dailyCount -ge $rateLimits.DailyLimit) { $warnings += "WARNING: Daily rate limit reached ($dailyCount/$($rateLimits.DailyLimit)). Further requests may fail." # For daily limits, we can't wait it out in the same session if (-not $delay) { Write-Warning "Daily API limit reached. Requests will reset at midnight UTC." } } # Check monthly limit if ($monthlyCount -ge $rateLimits.MonthlyLimit) { Write-Error "Monthly API limit reached ($monthlyCount/$($rateLimits.MonthlyLimit)). Cannot make more requests this month." -ErrorAction Stop } # Warn if approaching limits if ($minuteCount -ge ($rateLimits.MinuteLimit * 0.8)) { Write-Verbose "Approaching minute rate limit: $minuteCount/$($rateLimits.MinuteLimit)" } if ($dailyCount -ge ($rateLimits.DailyLimit * 0.8)) { Write-Warning "Approaching daily rate limit: $dailyCount/$($rateLimits.DailyLimit)" } if ($monthlyCount -ge ($rateLimits.MonthlyLimit * 0.8)) { Write-Warning "Approaching monthly rate limit: $monthlyCount/$($rateLimits.MonthlyLimit)" } # Apply delay if needed if ($delay -gt 0) { foreach ($warning in $warnings) { Write-Warning $warning } Write-Verbose "Applying rate limit delay: $delay ms" Start-Sleep -Milliseconds $delay } # Record this request $null = $rateLimits.MinuteHistory.Add($now) $null = $rateLimits.DailyHistory.Add($now) $rateLimits.MonthlyTotal++ Write-Verbose "Rate limits - Minute: $($minuteCount + 1)/$($rateLimits.MinuteLimit), Daily: $($dailyCount + 1)/$($rateLimits.DailyLimit), Monthly: $($monthlyCount + 1)/$($rateLimits.MonthlyLimit)" } end { Write-Verbose "Test-CMCRateLimit completed" } } function CleanupRateLimitHistory { param( [datetime]$Now, [hashtable]$RateLimits ) # Reset daily counter if it's a new day if ($Now.Date -gt $RateLimits.LastResetDate) { Write-Verbose "New day detected, resetting daily counter" $RateLimits.DailyHistory.Clear() $RateLimits.LastResetDate = $Now.Date } # Reset monthly counter if it's a new month if ($Now.Month -ne $RateLimits.LastResetMonth) { Write-Verbose "New month detected, resetting monthly counter" $RateLimits.MonthlyTotal = 0 $RateLimits.LastResetMonth = $Now.Month } # Remove minute history older than 60 seconds $minuteThreshold = $Now.AddSeconds(-60) $toRemove = @() for ($i = 0; $i -lt $RateLimits.MinuteHistory.Count; $i++) { if ($RateLimits.MinuteHistory[$i] -lt $minuteThreshold) { $toRemove += $RateLimits.MinuteHistory[$i] } else { break # List is ordered, so we can stop here } } foreach ($item in $toRemove) { $RateLimits.MinuteHistory.Remove($item) } # Remove daily history older than 24 hours $dailyThreshold = $Now.AddHours(-24) $toRemove = @() for ($i = 0; $i -lt $RateLimits.DailyHistory.Count; $i++) { if ($RateLimits.DailyHistory[$i] -lt $dailyThreshold) { $toRemove += $RateLimits.DailyHistory[$i] } else { break } } foreach ($item in $toRemove) { $RateLimits.DailyHistory.Remove($item) } } |