scripts/naturalFlow.js
How to Use
- Navigate to x.com and log in
- Open DevTools Console (F12 or Cmd+Option+I)
- Paste the script below and press Enter
Full Script
Copy and paste this entire script into your browser DevTools console on x.com.
// scripts/naturalFlow.js
// Simulates a natural X/Twitter browsing session:
// → scroll home timeline, like keyword-matched posts
// → occasionally reply, retweet, or bookmark
// → follow interesting authors (checks bio + followers first)
// → visit your own profile, scroll your posts
// → check notifications
// → come back to timeline
//
// Features: interactive setup, live floating HUD, context-aware replies,
// engagement scoring, cooldown escalation, session history
//
// Paste in DevTools console on x.com/home
// by nichxbt
(() => {
'use strict';
// =============================================
// SELECTORS
// =============================================
const SEL = {
tweet: 'article[data-testid="tweet"]',
tweetText: '[data-testid="tweetText"]',
likeBtn: '[data-testid="like"]',
unlikeBtn: '[data-testid="unlike"]',
replyBtn: '[data-testid="reply"]',
retweetBtn: '[data-testid="retweet"]',
retweetConf: '[data-testid="retweetConfirm"]',
bookmarkBtn: '[data-testid="bookmark"]',
removeBookmark:'[data-testid="removeBookmark"]',
shareBtn: '[data-testid="share"]',
tweetBox: '[data-testid="tweetTextarea_0"]',
tweetButton: '[data-testid="tweetButton"]',
followBtn: '[data-testid$="-follow"]',
unfollowBtn: '[data-testid$="-unfollow"]',
toast: '[data-testid="toast"]',
userCell: '[data-testid="UserCell"]',
notification: '[data-testid="notification"]',
profileNav: 'a[data-testid="AppTabBar_Profile_Link"]',
};
// =============================================
// UTILITIES
// =============================================
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const rand = (a, b) => Math.floor(a + Math.random() * (b - a));
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
const roll = (pct) => Math.random() < pct;
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
const $ = (sel, ctx = document) => ctx.querySelector(sel);
const $$ = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel));
const parseEngagement = (article) => {
let total = 0;
for (const btn of article.querySelectorAll('[role="button"]')) {
const label = (btn.getAttribute('aria-label') || '').toLowerCase();
const match = label.match(/([\d,]+)\s*(like|repost|repl|view|bookmark)/);
if (match) {
const n = parseInt(match[1].replace(/,/g, '')) || 0;
if (label.includes('like') || label.includes('repost') || label.includes('repl')) total += n;
}
}
return total;
};
// =============================================
// INTERACTIVE SETUP
// =============================================
const setupInteractive = () => {
const saved = getState();
if (saved && saved.config) {
console.log(`🔄 Resuming session (Phase ${saved.phase}/4)...`);
return saved.config;
}
const presetChoice = prompt(
'🌊 NATURAL FLOW — Choose a mode:\n\n' +
' 1 👀 Lurker — mostly scroll, like a few, no replies\n' +
' 2 🤝 Friendly — like + occasional reply, 1-2 follows\n' +
' 3 🚀 Growth — max engagement, replies + follows + retweets\n' +
' 4 ⚙️ Custom — set everything manually\n' +
' 5 🏃 Dry Run — preview the full session (safe)\n\n' +
'Enter 1-5:',
'2'
);
if (!presetChoice) return null;
const presets = {
'1': { maxLikes: 8, replyMax: 0, followMax: 0, retweetMax: 0, bookmarkMax: 2, likeChance: 0.4 },
'2': { maxLikes: 15, replyMax: 3, followMax: 2, retweetMax: 1, bookmarkMax: 3, likeChance: 0.6 },
'3': { maxLikes: 25, replyMax: 5, followMax: 4, retweetMax: 3, bookmarkMax: 5, likeChance: 0.75 },
};
let dryRun = presetChoice.trim() === '5';
let preset = presets[presetChoice.trim()] || presets['2'];
if (presetChoice.trim() === '4') {
const maxLikes = parseInt(prompt('Max likes per session:', '15')) || 15;
const replyMax = parseInt(prompt('Max replies:', '3')) || 3;
const followMax = parseInt(prompt('Max follows:', '2')) || 2;
const retweetMax = parseInt(prompt('Max retweets:', '2')) || 2;
preset = { maxLikes: clamp(maxLikes, 1, 50), replyMax, followMax, retweetMax, bookmarkMax: 3, likeChance: 0.6 };
dryRun = confirm('Enable dry run? (OK = safe preview, Cancel = live mode)');
}
const kwInput = prompt(
'🔍 Keywords to engage with (comma-separated):\n\n' +
'Leave empty to like ANY post on your timeline.',
'crypto, bitcoin, web3'
);
const keywords = kwInput ? kwInput.split(',').map(k => k.trim().toLowerCase()).filter(Boolean) : [];
const defaultReplies = [
'🔥 This is solid',
'Really interesting take on this',
'Great thread, appreciate the insight 🙏',
'Couldn\'t agree more — this needed to be said',
'📌 Saving this one. Great breakdown.',
'Bullish on this perspective 💯',
'Underrated take. More people need to see this.',
'This is the kind of content I\'m here for',
];
let replyTemplates = defaultReplies;
if (preset.replyMax > 0) {
const customReply = prompt(
'💬 Reply templates (one per line, or press OK for defaults):\n\n' +
'Default replies:\n' + defaultReplies.slice(0, 4).map(r => ` "${r}"`).join('\n'),
''
);
if (customReply && customReply.trim()) {
replyTemplates = customReply.split('\n').map(r => r.trim()).filter(Boolean);
}
}
return {
keywords,
dryRun,
skipKeywords: ['promoted', 'ad', 'giveaway', 'sponsor'],
timeline: {
scrolls: 15,
maxLikes: preset.maxLikes,
likeChance: preset.likeChance,
minEngagement: 2,
},
replies: {
enabled: preset.replyMax > 0,
max: preset.replyMax,
chance: 0.2,
templates: replyTemplates,
},
retweets: {
enabled: preset.retweetMax > 0,
max: preset.retweetMax,
chance: 0.1,
},
bookmarks: {
enabled: preset.bookmarkMax > 0,
max: preset.bookmarkMax,
chance: 0.15,
},
follows: {
enabled: preset.followMax > 0,
max: preset.followMax,
chance: 0.25,
},
selfProfile: { enabled: true, username: '', scrolls: 4 },
notifications: { enabled: true, pauseSeconds: 8 },
delays: {
betweenActions: [3000, 7000],
betweenPhases: [8000, 15000],
readingPause: [2000, 6000],
scrollPause: [1500, 3000],
replyTyping: [3000, 6000],
},
};
};
// =============================================
// FLOATING HUD — on-page stats overlay
// =============================================
const createHUD = () => {
const existing = document.getElementById('xactions-hud');
if (existing) existing.remove();
const hud = document.createElement('div');
hud.id = 'xactions-hud';
hud.innerHTML = `
<div style="
position: fixed; bottom: 20px; right: 20px; z-index: 999999;
background: rgba(0,0,0,0.92); border: 1px solid #1d9bf0; border-radius: 12px;
padding: 14px 18px; font-family: -apple-system, sans-serif; font-size: 13px;
color: #e7e9ea; min-width: 200px; backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(29,155,240,0.15);
">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<span style="font-weight:700; color:#1d9bf0;">🌊 Natural Flow</span>
<span id="xhud-phase" style="font-size:11px; color:#71767b;">Phase 1/4</span>
</div>
<div id="xhud-progress" style="background:#333; border-radius:4px; height:6px; margin-bottom:10px; overflow:hidden;">
<div id="xhud-bar" style="background:#1d9bf0; height:100%; width:0%; transition:width 0.3s;"></div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:4px 12px; font-size:12px;">
<span>❤️ Liked</span> <span id="xhud-liked" style="text-align:right; font-weight:600;">0</span>
<span>💬 Replied</span> <span id="xhud-replied" style="text-align:right; font-weight:600;">0</span>
<span>🔄 Retweeted</span> <span id="xhud-retweeted" style="text-align:right; font-weight:600;">0</span>
<span>🔖 Bookmarked</span> <span id="xhud-bookmarked" style="text-align:right; font-weight:600;">0</span>
<span>➕ Followed</span> <span id="xhud-followed" style="text-align:right; font-weight:600;">0</span>
<span>⏭️ Skipped</span> <span id="xhud-skipped" style="text-align:right; font-weight:600;">0</span>
</div>
<div id="xhud-latest" style="margin-top:8px; font-size:11px; color:#71767b; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"></div>
<div style="margin-top:8px; display:flex; gap:8px;">
<button id="xhud-pause" style="flex:1; background:#333; color:#e7e9ea; border:1px solid #555; border-radius:6px; padding:4px 0; cursor:pointer; font-size:11px;">⏸ Pause</button>
<button id="xhud-stop" style="flex:1; background:#67070f; color:#e7e9ea; border:1px solid #f4212e; border-radius:6px; padding:4px 0; cursor:pointer; font-size:11px;">⏹ Stop</button>
</div>
</div>
`;
document.body.appendChild(hud);
document.getElementById('xhud-stop').addEventListener('click', () => {
aborted = true;
updateHUD('latest', '🛑 Stopping...');
});
document.getElementById('xhud-pause').addEventListener('click', () => {
paused = !paused;
document.getElementById('xhud-pause').textContent = paused ? '▶ Resume' : '⏸ Pause';
updateHUD('latest', paused ? '⏸ Paused' : '▶ Resumed');
});
return hud;
};
const updateHUD = (field, value) => {
const el = document.getElementById(`xhud-${field}`);
if (el) el.textContent = value;
};
const updateProgress = (current, max) => {
const bar = document.getElementById('xhud-bar');
if (bar) bar.style.width = `${Math.min(100, (current / max) * 100)}%`;
};
const removeHUD = () => {
const hud = document.getElementById('xactions-hud');
if (hud) hud.remove();
};
// =============================================
// STATE + HISTORY
// =============================================
let aborted = false;
let paused = false;
window.XActions = window.XActions || {};
window.XActions.stop = () => { aborted = true; console.log('🛑 Stopping...'); };
window.XActions.pause = () => { paused = !paused; console.log(paused ? '⏸ Paused' : '▶ Resumed'); };
const stats = { liked: 0, replied: 0, retweeted: 0, bookmarked: 0, followed: 0, scrolled: 0, skipped: 0 };
const actionLog = [];
const seen = new Set();
const STATE_KEY = 'xactions_natural_flow';
const HISTORY_KEY = 'xactions_nf_history';
const getState = () => { try { return JSON.parse(sessionStorage.getItem(STATE_KEY)); } catch { return null; } };
const setState = (s) => sessionStorage.setItem(STATE_KEY, JSON.stringify(s));
const clearState = () => sessionStorage.removeItem(STATE_KEY);
const getHistory = () => { try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch { return []; } };
const addHistory = (entry) => {
const hist = getHistory();
hist.push(entry);
if (hist.length > 30) hist.splice(0, hist.length - 30);
localStorage.setItem(HISTORY_KEY, JSON.stringify(hist));
};
const checkRecentSession = () => {
const hist = getHistory();
if (hist.length === 0) return true;
const last = hist[hist.length - 1];
const hoursSince = (Date.now() - last.ts) / 3600000;
if (hoursSince < 2) {
const ok = confirm(
`⚠️ You ran Natural Flow ${hoursSince.toFixed(1)} hours ago ` +
`(${last.liked} likes, ${last.replied} replies).\n\n` +
`Running too frequently increases detection risk.\n\n` +
`Continue anyway?`
);
return ok;
}
return true;
};
// =============================================
// CORE HELPERS
// =============================================
const waitForUnpause = async () => { while (paused && !aborted) await sleep(300); };
const isRateLimited = () => {
for (const el of $$(`${SEL.toast}, [role="alert"]`)) {
if (/rate limit|try again|too many|slow down/i.test(el.textContent)) return true;
}
return false;
};
const checkRateLimit = async () => {
if (!isRateLimited()) return false;
console.log(' 🚨 Rate limited — pausing 120s...');
updateHUD('latest', '🚨 Rate limited — pausing...');
await sleep(120000);
return isRateLimited();
};
const matchesKeywords = (config, text) => {
if (!config.keywords || config.keywords.length === 0) return true;
const lower = text.toLowerCase();
return config.keywords.some(kw => lower.includes(kw));
};
const shouldSkip = (config, text) => {
const lower = text.toLowerCase();
return config.skipKeywords.some(kw => lower.includes(kw));
};
const getAuthor = (article) => {
const link = article.querySelector('a[href^="/"][role="link"]');
if (!link) return null;
const match = (link.getAttribute('href') || '').match(/^\/([A-Za-z0-9_]+)/);
if (!match) return null;
const name = match[1];
if (['home', 'explore', 'notifications', 'messages', 'i', 'search', 'settings', 'compose'].includes(name)) return null;
return name;
};
const getMyUsername = (config) => {
if (config.selfProfile.username) return config.selfProfile.username;
const navLink = document.querySelector(SEL.profileNav);
if (navLink) {
const match = navLink.getAttribute('href')?.match(/^\/([A-Za-z0-9_]+)/);
if (match) return match[1];
}
return null;
};
// Cooldown escalation: delays increase as session progresses
const escalatedDelay = (config, key, actionsSoFar) => {
const base = config.delays[key];
const multiplier = 1 + (actionsSoFar * 0.03);
const lo = Math.floor(base[0] * multiplier);
const hi = Math.floor(base[1] * multiplier);
return sleep(rand(lo, hi));
};
// =============================================
// ACTIONS
// =============================================
const doLike = async (config, article, text) => {
if ($(SEL.unlikeBtn, article)) return false;
const btn = $(SEL.likeBtn, article);
if (!btn) return false;
article.scrollIntoView({ behavior: 'smooth', block: 'center' });
await escalatedDelay(config, 'readingPause', stats.liked);
if (config.dryRun) {
console.log(` ❤️ [DRY] Like: "${text.slice(0, 55)}..."`);
} else {
btn.click();
await sleep(rand(300, 600));
}
stats.liked++;
updateHUD('liked', stats.liked);
return true;
};
const doReply = async (config, article, author, tweetText) => {
const replyBtn = $(SEL.replyBtn, article);
if (!replyBtn) return false;
// Context-aware: pick a relevant template based on tweet content
let replyText = pick(config.replies.templates);
const lower = tweetText.toLowerCase();
if (/thread|breakdown|analysis/i.test(lower)) {
replyText = pick(['📌 Saving this one. Great breakdown.', 'Great thread, appreciate the insight 🙏', replyText]);
} else if (/agree|disagree|opinion|take/i.test(lower)) {
replyText = pick(['Couldn\'t agree more — this needed to be said', 'Really interesting take on this', replyText]);
} else if (/data|chart|numbers|stats/i.test(lower)) {
replyText = pick(['The data speaks for itself 📊', 'Underrated take. More people need to see this.', replyText]);
}
if (config.dryRun) {
console.log(` 💬 [DRY] Reply to @${author}: "${replyText}"`);
stats.replied++;
updateHUD('replied', stats.replied);
return true;
}
replyBtn.click();
await sleep(1500);
const tweetBox = $(SEL.tweetBox);
if (!tweetBox) { console.log(' ⚠️ Reply box not found'); return false; }
tweetBox.focus();
await escalatedDelay(config, 'replyTyping', stats.replied);
document.execCommand('insertText', false, replyText);
await sleep(rand(600, 1000));
const sendBtn = $(SEL.tweetButton);
if (!sendBtn) { console.log(' ⚠️ Send button not found'); return false; }
sendBtn.click();
await sleep(2000);
stats.replied++;
updateHUD('replied', stats.replied);
return true;
};
const doRetweet = async (config, article, text) => {
const rtBtn = $(SEL.retweetBtn, article);
if (!rtBtn) return false;
if (config.dryRun) {
console.log(` 🔄 [DRY] Retweet: "${text.slice(0, 50)}..."`);
stats.retweeted++;
updateHUD('retweeted', stats.retweeted);
return true;
}
rtBtn.click();
await sleep(800);
const confirmBtn = $(SEL.retweetConf);
if (confirmBtn) {
confirmBtn.click();
await sleep(500);
}
stats.retweeted++;
updateHUD('retweeted', stats.retweeted);
return true;
};
const doBookmark = async (config, article, text) => {
const shareBtn = $(SEL.shareBtn, article);
if (!shareBtn) return false;
if (config.dryRun) {
console.log(` 🔖 [DRY] Bookmark: "${text.slice(0, 50)}..."`);
stats.bookmarked++;
updateHUD('bookmarked', stats.bookmarked);
return true;
}
shareBtn.click();
await sleep(600);
const bmBtn = document.querySelector('[data-testid="bookmark"], [role="menuitem"]');
if (bmBtn && /bookmark/i.test(bmBtn.textContent)) {
bmBtn.click();
await sleep(400);
stats.bookmarked++;
updateHUD('bookmarked', stats.bookmarked);
return true;
}
document.body.click();
await sleep(300);
return false;
};
const doFollow = async (config, author) => {
if (config.dryRun) {
console.log(` ➕ [DRY] Follow @${author}`);
stats.followed++;
updateHUD('followed', stats.followed);
return true;
}
console.log(` ➕ Following @${author}...`);
window.location.href = `https://x.com/${author}`;
let loaded = false;
for (let i = 0; i < 20; i++) {
await sleep(500);
if (document.querySelector(SEL.unfollowBtn)) {
console.log(` ℹ️ Already following @${author}`);
window.history.back();
await sleep(3000);
return false;
}
const followBtn = document.querySelector('[data-testid$="-follow"]:not([data-testid$="-unfollow"])');
if (followBtn) { loaded = true; break; }
}
if (loaded) {
const followBtn = document.querySelector('[data-testid$="-follow"]:not([data-testid$="-unfollow"])');
if (followBtn) {
followBtn.click();
await sleep(1000);
stats.followed++;
updateHUD('followed', stats.followed);
console.log(` ✅ Followed @${author}`);
}
}
await sleep(rand(2000, 4000));
window.history.back();
await sleep(3000);
return true;
};
// =============================================
// PHASES
// =============================================
const phaseTimeline = async (config) => {
console.log('\n📱 PHASE 1 — Scrolling home timeline...');
console.log(` Keywords: ${config.keywords.length ? config.keywords.join(', ') : 'everything'}`);
updateHUD('phase', 'Phase 1/4');
const authorsToFollow = [];
const totalActions = config.timeline.maxLikes;
for (let scroll = 0; scroll < config.timeline.scrolls && !aborted; scroll++) {
await waitForUnpause();
const articles = $$(SEL.tweet);
for (const article of articles) {
if (aborted) break;
if (stats.liked >= config.timeline.maxLikes) break;
if (await checkRateLimit()) { aborted = true; break; }
await waitForUnpause();
const textEl = $(SEL.tweetText, article);
const text = textEl ? textEl.textContent.trim() : '';
const link = article.querySelector('a[href*="/status/"]')?.href || '';
const id = link || text.slice(0, 80);
if (!id || seen.has(id)) continue;
seen.add(id);
if (shouldSkip(config, text)) { stats.skipped++; updateHUD('skipped', stats.skipped); continue; }
if (!matchesKeywords(config, text)) { stats.skipped++; updateHUD('skipped', stats.skipped); continue; }
// Engagement scoring — skip very low-engagement posts
const engagement = parseEngagement(article);
if (engagement < config.timeline.minEngagement && config.timeline.minEngagement > 0) {
stats.skipped++;
updateHUD('skipped', stats.skipped);
continue;
}
const author = getAuthor(article);
// --- Like ---
if (roll(config.timeline.likeChance)) {
const liked = await doLike(config, article, text);
if (liked) {
updateHUD('latest', `❤️ @${author || '?'}: ${text.slice(0, 40)}...`);
updateProgress(stats.liked, totalActions);
actionLog.push({ action: 'like', author, text: text.slice(0, 100), engagement, ts: Date.now() });
await escalatedDelay(config, 'betweenActions', stats.liked);
// --- Maybe reply (context-aware) ---
if (config.replies.enabled && stats.replied < config.replies.max && roll(config.replies.chance)) {
const replied = await doReply(config, article, author, text);
if (replied) {
actionLog.push({ action: 'reply', author, ts: Date.now() });
updateHUD('latest', `💬 Replied to @${author}`);
await escalatedDelay(config, 'betweenActions', stats.liked + stats.replied);
}
}
// --- Maybe retweet (sparingly, only high-engagement) ---
if (config.retweets.enabled && stats.retweeted < config.retweets.max && roll(config.retweets.chance) && engagement >= 10) {
await doRetweet(config, article, text);
actionLog.push({ action: 'retweet', author, text: text.slice(0, 60), ts: Date.now() });
updateHUD('latest', `🔄 Retweeted @${author}`);
await sleep(rand(1000, 2000));
}
// --- Maybe bookmark ---
if (config.bookmarks.enabled && stats.bookmarked < config.bookmarks.max && roll(config.bookmarks.chance)) {
await doBookmark(config, article, text);
actionLog.push({ action: 'bookmark', author, ts: Date.now() });
await sleep(rand(500, 1000));
}
// --- Queue follow ---
if (config.follows.enabled && author && stats.followed < config.follows.max && roll(config.follows.chance)) {
if (!authorsToFollow.includes(author)) authorsToFollow.push(author);
}
}
} else {
// Scroll past — simulate reading
article.scrollIntoView({ behavior: 'smooth', block: 'center' });
await sleep(rand(400, 1200));
}
}
if (stats.liked >= config.timeline.maxLikes) break;
window.scrollBy(0, rand(600, 1200));
stats.scrolled++;
const pct = Math.round((stats.liked / totalActions) * 100);
console.log(` 📜 Scroll ${scroll + 1}/${config.timeline.scrolls} — ${stats.liked} liked (${pct}%), ${stats.skipped} skipped`);
await escalatedDelay(config, 'scrollPause', stats.scrolled);
}
console.log(` ✅ Timeline: ${stats.liked} liked, ${stats.replied} replied, ${stats.retweeted} RT, ${stats.bookmarked} saved`);
// Follow queued authors
if (authorsToFollow.length > 0 && !aborted) {
console.log(`\n 👥 Following ${Math.min(authorsToFollow.length, config.follows.max)} accounts...`);
for (const author of authorsToFollow.slice(0, config.follows.max - stats.followed)) {
if (aborted) break;
await waitForUnpause();
await doFollow(config, author);
actionLog.push({ action: 'follow', author, ts: Date.now() });
updateHUD('latest', `➕ Followed @${author}`);
await escalatedDelay(config, 'betweenActions', stats.followed);
}
}
};
const phaseSelfProfile = async (config) => {
if (!config.selfProfile.enabled) return;
const username = getMyUsername(config);
if (!username) {
console.log('\n👤 PHASE 2 — Skipping (couldn\'t detect username)');
return;
}
console.log(`\n👤 PHASE 2 — Visiting your profile (@${username})...`);
updateHUD('phase', 'Phase 2/4');
updateHUD('latest', `👤 Browsing @${username}`);
if (!config.dryRun) {
window.location.href = `https://x.com/${username}`;
for (let i = 0; i < 20; i++) { await sleep(500); if ($$(SEL.tweet).length > 0) break; }
await sleep(2000);
} else {
console.log(` 🏃 [DRY] Would navigate to x.com/${username}`);
}
for (let i = 0; i < config.selfProfile.scrolls; i++) {
if (config.dryRun) console.log(` 📜 [DRY] Scroll profile ${i + 1}/${config.selfProfile.scrolls}`);
else window.scrollBy(0, rand(400, 900));
await escalatedDelay(config, 'scrollPause', i);
}
console.log(` ✅ Scrolled own profile`);
actionLog.push({ action: 'self_profile', username, ts: Date.now() });
};
const phaseNotifications = async (config) => {
if (!config.notifications.enabled) return;
console.log('\n🔔 PHASE 3 — Checking notifications...');
updateHUD('phase', 'Phase 3/4');
updateHUD('latest', '🔔 Reading notifications...');
if (!config.dryRun) {
window.location.href = 'https://x.com/notifications';
for (let i = 0; i < 20; i++) { await sleep(500); if ($(SEL.notification) || window.location.pathname.includes('notifications')) break; }
await sleep(2000);
} else {
console.log(` 🏃 [DRY] Would navigate to notifications`);
}
console.log(` 👀 Reading for ${config.notifications.pauseSeconds}s...`);
await sleep(config.notifications.pauseSeconds * 1000);
for (let i = 0; i < 3; i++) {
if (!config.dryRun) window.scrollBy(0, rand(300, 600));
await sleep(rand(1000, 2000));
}
console.log(' ✅ Notifications checked');
actionLog.push({ action: 'notifications', ts: Date.now() });
};
const phaseReturnHome = async (config) => {
console.log('\n🏠 PHASE 4 — Returning to home timeline...');
updateHUD('phase', 'Phase 4/4');
updateHUD('latest', '🏠 Heading home...');
if (!config.dryRun) {
window.location.href = 'https://x.com/home';
for (let i = 0; i < 20; i++) { await sleep(500); if ($$(SEL.tweet).length > 0) break; }
await sleep(2000);
} else {
console.log(' 🏃 [DRY] Would navigate to home');
}
for (let i = 0; i < 3; i++) {
if (!config.dryRun) window.scrollBy(0, rand(400, 800));
await sleep(rand(1500, 3000));
}
console.log(' ✅ Back on home timeline');
};
// =============================================
// SESSION SUMMARY
// =============================================
const printSummary = (elapsed) => {
const W = 52;
console.log('\n' + '━'.repeat(W));
console.log(' 🌊 NATURAL FLOW — SESSION COMPLETE');
console.log('━'.repeat(W));
console.log(` ❤️ Liked: ${stats.liked}`);
console.log(` 💬 Replied: ${stats.replied}`);
console.log(` 🔄 Retweeted: ${stats.retweeted}`);
console.log(` 🔖 Bookmarked: ${stats.bookmarked}`);
console.log(` ➕ Followed: ${stats.followed}`);
console.log(` 📜 Scrolls: ${stats.scrolled}`);
console.log(` ⏭️ Skipped: ${stats.skipped}`);
console.log(` ⏱️ Duration: ${elapsed} min`);
console.log('━'.repeat(W));
const uniqueAuthors = new Set(actionLog.filter(l => l.author).map(l => l.author));
console.log(` Engaged with ${uniqueAuthors.size} unique accounts\n`);
addHistory({
ts: Date.now(),
liked: stats.liked,
replied: stats.replied,
retweeted: stats.retweeted,
bookmarked: stats.bookmarked,
followed: stats.followed,
authors: uniqueAuthors.size,
});
if (actionLog.length > 0) {
try {
const blob = new Blob([JSON.stringify(actionLog, null, 2)], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `xactions-natural-flow-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
a.remove();
console.log('📥 Session log exported.\n');
} catch {}
}
removeHUD();
};
// =============================================
// MAIN — DRY RUN (single page, no navigation)
// =============================================
const runDry = async (config) => {
const W = 52;
console.log('╔' + '═'.repeat(W) + '╗');
console.log('║ 🌊 NATURAL FLOW — Human-Like Session ║');
console.log('║ by nichxbt — XActions ║');
console.log('╚' + '═'.repeat(W) + '╝');
console.log('\n🏃 DRY RUN — previewing the full session.\n');
const startTime = Date.now();
createHUD();
try {
await phaseTimeline(config);
if (!aborted) { await sleep(rand(...config.delays.betweenPhases)); await phaseSelfProfile(config); }
if (!aborted) { await sleep(rand(...config.delays.betweenPhases)); await phaseNotifications(config); }
if (!aborted) { await sleep(rand(...config.delays.betweenPhases)); await phaseReturnHome(config); }
} catch (e) { if (e !== 'aborted') console.error('❌ Error:', e); }
printSummary(((Date.now() - startTime) / 60000).toFixed(1));
clearState();
};
// =============================================
// MAIN — LIVE MODE (multi-page with resume)
// =============================================
const runLive = async (config) => {
let state = getState() || { phase: 1, stats: { liked:0, replied:0, retweeted:0, bookmarked:0, followed:0, scrolled:0, skipped:0 }, log: [], config };
Object.assign(stats, state.stats);
actionLog.push(...state.log);
const W = 52;
console.log('╔' + '═'.repeat(W) + '╗');
console.log('║ 🌊 NATURAL FLOW — Human-Like Session ║');
console.log('║ by nichxbt — XActions ║');
console.log('╚' + '═'.repeat(W) + '╝');
console.log(`\n⚠️ LIVE MODE — Phase ${state.phase}/4`);
console.log(` ℹ️ XActions.stop() or click 🛑 to abort\n`);
createHUD();
updateHUD('phase', `Phase ${state.phase}/4`);
updateHUD('liked', stats.liked);
updateHUD('replied', stats.replied);
updateHUD('retweeted', stats.retweeted);
updateHUD('bookmarked', stats.bookmarked);
updateHUD('followed', stats.followed);
updateHUD('skipped', stats.skipped);
const startTime = Date.now();
const saveAndNav = (nextPhase, url) => {
state.phase = nextPhase;
state.stats = { ...stats };
state.log = [...actionLog];
state.config = config;
setState(state);
console.log(`\n ➡️ Navigating... Re-paste script after page loads.`);
sleep(rand(5000, 10000)).then(() => { window.location.href = url; });
};
try {
if (state.phase === 1) {
if (!window.location.pathname.includes('/home') && window.location.pathname !== '/') {
window.location.href = 'https://x.com/home';
return;
}
await phaseTimeline(config);
if (!aborted && config.selfProfile.enabled) {
const username = getMyUsername(config);
if (username) { saveAndNav(2, `https://x.com/${username}`); return; }
}
state.phase = config.notifications.enabled ? 3 : 4;
state.stats = { ...stats };
state.log = [...actionLog];
state.config = config;
setState(state);
}
if (state.phase === 2) {
console.log('\n👤 PHASE 2 — Browsing own profile');
updateHUD('phase', 'Phase 2/4');
for (let i = 0; i < config.selfProfile.scrolls; i++) {
window.scrollBy(0, rand(400, 900));
await escalatedDelay(config, 'scrollPause', i);
}
console.log(` ✅ Scrolled own profile`);
actionLog.push({ action: 'self_profile', ts: Date.now() });
if (!aborted && config.notifications.enabled) { saveAndNav(3, 'https://x.com/notifications'); return; }
state.phase = 4;
state.stats = { ...stats };
state.log = [...actionLog];
state.config = config;
setState(state);
}
if (state.phase === 3) {
console.log('\n🔔 PHASE 3 — Checking notifications');
updateHUD('phase', 'Phase 3/4');
await sleep(config.notifications.pauseSeconds * 1000);
for (let i = 0; i < 3; i++) { window.scrollBy(0, rand(300, 600)); await sleep(rand(1000, 2000)); }
console.log(' ✅ Notifications checked');
actionLog.push({ action: 'notifications', ts: Date.now() });
if (!aborted) { saveAndNav(4, 'https://x.com/home'); return; }
}
if (state.phase === 4) {
console.log('\n🏠 PHASE 4 — Back on home timeline');
updateHUD('phase', 'Phase 4/4');
for (let i = 0; i < 3; i++) { window.scrollBy(0, rand(400, 800)); await sleep(rand(1500, 3000)); }
console.log(' ✅ Final browse complete');
}
} catch (e) {
if (e !== 'aborted') console.error('❌ Error:', e);
}
printSummary(((Date.now() - startTime) / 60000).toFixed(1));
clearState();
};
// =============================================
// ENTRY POINT
// =============================================
const main = async () => {
if (!checkRecentSession()) {
console.log('❌ Cancelled — too soon since last session.');
return;
}
const config = setupInteractive();
if (!config) {
console.log('❌ Setup cancelled.');
return;
}
if (config.dryRun) {
await runDry(config);
} else {
await runLive(config);
}
};
main();
})();
⚡ More XActions Scripts
Browse 300+ free browser scripts for X/Twitter automation. No API keys, no fees.
Browse All Scripts