Shane 7 months ago
parent 96bba2398c
commit ac92b20bf8
  1. 63
      foodie_engagement_tweet.py

@ -8,6 +8,7 @@ import fcntl
import os import os
import time import time
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import tweepy
from openai import OpenAI from openai import OpenAI
from foodie_utils import post_tweet, load_post_counts, save_post_counts from foodie_utils import post_tweet, load_post_counts, save_post_counts
from foodie_config import ( from foodie_config import (
@ -61,6 +62,7 @@ def setup_logging():
console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(console_handler) logging.getLogger().addHandler(console_handler)
logging.getLogger("openai").setLevel(logging.WARNING) logging.getLogger("openai").setLevel(logging.WARNING)
logging.getLogger("tweepy").setLevel(logging.WARNING)
logging.info("Logging initialized for foodie_engagement_tweet.py") logging.info("Logging initialized for foodie_engagement_tweet.py")
except Exception as e: except Exception as e:
print(f"Failed to setup logging: {e}") print(f"Failed to setup logging: {e}")
@ -101,10 +103,39 @@ except Exception as e:
try: try:
with open(AUTHOR_BACKGROUNDS_FILE, 'r') as f: with open(AUTHOR_BACKGROUNDS_FILE, 'r') as f:
AUTHOR_BACKGROUNDS = json.load(f) AUTHOR_BACKGROUNDS = json.load(f)
logging.debug(f"Loaded author backgrounds: {[bg['username'] for bg in AUTHOR_BACKGROUNDS]}")
except Exception as e: except Exception as e:
logging.error(f"Failed to load author_backgrounds.json: {e}", exc_info=True) logging.error(f"Failed to load author_backgrounds.json: {e}", exc_info=True)
AUTHOR_BACKGROUNDS = []
sys.exit(1) sys.exit(1)
def validate_twitter_credentials(author):
"""Validate Twitter API credentials for a specific author."""
username = author["username"]
credentials = X_API_CREDENTIALS.get(username)
if not credentials:
logging.error(f"No X credentials found for {username}")
return False
for attempt in range(MAX_RETRIES):
try:
twitter_client = tweepy.Client(
consumer_key=credentials["api_key"],
consumer_secret=credentials["api_secret"],
access_token=credentials["access_token"],
access_token_secret=credentials["access_token_secret"]
)
user = twitter_client.get_me()
logging.info(f"Credentials valid for {username} (handle: {credentials['x_username']})")
return True
except tweepy.TweepyException as e:
logging.warning(f"Failed to validate credentials for {username} (attempt {attempt + 1}): {e}")
if attempt < MAX_RETRIES - 1:
time.sleep(RETRY_BACKOFF * (2 ** attempt))
else:
logging.error(f"Credentials invalid for {username} after {MAX_RETRIES} attempts")
return False
return False
def get_reference_date(): def get_reference_date():
"""Load or initialize the reference date for the 2-day interval.""" """Load or initialize the reference date for the 2-day interval."""
os.makedirs(os.path.dirname(REFERENCE_DATE_FILE), exist_ok=True) os.makedirs(os.path.dirname(REFERENCE_DATE_FILE), exist_ok=True)
@ -130,20 +161,26 @@ def get_reference_date():
def generate_engagement_tweet(author): def generate_engagement_tweet(author):
"""Generate an engagement tweet using author background themes and persona.""" """Generate an engagement tweet using author background themes and persona."""
username = author["username"] username = author["username"]
credentials = X_API_CREDENTIALS.get(username) if not validate_twitter_credentials(author):
if not credentials: logging.error(f"Skipping tweet generation for {username} due to invalid credentials")
logging.error(f"No X credentials found for {username}")
return None return None
credentials = X_API_CREDENTIALS.get(username)
author_handle = credentials["x_username"] author_handle = credentials["x_username"]
persona = author["persona"] persona = author["persona"]
persona_config = PERSONA_CONFIGS.get(persona, PERSONA_CONFIGS["Visionary Editor"]) persona_config = PERSONA_CONFIGS.get(persona, PERSONA_CONFIGS["Visionary Editor"])
background = next((bg for bg in AUTHOR_BACKGROUNDS if bg["username"] == username), {}) # Case-insensitive lookup for background
background = next(
(bg for bg in AUTHOR_BACKGROUNDS if bg["username"].lower() == username.lower()),
{}
)
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 {username}, using default theme") 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"])
logging.debug(f"Selected engagement theme '{theme}' for {username}")
base_prompt = persona_config["x_prompt"].format( base_prompt = persona_config["x_prompt"].format(
description=persona_config["description"], description=persona_config["description"],
@ -152,7 +189,8 @@ def generate_engagement_tweet(author):
prompt = ( prompt = (
f"{base_prompt}\n\n" f"{base_prompt}\n\n"
f"Generate an engagement tweet for {author_handle} asking a question about {theme} to engage the public. " 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"Keep it under 230 characters to ensure room for the URL. "
f"Use {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). "
@ -167,12 +205,14 @@ def generate_engagement_tweet(author):
{"role": "system", "content": "You are a social media expert crafting engaging tweets."}, {"role": "system", "content": "You are a social media expert crafting engaging tweets."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ],
max_tokens=100, max_tokens=80, # Reduced to ensure shorter tweets
temperature=0.7 temperature=0.7
) )
tweet = response.choices[0].message.content.strip() tweet = response.choices[0].message.content.strip()
if len(tweet) > 280: # Ensure tweet length is within limits (accounting for URL)
tweet = tweet[:277] + "..." url_length = 23 # Twitter shortens URLs
if len(tweet) > (280 - url_length):
tweet = tweet[:(280 - url_length - 3)] + "..."
logging.debug(f"Generated engagement tweet for {username}: {tweet}") logging.debug(f"Generated engagement tweet for {username}: {tweet}")
return tweet return tweet
except Exception as e: except Exception as e:
@ -182,11 +222,14 @@ def generate_engagement_tweet(author):
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")
fallback = ( fallback = (
f"What's the hottest {theme} you're into? Share and follow {author_handle} for more on InsiderFoodie.com! " f"What's the hottest {theme}? Share and follow {author_handle} for more on InsiderFoodie.com! "
f"Link: https://insiderfoodie.com" f"https://insiderfoodie.com"
) )
if len(fallback) > (280 - url_length):
fallback = fallback[:(280 - url_length - 3)] + "..."
logging.info(f"Using fallback engagement tweet: {fallback}") logging.info(f"Using fallback engagement tweet: {fallback}")
return fallback return fallback
return None
def post_engagement_tweet(): def post_engagement_tweet():
"""Post engagement tweets for authors every 2 days.""" """Post engagement tweets for authors every 2 days."""

Loading…
Cancel
Save