384 lines
12 KiB
JavaScript
384 lines
12 KiB
JavaScript
const axios = require('axios');
|
|
const cron = require('node-cron');
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
require('dotenv').config();
|
|
|
|
class SocialMediaBot {
|
|
constructor() {
|
|
this.platforms = {
|
|
instagram: {
|
|
enabled: process.env.INSTAGRAM_ENABLED === 'true',
|
|
username: process.env.INSTAGRAM_USERNAME,
|
|
password: process.env.INSTAGRAM_PASSWORD
|
|
},
|
|
facebook: {
|
|
enabled: process.env.FACEBOOK_ENABLED === 'true',
|
|
accessToken: process.env.FACEBOOK_ACCESS_TOKEN,
|
|
pageId: process.env.FACEBOOK_PAGE_ID
|
|
},
|
|
tiktok: {
|
|
enabled: process.env.TIKTOK_ENABLED === 'true',
|
|
accessToken: process.env.TIKTOK_ACCESS_TOKEN
|
|
},
|
|
mastodon: {
|
|
enabled: process.env.MASTODON_ENABLED === 'true',
|
|
accessToken: process.env.MASTODON_ACCESS_TOKEN,
|
|
apiUrl: process.env.MASTODON_API_URL || 'https://mastodon.social'
|
|
},
|
|
youtube: {
|
|
enabled: process.env.YOUTUBE_ENABLED === 'true',
|
|
apiKey: process.env.YOUTUBE_API_KEY,
|
|
channelId: process.env.YOUTUBE_CHANNEL_ID
|
|
}
|
|
};
|
|
|
|
this.contentTemplates = this.loadContentTemplates();
|
|
}
|
|
|
|
loadContentTemplates() {
|
|
return {
|
|
wedding: [
|
|
{
|
|
text: "💐 Ein traumhafter Tag verdient traumhafte Blumen! Unser neuestes Hochzeitsarrangement vereint Eleganz und Romantik. #Hochzeit #Brautstrauß #FloraleEmotion #Blumen #Hochzeitsfloristik",
|
|
hashtags: ["#Hochzeit", "#Brautstrauß", "#FloraleEmotion", "#Blumen", "#Hochzeitsfloristik", "#Romantik", "#Liebe"]
|
|
},
|
|
{
|
|
text: "🌹 Jede Hochzeit erzählt ihre eigene Geschichte - wir sorgen für die perfekte florale Begleitung. Von der Zeremonie bis zur Feier. #Hochzeitsplanung #Blumendeko #Traumhochzeit",
|
|
hashtags: ["#Hochzeitsplanung", "#Blumendeko", "#Traumhochzeit", "#FloraleEmotion", "#Hochzeitsfloristik"]
|
|
}
|
|
],
|
|
funeral: [
|
|
{
|
|
text: "🕊️ In stillen Momenten des Abschieds begleiten wir Sie mit würdevollen Arrangements. Unsere Trauerfloristik drückt aus, was Worte nicht können. #Trauerfloristik #Abschied #Erinnerung",
|
|
hashtags: ["#Trauerfloristik", "#Abschied", "#Erinnerung", "#FloraleEmotion", "#Würde", "#Respekt"]
|
|
}
|
|
],
|
|
corporate: [
|
|
{
|
|
text: "🏢 Professionelle Events verdienen professionelle Floristik. Wir gestalten Ihre Firmenfeier mit stilvollen Arrangements in Ihren Unternehmensfarben. #CorporateEvents #Firmenfeiern #Eventfloristik",
|
|
hashtags: ["#CorporateEvents", "#Firmenfeiern", "#Eventfloristik", "#FloraleEmotion", "#Business"]
|
|
}
|
|
],
|
|
seasonal: [
|
|
{
|
|
text: "🌸 Der Frühling erwacht und mit ihm die schönsten Blumen der Saison! Lassen Sie sich von der Natur inspirieren. #Frühling #Saisonblumen #Inspiration",
|
|
hashtags: ["#Frühling", "#Saisonblumen", "#Inspiration", "#FloraleEmotion", "#Natur"]
|
|
},
|
|
{
|
|
text: "🍂 Herbstliche Arrangements mit warmen Farben und natürlichen Materialien - perfekt für gemütliche Events. #Herbst #Herbstdeko #Gemütlichkeit",
|
|
hashtags: ["#Herbst", "#Herbstdeko", "#Gemütlichkeit", "#FloraleEmotion", "#Saison"]
|
|
}
|
|
],
|
|
tips: [
|
|
{
|
|
text: "💡 Tipp des Tages: Schnittblumen halten länger, wenn Sie die Stiele schräg anschneiden und täglich frisches Wasser geben. #Blumentipps #Pflege #Ratgeber",
|
|
hashtags: ["#Blumentipps", "#Pflege", "#Ratgeber", "#FloraleEmotion", "#Wissen"]
|
|
},
|
|
{
|
|
text: "🌺 Wussten Sie? Die Farbe der Blumen kann die Stimmung beeinflussen. Warme Töne wirken energetisierend, kühle Farben beruhigend. #Farbpsychologie #Blumen #Wissen",
|
|
hashtags: ["#Farbpsychologie", "#Blumen", "#Wissen", "#FloraleEmotion", "#Psychologie"]
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
async postToInstagram(content, imagePath = null) {
|
|
if (!this.platforms.instagram.enabled) {
|
|
console.log('Instagram posting disabled');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Note: Instagram API requires business account and approval
|
|
// This is a placeholder for the actual Instagram API implementation
|
|
console.log('Posting to Instagram:', content.text);
|
|
|
|
// For actual implementation, you would use Instagram Basic Display API
|
|
// or Instagram Graph API for business accounts
|
|
|
|
return { success: true, platform: 'instagram' };
|
|
} catch (error) {
|
|
console.error('Error posting to Instagram:', error);
|
|
return { success: false, platform: 'instagram', error: error.message };
|
|
}
|
|
}
|
|
|
|
async postToFacebook(content, imagePath = null) {
|
|
if (!this.platforms.facebook.enabled) {
|
|
console.log('Facebook posting disabled');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const url = `https://graph.facebook.com/v18.0/${this.platforms.facebook.pageId}/feed`;
|
|
|
|
const postData = {
|
|
message: content.text,
|
|
access_token: this.platforms.facebook.accessToken
|
|
};
|
|
|
|
if (imagePath) {
|
|
// For image posts, you would need to upload the image first
|
|
// This is a simplified version
|
|
}
|
|
|
|
const response = await axios.post(url, postData);
|
|
console.log('Posted to Facebook:', response.data);
|
|
|
|
return { success: true, platform: 'facebook', postId: response.data.id };
|
|
} catch (error) {
|
|
console.error('Error posting to Facebook:', error);
|
|
return { success: false, platform: 'facebook', error: error.message };
|
|
}
|
|
}
|
|
|
|
async postToMastodon(content, imagePath = null) {
|
|
if (!this.platforms.mastodon.enabled) {
|
|
console.log('Mastodon posting disabled');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const url = `${this.platforms.mastodon.apiUrl}/api/v1/statuses`;
|
|
|
|
const postData = {
|
|
status: content.text + ' ' + content.hashtags.join(' ')
|
|
};
|
|
|
|
const response = await axios.post(url, postData, {
|
|
headers: {
|
|
'Authorization': `Bearer ${this.platforms.mastodon.accessToken}`,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
console.log('Posted to Mastodon:', response.data);
|
|
return { success: true, platform: 'mastodon', postId: response.data.id };
|
|
} catch (error) {
|
|
console.error('Error posting to Mastodon:', error);
|
|
return { success: false, platform: 'mastodon', error: error.message };
|
|
}
|
|
}
|
|
|
|
async postToTikTok(content, videoPath) {
|
|
if (!this.platforms.tiktok.enabled) {
|
|
console.log('TikTok posting disabled');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// TikTok API implementation would go here
|
|
// Note: TikTok for Business API has specific requirements
|
|
console.log('TikTok posting not yet implemented');
|
|
|
|
return { success: false, platform: 'tiktok', error: 'Not implemented' };
|
|
} catch (error) {
|
|
console.error('Error posting to TikTok:', error);
|
|
return { success: false, platform: 'tiktok', error: error.message };
|
|
}
|
|
}
|
|
|
|
getRandomContent(category) {
|
|
const templates = this.contentTemplates[category];
|
|
if (!templates || templates.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return templates[Math.floor(Math.random() * templates.length)];
|
|
}
|
|
|
|
async postToAllPlatforms(content, imagePath = null) {
|
|
const results = [];
|
|
|
|
if (this.platforms.instagram.enabled) {
|
|
results.push(await this.postToInstagram(content, imagePath));
|
|
}
|
|
|
|
if (this.platforms.facebook.enabled) {
|
|
results.push(await this.postToFacebook(content, imagePath));
|
|
}
|
|
|
|
if (this.platforms.mastodon.enabled) {
|
|
results.push(await this.postToMastodon(content, imagePath));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async scheduledPost() {
|
|
console.log('Running scheduled social media post...');
|
|
|
|
// Determine content type based on day of week
|
|
const today = new Date();
|
|
const dayOfWeek = today.getDay();
|
|
|
|
let contentCategory;
|
|
switch (dayOfWeek) {
|
|
case 1: // Monday - Tips
|
|
contentCategory = 'tips';
|
|
break;
|
|
case 3: // Wednesday - Wedding content
|
|
contentCategory = 'wedding';
|
|
break;
|
|
case 5: // Friday - Seasonal content
|
|
contentCategory = 'seasonal';
|
|
break;
|
|
default:
|
|
contentCategory = 'wedding'; // Default to wedding content
|
|
}
|
|
|
|
const content = this.getRandomContent(contentCategory);
|
|
if (!content) {
|
|
console.log('No content available for category:', contentCategory);
|
|
return;
|
|
}
|
|
|
|
const results = await this.postToAllPlatforms(content);
|
|
|
|
// Log results
|
|
results.forEach(result => {
|
|
if (result.success) {
|
|
console.log(`✅ Successfully posted to ${result.platform}`);
|
|
} else {
|
|
console.log(`❌ Failed to post to ${result.platform}: ${result.error}`);
|
|
}
|
|
});
|
|
|
|
// Save posting log
|
|
await this.savePostingLog({
|
|
timestamp: new Date().toISOString(),
|
|
category: contentCategory,
|
|
content: content,
|
|
results: results
|
|
});
|
|
}
|
|
|
|
async savePostingLog(logEntry) {
|
|
try {
|
|
const logFile = path.join(__dirname, 'logs', 'posting-log.json');
|
|
|
|
// Ensure logs directory exists
|
|
await fs.mkdir(path.dirname(logFile), { recursive: true });
|
|
|
|
let logs = [];
|
|
try {
|
|
const existingLogs = await fs.readFile(logFile, 'utf8');
|
|
logs = JSON.parse(existingLogs);
|
|
} catch (error) {
|
|
// File doesn't exist yet, start with empty array
|
|
}
|
|
|
|
logs.push(logEntry);
|
|
|
|
// Keep only last 100 entries
|
|
if (logs.length > 100) {
|
|
logs = logs.slice(-100);
|
|
}
|
|
|
|
await fs.writeFile(logFile, JSON.stringify(logs, null, 2));
|
|
} catch (error) {
|
|
console.error('Error saving posting log:', error);
|
|
}
|
|
}
|
|
|
|
startScheduler() {
|
|
// Post Monday, Wednesday, Friday at 10:00 AM
|
|
cron.schedule('0 10 * * 1,3,5', () => {
|
|
this.scheduledPost();
|
|
}, {
|
|
timezone: "Europe/Berlin"
|
|
});
|
|
|
|
console.log('Social media scheduler started');
|
|
console.log('Posts will be published Monday, Wednesday, Friday at 10:00 AM (Berlin time)');
|
|
}
|
|
|
|
async manualPost(category, platforms = ['all']) {
|
|
const content = this.getRandomContent(category);
|
|
if (!content) {
|
|
throw new Error(`No content available for category: ${category}`);
|
|
}
|
|
|
|
let results = [];
|
|
|
|
if (platforms.includes('all')) {
|
|
results = await this.postToAllPlatforms(content);
|
|
} else {
|
|
for (const platform of platforms) {
|
|
switch (platform) {
|
|
case 'instagram':
|
|
results.push(await this.postToInstagram(content));
|
|
break;
|
|
case 'facebook':
|
|
results.push(await this.postToFacebook(content));
|
|
break;
|
|
case 'mastodon':
|
|
results.push(await this.postToMastodon(content));
|
|
break;
|
|
case 'tiktok':
|
|
results.push(await this.postToTikTok(content));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
await this.savePostingLog({
|
|
timestamp: new Date().toISOString(),
|
|
category: category,
|
|
content: content,
|
|
results: results,
|
|
manual: true
|
|
});
|
|
|
|
return results;
|
|
}
|
|
}
|
|
|
|
// CLI interface
|
|
if (require.main === module) {
|
|
const bot = new SocialMediaBot();
|
|
|
|
const args = process.argv.slice(2);
|
|
const command = args[0];
|
|
|
|
switch (command) {
|
|
case 'start':
|
|
bot.startScheduler();
|
|
break;
|
|
|
|
case 'post':
|
|
const category = args[1] || 'wedding';
|
|
const platforms = args.slice(2);
|
|
|
|
bot.manualPost(category, platforms.length > 0 ? platforms : ['all'])
|
|
.then(results => {
|
|
console.log('Manual post results:', results);
|
|
process.exit(0);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error with manual post:', error);
|
|
process.exit(1);
|
|
});
|
|
break;
|
|
|
|
case 'test':
|
|
console.log('Testing bot configuration...');
|
|
console.log('Platforms configured:');
|
|
Object.entries(bot.platforms).forEach(([platform, config]) => {
|
|
console.log(`${platform}: ${config.enabled ? '✅ Enabled' : '❌ Disabled'}`);
|
|
});
|
|
break;
|
|
|
|
default:
|
|
console.log('Usage:');
|
|
console.log(' node bot.js start - Start the scheduler');
|
|
console.log(' node bot.js post [category] [platforms...] - Manual post');
|
|
console.log(' node bot.js test - Test configuration');
|
|
console.log('');
|
|
console.log('Categories: wedding, funeral, corporate, seasonal, tips');
|
|
console.log('Platforms: instagram, facebook, mastodon, tiktok');
|
|
}
|
|
}
|
|
|
|
module.exports = SocialMediaBot; |