← 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
};