- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
282 lines
8.8 KiB
JavaScript
282 lines
8.8 KiB
JavaScript
/**
|
|
* EasyStream Content Syndication - Node.js Example
|
|
* Extracts video metadata and reposts to other platforms
|
|
*/
|
|
|
|
const axios = require('axios');
|
|
|
|
class EasyStreamSyndicator {
|
|
constructor(apiBaseUrl, jwtToken) {
|
|
this.apiBaseUrl = apiBaseUrl.replace(/\/$/, '');
|
|
this.headers = {
|
|
'Authorization': `Bearer ${jwtToken}`,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
}
|
|
|
|
async getVideos(limit = 50, category = null, sinceDate = null) {
|
|
try {
|
|
const params = { limit };
|
|
if (category) params.category = category;
|
|
if (sinceDate) params.since = sinceDate;
|
|
|
|
const response = await axios.get(`${this.apiBaseUrl}/api/v1/videos`, {
|
|
headers: this.headers,
|
|
params
|
|
});
|
|
|
|
return response.data.data.videos;
|
|
} catch (error) {
|
|
console.error('Error fetching videos:', error.response?.status);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getVideoDetails(videoId) {
|
|
try {
|
|
const response = await axios.get(`${this.apiBaseUrl}/api/v1/videos/${videoId}`, {
|
|
headers: this.headers
|
|
});
|
|
|
|
return response.data.data;
|
|
} catch (error) {
|
|
console.error(`Error fetching video ${videoId}:`, error.response?.status);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async postToYouTube(videoData) {
|
|
// YouTube API integration would go here
|
|
const youtubeData = {
|
|
title: videoData.title,
|
|
description: this.formatDescriptionForYouTube(videoData),
|
|
tags: videoData.tags || [],
|
|
category: this.mapCategoryToYouTube(videoData.category),
|
|
privacy: videoData.privacy === 'public' ? 'public' : 'unlisted'
|
|
};
|
|
|
|
console.log(`📺 Would post to YouTube: ${youtubeData.title}`);
|
|
|
|
// Example: Upload to YouTube using googleapis
|
|
/*
|
|
const youtube = google.youtube({ version: 'v3', auth: oauth2Client });
|
|
const result = await youtube.videos.insert({
|
|
part: 'snippet,status',
|
|
requestBody: {
|
|
snippet: {
|
|
title: youtubeData.title,
|
|
description: youtubeData.description,
|
|
tags: youtubeData.tags,
|
|
categoryId: youtubeData.category
|
|
},
|
|
status: {
|
|
privacyStatus: youtubeData.privacy
|
|
}
|
|
},
|
|
media: {
|
|
body: fs.createReadStream(videoPath)
|
|
}
|
|
});
|
|
*/
|
|
|
|
return youtubeData;
|
|
}
|
|
|
|
async postToTwitter(videoData) {
|
|
// Twitter API integration would go here
|
|
const tweetText = this.formatTweet(videoData);
|
|
|
|
console.log(`🐦 Would tweet: ${tweetText}`);
|
|
|
|
// Example: Post to Twitter using twitter-api-v2
|
|
/*
|
|
const twitterClient = new TwitterApi(bearerToken);
|
|
const tweet = await twitterClient.v2.tweet({
|
|
text: tweetText,
|
|
media: {
|
|
media_ids: [mediaId] // Upload thumbnail first
|
|
}
|
|
});
|
|
*/
|
|
|
|
return { text: tweetText };
|
|
}
|
|
|
|
async postToInstagram(videoData) {
|
|
// Instagram API integration would go here
|
|
const instagramData = {
|
|
caption: this.formatInstagramCaption(videoData),
|
|
media_url: `${this.apiBaseUrl}${videoData.video_url}`,
|
|
thumbnail_url: `${this.apiBaseUrl}${videoData.thumbnail_url}`
|
|
};
|
|
|
|
console.log(`📸 Would post to Instagram: ${videoData.title}`);
|
|
return instagramData;
|
|
}
|
|
|
|
async postToTikTok(videoData) {
|
|
// TikTok API integration would go here
|
|
const tiktokData = {
|
|
title: this.truncateTitleForTikTok(videoData.title),
|
|
description: this.formatDescriptionForTikTok(videoData),
|
|
hashtags: this.extractHashtags(videoData)
|
|
};
|
|
|
|
console.log(`🎵 Would post to TikTok: ${tiktokData.title}`);
|
|
return tiktokData;
|
|
}
|
|
|
|
async syndicateContent(platforms = ['youtube', 'twitter', 'instagram']) {
|
|
console.log('🚀 Starting content syndication...');
|
|
|
|
// Get recent videos (last 24 hours)
|
|
const sinceDate = new Date().toISOString().split('T')[0];
|
|
const videos = await this.getVideos(10, null, sinceDate);
|
|
|
|
console.log(`📹 Found ${videos.length} videos to syndicate`);
|
|
|
|
for (const video of videos) {
|
|
console.log(`\n📝 Processing: ${video.title}`);
|
|
|
|
// Get detailed video info
|
|
const videoDetails = await this.getVideoDetails(video.id);
|
|
if (!videoDetails) continue;
|
|
|
|
// Post to selected platforms
|
|
const promises = [];
|
|
|
|
if (platforms.includes('youtube')) {
|
|
promises.push(this.postToYouTube(videoDetails));
|
|
}
|
|
|
|
if (platforms.includes('twitter')) {
|
|
promises.push(this.postToTwitter(videoDetails));
|
|
}
|
|
|
|
if (platforms.includes('instagram')) {
|
|
promises.push(this.postToInstagram(videoDetails));
|
|
}
|
|
|
|
if (platforms.includes('tiktok')) {
|
|
promises.push(this.postToTikTok(videoDetails));
|
|
}
|
|
|
|
// Execute all platform posts concurrently
|
|
await Promise.all(promises);
|
|
|
|
// Rate limiting
|
|
await this.sleep(2000);
|
|
}
|
|
|
|
console.log('\n✅ Content syndication completed!');
|
|
}
|
|
|
|
// Helper methods
|
|
formatDescriptionForYouTube(videoData) {
|
|
let description = videoData.description;
|
|
|
|
// Add source attribution
|
|
description += `\n\n🎥 Originally posted on EasyStream`;
|
|
description += `\n👤 Creator: ${videoData.uploader.display_name}`;
|
|
description += `\n🔗 Watch original: ${this.apiBaseUrl}${videoData.video_url}`;
|
|
|
|
// Add hashtags
|
|
if (videoData.tags && videoData.tags.length > 0) {
|
|
const hashtags = videoData.tags.slice(0, 10).map(tag => `#${tag}`).join(' ');
|
|
description += `\n\n${hashtags}`;
|
|
}
|
|
|
|
return description;
|
|
}
|
|
|
|
formatTweet(videoData) {
|
|
const title = videoData.title.substring(0, 100);
|
|
const url = `${this.apiBaseUrl}${videoData.video_url}`;
|
|
|
|
// Calculate remaining space for hashtags
|
|
const baseText = `🎥 ${title}\n\n👀 Watch: ${url}`;
|
|
const remaining = 280 - baseText.length - 10; // Buffer
|
|
|
|
const hashtags = this.extractHashtags(videoData, remaining);
|
|
|
|
return `${baseText}\n\n${hashtags}`;
|
|
}
|
|
|
|
formatInstagramCaption(videoData) {
|
|
let caption = `🎥 ${videoData.title}\n\n`;
|
|
caption += `${videoData.description.substring(0, 300)}...\n\n`;
|
|
caption += `👤 By: ${videoData.uploader.display_name}\n`;
|
|
caption += `👀 ${videoData.views} views\n\n`;
|
|
|
|
// Add hashtags
|
|
const hashtags = this.extractHashtags(videoData, 200);
|
|
caption += hashtags;
|
|
|
|
return caption;
|
|
}
|
|
|
|
formatDescriptionForTikTok(videoData) {
|
|
const description = videoData.description.substring(0, 100) + '...';
|
|
const hashtags = this.extractHashtags(videoData, 50);
|
|
return `${description} ${hashtags}`;
|
|
}
|
|
|
|
extractHashtags(videoData, maxLength = 100) {
|
|
const tags = videoData.tags || [];
|
|
const category = videoData.category || '';
|
|
|
|
const hashtags = [];
|
|
if (category) {
|
|
hashtags.push(`#${category}`);
|
|
}
|
|
|
|
for (const tag of tags) {
|
|
const hashtag = `#${tag.replace(/\s+/g, '')}`;
|
|
if (hashtags.join(' ').length + hashtag.length <= maxLength) {
|
|
hashtags.push(hashtag);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hashtags.join(' ');
|
|
}
|
|
|
|
truncateTitleForTikTok(title) {
|
|
return title.length > 50 ? title.substring(0, 50) + '...' : title;
|
|
}
|
|
|
|
mapCategoryToYouTube(category) {
|
|
const categoryMap = {
|
|
'gaming': '20',
|
|
'music': '10',
|
|
'education': '27',
|
|
'entertainment': '24',
|
|
'sports': '17',
|
|
'tech': '28'
|
|
};
|
|
return categoryMap[category] || '24'; // Default to Entertainment
|
|
}
|
|
|
|
sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
}
|
|
|
|
// Usage example
|
|
async function main() {
|
|
const API_BASE_URL = "https://yourdomain.com";
|
|
const JWT_TOKEN = "your_jwt_token_here";
|
|
|
|
const syndicator = new EasyStreamSyndicator(API_BASE_URL, JWT_TOKEN);
|
|
|
|
// Syndicate to selected platforms
|
|
await syndicator.syndicateContent(['youtube', 'twitter', 'instagram', 'tiktok']);
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
main().catch(console.error);
|
|
}
|
|
|
|
module.exports = EasyStreamSyndicator; |