Shane 7 months ago
parent be9323a266
commit d68e0435c8
  1. 42
      foodie_engagement_tweet.py

@ -8,6 +8,7 @@ import fcntl
import os import os
import time import time
import re import re
import unicodedata
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import tweepy import tweepy
from openai import OpenAI from openai import OpenAI
@ -28,6 +29,7 @@ MAX_RETRIES = 3
RETRY_BACKOFF = 2 RETRY_BACKOFF = 2
URL = "https://insiderfoodie.com" URL = "https://insiderfoodie.com"
URL_SHORTENED_LENGTH = 23 # Twitter's shortened URL length URL_SHORTENED_LENGTH = 23 # Twitter's shortened URL length
CURRENT_YEAR = "2025" # Explicitly set the current year for the prompt
def setup_logging(): def setup_logging():
"""Initialize logging with pruning of old logs.""" """Initialize logging with pruning of old logs."""
@ -113,6 +115,7 @@ try:
logging.error(f"Invalid format in {AUTHOR_BACKGROUNDS_FILE}: Expected a list, got {type(background_list)}") logging.error(f"Invalid format in {AUTHOR_BACKGROUNDS_FILE}: Expected a list, got {type(background_list)}")
raise ValueError("Author backgrounds must be a list") raise ValueError("Author backgrounds must be a list")
AUTHOR_BACKGROUNDS = {} AUTHOR_BACKGROUNDS = {}
AUTHOR_BACKGROUNDS_LIST = background_list # Keep the original list for fallback lookup
for bg in background_list: for bg in background_list:
if "username" not in bg: if "username" not in bg:
logging.error(f"Invalid entry in {AUTHOR_BACKGROUNDS_FILE}: Missing 'username' key in {bg}") logging.error(f"Invalid entry in {AUTHOR_BACKGROUNDS_FILE}: Missing 'username' key in {bg}")
@ -121,13 +124,16 @@ try:
if not isinstance(username, str): if not isinstance(username, str):
logging.error(f"Invalid username type in {AUTHOR_BACKGROUNDS_FILE}: {username} (type: {type(username)})") logging.error(f"Invalid username type in {AUTHOR_BACKGROUNDS_FILE}: {username} (type: {type(username)})")
raise ValueError("Username must be a string") raise ValueError("Username must be a string")
cleaned_username = username.strip().lower() # Normalize the username to handle encoding differences
cleaned_username = unicodedata.normalize('NFC', username.strip().lower())
AUTHOR_BACKGROUNDS[cleaned_username] = bg AUTHOR_BACKGROUNDS[cleaned_username] = bg
logging.debug(f"Added to AUTHOR_BACKGROUNDS: key='{cleaned_username}', value={bg}")
loaded_usernames = list(AUTHOR_BACKGROUNDS.keys()) loaded_usernames = list(AUTHOR_BACKGROUNDS.keys())
logging.debug(f"Loaded author backgrounds: {loaded_usernames}") logging.debug(f"Loaded author backgrounds: {loaded_usernames}")
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 = {} AUTHOR_BACKGROUNDS = {}
AUTHOR_BACKGROUNDS_LIST = []
sys.exit(1) sys.exit(1)
def validate_twitter_credentials(author): def validate_twitter_credentials(author):
@ -158,8 +164,8 @@ def validate_twitter_credentials(author):
return False return False
def remove_emojis(text): def remove_emojis(text):
"""Remove emojis from the given text.""" """Remove emojis from the given text, including variation selectors."""
# Unicode ranges for emojis # Unicode ranges for emojis, including variation selectors and combining characters
emoji_pattern = re.compile( emoji_pattern = re.compile(
"[" "["
"\U0001F600-\U0001F64F" # Emoticons "\U0001F600-\U0001F64F" # Emoticons
@ -173,6 +179,9 @@ def remove_emojis(text):
"\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A
"\U00002700-\U000027BF" # Dingbats "\U00002700-\U000027BF" # Dingbats
"\U00002600-\U000026FF" # Miscellaneous Symbols "\U00002600-\U000026FF" # Miscellaneous Symbols
"\U0000FE00-\U0000FE0F" # Variation Selectors
"\U0000200D" # Zero Width Joiner
"\U0000200C" # Zero Width Non-Joiner
"]+", "]+",
flags=re.UNICODE flags=re.UNICODE
) )
@ -212,8 +221,9 @@ def generate_engagement_tweet(author):
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"])
# Lookup background using a dictionary # Normalize and lookup background
username_cleaned = username.strip().lower() username_cleaned = unicodedata.normalize('NFC', username.strip().lower())
logging.debug(f"Looking up background for username: raw='{username}', cleaned='{username_cleaned}'")
background = AUTHOR_BACKGROUNDS.get(username_cleaned, {}) background = AUTHOR_BACKGROUNDS.get(username_cleaned, {})
# Debug comparison # Debug comparison
@ -222,7 +232,7 @@ def generate_engagement_tweet(author):
logging.debug(f"Direct key check: '{username_cleaned}' found in AUTHOR_BACKGROUNDS keys") logging.debug(f"Direct key check: '{username_cleaned}' found in AUTHOR_BACKGROUNDS keys")
else: else:
logging.debug(f"Direct key check: '{username_cleaned}' NOT found in AUTHOR_BACKGROUNDS keys") logging.debug(f"Direct key check: '{username_cleaned}' NOT found in AUTHOR_BACKGROUNDS keys")
# Log byte-level comparison for the first available username as a sample # Byte-level comparison for the first available username
if available_usernames: if available_usernames:
sample_key = available_usernames[0] sample_key = available_usernames[0]
logging.debug( logging.debug(
@ -231,6 +241,25 @@ def generate_engagement_tweet(author):
f"sample background key bytes = {list(sample_key.encode('utf-8'))}" f"sample background key bytes = {list(sample_key.encode('utf-8'))}"
) )
# Fallback lookup if dictionary fails
if not background:
logging.debug(f"Dictionary lookup failed for '{username_cleaned}', attempting fallback lookup")
for bg in AUTHOR_BACKGROUNDS_LIST:
bg_username = bg.get("username", "")
if not isinstance(bg_username, str):
logging.warning(f"Skipping background entry with non-string username: {bg_username} (type: {type(bg_username)})")
continue
bg_username_cleaned = unicodedata.normalize('NFC', bg_username.strip().lower())
logging.debug(
f"Fallback comparison: "
f"author username (cleaned) = '{username_cleaned}', "
f"background username (cleaned) = '{bg_username_cleaned}'"
)
if bg_username_cleaned == username_cleaned:
background = bg
logging.debug(f"Fallback lookup succeeded for '{username_cleaned}'")
break
if not background or "engagement_themes" not in background: if not background or "engagement_themes" not in background:
logging.warning( logging.warning(
f"No background or engagement themes found for {username}. " f"No background or engagement themes found for {username}. "
@ -249,6 +278,7 @@ 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"The current year is {CURRENT_YEAR}, and all references to the year should use {CURRENT_YEAR}. "
f"Keep it under 230 characters to ensure room for the URL. " f"Keep it under 230 characters to ensure room for the URL. "
f"Use {persona_config['tone']}. " f"Use {persona_config['tone']}. "
f"Include a call to action to follow {author_handle} or like the tweet, followed by the URL {URL} (do not mention InsiderFoodie.com separately in the text). " f"Include a call to action to follow {author_handle} or like the tweet, followed by the URL {URL} (do not mention InsiderFoodie.com separately in the text). "

Loading…
Cancel
Save