diff --git a/foodie_engagement_tweet.py b/foodie_engagement_tweet.py index 2372e0b..f8c4235 100644 --- a/foodie_engagement_tweet.py +++ b/foodie_engagement_tweet.py @@ -6,6 +6,8 @@ import signal import sys import fcntl import os +from foodie_config import ENGAGEMENT_REFERENCE_DATE_FILE +REFERENCE_DATE_FILE = ENGAGEMENT_REFERENCE_DATE_FILE from datetime import datetime, timedelta, timezone from openai import OpenAI from foodie_utils import post_tweet, AUTHORS, SUMMARY_MODEL, load_post_counts, save_post_counts diff --git a/foodie_weekly_thread.py b/foodie_weekly_thread.py index 374a11c..c56ecc7 100644 --- a/foodie_weekly_thread.py +++ b/foodie_weekly_thread.py @@ -197,17 +197,18 @@ def filter_posts_for_week(posts, start_date, end_date): return filtered_posts def generate_intro_tweet(author): - """Generate an intro tweet for the weekly thread.""" credentials = next((cred for cred in X_API_CREDENTIALS if cred["username"] == author["username"]), None) if not credentials: logging.error(f"No X credentials found for {author['username']}") return None author_handle = credentials["x_username"] - logging.debug(f"Generating intro tweet for {author_handle}") + persona = author["persona"] # Add persona + persona_config = PERSONA_CONFIGS[persona] + logging.debug(f"Generating intro tweet for {author_handle} as {persona}") prompt = ( - f"Generate a concise tweet (under 280 characters) for {author_handle}. " - f"Introduce a thread of their top 10 foodie posts of the week on InsiderFoodie.com. " + f"Generate a concise tweet (under 280 characters) for {author_handle} as {persona_config['description']}. " + f"Introduce a thread of their top 10 foodie posts of the week on InsiderFoodie.com in {persona_config['tone']}. " f"Make it engaging, create curiosity, and include a call to action to visit InsiderFoodie.com, follow {author_handle}, or like the thread. " f"Avoid using the word 'elevate'—use more humanized language like 'level up' or 'bring to life'. " f"Do not include emojis, hashtags, or reward-driven incentives (e.g., giveaways)." diff --git a/foodie_x_poster.py b/foodie_x_poster.py index 5326abf..814b7f1 100644 --- a/foodie_x_poster.py +++ b/foodie_x_poster.py @@ -6,6 +6,7 @@ import time import sys import signal import os +from foodie_utils import load_post_counts, save_post_counts # Add imports from datetime import datetime, timezone, timedelta from openai import OpenAI from foodie_config import OPENAI_API_KEY, AUTHORS, LIGHT_TASK_MODEL, PERSONA_CONFIGS, AUTHOR_BACKGROUNDS_FILE @@ -16,6 +17,8 @@ load_dotenv() LOG_FILE = "/home/shane/foodie_automator/foodie_x_poster.log" LOG_PRUNE_DAYS = 30 +MAX_RETRIES = 3 +RETRY_BACKOFF = 2 def setup_logging(): if os.path.exists(LOG_FILE): @@ -76,35 +79,54 @@ def generate_engagement_tweet(author, persona): "For engagement tweets, ask a question about food trends, foods, or articles to engage the public.", f"Generate an engagement tweet asking a question about {theme} to engage the public." ) - try: - response = client.chat.completions.create( - model=LIGHT_TASK_MODEL, - messages=[ - {"role": "system", "content": prompt}, - {"role": "user", "content": f"Generate engagement tweet for {author['username']} about {theme}."} - ], - max_tokens=100, - temperature=0.9 - ) - tweet = response.choices[0].message.content.strip() - if len(tweet) > 280: - tweet = tweet[:277] + "..." - logging.info(f"Generated engagement tweet for {author['username']}: {tweet}") - 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!" + for attempt in range(MAX_RETRIES): + try: + response = client.chat.completions.create( + model=LIGHT_TASK_MODEL, + messages=[ + {"role": "system", "content": prompt}, + {"role": "user", "content": f"Generate engagement tweet for {author['username']} about {theme}."} + ], + max_tokens=100, + temperature=0.9 + ) + tweet = response.choices[0].message.content.strip() + if len(tweet) > 280: + tweet = tweet[:277] + "..." + logging.info(f"Generated engagement tweet for {author['username']}: {tweet}") + return tweet + except Exception as e: + logging.warning(f"Failed to generate engagement tweet for {author['username']} (attempt {attempt + 1}): {e}") + if attempt < MAX_RETRIES - 1: + time.sleep(RETRY_BACKOFF * (2 ** attempt)) + else: + logging.error(f"Failed to generate engagement tweet after {MAX_RETRIES} attempts") + return f"What’s your take on {theme}? Let’s talk!" def main(): global is_posting logging.info("***** X Poster Launched *****") + post_counts = load_post_counts() # Load counts for author in AUTHORS: + # Check limits + author_count = next((entry for entry in post_counts if entry["username"] == author["username"]), None) + if not author_count: + logging.error(f"No post count entry for {author['username']}, skipping") + continue + if author_count["monthly_count"] >= 500: + logging.warning(f"Monthly post limit (500) reached for {author['username']}, skipping") + continue + if author_count["daily_count"] >= 20: + logging.warning(f"Daily post limit (20) reached for {author['username']}, skipping") + continue is_posting = True tweet = generate_engagement_tweet(author, author["persona"]) - post_tweet(author, tweet) + if post_tweet(author, tweet): + author_count["monthly_count"] += 1 # Update counts + author_count["daily_count"] += 1 + save_post_counts(post_counts) is_posting = False time.sleep(random.uniform(3600, 7200)) - logging.info("X posting completed") return random.randint(600, 1800)