-
-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathcontinuousMonitor.js
More file actions
145 lines (123 loc) · 5.06 KB
/
Copy pathcontinuousMonitor.js
File metadata and controls
145 lines (123 loc) · 5.06 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/continuousMonitor.js
// Browser console script for continuous auto-refresh monitoring of followers/following
// Paste in DevTools console on x.com/USERNAME/followers or /following
// by nichxbt
(() => {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// =============================================
// CONFIGURATION
// =============================================
const CONFIG = {
checkIntervalMinutes: 30,
enableNotifications: true,
autoScroll: true,
};
// =============================================
const path = window.location.pathname;
const isFollowers = path.includes('/followers');
const isFollowing = path.includes('/following');
if (!isFollowers && !isFollowing) {
console.error('❌ Navigate to a /followers or /following page first!');
return;
}
const pageType = isFollowers ? 'followers' : 'following';
const targetUser = path.split('/')[1].toLowerCase();
const storageKey = `xactions_continuous_${targetUser}_${pageType}`;
if (CONFIG.enableNotifications && 'Notification' in window) {
Notification.requestPermission();
}
const notify = (title, body) => {
console.log(`🔔 ${title}: ${body}`);
if (CONFIG.enableNotifications && Notification.permission === 'granted') {
new Notification(title, { body });
}
};
const scrapeUsers = async () => {
const users = new Set();
let prevSize = 0;
let retries = 0;
if (CONFIG.autoScroll) {
while (retries < 3) {
window.scrollTo(0, document.body.scrollHeight);
await sleep(1500);
document.querySelectorAll('[data-testid="UserCell"]').forEach(cell => {
const link = cell.querySelector('a[href^="/"]');
if (link) {
const username = link.getAttribute('href').replace('/', '').split('/')[0].toLowerCase();
if (username && username !== targetUser) users.add(username);
}
});
if (users.size === prevSize) retries++;
else { retries = 0; prevSize = users.size; }
}
} else {
document.querySelectorAll('[data-testid="UserCell"]').forEach(cell => {
const link = cell.querySelector('a[href^="/"]');
if (link) {
const username = link.getAttribute('href').replace('/', '').split('/')[0].toLowerCase();
if (username && username !== targetUser) users.add(username);
}
});
}
return [...users];
};
const loadPrevious = () => {
try { return JSON.parse(localStorage.getItem(storageKey)); } catch { return null; }
};
const saveData = (users) => {
const data = { target: targetUser, type: pageType, users, timestamp: new Date().toISOString(), count: users.length };
localStorage.setItem(storageKey, JSON.stringify(data));
return data;
};
let checkCount = 0;
const runCheck = async () => {
checkCount++;
const time = new Date().toLocaleTimeString();
console.log(`\n⏰ [${time}] Check #${checkCount} — Scanning @${targetUser}'s ${pageType}...`);
window.scrollTo(0, 0);
await sleep(500);
const currentUsers = await scrapeUsers();
const previous = loadPrevious();
if (previous) {
const prevSet = new Set(previous.users);
const currSet = new Set(currentUsers);
const removed = previous.users.filter(u => !currSet.has(u));
const added = currentUsers.filter(u => !prevSet.has(u));
if (removed.length > 0 || added.length > 0) {
if (pageType === 'followers') {
if (removed.length > 0) {
notify('👋 Lost Followers', `${removed.length} unfollowed @${targetUser}`);
console.log(`🚨 UNFOLLOWED BY: ${removed.map(u => '@' + u).join(', ')}`);
}
if (added.length > 0) {
notify('🎉 New Followers', `${added.length} new for @${targetUser}`);
console.log(`✨ NEW FOLLOWERS: ${added.map(u => '@' + u).join(', ')}`);
}
} else {
if (removed.length > 0) {
notify('👋 Unfollowed', `@${targetUser} unfollowed ${removed.length}`);
console.log(`📤 UNFOLLOWED: ${removed.map(u => '@' + u).join(', ')}`);
}
if (added.length > 0) {
notify('➕ New Follow', `@${targetUser} followed ${added.length}`);
console.log(`📥 FOLLOWED: ${added.map(u => '@' + u).join(', ')}`);
}
}
} else {
console.log(' No changes detected.');
}
}
saveData(currentUsers);
console.log(` Total: ${currentUsers.length} | Next check in ${CONFIG.checkIntervalMinutes} min`);
};
console.log(`\n🔭 CONTINUOUS MONITOR — @${targetUser}'s ${pageType} — by nichxbt`);
console.log(` Interval: every ${CONFIG.checkIntervalMinutes} min | Keep this tab open!`);
console.log(' Run stopXActionsMonitor() to stop.\n');
runCheck();
const intervalId = setInterval(runCheck, CONFIG.checkIntervalMinutes * 60 * 1000);
window.stopXActionsMonitor = () => {
clearInterval(intervalId);
console.log('\n🛑 Monitoring stopped.');
};
})();