-
-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathfollowerGrowthTracker.js
More file actions
145 lines (124 loc) · 5.8 KB
/
Copy pathfollowerGrowthTracker.js
File metadata and controls
145 lines (124 loc) · 5.8 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
// Copyright (c) 2024-2026 nich (@nichxbt). Licensed under the Apache License, Version 2.0.
// scripts/followerGrowthTracker.js
// Browser console script for tracking follower growth over time on X/Twitter
// Paste in DevTools console on x.com/USERNAME (any profile page)
// by nichxbt
(() => {
// =============================================
// No config needed — auto-detects profile
// =============================================
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 parseCount = (str) => {
if (!str) return 0;
if (typeof str === 'number') return str;
str = str.replace(/,/g, '').trim();
const m = str.match(/([\d.]+)\s*([KMBkmb])?/);
if (!m) return 0;
let n = parseFloat(m[1]);
if (m[2]) n *= { k: 1e3, m: 1e6, b: 1e9 }[m[2].toLowerCase()] || 1;
return Math.round(n);
};
const fmt = (n) => n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? (n / 1e3).toFixed(1) + 'K' : String(n);
const sign = (n) => n > 0 ? `+${fmt(n)}` : n < 0 ? `-${fmt(Math.abs(n))}` : '0';
const run = () => {
console.log('📈 FOLLOWER GROWTH TRACKER — by nichxbt');
// Detect profile
const pathMatch = window.location.pathname.match(/^\/([A-Za-z0-9_]+)/);
if (!pathMatch || ['home', 'explore', 'notifications', 'messages', 'i', 'settings'].includes(pathMatch[1])) {
console.error('❌ Navigate to a profile page first! (x.com/USERNAME)');
return;
}
const username = pathMatch[1];
// Parse follower/following counts
let followers = 0, following = 0;
for (const link of document.querySelectorAll('a[href*="/followers"], a[href*="/following"]')) {
const href = link.getAttribute('href') || '';
const count = parseCount(link.textContent);
if (href.includes('/following')) following = count;
else if (href.includes('/followers') && !href.includes('/verified')) followers = count;
}
const snapshot = {
username, followers, following,
ratio: following > 0 ? (followers / following).toFixed(3) : '∞',
timestamp: new Date().toISOString(),
epoch: Date.now(),
};
console.log(`\n👤 @${username}`);
console.log(`📊 ${fmt(followers)} followers | ${fmt(following)} following | ratio: ${snapshot.ratio}`);
// Load/save history
const storageKey = `xactions_growth_${username}`;
let history = [];
try { history = JSON.parse(localStorage.getItem(storageKey) || '[]'); } catch {}
// Dedup (skip if last snapshot < 10 min ago)
const last = history[history.length - 1];
if (last && (snapshot.epoch - last.epoch) < 600000) {
console.log('\nℹ️ Snapshot taken within last 10 min. Showing existing data.');
} else {
history.push(snapshot);
localStorage.setItem(storageKey, JSON.stringify(history));
console.log(`\n💾 Snapshot #${history.length} saved.`);
}
if (history.length < 2) {
console.log('\n📋 First snapshot! Run again later to see growth data.');
console.log(' Tip: Run daily for best trendline.\n');
return;
}
// Analysis
const first = history[0];
const prev = history[history.length - 2];
const sinceLast = snapshot.followers - prev.followers;
const hoursSinceLast = ((snapshot.epoch - prev.epoch) / 3600000).toFixed(1);
console.log(`\n🔄 Since last check (${hoursSinceLast}h ago):`);
console.log(` Followers: ${sign(sinceLast)}`);
console.log(` Following: ${sign(snapshot.following - prev.following)}`);
const allTimeGrowth = snapshot.followers - first.followers;
const totalDays = (snapshot.epoch - first.epoch) / 86400000;
console.log(`\n📈 All-time (${totalDays.toFixed(1)} days, ${history.length} snapshots):`);
console.log(` Total growth: ${sign(allTimeGrowth)}`);
if (totalDays > 0) {
const dailyRate = allTimeGrowth / totalDays;
console.log(` Daily avg: ${sign(Math.round(dailyRate))}/day`);
console.log(` Weekly avg: ${sign(Math.round(dailyRate * 7))}/week`);
console.log(` Monthly avg: ${sign(Math.round(dailyRate * 30))}/month`);
// Projections
if (dailyRate > 0) {
console.log('\n🎯 Projections:');
const milestones = [100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000];
for (const goal of milestones) {
if (goal <= snapshot.followers) continue;
const daysTo = (goal - snapshot.followers) / dailyRate;
if (daysTo > 3650) break;
const date = new Date(Date.now() + daysTo * 86400000);
console.log(` ${fmt(goal)}: ~${Math.round(daysTo)} days (${date.toLocaleDateString()})`);
if (goal >= snapshot.followers * 10) break;
}
}
}
// Best/Worst periods
if (history.length >= 3) {
let bestGain = -Infinity, worstLoss = Infinity, bestIdx = 0, worstIdx = 0;
for (let i = 1; i < history.length; i++) {
const diff = history[i].followers - history[i - 1].followers;
if (diff > bestGain) { bestGain = diff; bestIdx = i; }
if (diff < worstLoss) { worstLoss = diff; worstIdx = i; }
}
console.log('\n🏆 Best/Worst:');
console.log(` 📈 Best: ${sign(bestGain)} (${new Date(history[bestIdx].timestamp).toLocaleString()})`);
console.log(` 📉 Worst: ${sign(worstLoss)} (${new Date(history[worstIdx].timestamp).toLocaleString()})`);
}
// Export
if (history.length > 1) {
download(history, `xactions-growth-${username}-${new Date().toISOString().slice(0, 10)}.json`);
}
console.log('\n💡 Run again tomorrow to build your growth trendline.\n');
};
run();
})();