Content calendars are broken.
You spend Sunday night staring at an empty spreadsheet. "What should I post tomorrow?" becomes existential dread.
Meanwhile, your competitors post consistently. Their content performs. They've figured something out.
What if you could reverse-engineer their strategy and generate a week of content ideas in 30 seconds?
In this tutorial, we'll build an AI Content Calendar Generator that:
- Analyzes your competitors' top-performing content
- Identifies winning themes, formats, and posting patterns
- Generates a personalized 7-day content calendar
No more blank page syndrome.
The Strategy Behind This
Great content calendars aren't creativeβthey're strategic.
Top creators:
- Post at consistent times (their audience expects it)
- Repeat formats that work (series, templates, hooks)
- Ride trends while they're still fresh
- Balance content types (educational, entertaining, promotional)
We're going to extract these patterns automatically.
The Stack
- Node.js: Runtime
- SociaVault API: To analyze competitor content
- OpenAI API: To generate calendar recommendations
Step 1: Setup
mkdir content-calendar-generator
cd content-calendar-generator
npm init -y
npm install axios openai dotenv
Create .env:
SOCIAVAULT_API_KEY=your_sociavault_key
OPENAI_API_KEY=your_openai_key
Step 2: Competitor Content Fetcher
Create index.js:
require('dotenv').config();
const axios = require('axios');
const OpenAI = require('openai');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const SOCIAVAULT_BASE = 'https://api.sociavault.com';
const headers = { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` };
// Fetch TikTok content
async function getTikTokContent(handle) {
console.log(`π₯ Fetching TikTok content from @${handle}...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/videos`, {
params: { handle, amount: 50 },
headers
});
const videos = response.data.data || [];
return videos.map(v => ({
platform: 'tiktok',
description: v.desc || v.description || '',
likes: v.diggCount || v.stats?.diggCount || 0,
comments: v.commentCount || v.stats?.commentCount || 0,
shares: v.shareCount || v.stats?.shareCount || 0,
views: v.playCount || v.stats?.playCount || 0,
created: v.createTime ? new Date(v.createTime * 1000) : null,
music: v.music?.title || 'Original Sound',
hashtags: v.challenges?.map(c => c.title) || extractHashtags(v.desc || '')
}));
} catch (error) {
console.error('TikTok error:', error.message);
return [];
}
}
// Fetch Instagram content
async function getInstagramContent(handle) {
console.log(`π₯ Fetching Instagram content from @${handle}...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/posts`, {
params: { handle },
headers
});
const posts = response.data.data || [];
return posts.map(p => ({
platform: 'instagram',
description: p.caption || '',
likes: p.like_count || p.likes || 0,
comments: p.comment_count || p.comments || 0,
type: p.media_type, // IMAGE, VIDEO, CAROUSEL_ALBUM
created: p.taken_at ? new Date(p.taken_at * 1000) : null,
hashtags: extractHashtags(p.caption || '')
}));
} catch (error) {
console.error('Instagram error:', error.message);
return [];
}
}
// Fetch Twitter content
async function getTwitterContent(handle) {
console.log(`π₯ Fetching Twitter content from @${handle}...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/twitter/user-tweets`, {
params: { handle },
headers
});
const tweets = response.data.data || [];
return tweets.map(t => ({
platform: 'twitter',
description: t.full_text || t.text || '',
likes: t.favorite_count || t.likes || 0,
retweets: t.retweet_count || 0,
replies: t.reply_count || 0,
views: t.views_count || t.views || 0,
created: t.created_at ? new Date(t.created_at) : null,
hashtags: extractHashtags(t.full_text || t.text || '')
}));
} catch (error) {
console.error('Twitter error:', error.message);
return [];
}
}
// Fetch LinkedIn content
async function getLinkedInContent(profileUrl) {
console.log(`π₯ Fetching LinkedIn content...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/linkedin/profile`, {
params: { url: profileUrl },
headers
});
const profile = response.data.data;
const posts = profile.posts || [];
return posts.map(p => ({
platform: 'linkedin',
description: p.text || p.commentary || '',
likes: p.num_likes || p.reactions || 0,
comments: p.num_comments || 0,
reposts: p.num_reposts || 0,
created: p.posted_on?.day ? new Date(p.posted_on.year, p.posted_on.month - 1, p.posted_on.day) : null,
hashtags: extractHashtags(p.text || '')
}));
} catch (error) {
console.error('LinkedIn error:', error.message);
return [];
}
}
function extractHashtags(text) {
const matches = text.match(/#[\w]+/g) || [];
return matches.map(h => h.replace('#', ''));
}
Step 3: Content Analysis Engine
function analyzeContentPatterns(content) {
if (content.length === 0) {
return { error: 'No content to analyze' };
}
// Sort by engagement
const sorted = [...content].sort((a, b) => {
const aEng = (a.likes || 0) + (a.comments || 0) * 2 + (a.shares || a.retweets || 0) * 3;
const bEng = (b.likes || 0) + (b.comments || 0) * 2 + (b.shares || b.retweets || 0) * 3;
return bEng - aEng;
});
// Top performers (top 20%)
const topCount = Math.max(Math.floor(content.length * 0.2), 3);
const topPerformers = sorted.slice(0, topCount);
// Posting frequency analysis
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const postsByDay = {};
const postsByHour = {};
content.forEach(c => {
if (c.created) {
const day = daysOfWeek[c.created.getDay()];
const hour = c.created.getHours();
postsByDay[day] = (postsByDay[day] || 0) + 1;
postsByHour[hour] = (postsByHour[hour] || 0) + 1;
}
});
// Find most active posting times
const sortedDays = Object.entries(postsByDay).sort((a, b) => b[1] - a[1]);
const sortedHours = Object.entries(postsByHour).sort((a, b) => b[1] - a[1]);
// Hashtag analysis
const hashtagCounts = {};
content.forEach(c => {
(c.hashtags || []).forEach(h => {
hashtagCounts[h.toLowerCase()] = (hashtagCounts[h.toLowerCase()] || 0) + 1;
});
});
const topHashtags = Object.entries(hashtagCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([tag]) => tag);
// Content length analysis
const avgLength = content.reduce((sum, c) => sum + (c.description?.length || 0), 0) / content.length;
const topAvgLength = topPerformers.reduce((sum, c) => sum + (c.description?.length || 0), 0) / topPerformers.length;
return {
totalPosts: content.length,
topPerformers,
postingPattern: {
bestDays: sortedDays.slice(0, 3),
bestHours: sortedHours.slice(0, 3),
postsPerWeek: Math.round(content.length / 4) // Assuming ~1 month of data
},
topHashtags,
contentLength: {
average: Math.round(avgLength),
topPerformerAverage: Math.round(topAvgLength)
}
};
}
Step 4: AI Calendar Generator
async function generateContentCalendar(analysis, niche, platform) {
console.log('π€ Generating content calendar...');
const topContent = analysis.topPerformers.map(c => ({
description: c.description?.substring(0, 200),
engagement: c.likes + (c.comments * 2)
}));
const prompt = `
You are a social media strategist. Create a 7-day content calendar.
CONTEXT:
- Platform: ${platform}
- Niche: ${niche}
- Best posting days: ${analysis.postingPattern.bestDays.map(d => d[0]).join(', ')}
- Best posting hours: ${analysis.postingPattern.bestHours.map(h => h[0] + ':00').join(', ')}
- Top hashtags in niche: ${analysis.topHashtags.join(', ')}
- Top performing caption length: ~${analysis.contentLength.topPerformerAverage} characters
TOP PERFORMING CONTENT FROM COMPETITORS:
${JSON.stringify(topContent, null, 2)}
CALENDAR REQUIREMENTS:
1. One post per day for 7 days
2. Mix of content types (educational, entertaining, promotional, engagement-bait)
3. Include specific post ideas, not generic advice
4. Suggest optimal posting time for each day
5. Include relevant hashtags for each post
6. Make hooks attention-grabbing
Return JSON:
{
"calendar": [
{
"day": "Monday",
"postTime": "9:00 AM",
"contentType": "educational|entertaining|promotional|engagement",
"hook": "first line that grabs attention",
"fullCaption": "complete caption ready to post",
"hashtags": ["relevant", "hashtags"],
"callToAction": "what you want viewers to do",
"notes": "why this will work"
}
],
"strategy": {
"weeklyTheme": "overarching theme for the week",
"contentMix": {
"educational": "X posts",
"entertaining": "X posts",
"promotional": "X posts",
"engagement": "X posts"
},
"keyInsights": ["3 key insights from competitor analysis"]
}
}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
Step 5: The Main Generator
async function createContentCalendar(config) {
const { platform, competitors, niche } = config;
console.log('\nπ
AI CONTENT CALENDAR GENERATOR\n');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
// 1. Fetch competitor content
let allContent = [];
for (const competitor of competitors) {
let content;
switch (platform) {
case 'tiktok':
content = await getTikTokContent(competitor);
break;
case 'instagram':
content = await getInstagramContent(competitor);
break;
case 'twitter':
content = await getTwitterContent(competitor);
break;
case 'linkedin':
content = await getLinkedInContent(competitor);
break;
}
if (content.length > 0) {
console.log(` β
@${competitor}: ${content.length} posts`);
allContent = [...allContent, ...content];
}
// Rate limiting
await new Promise(r => setTimeout(r, 1000));
}
console.log(`\nπ Total content analyzed: ${allContent.length} posts\n`);
// 2. Analyze patterns
const analysis = analyzeContentPatterns(allContent);
console.log('βββββββββββββββββββββββββββββββββββββββββββ');
console.log('π COMPETITOR INSIGHTS');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log('Best Posting Days:');
analysis.postingPattern.bestDays.forEach(([day, count]) => {
console.log(` β’ ${day}: ${count} posts`);
});
console.log('\nBest Posting Hours:');
analysis.postingPattern.bestHours.forEach(([hour, count]) => {
const time = hour < 12 ? `${hour}:00 AM` : `${hour - 12}:00 PM`;
console.log(` β’ ${time}: ${count} posts`);
});
console.log('\nTop Hashtags:');
console.log(` ${analysis.topHashtags.slice(0, 5).map(h => `#${h}`).join(' ')}`);
console.log(`\nOptimal Caption Length: ~${analysis.contentLength.topPerformerAverage} characters`);
// 3. Generate calendar
const calendar = await generateContentCalendar(analysis, niche, platform);
// 4. Display calendar
console.log('\nβββββββββββββββββββββββββββββββββββββββββββ');
console.log('π
YOUR 7-DAY CONTENT CALENDAR');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
calendar.calendar.forEach((day, i) => {
console.log(`βββββββββββββββββββββββββββββββββββββββββββ`);
console.log(`β ${day.day.toUpperCase().padEnd(39)}β`);
console.log(`βββββββββββββββββββββββββββββββββββββββββββ€`);
console.log(`β π ${day.postTime.padEnd(36)}β`);
console.log(`β π ${day.contentType.toUpperCase().padEnd(36)}β`);
console.log(`βββββββββββββββββββββββββββββββββββββββββββ`);
console.log(`\nπͺ HOOK:`);
console.log(`"${day.hook}"\n`);
console.log(`π FULL CAPTION:`);
console.log(`${day.fullCaption}\n`);
console.log(`#οΈβ£ ${day.hashtags.map(h => `#${h}`).join(' ')}\n`);
console.log(`π CTA: ${day.callToAction}`);
console.log(`π‘ Why: ${day.notes}\n`);
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
});
// 5. Strategy summary
console.log('βββββββββββββββββββββββββββββββββββββββββββ');
console.log('π WEEKLY STRATEGY');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log(`Theme: ${calendar.strategy.weeklyTheme}\n`);
console.log('Content Mix:');
Object.entries(calendar.strategy.contentMix).forEach(([type, count]) => {
console.log(` β’ ${type}: ${count}`);
});
console.log('\nKey Insights:');
calendar.strategy.keyInsights.forEach((insight, i) => {
console.log(` ${i + 1}. ${insight}`);
});
return { analysis, calendar };
}
Step 6: Run It
async function main() {
await createContentCalendar({
platform: 'tiktok',
competitors: [
'garyvee',
'alexhormozi',
'noahkagan'
],
niche: 'entrepreneurship and business advice'
});
}
main();
Run with:
node index.js
Sample Output
π
AI CONTENT CALENDAR GENERATOR
βββββββββββββββββββββββββββββββββββββββββββ
π₯ Fetching TikTok content from @garyvee...
β
@garyvee: 50 posts
π₯ Fetching TikTok content from @alexhormozi...
β
@alexhormozi: 50 posts
π₯ Fetching TikTok content from @noahkagan...
β
@noahkagan: 50 posts
π Total content analyzed: 150 posts
βββββββββββββββββββββββββββββββββββββββββββ
π COMPETITOR INSIGHTS
βββββββββββββββββββββββββββββββββββββββββββ
Best Posting Days:
β’ Tuesday: 28 posts
β’ Wednesday: 24 posts
β’ Thursday: 22 posts
Best Posting Hours:
β’ 9:00 AM: 32 posts
β’ 12:00 PM: 28 posts
β’ 6:00 PM: 25 posts
Top Hashtags:
#business #entrepreneur #mindset #motivation #success
Optimal Caption Length: ~145 characters
π€ Generating content calendar...
βββββββββββββββββββββββββββββββββββββββββββ
π
YOUR 7-DAY CONTENT CALENDAR
βββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
β MONDAY β
βββββββββββββββββββββββββββββββββββββββββββ€
β π 9:00 AM β
β π EDUCATIONAL β
βββββββββββββββββββββββββββββββββββββββββββ
πͺ HOOK:
"The #1 reason your business isn't growing (it's not what you think)"
π FULL CAPTION:
Most people blame their marketing.
But I've seen 1000+ businesses and the real problem is simpler:
You're solving a problem nobody cares about.
Before you spend another dollar on ads, ask 10 potential customers:
"Would you pay $100 to solve this problem?"
If less than 7 say yes, pivot.
Comment "PROBLEM" and I'll help you find yours.
#οΈβ£ #business #entrepreneur #startuptips #businessadvice #growth
π CTA: Comment "PROBLEM" for personalized feedback
π‘ Why: Question-based hooks drove 3x engagement in competitor data. Educational content performs best on Mondays.
βββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
β TUESDAY β
βββββββββββββββββββββββββββββββββββββββββββ€
β π 12:00 PM β
β π ENTERTAINING β
βββββββββββββββββββββββββββββββββββββββββββ
πͺ HOOK:
"POV: You just quit your 9-5 to start a business"
π FULL CAPTION:
Day 1: Freedom! I'm my own boss!
Day 7: Wait, I have to do EVERYTHING?
Day 30: Why did no one tell me about taxes?
Day 90: Finally made $1
Day 180: Starting to figure this out
Day 365: Would never go back
The first year is survival. The second year is growth.
Tag someone who needs to see this π
#οΈβ£ #entrepreneurlife #startup #business #quityourjob #hustle
π CTA: Tag a friend starting their journey
π‘ Why: POV format is trending. Relatable struggle content gets high shares.
βββββββββββββββββββββββββββββββββββββββββββ
[... continues for 7 days ...]
βββββββββββββββββββββββββββββββββββββββββββ
π WEEKLY STRATEGY
βββββββββββββββββββββββββββββββββββββββββββ
Theme: "From Employee to Entrepreneur" - A week of content for people considering or starting their business journey
Content Mix:
β’ educational: 3 posts
β’ entertaining: 2 posts
β’ promotional: 1 post
β’ engagement: 1 post
Key Insights:
1. Question-based hooks outperform statements by 2.3x in this niche
2. Competitors post most frequently Tue-Thu, suggesting higher engagement windows
3. Personal story content (failures, lessons) drives 40% more comments than pure advice
Export to Notion/Google Sheets
function exportToCSV(calendar) {
const headers = 'Day,Time,Type,Hook,Caption,Hashtags,CTA\n';
const rows = calendar.calendar.map(day =>
`"${day.day}","${day.postTime}","${day.contentType}","${day.hook.replace(/"/g, '""')}","${day.fullCaption.replace(/"/g, '""')}","${day.hashtags.join(' ')}","${day.callToAction}"`
).join('\n');
const fs = require('fs');
fs.writeFileSync('content-calendar.csv', headers + rows);
console.log('\nβ
Exported to content-calendar.csv');
}
Adding More Competitors
// Analyze multiple platforms at once
async function multiPlatformCalendar(config) {
const results = {};
for (const [platform, competitors] of Object.entries(config.platforms)) {
console.log(`\nπ± Analyzing ${platform}...`);
results[platform] = await createContentCalendar({
platform,
competitors,
niche: config.niche
});
}
return results;
}
// Usage
multiPlatformCalendar({
niche: 'fitness and wellness',
platforms: {
tiktok: ['blogilates', 'sydneycummings'],
instagram: ['kaikiakoe', 'whitneyysimmons'],
twitter: ['mindpumpmedia']
}
});
Cost Comparison
Content planning services:
- Later: $25/month (just scheduling)
- Hootsuite: $99/month (basic analytics)
- Sprout Social: $249/month (competitor analysis)
- Content agency: $2,000+/month
Your version:
- SociaVault: ~$0.15 per competitor analysis
- OpenAI: ~$0.03 per calendar generation
- Total: ~$0.50 per week of content
That's 500x cheaper than hiring an agency.
What You Just Built
This is literally what social media managers do for 40 hours a week:
- Research competitors
- Identify what works
- Plan content calendar
- Write captions and hooks
You just automated the entire process.
Get Started
- Get your SociaVault API Key
- Add your OpenAI key
- List your top 3 competitors
- Generate a week of content in 30 seconds
Writer's block is now a solved problem.
The best content strategies aren't creative, they're analytical.
Top comments (0)