Shane 7 months ago
parent 83e69a35b7
commit ac50299b94
  1. 46
      foodie_utils.py

@ -1151,21 +1151,11 @@ def select_best_author(content, interest_score):
logging.error(f"Error in select_best_author: {e}")
return random.choice([author["username"] for author in AUTHORS])
def check_rate_limit(response):
"""Extract rate limit information from Twitter API response headers."""
try:
remaining = int(response.get('x-rate-limit-remaining', 0))
reset = int(response.get('x-rate-limit-reset', 0))
return remaining, reset
except (ValueError, TypeError) as e:
logging.warning(f"Failed to parse rate limit headers: {e}")
return None, None
def check_author_rate_limit(author, max_tweets=17, tweet_window_seconds=86400):
"""
Check if an author is rate-limited for tweets using real-time X API v2 data.
Returns (can_post, remaining, reset_timestamp) where can_post is True if tweets are available.
Caches API results in memory for 1 minute.
Caches API results in memory for 5 minutes.
Falls back to rate_limit_info.json or assumes 1 tweet remaining if API fails.
"""
logger = logging.getLogger(__name__)
@ -1177,7 +1167,7 @@ def check_author_rate_limit(author, max_tweets=17, tweet_window_seconds=86400):
check_author_rate_limit.cache = {}
username = author['username']
cache_key = f"{username}_{int(current_time // 60)}" # Cache for 1 minute
cache_key = f"{username}_{int(current_time // 300)}" # Cache for 5 minutes
if cache_key in check_author_rate_limit.cache:
remaining, reset = check_author_rate_limit.cache[cache_key]
@ -1215,19 +1205,31 @@ def check_author_rate_limit(author, max_tweets=17, tweet_window_seconds=86400):
def get_next_author_round_robin():
"""
Select the next author using round-robin, respecting real-time X API rate limits.
Returns None if no author is available.
Persists the last selected author index to ensure fair rotation across runs.
Returns an author dict or None if no authors are available.
"""
from foodie_config import AUTHORS
global round_robin_index
logger = logging.getLogger(__name__)
state_file = '/home/shane/foodie_automator/author_state.json'
for _ in range(len(AUTHORS)):
author = AUTHORS[round_robin_index % len(AUTHORS)]
round_robin_index = (round_robin_index + 1) % len(AUTHORS)
if not check_author_rate_limit(author):
logger.info(f"Selected author via round-robin: {author['username']}")
# Load or initialize state
state = load_json_file(state_file, default={'last_author_index': -1})
last_index = state.get('last_author_index', -1)
# Try each author, starting from the next one after last_index
for i in range(len(AUTHORS)):
index = (last_index + 1 + i) % len(AUTHORS)
author = AUTHORS[index]
username = author['username']
can_post, remaining, reset = check_author_rate_limit(author)
if can_post:
# Update state with the selected author index
state['last_author_index'] = index
save_json_file(state_file, state)
logger.info(f"Selected author {username} with {remaining}/17 tweets remaining")
return author
else:
reset_time = datetime.fromtimestamp(reset, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
logger.info(f"Author {username} is rate-limited. Remaining: {remaining}, Reset at: {reset_time}")
logger.warning("No authors available due to tweet rate limits.")
return None
@ -1278,7 +1280,7 @@ def get_x_rate_limit_status(author):
logger.info(f"Rate limit exceeded for {username}: {remaining} remaining, reset at {datetime.fromtimestamp(reset, tz=timezone.utc)}")
elif response.status_code == 403:
# Forbidden (e.g., account restrictions), but headers may still provide rate limit info
logger.warning(f"403 Forbidden for {username}, but rate limit info available: {remaining} remaining, reset at {datetime.fromtimestamp(reset, tz=timezone.utc)}")
logger.warning(f"403 Forbidden for {username}: {response.text}, rate limit info: {remaining} remaining, reset at {datetime.fromtimestamp(reset, tz=timezone.utc)}")
else:
logger.error(f"Unexpected response for {username}: {response.status_code} - {response.text}")
return None, None

Loading…
Cancel
Save