← back to Dear Bubbe Admin
backend/social-post-handler.js
233 lines
const fs = require('fs').promises;
const path = require('path');
// Bubbe intro phrases for variety
const BUBBE_INTROS = [
"Bubbe tells it like it is",
"Bubbe's got no filter",
"Bubbe says what you're thinking",
"Bubbe drops truth bombs",
"Bubbe keeps it real",
"Bubbe's wisdom hits different",
"Bubbe doesn't sugarcoat",
"Bubbe serves reality checks",
"Bubbe's brutal honesty",
"Bubbe speaks facts",
"Bubbe's savage advice",
"Bubbe pulls no punches",
"Bubbe's unfiltered truth",
"Bubbe's raw wisdom",
"Bubbe's tough love",
"Bubbe's straight talk",
"Bubbe's no-nonsense advice",
"Bubbe's reality check",
"Bubbe's harsh truth",
"Bubbe tells you straight"
];
// Name anonymization function
function anonymizeText(text) {
const nameReplacements = {
// Male names
'john': 'David', 'mike': 'Michael', 'steve': 'Robert', 'james': 'William',
'david': 'John', 'robert': 'James', 'michael': 'Thomas', 'william': 'Charles',
'richard': 'Joseph', 'joseph': 'Daniel', 'thomas': 'Paul', 'charles': 'Mark',
// Female names
'mary': 'Sarah', 'jennifer': 'Jessica', 'linda': 'Emma', 'patricia': 'Emily',
'elizabeth': 'Anna', 'susan': 'Rachel', 'jessica': 'Rebecca', 'sarah': 'Hannah',
'karen': 'Michelle', 'nancy': 'Laura', 'betty': 'Amanda', 'dorothy': 'Nicole',
// Spouse/partner references
'my husband': 'my partner', 'my wife': 'my partner',
'my boyfriend': 'my partner', 'my girlfriend': 'my partner'
};
let anonymized = text;
for (const [original, replacement] of Object.entries(nameReplacements)) {
const regex = new RegExp(`\\b${original}\\b`, 'gi');
anonymized = anonymized.replace(regex, replacement);
}
// Remove sensitive info
anonymized = anonymized.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[email]');
anonymized = anonymized.replace(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, '[phone]');
anonymized = anonymized.replace(/\d+\s+[A-Za-z]+\s+(Street|St|Avenue|Ave|Road|Rd)/gi, '[address]');
return anonymized;
}
// Get random intro
function getRandomIntro() {
return BUBBE_INTROS[Math.floor(Math.random() * BUBBE_INTROS.length)];
}
// Format post for Twitter
function formatTwitterPost(userMessage, bubbeResponse) {
const intro = getRandomIntro();
const anonymizedUser = anonymizeText(userMessage).substring(0, 60);
const anonymizedResponse = anonymizeText(bubbeResponse).substring(0, 100);
return `${intro}:\n\n"${anonymizedResponse}..." #BubbeAI #Bubbe #NoFilter #JewishGrandma\n\nVisit Bubbe.AI`;
}
// Format post for Bluesky
function formatBlueskyPost(userMessage, bubbeResponse) {
const intro = getRandomIntro();
const anonymizedResponse = anonymizeText(bubbeResponse).substring(0, 120);
return `${intro}: "${anonymizedResponse}..." #BubbeAI #Bubbe\n\nVisit Bubbe.AI`.substring(0, 300);
}
// Post to Twitter using official API v2
async function postToTwitter(text) {
try {
// Log the attempt
console.log('[TWITTER] Posting via API v2:', text.substring(0, 50) + '...');
// Use the official Twitter API
const { postTweet } = require('/root/Projects/dear-bubbe-nextjs/lib/twitter-v2-official.js');
const result = await postTweet(text);
if (result.success) {
console.log('[TWITTER] Successfully posted! Tweet ID:', result.tweetId);
console.log('[TWITTER] URL:', `https://x.com/DearBubbe/status/${result.tweetId}`);
// Log successful post
const logEntry = {
platform: 'twitter',
text,
timestamp: new Date().toISOString(),
status: 'posted',
tweetId: result.tweetId,
url: `https://x.com/DearBubbe/status/${result.tweetId}`
};
// Save to log file
const logsPath = path.join(__dirname, '../logs/twitter-posts.json');
let logs = [];
try {
logs = JSON.parse(await fs.readFile(logsPath, 'utf8'));
} catch (e) {
// File doesn't exist
}
logs.push(logEntry);
await fs.writeFile(logsPath, JSON.stringify(logs, null, 2));
return true;
} else {
console.error('[TWITTER] Failed to post:', result.error);
return false;
}
} catch (error) {
console.error('[TWITTER] Error:', error);
return false;
}
}
// Post to Bluesky using API or webhook
async function postToBluesky(text) {
try {
// Log the attempt
console.log('[BLUESKY] Posting:', text.substring(0, 50) + '...');
// TODO: Add actual Bluesky API implementation here
// For now, just log it
const logEntry = {
platform: 'bluesky',
text,
timestamp: new Date().toISOString(),
status: 'queued'
};
// Save to queue file
const queuePath = path.join(__dirname, '../logs/bluesky-queue.json');
let queue = [];
try {
queue = JSON.parse(await fs.readFile(queuePath, 'utf8'));
} catch (e) {
// File doesn't exist
}
queue.push(logEntry);
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
return true;
} catch (error) {
console.error('[BLUESKY] Error:', error);
return false;
}
}
// Main handler for immediate posting
async function handleImmediateSocialPost(data) {
const { userMessage, bubbeResponse, messageId, timestamp, userId, location, userName } = data;
console.log(`\n=== IMMEDIATE SOCIAL POST REQUEST ===`);
console.log(`Time: ${timestamp}`);
console.log(`User: ${userName} (${location})`);
console.log(`Message ID: ${messageId}`);
console.log(`User said: "${userMessage.substring(0, 50)}..."`);
console.log(`Bubbe said: "${bubbeResponse.substring(0, 50)}..."`);
// Create formatted posts
const twitterText = formatTwitterPost(userMessage, bubbeResponse);
const blueskyText = formatBlueskyPost(userMessage, bubbeResponse);
// Post to both platforms
const results = {
twitter: false,
bluesky: false
};
// Post to Twitter
results.twitter = await postToTwitter(twitterText);
// Post to Bluesky
results.bluesky = await postToBluesky(blueskyText);
// Log the results
const logEntry = {
timestamp: new Date().toISOString(),
messageId,
userId,
userName,
location,
userSnippet: userMessage.substring(0, 50),
bubbeSnippet: bubbeResponse.substring(0, 50),
twitterPosted: results.twitter,
blueskyPosted: results.bluesky,
twitterText: results.twitter ? twitterText : null,
blueskyText: results.bluesky ? blueskyText : null
};
const logPath = path.join(__dirname, '../logs/immediate-posts.json');
let logs = [];
try {
logs = JSON.parse(await fs.readFile(logPath, 'utf8'));
} catch (e) {
// File doesn't exist
}
logs.push(logEntry);
// Keep only last 100 entries
if (logs.length > 100) {
logs = logs.slice(-100);
}
await fs.writeFile(logPath, JSON.stringify(logs, null, 2));
console.log(`\nResults:`);
console.log(`Twitter: ${results.twitter ? '✅ POSTED' : '❌ FAILED'}`);
console.log(`Bluesky: ${results.bluesky ? '✅ POSTED' : '❌ FAILED'}`);
console.log(`=====================================\n`);
return results;
}
module.exports = {
handleImmediateSocialPost,
formatTwitterPost,
formatBlueskyPost,
anonymizeText
};