-
-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathcreatorStudio.js
More file actions
199 lines (169 loc) · 8.44 KB
/
Copy pathcreatorStudio.js
File metadata and controls
199 lines (169 loc) · 8.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright (c) 2024-2026 nich (@nichxbt). Licensed under the Apache License, Version 2.0.
// scripts/creatorStudio.js
// Browser console script for analyzing account analytics and content performance
// Paste in DevTools console on x.com/USERNAME (your profile)
// by nichxbt
(() => {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// =============================================
// CONFIGURATION
// =============================================
const CONFIG = {
maxPosts: 30,
scrollDelay: 1500,
exportResults: true,
};
// =============================================
const download = (data, filename) => {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }));
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
console.log(`📥 Downloaded: ${filename}`);
};
const parseMetric = (text) => {
if (!text) return 0;
text = text.trim().replace(/,/g, '');
if (text.endsWith('K')) return Math.round(parseFloat(text) * 1000);
if (text.endsWith('M')) return Math.round(parseFloat(text) * 1000000);
return parseInt(text) || 0;
};
const classifyPost = (article) => {
if (article.querySelector('[data-testid="tweetPhoto"]')) return 'image';
if (article.querySelector('video')) return 'video';
if (article.querySelector('[data-testid="card.wrapper"]')) return 'link';
if (article.querySelector('[data-testid="TextPoll"]')) return 'poll';
return 'text';
};
const run = async () => {
console.log('🎨 Creator Studio — Account Analytics');
console.log('━'.repeat(50));
// Extract profile stats
const profileStats = (() => {
const followersEl = document.querySelector('a[href$="/verified_followers"] span, a[href$="/followers"] span');
const followingEl = document.querySelector('a[href$="/following"] span');
const nameEl = document.querySelector('[data-testid="UserName"]');
return {
name: nameEl?.textContent?.trim() || 'Unknown',
followers: followersEl?.textContent?.trim() || '0',
following: followingEl?.textContent?.trim() || '0',
};
})();
console.log(`👤 ${profileStats.name}`);
console.log(`👥 Followers: ${profileStats.followers} | Following: ${profileStats.following}`);
// Collect posts
const posts = [];
const seen = new Set();
let scrollRounds = 0;
const maxRounds = Math.ceil(CONFIG.maxPosts / 3) + 2;
while (posts.length < CONFIG.maxPosts && scrollRounds < maxRounds) {
const articles = document.querySelectorAll('article[data-testid="tweet"]');
for (const article of articles) {
if (posts.length >= CONFIG.maxPosts) break;
const linkEl = article.querySelector('a[href*="/status/"]');
const href = linkEl?.href || '';
if (!href || seen.has(href)) continue;
seen.add(href);
const timeEl = article.querySelector('time');
const datetime = timeEl?.getAttribute('datetime') || '';
const tweetText = article.querySelector('[data-testid="tweetText"]')?.textContent?.trim() || '';
const likes = parseMetric(article.querySelector('[data-testid="like"] span')?.textContent);
const reposts = parseMetric(article.querySelector('[data-testid="retweet"] span')?.textContent);
const replies = parseMetric(article.querySelector('[data-testid="reply"] span')?.textContent);
const bookmarks = parseMetric(article.querySelector('[data-testid="bookmark"] span')?.textContent);
const views = parseMetric(article.querySelector('a[href*="/analytics"] span')?.textContent);
const type = classifyPost(article);
const totalEngagement = likes + reposts + replies + bookmarks;
const engagementRate = views > 0 ? ((totalEngagement / views) * 100) : 0;
posts.push({
url: href,
text: tweetText.slice(0, 120),
datetime,
type,
likes,
reposts,
replies,
bookmarks,
views,
totalEngagement,
engagementRate,
});
}
console.log(`📜 Scroll ${scrollRounds + 1}: ${posts.length}/${CONFIG.maxPosts} posts collected`);
window.scrollTo(0, document.body.scrollHeight);
await sleep(CONFIG.scrollDelay);
scrollRounds++;
}
if (posts.length === 0) {
console.error('❌ No posts found. Make sure you are on a profile page with tweets.');
return;
}
console.log(`\n✅ Collected ${posts.length} posts. Analyzing...\n`);
// --- Compute Stats ---
const avgEngRate = posts.reduce((s, p) => s + p.engagementRate, 0) / posts.length;
const avgLikes = posts.reduce((s, p) => s + p.likes, 0) / posts.length;
const avgReposts = posts.reduce((s, p) => s + p.reposts, 0) / posts.length;
const avgReplies = posts.reduce((s, p) => s + p.replies, 0) / posts.length;
const totalViews = posts.reduce((s, p) => s + p.views, 0);
console.log('━━━ 📊 ENGAGEMENT OVERVIEW ━━━');
console.log(` Avg engagement rate: ${avgEngRate.toFixed(2)}%`);
console.log(` Avg likes: ${avgLikes.toFixed(1)} | Avg reposts: ${avgReposts.toFixed(1)} | Avg replies: ${avgReplies.toFixed(1)}`);
console.log(` Total views across ${posts.length} posts: ${totalViews.toLocaleString()}`);
// --- Best post type ---
const typeMap = {};
for (const p of posts) {
if (!typeMap[p.type]) typeMap[p.type] = { count: 0, totalEng: 0, totalRate: 0 };
typeMap[p.type].count++;
typeMap[p.type].totalEng += p.totalEngagement;
typeMap[p.type].totalRate += p.engagementRate;
}
console.log('\n━━━ 🏷️ PERFORMANCE BY POST TYPE ━━━');
const typeEntries = Object.entries(typeMap).sort((a, b) => (b[1].totalRate / b[1].count) - (a[1].totalRate / a[1].count));
for (const [type, data] of typeEntries) {
const avg = (data.totalRate / data.count).toFixed(2);
const avgEng = (data.totalEng / data.count).toFixed(1);
const bar = '█'.repeat(Math.round(parseFloat(avg) * 5));
console.log(` ${type.padEnd(8)} — ${data.count} posts, avg rate: ${avg}%, avg engagement: ${avgEng} ${bar}`);
}
const bestType = typeEntries[0]?.[0] || 'text';
console.log(` 🏆 Best performing type: ${bestType}`);
// --- Posting frequency ---
const dates = posts.filter(p => p.datetime).map(p => new Date(p.datetime));
if (dates.length >= 2) {
dates.sort((a, b) => a - b);
const spanDays = (dates[dates.length - 1] - dates[0]) / 86400000;
const postsPerDay = spanDays > 0 ? (dates.length / spanDays).toFixed(2) : dates.length;
console.log(`\n━━━ 📅 POSTING FREQUENCY ━━━`);
console.log(` Period: ${spanDays.toFixed(0)} days`);
console.log(` Rate: ~${postsPerDay} posts/day`);
if (postsPerDay < 1) console.log(' 💡 Consider posting at least 1-2x daily for growth.');
else if (postsPerDay > 8) console.log(' 💡 Very active! Focus on quality over quantity.');
else console.log(' ✅ Healthy posting cadence.');
}
// --- Top posts ---
const topPosts = [...posts].sort((a, b) => b.totalEngagement - a.totalEngagement).slice(0, 5);
console.log('\n━━━ 🔥 TOP 5 POSTS ━━━');
for (const p of topPosts) {
console.log(` [${p.type}] ${p.totalEngagement} eng (${p.engagementRate.toFixed(2)}%) — "${p.text.slice(0, 60)}..."`);
}
// --- Growth indicator ---
console.log('\n━━━ 📈 GROWTH INDICATORS ━━━');
const growthMsg = avgEngRate > 5 ? '🚀 Excellent — strong growth' : avgEngRate > 2 ? '📈 Good — steady growth' : avgEngRate > 0.5 ? '📊 Average — focus on top types' : '⚠️ Low — experiment more';
console.log(` ${growthMsg}. Best type: "${bestType}" — post more of it!`);
// --- Export ---
if (CONFIG.exportResults) {
const report = {
profile: profileStats,
summary: { totalPosts: posts.length, avgEngagementRate: avgEngRate.toFixed(2) + '%', avgLikes: avgLikes.toFixed(1), totalViews, bestPostType: bestType },
topPosts: topPosts.map(p => ({ url: p.url, type: p.type, engagement: p.totalEngagement, rate: p.engagementRate.toFixed(2) + '%' })),
posts,
analyzedAt: new Date().toISOString(),
};
download(report, `xactions-creator-studio-${new Date().toISOString().slice(0, 10)}.json`);
}
console.log('\n✅ Creator Studio analysis complete.');
};
run();
})();