|
|
|
|
@ -7,6 +7,7 @@ import signal |
|
|
|
|
import sys |
|
|
|
|
import fcntl |
|
|
|
|
import time |
|
|
|
|
import re |
|
|
|
|
from datetime import datetime, timedelta, timezone |
|
|
|
|
from openai import OpenAI |
|
|
|
|
from foodie_utils import AUTHORS, SUMMARY_MODEL, load_json_file, save_json_file, update_system_activity |
|
|
|
|
@ -107,11 +108,11 @@ def generate_intro_tweet(author): |
|
|
|
|
logging.debug(f"Generating intro tweet for {author_handle}") |
|
|
|
|
|
|
|
|
|
prompt = ( |
|
|
|
|
f"Generate a concise tweet (under 280 characters) for {author_handle}. " |
|
|
|
|
f"Generate a concise tweet (under 200 characters) for {author_handle}. " |
|
|
|
|
f"Introduce a thread of their top 10 foodie posts of the week on InsiderFoodie.com. " |
|
|
|
|
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)." |
|
|
|
|
f"Make it engaging, create curiosity, and include a call to action to visit InsiderFoodie.com or follow {author_handle}. " |
|
|
|
|
f"Avoid using the word 'elevate'—use humanized language like 'level up' or 'bring to life'. " |
|
|
|
|
f"Strictly exclude emojis, hashtags, or reward-driven incentives (e.g., giveaways)." |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
for attempt in range(MAX_RETRIES): |
|
|
|
|
@ -122,10 +123,11 @@ def generate_intro_tweet(author): |
|
|
|
|
{"role": "system", "content": "You are a social media expert crafting engaging tweets."}, |
|
|
|
|
{"role": "user", "content": prompt} |
|
|
|
|
], |
|
|
|
|
max_tokens=100, |
|
|
|
|
max_tokens=150, |
|
|
|
|
temperature=0.7 |
|
|
|
|
) |
|
|
|
|
tweet = response.choices[0].message.content.strip() |
|
|
|
|
tweet = re.sub(r'[\U0001F000-\U0001FFFF]', '', tweet) # Remove emojis |
|
|
|
|
if len(tweet) > 280: |
|
|
|
|
tweet = tweet[:277] + "..." |
|
|
|
|
logging.debug(f"Generated intro tweet: {tweet}") |
|
|
|
|
@ -137,8 +139,7 @@ def generate_intro_tweet(author): |
|
|
|
|
else: |
|
|
|
|
logging.error(f"Failed to generate intro tweet after {MAX_RETRIES} attempts") |
|
|
|
|
fallback = ( |
|
|
|
|
f"This week's top 10 foodie finds by {author_handle}! Check out the best on InsiderFoodie.com. " |
|
|
|
|
f"Follow {author_handle} for more and like this thread to stay in the loop! Visit us at https://insiderfoodie.com" |
|
|
|
|
f"Top 10 foodie posts this week by {author_handle}! Visit InsiderFoodie.com and follow {author_handle} for more." |
|
|
|
|
) |
|
|
|
|
logging.info(f"Using fallback intro tweet: {fallback}") |
|
|
|
|
return fallback |
|
|
|
|
@ -153,13 +154,13 @@ def generate_final_cta(author): |
|
|
|
|
logging.debug(f"Generating final CTA tweet for {author_handle}") |
|
|
|
|
|
|
|
|
|
prompt = ( |
|
|
|
|
f"Generate a concise tweet (under 280 characters) for {author_handle}. " |
|
|
|
|
f"Generate a concise tweet (under 200 characters) for {author_handle}. " |
|
|
|
|
f"Conclude a thread of their top 10 foodie posts of the week on InsiderFoodie.com. " |
|
|
|
|
f"Make it engaging, value-driven, and urgent, in the style of Neil Patel. " |
|
|
|
|
f"Make it engaging, value-driven, in the style of Neil Patel. " |
|
|
|
|
f"Include a call to action to visit InsiderFoodie.com and follow {author_handle}. " |
|
|
|
|
f"Mention that the top 10 foodie trends are shared every Monday. " |
|
|
|
|
f"Mention that top 10 foodie trends are shared every Monday. " |
|
|
|
|
f"Avoid using the word 'elevate'—use humanized language like 'level up' or 'bring to life'. " |
|
|
|
|
f"Do not include emojis, hashtags, or reward-driven incentives (e.g., giveaways)." |
|
|
|
|
f"Strictly exclude emojis, hashtags, or reward-driven incentives (e.g., giveaways)." |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
for attempt in range(MAX_RETRIES): |
|
|
|
|
@ -170,10 +171,11 @@ def generate_final_cta(author): |
|
|
|
|
{"role": "system", "content": "You are a social media expert crafting engaging tweets."}, |
|
|
|
|
{"role": "user", "content": prompt} |
|
|
|
|
], |
|
|
|
|
max_tokens=100, |
|
|
|
|
max_tokens=150, |
|
|
|
|
temperature=0.7 |
|
|
|
|
) |
|
|
|
|
tweet = response.choices[0].message.content.strip() |
|
|
|
|
tweet = re.sub(r'[\U0001F000-\U0001FFFF]', '', tweet) # Remove emojis |
|
|
|
|
if len(tweet) > 280: |
|
|
|
|
tweet = tweet[:277] + "..." |
|
|
|
|
logging.debug(f"Generated final CTA tweet: {tweet}") |
|
|
|
|
@ -185,8 +187,8 @@ def generate_final_cta(author): |
|
|
|
|
else: |
|
|
|
|
logging.error(f"Failed to generate final CTA tweet after {MAX_RETRIES} attempts") |
|
|
|
|
fallback = ( |
|
|
|
|
f"Want more foodie insights like these? Check out insiderfoodie.com and follow {author_handle} " |
|
|
|
|
f"for the world’s top 10 foodie trends every Monday. Don’t miss out!" |
|
|
|
|
f"Want more foodie insights? Visit insiderfoodie.com and follow {author_handle} " |
|
|
|
|
f"for top 10 foodie trends every Monday." |
|
|
|
|
) |
|
|
|
|
logging.info(f"Using fallback final CTA tweet: {fallback}") |
|
|
|
|
return fallback |
|
|
|
|
@ -293,8 +295,9 @@ def generate_weekly_thread(): |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
# Select top 2 posts (to fit within 3-tweet limit: lead + 2 posts) |
|
|
|
|
author_posts = sorted(author_posts, key=lambda x: datetime.fromisoformat(x["timestamp"]), reverse=True)[:2] |
|
|
|
|
logging.info(f"Selected {len(author_posts)} posts for {username}") |
|
|
|
|
author_posts = sorted(author_posts, key=lambda x: datetime.fromisoformat(x["timestamp"]), reverse=True) |
|
|
|
|
selected_posts = author_posts[:2] |
|
|
|
|
logging.info(f"Found {len(author_posts)} posts for {username}, selected {len(selected_posts)}") |
|
|
|
|
|
|
|
|
|
# Generate thread content |
|
|
|
|
try: |
|
|
|
|
@ -306,11 +309,13 @@ def generate_weekly_thread(): |
|
|
|
|
|
|
|
|
|
# Generate thread tweets (up to 2) |
|
|
|
|
thread_tweets = [] |
|
|
|
|
for i, post in enumerate(author_posts, 1): |
|
|
|
|
for i, post in enumerate(selected_posts, 1): |
|
|
|
|
thread_tweet = ( |
|
|
|
|
f"{i}. {post['title']} " |
|
|
|
|
f"Read more: {post['url']}" |
|
|
|
|
) |
|
|
|
|
if len(thread_tweet) > 280: |
|
|
|
|
thread_tweet = f"{i}. {post['title'][:200]}... Read more: {post['url']}" |
|
|
|
|
thread_tweets.append(thread_tweet) |
|
|
|
|
logging.info(f"Generated thread tweet {i} for {username}: {thread_tweet}") |
|
|
|
|
|
|
|
|
|
@ -319,8 +324,8 @@ def generate_weekly_thread(): |
|
|
|
|
if not final_cta: |
|
|
|
|
logging.error(f"Failed to generate final CTA tweet for {username}, using fallback") |
|
|
|
|
final_cta = ( |
|
|
|
|
f"Want more foodie insights like these? Check out insiderfoodie.com and follow @{X_API_CREDENTIALS[username]['x_username']} " |
|
|
|
|
f"for the world’s top 10 foodie trends every Monday. Don’t miss out!" |
|
|
|
|
f"Want more foodie insights? Visit insiderfoodie.com and follow {X_API_CREDENTIALS[username]['x_username']} " |
|
|
|
|
f"for top 10 foodie trends every Monday." |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# Collect thread content for this author |
|
|
|
|
|