@ -7,6 +7,7 @@ import sys
import fcntl
import fcntl
import os
import os
import time
import time
import re
from datetime import datetime , timedelta , timezone
from datetime import datetime , timedelta , timezone
import tweepy
import tweepy
from openai import OpenAI
from openai import OpenAI
@ -56,7 +57,7 @@ def setup_logging():
logging . basicConfig (
logging . basicConfig (
filename = LOG_FILE ,
filename = LOG_FILE ,
level = logging . DEBUG , # Changed to DEBUG to show more details
level = logging . DEBUG ,
format = ' %(asctime)s - %(levelname)s - %(message)s ' ,
format = ' %(asctime)s - %(levelname)s - %(message)s ' ,
datefmt = ' % Y- % m- %d % H: % M: % S '
datefmt = ' % Y- % m- %d % H: % M: % S '
)
)
@ -149,6 +150,27 @@ def validate_twitter_credentials(author):
return False
return False
return False
return False
def remove_emojis ( text ) :
""" Remove emojis from the given text. """
# Unicode ranges for emojis
emoji_pattern = re . compile (
" [ "
" \U0001F600 - \U0001F64F " # Emoticons
" \U0001F300 - \U0001F5FF " # Symbols & Pictographs
" \U0001F680 - \U0001F6FF " # Transport & Map Symbols
" \U0001F700 - \U0001F77F " # Alchemical Symbols
" \U0001F780 - \U0001F7FF " # Geometric Shapes Extended
" \U0001F800 - \U0001F8FF " # Supplemental Arrows-C
" \U0001F900 - \U0001F9FF " # Supplemental Symbols and Pictographs
" \U0001FA00 - \U0001FA6F " # Chess Symbols
" \U0001FA70 - \U0001FAFF " # Symbols and Pictographs Extended-A
" \U00002700 - \U000027BF " # Dingbats
" \U00002600 - \U000026FF " # Miscellaneous Symbols
" ]+ " ,
flags = re . UNICODE
)
return emoji_pattern . sub ( r " " , text )
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 )
@ -185,12 +207,28 @@ def generate_engagement_tweet(author):
# Case-insensitive lookup for background with whitespace stripping
# Case-insensitive lookup for background with whitespace stripping
username_cleaned = username . strip ( ) . lower ( )
username_cleaned = username . strip ( ) . lower ( )
background = next (
background = { }
( bg for bg in AUTHOR_BACKGROUNDS if bg [ " username " ] . strip ( ) . lower ( ) == username_cleaned ) ,
available_usernames = [ ]
{ }
for bg in AUTHOR_BACKGROUNDS :
)
bg_username = bg . get ( " username " )
if bg_username is None :
logging . warning ( f " Skipping background entry with missing username: { bg } " )
continue
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 = bg_username . strip ( ) . lower ( )
available_usernames . append ( bg_username )
logging . debug (
f " Comparing usernames for { username } : "
f " author username (cleaned) = ' { username_cleaned } ' , "
f " background username (cleaned) = ' { bg_username_cleaned } ' "
)
if bg_username_cleaned == username_cleaned :
background = bg
break
if not background or " engagement_themes " not in background :
if not background or " engagement_themes " not in background :
available_usernames = [ bg [ " username " ] for bg in AUTHOR_BACKGROUNDS ]
logging . warning (
logging . warning (
f " No background or engagement themes found for { username } . "
f " No background or engagement themes found for { username } . "
f " Attempted username (cleaned): { username_cleaned } . "
f " Attempted username (cleaned): { username_cleaned } . "
@ -211,8 +249,7 @@ def generate_engagement_tweet(author):
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). "
f " Avoid using the word ' elevate ' —use more humanized language like ' level up ' or ' bring to life ' . "
f " Strictly avoid using any emojis, hashtags, or reward-driven incentives (e.g., giveaways)—do not include them under any circumstances. "
f " Do not include emojis, hashtags, or reward-driven incentives (e.g., giveaways). "
f " Return only the tweet text. "
f " Return only the tweet text. "
)
)
@ -228,6 +265,8 @@ def generate_engagement_tweet(author):
temperature = 0.7
temperature = 0.7
)
)
tweet = response . choices [ 0 ] . message . content . strip ( )
tweet = response . choices [ 0 ] . message . content . strip ( )
# Remove emojis as a safeguard
tweet = remove_emojis ( tweet )
# Check for duplicate URLs and remove if present
# Check for duplicate URLs and remove if present
url_count = tweet . lower ( ) . count ( URL . lower ( ) )
url_count = tweet . lower ( ) . count ( URL . lower ( ) )
if url_count > 1 :
if url_count > 1 :
@ -266,6 +305,8 @@ def generate_engagement_tweet(author):
if total_length > 280 :
if total_length > 280 :
tweet_without_url = tweet_without_url [ : ( 280 - URL_SHORTENED_LENGTH - 3 ) ]
tweet_without_url = tweet_without_url [ : ( 280 - URL_SHORTENED_LENGTH - 3 ) ]
fallback = tweet_without_url + " ... " + " " + URL
fallback = tweet_without_url + " ... " + " " + URL
# Remove emojis from fallback as well
fallback = remove_emojis ( fallback )
logging . info ( f " Using fallback engagement tweet: { fallback } " )
logging . info ( f " Using fallback engagement tweet: { fallback } " )
return fallback
return fallback
return None
return None