try
This commit is contained in:
+24
-22
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user