← back to Dear Bubbe Nextjs

lib/twitter-optimizer.js

272 lines

const fs = require('fs').promises;
const path = require('path');

// Viral hashtags that get engagement
const VIRAL_HASHTAGS = [
  // Jewish/Cultural
  '#JewishTwitter', '#JewishHumor', '#JewishMom', '#JewishGrandma', '#Yiddish',
  '#Bubbe', '#BubbeWisdom', '#JewishLife', '#Shabbat', '#Kosher', '#Mazel',
  
  // Comedy/Entertainment  
  '#Funny', '#Comedy', '#Humor', '#LOL', '#Hilarious', '#Savage', '#NoFilter',
  '#RoastMe', '#Truth', '#RealTalk', '#Sarcasm', '#SavageMode', '#Relatable',
  
  // Trending/Viral
  '#MondayMotivation', '#TuesdayThoughts', '#WednesdayWisdom', '#ThursdayThoughts',
  '#FridayFeeling', '#SaturdayVibes', '#SundayFunday', '#MondayMood',
  
  // Advice/Life
  '#LifeAdvice', '#Dating', '#Relationships', '#Marriage', '#Family', '#Parenting',
  '#MomLife', '#DadLife', '#Millennials', '#GenZ', '#Adulting', '#Life',
  
  // Questions/Engagement
  '#AITA', '#RedFlags', '#ToxicPeople', '#Boundaries', '#SelfCare', '#MentalHealth',
  '#Therapy', '#Growth', '#Wisdom', '#ElderWisdom', '#GrandmaKnowsBest'
];

// Accounts to @ mention for visibility (rotate through these)
const MENTION_ACCOUNTS = [
  // Jewish comedy/culture accounts
  '@JewishComedy', '@ModernTribe', '@JewishJournal', '@JewBelong',
  
  // Comedy accounts
  '@funnyordie', '@TheOnion', '@ComedyCentral', '@CollegeHumor',
  
  // Viral content accounts  
  '@BuzzFeed', '@9GAG', '@reddit', '@WorldStarHipHop',
  
  // Relationship/advice accounts
  '@Relationships', '@Dating', '@AskMen', '@AskWomen',
  
  // News/trending accounts
  '@NBCNews', '@CNN', '@BBCBreaking', '@Reuters'
];

// Day-specific hashtag strategies
const DAY_HASHTAGS = {
  0: ['#SundayFunday', '#WeekendVibes', '#SundayMood', '#Relax'],
  1: ['#MondayMotivation', '#MondayMood', '#BackToWork', '#MondayBlues'],
  2: ['#TuesdayThoughts', '#TuesdayVibes', '#TuesdayMotivation', '#Tuesday'],
  3: ['#WednesdayWisdom', '#HumpDay', '#WednesdayMotivation', '#Midweek'],
  4: ['#ThursdayThoughts', '#ThirstyThursday', '#ThursdayVibes', '#AlmostFriday'],
  5: ['#FridayFeeling', '#TGIF', '#FridayMood', '#Weekend'],
  6: ['#SaturdayVibes', '#Weekend', '#SaturdayMood', '#SaturdayNight']
};

// Time-based content strategies
const TIME_STRATEGIES = {
  morning: { // 6am-10am
    hashtags: ['#GoodMorning', '#MorningMotivation', '#Coffee', '#Breakfast'],
    topics: ['work', 'motivation', 'coffee', 'commute']
  },
  lunch: { // 11am-2pm
    hashtags: ['#LunchTime', '#LunchBreak', '#Foodie', '#Hungry'],
    topics: ['food', 'diet', 'coworkers', 'lunch']
  },
  afternoon: { // 2pm-5pm
    hashtags: ['#AfternoonVibes', '#WorkLife', '#Office', '#Productivity'],
    topics: ['work', 'boss', 'career', 'stress']
  },
  evening: { // 5pm-8pm
    hashtags: ['#EveningVibes', '#HomeTime', '#Dinner', '#Family'],
    topics: ['family', 'dinner', 'kids', 'spouse']
  },
  night: { // 8pm-11pm
    hashtags: ['#NightVibes', '#Netflix', '#Relaxing', '#NightOwl'],
    topics: ['dating', 'relationships', 'entertainment', 'life']
  },
  latenight: { // 11pm-2am
    hashtags: ['#LateNight', '#Insomnia', '#NightThoughts', '#CantSleep'],
    topics: ['anxiety', 'overthinking', 'relationships', 'life']
  }
};

// Get current time period
function getTimePeriod() {
  const hour = new Date().getHours();
  if (hour >= 6 && hour < 10) return 'morning';
  if (hour >= 10 && hour < 14) return 'lunch';
  if (hour >= 14 && hour < 17) return 'afternoon';
  if (hour >= 17 && hour < 20) return 'evening';
  if (hour >= 20 && hour < 23) return 'night';
  return 'latenight';
}

// Select best hashtags for current context
function selectHashtags(text, count = 5) {
  const hashtags = new Set();
  const day = new Date().getDay();
  const period = getTimePeriod();
  
  // Always include brand hashtags
  hashtags.add('#BubbeAI');
  hashtags.add('#Bubbe');
  
  // Add day-specific hashtags
  const dayTags = DAY_HASHTAGS[day];
  if (dayTags && dayTags.length > 0) {
    hashtags.add(dayTags[0]);
  }
  
  // Add time-specific hashtags
  const timeStrategy = TIME_STRATEGIES[period];
  if (timeStrategy && timeStrategy.hashtags.length > 0) {
    hashtags.add(timeStrategy.hashtags[0]);
  }
  
  // Add content-relevant hashtags
  const textLower = (text || '').toLowerCase();
  
  if (textLower.includes('dating') || textLower.includes('boyfriend') || textLower.includes('girlfriend')) {
    hashtags.add('#Dating');
    hashtags.add('#Relationships');
  }
  if (textLower.includes('marriage') || textLower.includes('husband') || textLower.includes('wife')) {
    hashtags.add('#Marriage');
    hashtags.add('#MarriedLife');
  }
  if (textLower.includes('mom') || textLower.includes('mother')) {
    hashtags.add('#MomLife');
    hashtags.add('#JewishMom');
  }
  if (textLower.includes('work') || textLower.includes('job') || textLower.includes('boss')) {
    hashtags.add('#WorkLife');
    hashtags.add('#Career');
  }
  if (textLower.includes('money') || textLower.includes('dollar') || textLower.includes('pay')) {
    hashtags.add('#Money');
    hashtags.add('#Finance');
  }
  
  // Add some viral hashtags
  const viralOptions = VIRAL_HASHTAGS.filter(tag => !hashtags.has(tag));
  const randomViral = viralOptions[Math.floor(Math.random() * viralOptions.length)];
  if (randomViral) hashtags.add(randomViral);
  
  // Convert to array and limit
  return Array.from(hashtags).slice(0, count);
}

// Select @ mention for engagement
function selectMention(text) {
  const textLower = text.toLowerCase();
  
  // Context-based mentions
  if (textLower.includes('dating') || textLower.includes('relationship')) {
    return '@Dating';
  }
  if (textLower.includes('jewish') || textLower.includes('kosher')) {
    return '@JewishComedy';
  }
  if (textLower.includes('news') || textLower.includes('today')) {
    return '@BBCBreaking';
  }
  
  // Random mention from list
  return MENTION_ACCOUNTS[Math.floor(Math.random() * MENTION_ACCOUNTS.length)];
}

// Format tweet to maximize length (280 chars)
function optimizeTweet(userMessage, bubbeResponse, includeUser = true) {
  const intro = getIntro();
  const hashtags = selectHashtags(bubbeResponse);
  const mention = Math.random() > 0.7 ? selectMention(bubbeResponse) : ''; // 30% chance of @mention
  
  // Build components
  const hashtagStr = hashtags.join(' ');
  const cta = 'Bubbe.AI';
  
  // Calculate available space
  const fixedLength = intro.length + hashtagStr.length + cta.length + 10; // 10 for spacing/formatting
  const availableForContent = 280 - fixedLength - (mention ? mention.length + 1 : 0);
  
  // Format content
  let content = '';
  if (includeUser && userMessage && availableForContent > 100) {
    // Include both user and Bubbe
    const userSpace = Math.floor(availableForContent * 0.3);
    const bubbeSpace = availableForContent - userSpace - 10; // 10 for quotes and spacing
    
    let userPart = anonymizeText(userMessage);
    if (userPart.length > userSpace) {
      userPart = userPart.substring(0, userSpace - 3) + '...';
    }
    
    let bubbePart = bubbeResponse;
    if (bubbePart.length > bubbeSpace) {
      bubbePart = bubbePart.substring(0, bubbeSpace - 3) + '...';
    }
    
    content = `"${userPart}"\n\n"${bubbePart}"`;
  } else {
    // Just Bubbe's response
    let bubbePart = bubbeResponse;
    if (bubbePart.length > availableForContent - 6) { // 6 for quotes and ellipsis
      bubbePart = bubbePart.substring(0, availableForContent - 9) + '...';
    }
    content = `"${bubbePart}"`;
  }
  
  // Construct final tweet - more compact format
  let tweet = `${intro} ${content} ${hashtagStr} ${cta}`;
  if (mention) {
    tweet = `${mention} ${tweet}`;
  }
  
  // Final length check
  if (tweet.length > 280) {
    // Trim content if still too long
    const excess = tweet.length - 280;
    const contentEnd = content.lastIndexOf('...');
    if (contentEnd > 0) {
      content = content.substring(0, contentEnd - excess - 3) + '...';
      tweet = `${intro}\n\n${content}\n\n${hashtagStr}\n${cta}`;
      if (mention) tweet = `${mention} ${tweet}`;
    }
  }
  
  return tweet;
}

// Get varied intro phrases
function getIntro() {
  const intros = [
    "Bubbe's got no chill:",
    "Bubbe tells it like it is:",
    "Bubbe's savage wisdom:",
    "Bubbe drops truth bombs:",
    "Bubbe's unfiltered advice:",
    "Bubbe keeps it real:",
    "Bubbe's brutal honesty:",
    "Bubbe roasts everyone:",
    "Bubbe has no filter:",
    "Bubbe's tough love:",
    "Bubbe speaks facts:",
    "Bubbe's reality check:",
    "Bubbe pulls no punches:",
    "Bubbe's hot take:",
    "Bubbe's spicy opinion:"
  ];
  
  return intros[Math.floor(Math.random() * intros.length)];
}

// Anonymize text for privacy
function anonymizeText(text) {
  return text
    .replace(/\b[A-Z][a-z]+\s+[A-Z][a-z]+\b/g, '[NAME]')
    .replace(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, '[PHONE]')
    .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL]')
    .replace(/\$\d+/g, '$[AMOUNT]');
}

module.exports = {
  optimizeTweet,
  selectHashtags,
  selectMention,
  getIntro,
  anonymizeText,
  getTimePeriod,
  VIRAL_HASHTAGS,
  MENTION_ACCOUNTS
};