← back to Dear Bubbe Nextjs
lib/twitter-oauth.js
158 lines
const https = require('https');
const OAuth = require('oauth-1.0a');
const crypto = require('crypto');
const fs = require('fs').promises;
const querystring = require('querystring');
// Load config
const config = require('./twitter-api-config.json');
// OAuth configuration
const oauth = OAuth({
consumer: {
key: config.appKey,
secret: config.appSecret
},
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto.createHmac('sha1', key).update(base_string).digest('base64');
}
});
const token = {
key: config.accessToken,
secret: config.accessSecret
};
async function postTweet(text) {
console.log('🐦 Posting via OAuth 1.0a (Twitter API v1.1)...');
console.log('Tweet:', text.substring(0, 80) + '...\n');
return new Promise((resolve, reject) => {
const request_data = {
url: 'https://api.twitter.com/1.1/statuses/update.json',
method: 'POST',
data: { status: text }
};
const authHeader = oauth.toHeader(oauth.authorize(request_data, token));
const postData = querystring.stringify(request_data.data);
const options = {
hostname: 'api.twitter.com',
path: '/1.1/statuses/update.json',
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
try {
const tweet = JSON.parse(data);
console.log('✅ Tweet posted successfully!');
console.log('Tweet ID:', tweet.id_str);
console.log('URL: https://x.com/DearBubbe/status/' + tweet.id_str);
resolve({ success: true, tweetId: tweet.id_str });
} catch (e) {
console.error('❌ Failed to parse response');
resolve({ success: false, error: 'Parse error' });
}
} else {
console.error('❌ Error:', res.statusCode, data);
resolve({ success: false, error: data });
}
});
});
req.on('error', (e) => {
console.error('❌ Request error:', e.message);
reject(e);
});
req.write(postData);
req.end();
});
}
async function postFromQueue() {
try {
const queuePath = '/root/Projects/dear-bubbe-admin/logs/twitter-queue.json';
const queue = JSON.parse(await fs.readFile(queuePath, 'utf8'));
const toPost = queue.find(t => t.status === 'queued');
if (!toPost) {
console.log('No posts in queue');
return false;
}
console.log('📤 Posting from queue...');
const result = await postTweet(toPost.text);
if (result.success) {
toPost.status = 'posted';
toPost.postedAt = new Date().toISOString();
toPost.tweetId = result.tweetId;
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
console.log('✅ Queue updated');
// Log to immediate posts
const immediateLogsPath = '/root/Projects/dear-bubbe-admin/logs/immediate-posts.json';
try {
const logs = JSON.parse(await fs.readFile(immediateLogsPath, 'utf8'));
logs.push({
timestamp: new Date().toISOString(),
platform: 'twitter',
status: 'posted',
tweetId: result.tweetId,
text: toPost.text,
url: `https://x.com/DearBubbe/status/${result.tweetId}`
});
await fs.writeFile(immediateLogsPath, JSON.stringify(logs, null, 2));
} catch (e) {
// Log file might not exist
}
}
return result.success;
} catch (error) {
console.error('Error:', error);
return false;
}
}
async function postAll() {
console.log('📬 Posting all queued tweets...\n');
let posted = 0;
while (true) {
const success = await postFromQueue();
if (!success) break;
posted++;
// Wait 5 seconds between posts to avoid rate limits
console.log('Waiting 5 seconds before next post...\n');
await new Promise(r => setTimeout(r, 5000));
}
console.log(`\n✅ Posted ${posted} tweets total`);
return posted;
}
module.exports = { postTweet, postFromQueue, postAll };
if (require.main === module) {
if (process.argv[2] === 'test') {
postTweet('Bubbe is back! The API is working, bubbeleh! #BubbeAI Visit Bubbe.AI').then(console.log);
} else if (process.argv[2] === 'all') {
postAll();
} else {
postFromQueue();
}
}