|
|
|
@ -270,11 +270,16 @@ def generate_final_cta(author): |
|
|
|
return fallback |
|
|
|
return fallback |
|
|
|
|
|
|
|
|
|
|
|
def post_weekly_thread(): |
|
|
|
def post_weekly_thread(): |
|
|
|
"""Generate and post a weekly thread of top posts for each author.""" |
|
|
|
"""Generate and post a weekly thread of top posts for each author on Mondays.""" |
|
|
|
logging.info("Starting foodie_weekly_thread.py") |
|
|
|
logging.info("Starting foodie_weekly_thread.py") |
|
|
|
|
|
|
|
|
|
|
|
# Calculate date range: 7 days prior to run date |
|
|
|
# Check if today is Monday |
|
|
|
today = datetime.now(timezone.utc) |
|
|
|
today = datetime.now(timezone.utc) |
|
|
|
|
|
|
|
if today.weekday() != 0: # 0 = Monday |
|
|
|
|
|
|
|
logging.info(f"Today is not Monday (weekday: {today.weekday()}), skipping weekly thread") |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate date range: 7 days prior to run date |
|
|
|
start_date = (today - timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
|
start_date = (today - timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
|
end_date = (today - timedelta(days=1)).replace(hour=23, minute=59, second=59, microsecond=999999) |
|
|
|
end_date = (today - timedelta(days=1)).replace(hour=23, minute=59, second=59, microsecond=999999) |
|
|
|
logging.info(f"Fetching posts from {start_date} to {end_date}") |
|
|
|
logging.info(f"Fetching posts from {start_date} to {end_date}") |
|
|
|
@ -305,6 +310,9 @@ def post_weekly_thread(): |
|
|
|
if username in posts_by_author: |
|
|
|
if username in posts_by_author: |
|
|
|
posts_by_author[username].append(post) |
|
|
|
posts_by_author[username].append(post) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Load post counts to check limits |
|
|
|
|
|
|
|
post_counts = load_post_counts() |
|
|
|
|
|
|
|
|
|
|
|
# Post threads for each author |
|
|
|
# Post threads for each author |
|
|
|
for author in AUTHORS: |
|
|
|
for author in AUTHORS: |
|
|
|
username = author["username"] |
|
|
|
username = author["username"] |
|
|
|
@ -313,29 +321,44 @@ def post_weekly_thread(): |
|
|
|
logging.info(f"No posts found for {username}, skipping") |
|
|
|
logging.info(f"No posts found for {username}, skipping") |
|
|
|
continue |
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
# Select top 10 posts (or fewer if less than 10) |
|
|
|
# Check daily limit (each thread will use 3 tweets: lead + 2 thread tweets) |
|
|
|
author_posts = sorted(author_posts, key=lambda x: datetime.fromisoformat(x["timestamp"]), reverse=True)[:10] |
|
|
|
author_count = next((entry for entry in post_counts if entry["username"] == username), None) |
|
|
|
|
|
|
|
if not author_count: |
|
|
|
|
|
|
|
logging.error(f"No post count entry for {username}, skipping") |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
if author_count["daily_count"] >= 15: |
|
|
|
|
|
|
|
logging.warning(f"Daily post limit (15) reached for {username}, skipping") |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
if author_count["daily_count"] + 3 > 15: |
|
|
|
|
|
|
|
logging.warning(f"Posting thread for {username} would exceed daily limit (current: {author_count['daily_count']}, needed: 3), skipping") |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
if author_count["monthly_count"] >= 500: |
|
|
|
|
|
|
|
logging.warning(f"Monthly post limit (500) reached for {username}, skipping") |
|
|
|
|
|
|
|
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}") |
|
|
|
logging.info(f"Selected {len(author_posts)} posts for {username}") |
|
|
|
|
|
|
|
|
|
|
|
# Generate and post thread |
|
|
|
# Generate and post thread |
|
|
|
try: |
|
|
|
try: |
|
|
|
# Post lead tweet |
|
|
|
# Post lead tweet |
|
|
|
lead_tweet = ( |
|
|
|
intro_tweet = generate_intro_tweet(author) |
|
|
|
f"Top foodie finds this week from {author['name']} (@{author['x_username']})! " |
|
|
|
if not intro_tweet: |
|
|
|
f"Check out these {len(author_posts)} posts on InsiderFoodie.com 🍽️" |
|
|
|
logging.error(f"Failed to generate intro tweet for {username}, skipping") |
|
|
|
) |
|
|
|
continue |
|
|
|
lead_response = post_tweet(author, lead_tweet) |
|
|
|
lead_response = post_tweet(author, intro_tweet) |
|
|
|
if not lead_response: |
|
|
|
if not lead_response: |
|
|
|
logging.error(f"Failed to post lead tweet for {username}, skipping") |
|
|
|
logging.error(f"Failed to post lead tweet for {username}, skipping") |
|
|
|
continue |
|
|
|
continue |
|
|
|
lead_tweet_id = lead_response["id"] |
|
|
|
lead_tweet_id = lead_response["id"] |
|
|
|
logging.info(f"Posted lead tweet for {username}: {lead_tweet}") |
|
|
|
logging.info(f"Posted lead tweet for {username}: {intro_tweet}") |
|
|
|
|
|
|
|
|
|
|
|
# Post thread tweets |
|
|
|
# Post thread tweets (up to 2) |
|
|
|
for i, post in enumerate(author_posts, 1): |
|
|
|
for i, post in enumerate(author_posts, 1): |
|
|
|
thread_tweet = ( |
|
|
|
thread_tweet = ( |
|
|
|
f"{i}. {post['title']} " |
|
|
|
f"{i}. {post['title']} " |
|
|
|
f"Read more: {post['url']} #FoodieThread" |
|
|
|
f"Read more: {post['url']}" |
|
|
|
) |
|
|
|
) |
|
|
|
thread_response = post_tweet(author, thread_tweet, reply_to_id=lead_tweet_id) |
|
|
|
thread_response = post_tweet(author, thread_tweet, reply_to_id=lead_tweet_id) |
|
|
|
if thread_response: |
|
|
|
if thread_response: |
|
|
|
@ -344,11 +367,11 @@ def post_weekly_thread(): |
|
|
|
else: |
|
|
|
else: |
|
|
|
logging.warning(f"Failed to post thread tweet {i} for {username}") |
|
|
|
logging.warning(f"Failed to post thread tweet {i} for {username}") |
|
|
|
|
|
|
|
|
|
|
|
# Post engagement tweet |
|
|
|
# Post final CTA tweet |
|
|
|
engagement_tweet = generate_engagement_tweet(author) |
|
|
|
final_cta = generate_final_cta(author) |
|
|
|
if engagement_tweet: |
|
|
|
if final_cta: |
|
|
|
post_tweet(author, engagement_tweet, reply_to_id=lead_tweet_id) |
|
|
|
post_tweet(author, final_cta, reply_to_id=lead_tweet_id) |
|
|
|
logging.info(f"Posted engagement tweet for {username}: {engagement_tweet}") |
|
|
|
logging.info(f"Posted final CTA tweet for {username}: {final_cta}") |
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
logging.error(f"Error posting thread for {username}: {e}", exc_info=True) |
|
|
|
logging.error(f"Error posting thread for {username}: {e}", exc_info=True) |
|
|
|
continue |
|
|
|
continue |
|
|
|
|