feat: Add comprehensive documentation suite and reorganize project structure
- 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
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user