Shane 7 months ago
parent 8a2ae4ebf5
commit 96bba2398c
  1. 90
      foodie_engagement_tweet.py

@ -6,19 +6,21 @@ import signal
import sys import sys
import fcntl import fcntl
import os import os
import time
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from openai import OpenAI from openai import OpenAI
from foodie_utils import post_tweet, AUTHORS, SUMMARY_MODEL, load_post_counts, save_post_counts from foodie_utils import post_tweet, load_post_counts, save_post_counts
from foodie_config import X_API_CREDENTIALS, AUTHOR_BACKGROUNDS_FILE from foodie_config import (
AUTHORS, SUMMARY_MODEL, X_API_CREDENTIALS, AUTHOR_BACKGROUNDS_FILE,
PERSONA_CONFIGS, ENGAGEMENT_REFERENCE_DATE_FILE
)
from dotenv import load_dotenv from dotenv import load_dotenv
from foodie_config import ENGAGEMENT_REFERENCE_DATE_FILE
load_dotenv() load_dotenv()
REFERENCE_DATE_FILE = ENGAGEMENT_REFERENCE_DATE_FILE REFERENCE_DATE_FILE = ENGAGEMENT_REFERENCE_DATE_FILE
LOCK_FILE = "/home/shane/foodie_automator/locks/foodie_engagement_tweet.lock" LOCK_FILE = "/home/shane/foodie_automator/locks/foodie_engagement_tweet.lock"
LOG_FILE = "/home/shane/foodie_automator/logs/foodie_engagement_tweet.log" LOG_FILE = "/home/shane/foodie_automator/logs/foodie_engagement_tweet.log"
REFERENCE_DATE_FILE = "/home/shane/foodie_automator/engagement_reference_date.json"
LOG_PRUNE_DAYS = 30 LOG_PRUNE_DAYS = 30
MAX_RETRIES = 3 MAX_RETRIES = 3
RETRY_BACKOFF = 2 RETRY_BACKOFF = 2
@ -116,7 +118,6 @@ def get_reference_date():
except (json.JSONDecodeError, KeyError, ValueError) as e: except (json.JSONDecodeError, KeyError, ValueError) as e:
logging.error(f"Failed to load reference date from {REFERENCE_DATE_FILE}: {e}. Initializing new date.") logging.error(f"Failed to load reference date from {REFERENCE_DATE_FILE}: {e}. Initializing new date.")
# Initialize with current date (start of day)
reference_date = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) reference_date = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
try: try:
with open(REFERENCE_DATE_FILE, 'w') as f: with open(REFERENCE_DATE_FILE, 'w') as f:
@ -127,26 +128,35 @@ def get_reference_date():
return reference_date return reference_date
def generate_engagement_tweet(author): def generate_engagement_tweet(author):
"""Generate an engagement tweet using author background themes.""" """Generate an engagement tweet using author background themes and persona."""
credentials = next((cred for cred in X_API_CREDENTIALS if cred["username"] == author["username"]), None) username = author["username"]
credentials = X_API_CREDENTIALS.get(username)
if not credentials: if not credentials:
logging.error(f"No X credentials found for {author['username']}") logging.error(f"No X credentials found for {username}")
return None return None
author_handle = credentials["x_username"] author_handle = credentials["x_username"]
persona = author["persona"]
persona_config = PERSONA_CONFIGS.get(persona, PERSONA_CONFIGS["Visionary Editor"])
background = next((bg for bg in AUTHOR_BACKGROUNDS if bg["username"] == author["username"]), {}) background = next((bg for bg in AUTHOR_BACKGROUNDS if bg["username"] == username), {})
if not background or "engagement_themes" not in background: if not background or "engagement_themes" not in background:
logging.warning(f"No background or engagement themes found for {author['username']}") logging.warning(f"No background or engagement themes found for {username}, using default theme")
theme = "food trends" theme = "food trends"
else: else:
theme = random.choice(background["engagement_themes"]) theme = random.choice(background["engagement_themes"])
base_prompt = persona_config["x_prompt"].format(
description=persona_config["description"],
tone=persona_config["tone"]
)
prompt = ( prompt = (
f"Generate a concise tweet (under 280 characters) for {author_handle}. " f"{base_prompt}\n\n"
f"Create an engaging question or statement about {theme} to spark interaction. " f"Generate an engagement tweet for {author_handle} asking a question about {theme} to engage the public. "
f"Keep it under 280 characters, using {persona_config['tone']}. "
f"Include a call to action to follow {author_handle} or like the tweet, and mention InsiderFoodie.com with a link to https://insiderfoodie.com. " f"Include a call to action to follow {author_handle} or like the tweet, and mention InsiderFoodie.com with a link to https://insiderfoodie.com. "
f"Avoid using the word 'elevate'—use more humanized language like 'level up' or 'bring to life'. " 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"Do not include emojis, hashtags, or reward-driven incentives (e.g., giveaways). "
f"Return only the tweet text."
) )
for attempt in range(MAX_RETRIES): for attempt in range(MAX_RETRIES):
@ -163,23 +173,20 @@ def generate_engagement_tweet(author):
tweet = response.choices[0].message.content.strip() tweet = response.choices[0].message.content.strip()
if len(tweet) > 280: if len(tweet) > 280:
tweet = tweet[:277] + "..." tweet = tweet[:277] + "..."
logging.debug(f"Generated engagement tweet: {tweet}") logging.debug(f"Generated engagement tweet for {username}: {tweet}")
return tweet return tweet
except Exception as e: except Exception as e:
logging.warning(f"Failed to generate engagement tweet for {author['username']} (attempt {attempt + 1}): {e}") logging.warning(f"Failed to generate engagement tweet for {username} (attempt {attempt + 1}): {e}")
if attempt < MAX_RETRIES - 1: if attempt < MAX_RETRIES - 1:
time.sleep(RETRY_BACKOFF * (2 ** attempt)) time.sleep(RETRY_BACKOFF * (2 ** attempt))
else: else:
logging.error(f"Failed to generate engagement tweet after {MAX_RETRIES} attempts") logging.error(f"Failed to generate engagement tweet after {MAX_RETRIES} attempts")
engagement_templates = [ fallback = (
f"What's the most mouthwatering {theme} you've seen this week? Share below and follow {author_handle} for more on InsiderFoodie.com! Link: https://insiderfoodie.com", f"What's the hottest {theme} you're into? Share and follow {author_handle} for more on InsiderFoodie.com! "
f"{theme.capitalize()} lovers unite! What's your go-to pick? Tell us and like this tweet for more from {author_handle} on InsiderFoodie.com! Link: https://insiderfoodie.com", f"Link: https://insiderfoodie.com"
f"Ever tried a {theme} that blew your mind? Share your favorites and follow {author_handle} for more on InsiderFoodie.com! Link: https://insiderfoodie.com", )
f"What {theme} trend are you loving right now? Let us know and like this tweet to keep up with {author_handle} on InsiderFoodie.com! Link: https://insiderfoodie.com" logging.info(f"Using fallback engagement tweet: {fallback}")
] return fallback
template = random.choice(engagement_templates)
logging.info(f"Using fallback engagement tweet: {template}")
return template
def post_engagement_tweet(): def post_engagement_tweet():
"""Post engagement tweets for authors every 2 days.""" """Post engagement tweets for authors every 2 days."""
@ -187,52 +194,57 @@ def post_engagement_tweet():
logging.info("Starting foodie_engagement_tweet.py") logging.info("Starting foodie_engagement_tweet.py")
print("Starting foodie_engagement_tweet.py") print("Starting foodie_engagement_tweet.py")
# Get reference date
reference_date = get_reference_date() reference_date = get_reference_date()
current_date = datetime.now(timezone.utc) current_date = datetime.now(timezone.utc)
days_since_reference = (current_date - reference_date).days days_since_reference = (current_date - reference_date).days
logging.info(f"Days since reference date ({reference_date.date()}): {days_since_reference}") logging.info(f"Days since reference date ({reference_date.date()}): {days_since_reference}")
print(f"Days since reference date ({reference_date.date()}): {days_since_reference}") print(f"Days since reference date ({reference_date.date()}): {days_since_reference}")
# Post only if the number of days since the reference date is divisible by 2
if days_since_reference % 2 == 0: if days_since_reference % 2 == 0:
logging.info("Today is an engagement tweet day (every 2 days). Posting...") logging.info("Today is an engagement tweet day (every 2 days). Posting...")
print("Today is an engagement tweet day (every 2 days). Posting...") print("Today is an engagement tweet day (every 2 days). Posting...")
# Load post counts to check limits
post_counts = load_post_counts() post_counts = load_post_counts()
for author in AUTHORS: for author in AUTHORS:
username = author["username"]
try: try:
# Check post limits author_count = next((entry for entry in post_counts if entry["username"] == username), None)
author_count = next((entry for entry in post_counts if entry["username"] == author["username"]), None)
if not author_count: if not author_count:
logging.error(f"No post count entry for {author['username']}, skipping") logging.warning(f"No post count entry for {username}, initializing new entry")
continue author_count = {
"username": username,
"month": datetime.now(timezone.utc).strftime("%Y-%m"),
"monthly_count": 0,
"day": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
"daily_count": 0
}
post_counts.append(author_count)
save_post_counts(post_counts)
if author_count["monthly_count"] >= 500: if author_count["monthly_count"] >= 500:
logging.warning(f"Monthly post limit (500) reached for {author['username']}, skipping") logging.warning(f"Monthly post limit (500) reached for {username}, skipping")
continue continue
if author_count["daily_count"] >= 20: if author_count["daily_count"] >= 20:
logging.warning(f"Daily post limit (20) reached for {author['username']}, skipping") logging.warning(f"Daily post limit (20) reached for {username}, skipping")
continue continue
tweet = generate_engagement_tweet(author) tweet = generate_engagement_tweet(author)
if not tweet: if not tweet:
logging.error(f"Failed to generate engagement tweet for {author['username']}, skipping") logging.error(f"Failed to generate engagement tweet for {username}, skipping")
continue continue
logging.info(f"Posting engagement tweet for {author['username']}: {tweet}") logging.info(f"Posting engagement tweet for {username}: {tweet}")
print(f"Posting engagement tweet for {author['username']}: {tweet}") print(f"Posting engagement tweet for {username}: {tweet}")
if post_tweet(author, tweet): if post_tweet(author, tweet):
logging.info(f"Successfully posted engagement tweet for {author['username']}") logging.info(f"Successfully posted engagement tweet for {username}")
# Update post counts
author_count["monthly_count"] += 1 author_count["monthly_count"] += 1
author_count["daily_count"] += 1 author_count["daily_count"] += 1
save_post_counts(post_counts) save_post_counts(post_counts)
else: else:
logging.warning(f"Failed to post engagement tweet for {author['username']}") logging.warning(f"Failed to post engagement tweet for {username}")
except Exception as e: except Exception as e:
logging.error(f"Error posting engagement tweet for {author['username']}: {e}", exc_info=True) logging.error(f"Error posting engagement tweet for {username}: {e}", exc_info=True)
continue continue
else: else:
logging.info(f"Today is not an engagement tweet day (every 2 days). Days since reference: {days_since_reference}. Skipping...") logging.info(f"Today is not an engagement tweet day (every 2 days). Days since reference: {days_since_reference}. Skipping...")

Loading…
Cancel
Save