From eff6f585bb0600574d07cfe28152e0e4ce8f6c7c Mon Sep 17 00:00:00 2001 From: Shane Date: Thu, 15 May 2025 12:10:37 +1000 Subject: [PATCH] fix --- check_x_capacity.py | 36 ++++++++++++++++++++++++++++- foodie_engagement_tweet.py | 43 +++++++++++++++++------------------ foodie_utils.py | 7 +++--- foodie_x_poster.py | 46 +++++++++++++++++++++----------------- 4 files changed, 86 insertions(+), 46 deletions(-) diff --git a/check_x_capacity.py b/check_x_capacity.py index ac87bcf..341cada 100755 --- a/check_x_capacity.py +++ b/check_x_capacity.py @@ -3,7 +3,8 @@ import logging from datetime import datetime, timezone from foodie_utils import ( AUTHORS, check_author_rate_limit, load_json_file, - get_x_rate_limit_status, update_system_activity, is_any_script_running + get_x_rate_limit_status, update_system_activity, is_any_script_running, + save_json_file ) import time import sys @@ -20,8 +21,41 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +# File to track sent notifications +NOTIFICATION_FILE = '/home/shane/foodie_automator/notification_tracking.json' + +def load_notification_tracking(): + """Load notification tracking data.""" + return load_json_file(NOTIFICATION_FILE, default={}) + +def save_notification_tracking(tracking_data): + """Save notification tracking data.""" + save_json_file(NOTIFICATION_FILE, tracking_data) + +def should_send_notification(username, reset_time): + """Check if we should send a notification for this author.""" + tracking = load_notification_tracking() + author_data = tracking.get(username, {}) + + # If we've never notified for this author or the reset time has changed + if not author_data or author_data.get('reset_time') != reset_time: + # Update tracking + tracking[username] = { + 'last_notification': datetime.now(timezone.utc).isoformat(), + 'reset_time': reset_time + } + save_notification_tracking(tracking) + return True + + return False + def send_capacity_alert(username, remaining, reset_time): """Send email alert when an author's tweet capacity is full.""" + # Check if we should send notification + if not should_send_notification(username, reset_time): + logger.info(f"Skipping duplicate notification for {username}") + return + try: msg = MIMEMultipart() msg['From'] = EMAIL_CONFIG['from_email'] diff --git a/foodie_engagement_tweet.py b/foodie_engagement_tweet.py index 866151a..e02cbde 100644 --- a/foodie_engagement_tweet.py +++ b/foodie_engagement_tweet.py @@ -164,29 +164,28 @@ def post_engagement_tweet(): logging.info("Starting foodie_engagement_tweet.py") posted = False - for author in AUTHORS: - # Check if the author can post before generating the tweet - can_post, remaining, reset = check_author_rate_limit(author) - if not can_post: - reset_time = datetime.fromtimestamp(reset, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S') if reset else "Unknown" - logging.info(f"Skipping engagement tweet for {author['username']} due to rate limit. Reset at: {reset_time}") - continue + # Get next available author using round-robin + author = get_next_author_round_robin() + if not author: + logging.info("No authors available due to rate limits") + sleep_time = random.randint(1200, 1800) # 20–30 minutes + return False, sleep_time - try: - tweet = generate_engagement_tweet(author) - if not tweet: - logging.error(f"Failed to generate engagement tweet for {author['username']}, skipping") - continue - - logging.info(f"Posting engagement tweet for {author['username']}: {tweet}") - if post_tweet(author, tweet): - logging.info(f"Successfully posted engagement tweet for {author['username']}") - posted = True - else: - logging.warning(f"Failed to post engagement tweet for {author['username']}") - except Exception as e: - logging.error(f"Error posting engagement tweet for {author['username']}: {e}", exc_info=True) - continue + try: + tweet = generate_engagement_tweet(author) + if not tweet: + logging.error(f"Failed to generate engagement tweet for {author['username']}, skipping") + sleep_time = random.randint(1200, 1800) # 20–30 minutes + return False, sleep_time + + logging.info(f"Posting engagement tweet for {author['username']}: {tweet}") + if post_tweet(author, tweet): + logging.info(f"Successfully posted engagement tweet for {author['username']}") + posted = True + else: + logging.warning(f"Failed to post engagement tweet for {author['username']}") + except Exception as e: + logging.error(f"Error posting engagement tweet for {author['username']}: {e}", exc_info=True) logging.info("Completed foodie_engagement_tweet.py") sleep_time = random.randint(1200, 1800) # 20–30 minutes diff --git a/foodie_utils.py b/foodie_utils.py index 6115014..7437237 100644 --- a/foodie_utils.py +++ b/foodie_utils.py @@ -235,7 +235,7 @@ def post_tweet(author, content, media_ids=None, reply_to_id=None, tweet_type="rs author_info = rate_limit_info[username] if response.status_code == 201: - # Successful post - update remaining tweets + # Successful post - update remaining tweets and increment posted count author_info['tweets_posted_in_run'] = author_info.get('tweets_posted_in_run', 0) + 1 author_info['tweet_remaining'] = remaining - 1 # Decrement remaining tweets rate_limit_info[username] = author_info @@ -251,6 +251,7 @@ def post_tweet(author, content, media_ids=None, reply_to_id=None, tweet_type="rs reset = int(reset_str) author_info['tweet_remaining'] = remaining author_info['tweet_reset'] = reset + # Don't reset tweets_posted_in_run here rate_limit_info[username] = author_info save_json_file(rate_limit_file, rate_limit_info) logger.info(f"Updated rate limit info from API for {username}: {remaining}/17 tweets remaining") @@ -1798,10 +1799,10 @@ def check_author_rate_limit(author, max_tweets=17, tweet_window_seconds=86400): remaining = min(remaining, max_tweets) # Ensure within Free tier limit reset = api_reset - # Update author info + # Update author info but preserve tweets_posted_in_run author_info['tweet_remaining'] = remaining author_info['tweet_reset'] = reset - author_info['tweets_posted_in_run'] = 0 + # Don't reset tweets_posted_in_run here rate_limit_info[username] = author_info save_json_file(rate_limit_file, rate_limit_info) diff --git a/foodie_x_poster.py b/foodie_x_poster.py index 9cd4c93..f945733 100644 --- a/foodie_x_poster.py +++ b/foodie_x_poster.py @@ -93,31 +93,37 @@ def generate_engagement_tweet(author, persona): return tweet except Exception as e: logging.error(f"Failed to generate engagement tweet for {author['username']}: {e}") - return f"What’s your take on {theme}? Let’s talk!" + return f"What's your take on {theme}? Let's talk!" -def main(): - global is_posting - logging.info("***** X Poster Launched *****") +def get_next_author_round_robin(): for author in AUTHORS: # Check if the author can post before generating the tweet can_post, remaining, reset = check_author_rate_limit(author) - if not can_post: - reset_time = datetime.fromtimestamp(reset, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S') if reset else "Unknown" - logging.info(f"Skipping engagement tweet for {author['username']} due to rate limit. Remaining: {remaining}, Reset at: {reset_time}") - continue + if can_post: + return author + return None + +def main(): + global is_posting + logging.info("***** X Poster Launched *****") + + # Get next available author using round-robin + author = get_next_author_round_robin() + if not author: + logging.info("No authors available due to rate limits") + return random.randint(600, 1800) - is_posting = True - try: - tweet = generate_engagement_tweet(author, author["persona"]) - if post_tweet(author, tweet): - logging.info(f"Successfully posted engagement tweet for {author['username']}") - else: - logging.warning(f"Failed to post engagement tweet for {author['username']}") - except Exception as e: - logging.error(f"Error posting engagement tweet for {author['username']}: {e}", exc_info=True) - finally: - is_posting = False - time.sleep(random.uniform(3600, 7200)) + is_posting = True + try: + tweet = generate_engagement_tweet(author, author["persona"]) + if post_tweet(author, tweet): + logging.info(f"Successfully posted engagement tweet for {author['username']}") + else: + logging.warning(f"Failed to post engagement tweet for {author['username']}") + except Exception as e: + logging.error(f"Error posting engagement tweet for {author['username']}: {e}", exc_info=True) + finally: + is_posting = False logging.info("X posting completed") return random.randint(600, 1800)