|
|
|
|
@ -15,7 +15,8 @@ from foodie_config import X_API_CREDENTIALS, RECENT_POSTS_FILE |
|
|
|
|
from dotenv import load_dotenv |
|
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
|
# Output file for weekly thread content |
|
|
|
|
WEEKLY_THREADS_FILE = "/home/shane/foodie_automator/weekly_threads.json" |
|
|
|
|
SCRIPT_NAME = "foodie_weekly_thread" |
|
|
|
|
LOCK_FILE = "/home/shane/foodie_automator/locks/foodie_weekly_thread.lock" |
|
|
|
|
LOG_FILE = "/home/shane/foodie_automator/logs/foodie_weekly_thread.log" |
|
|
|
|
@ -267,7 +268,7 @@ def generate_final_cta(author): |
|
|
|
|
return fallback |
|
|
|
|
|
|
|
|
|
def post_weekly_thread(): |
|
|
|
|
"""Generate and post a weekly thread of top posts for each author on Mondays.""" |
|
|
|
|
"""Generate weekly thread content for each author and save to file on Mondays.""" |
|
|
|
|
logging.info("Starting foodie_weekly_thread.py") |
|
|
|
|
|
|
|
|
|
# Check if today is Monday |
|
|
|
|
@ -307,7 +308,10 @@ def post_weekly_thread(): |
|
|
|
|
if username in posts_by_author: |
|
|
|
|
posts_by_author[username].append(post) |
|
|
|
|
|
|
|
|
|
# Post threads for each author |
|
|
|
|
# Generate thread content for each author and save to file |
|
|
|
|
thread_content = [] |
|
|
|
|
timestamp = datetime.now(timezone.utc).isoformat() |
|
|
|
|
|
|
|
|
|
for author in AUTHORS: |
|
|
|
|
username = author["username"] |
|
|
|
|
author_posts = posts_by_author.get(username, []) |
|
|
|
|
@ -315,56 +319,73 @@ def post_weekly_thread(): |
|
|
|
|
logging.info(f"No posts found for {username}, skipping") |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
# Check if the author can post before generating the thread |
|
|
|
|
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 weekly thread for {username} due to rate limit. Reset at: {reset_time}") |
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
|
# Generate and post thread |
|
|
|
|
# Generate thread content |
|
|
|
|
try: |
|
|
|
|
# Post lead tweet |
|
|
|
|
# Generate intro tweet |
|
|
|
|
intro_tweet = generate_intro_tweet(author) |
|
|
|
|
if not intro_tweet: |
|
|
|
|
logging.error(f"Failed to generate intro tweet for {username}, skipping") |
|
|
|
|
continue |
|
|
|
|
lead_response = post_tweet(author, intro_tweet) |
|
|
|
|
if not lead_response: |
|
|
|
|
logging.error(f"Failed to post lead tweet for {username}, skipping") |
|
|
|
|
continue |
|
|
|
|
lead_tweet_id = lead_response["id"] |
|
|
|
|
logging.info(f"Posted lead tweet for {username}: {intro_tweet}") |
|
|
|
|
|
|
|
|
|
# Post thread tweets (up to 2) |
|
|
|
|
# Generate thread tweets (up to 2) |
|
|
|
|
thread_tweets = [] |
|
|
|
|
for i, post in enumerate(author_posts, 1): |
|
|
|
|
thread_tweet = ( |
|
|
|
|
f"{i}. {post['title']} " |
|
|
|
|
f"Read more: {post['url']}" |
|
|
|
|
) |
|
|
|
|
thread_response = post_tweet(author, thread_tweet, reply_to_id=lead_tweet_id) |
|
|
|
|
if thread_response: |
|
|
|
|
lead_tweet_id = thread_response["id"] |
|
|
|
|
logging.info(f"Posted thread tweet {i} for {username}: {thread_tweet}") |
|
|
|
|
else: |
|
|
|
|
logging.warning(f"Failed to post thread tweet {i} for {username}") |
|
|
|
|
thread_tweets.append(thread_tweet) |
|
|
|
|
logging.info(f"Generated thread tweet {i} for {username}: {thread_tweet}") |
|
|
|
|
|
|
|
|
|
# Post final CTA tweet |
|
|
|
|
# Generate final CTA tweet |
|
|
|
|
final_cta = generate_final_cta(author) |
|
|
|
|
if final_cta: |
|
|
|
|
cta_response = post_tweet(author, final_cta, reply_to_id=lead_tweet_id) |
|
|
|
|
if cta_response: |
|
|
|
|
logging.info(f"Posted final CTA tweet for {username}: {final_cta}") |
|
|
|
|
else: |
|
|
|
|
logging.warning(f"Failed to post final CTA tweet for {username}") |
|
|
|
|
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!" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# Collect thread content for this author |
|
|
|
|
author_thread = { |
|
|
|
|
"username": username, |
|
|
|
|
"x_handle": X_API_CREDENTIALS[username]["x_username"], |
|
|
|
|
"intro_tweet": intro_tweet, |
|
|
|
|
"thread_tweets": thread_tweets, |
|
|
|
|
"final_cta": final_cta, |
|
|
|
|
"timestamp": timestamp |
|
|
|
|
} |
|
|
|
|
thread_content.append(author_thread) |
|
|
|
|
logging.info(f"Generated thread content for {username}") |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logging.error(f"Error posting thread for {username}: {e}", exc_info=True) |
|
|
|
|
logging.error(f"Error generating thread content for {username}: {e}", exc_info=True) |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Save thread content to file |
|
|
|
|
if thread_content: |
|
|
|
|
try: |
|
|
|
|
# Load existing threads, if any |
|
|
|
|
existing_threads = load_json_file(WEEKLY_THREADS_FILE, default=[]) |
|
|
|
|
# Append new thread content |
|
|
|
|
existing_threads.append({ |
|
|
|
|
"week_start": start_date.isoformat(), |
|
|
|
|
"week_end": end_date.isoformat(), |
|
|
|
|
"timestamp": timestamp, |
|
|
|
|
"threads": thread_content |
|
|
|
|
}) |
|
|
|
|
# Save to file |
|
|
|
|
save_json_file(WEEKLY_THREADS_FILE, existing_threads) |
|
|
|
|
logging.info(f"Saved thread content for {len(thread_content)} authors to {WEEKLY_THREADS_FILE}") |
|
|
|
|
except Exception as e: |
|
|
|
|
logging.error(f"Failed to save thread content to {WEEKLY_THREADS_FILE}: {e}") |
|
|
|
|
else: |
|
|
|
|
logging.warning("No thread content generated, nothing to save") |
|
|
|
|
|
|
|
|
|
logging.info("Completed foodie_weekly_thread.py") |
|
|
|
|
|
|
|
|
|
def main(): |
|
|
|
|
@ -374,6 +395,8 @@ def main(): |
|
|
|
|
lock_fd = acquire_lock() |
|
|
|
|
setup_logging() |
|
|
|
|
update_system_activity(SCRIPT_NAME, "running", os.getpid()) # Record start |
|
|
|
|
# Skip Twitter credentials validation since we're not posting |
|
|
|
|
# validate_twitter_credentials() |
|
|
|
|
post_weekly_thread() |
|
|
|
|
update_system_activity(SCRIPT_NAME, "stopped") # Record stop |
|
|
|
|
except Exception as e: |
|
|
|
|
|