XActions Core Module Reference (core.js)
The foundation of every XActions browser automation. Always paste this first.
Source: src/automation/core.js (521 lines)
Table of Contents
- Overview
- Loading
- CONFIG — Timing & Limits
- SELECTORS — DOM Element Map
- Utility Functions
- Storage System
- DOM Helpers
- User Extraction
- Extraction Diagnostics
- Rate Limiting
- Action Queue
- Full Public API
Overview
core.js creates window.XActions.Core — a namespace containing every shared utility that automation scripts need. It's an IIFE (immediately-invoked function expression) that returns a public API object.
Every other automation script checks for window.XActions?.Core at the top. If it's not loaded, the script aborts with:
❌ Core module not loaded! Paste core.js first.
When successfully loaded, you'll see:
✅ XActions Core loaded! Ready for automation scripts.
Loading
// In browser DevTools console on x.com:
// 1. Copy the contents of src/automation/core.js
// 2. Paste into the console
// 3. Press Enter
// Verify it loaded:
window.XActions.Core.log('Hello!', 'success');
// Output: ✅ [12:34:56] Hello!
CONFIG — Timing & Limits
Default configuration values used across all scripts.
const CONFIG = {
// Timing (milliseconds)
DELAY_SHORT: 500,
DELAY_MEDIUM: 1500,
DELAY_LONG: 3000,
DELAY_BETWEEN_ACTIONS: 2000,
// Rate limits
MAX_ACTIONS_PER_HOUR: 50,
MAX_FOLLOWS_PER_DAY: 100,
MAX_LIKES_PER_DAY: 200,
// Storage
STORAGE_PREFIX: 'xactions_',
// Debug
DEBUG: true,
};
Customizing at Runtime
// Slow down all actions
window.XActions.Core.CONFIG.DELAY_BETWEEN_ACTIONS = 5000;
// Disable debug logging
window.XActions.Core.CONFIG.DEBUG = false;
// Increase daily follow limit
window.XActions.Core.CONFIG.MAX_FOLLOWS_PER_DAY = 150;
Note: Individual scripts override these with their own
OPTIONSorSTRATEGYobjects. Changing CONFIG affects only scripts that read from it directly.
SELECTORS — DOM Element Map
CSS selectors for X/Twitter DOM elements. These use data-testid attributes (React testing IDs) for stability.
| Key | Selector | Element |
|---|---|---|
followButton |
[data-testid$="-follow"] |
Follow button (suffix match) |
unfollowButton |
[data-testid$="-unfollow"] |
Unfollow/Following button |
likeButton |
[data-testid="like"] |
Like (heart) button |
unlikeButton |
[data-testid="unlike"] |
Unlike (filled heart) |
retweetButton |
[data-testid="retweet"] |
Retweet button |
replyButton |
[data-testid="reply"] |
Reply button |
confirmButton |
[data-testid="confirmationSheetConfirm"] |
Confirm dialog |
tweet |
[data-testid="tweet"] |
Individual tweet/post |
tweetText |
[data-testid="tweetText"] |
Tweet text content |
tweetLink |
a[href*="/status/"] |
Tweet permalink |
userCell |
[data-testid="UserCell"] |
User in a list |
userAvatar |
[data-testid="UserAvatar-Container"] |
User avatar |
userName |
[data-testid="User-Name"] |
User display name |
userFollowIndicator |
[data-testid="userFollowIndicator"] |
"Follows you" badge |
userDescription |
[data-testid="UserDescription"] |
User bio text |
userLink |
a[href^="/"] |
Profile link |
verifiedBadge |
[data-testid="icon-verified"] |
Verified badge |
protectedIcon |
[data-testid="icon-lock"] |
Protected/private icon |
tweetInput |
[data-testid="tweetTextarea_0"] |
Tweet compose box |
searchInput |
[data-testid="SearchBox_Search_Input"] |
Search input |
primaryColumn |
[data-testid="primaryColumn"] |
Main content column |
timeline |
section[role="region"] |
Timeline section |
Usage
const { SELECTORS } = window.XActions.Core;
// Find all tweets on current page
document.querySelectorAll(SELECTORS.tweet);
// Find the follow button in a user cell
userCell.querySelector(SELECTORS.followButton);
// Check if a tweet is already liked
!!tweetElement.querySelector(SELECTORS.unlikeButton);
Why suffix match for follow/unfollow? X prefixes the
data-testidwith context (e.g.,UserCell-follow). Using$="-follow"matches regardless of prefix.
Utility Functions
sleep(ms)
Promise-based delay.
await Core.sleep(2000); // Wait 2 seconds
randomDelay(min, max)
Random delay between min and max milliseconds. Essential for human-like behavior.
await Core.randomDelay(1000, 3000); // Wait 1-3 seconds randomly
log(message, type)
Emoji-prefixed console output with timestamp.
| Type | Emoji | When to Use |
|---|---|---|
'info' |
📘 | General information |
'success' |
✅ | Successful actions |
'warning' |
⚠️ | Non-critical issues |
'error' |
❌ | Errors (always prints, even with DEBUG off) |
'action' |
🔧 | Action being performed |
Core.log('Found 15 users', 'info');
// Output: 📘 [12:34:56] Found 15 users
Core.log('Followed @user!', 'success');
// Output: ✅ [12:34:57] Followed @user!
When
CONFIG.DEBUGisfalse, only'error'type logs are printed.
scrollToBottom() / scrollToTop() / scrollBy(pixels)
Smooth scrolling helpers.
Core.scrollToBottom(); // Scroll to page bottom
Core.scrollToTop(); // Scroll to page top
Core.scrollBy(800); // Scroll down 800px
Core.scrollBy(-400); // Scroll up 400px
Storage System
localStorage wrapper with automatic JSON serialization. All keys are prefixed with xactions_.
storage.get(key)
Read a value. Returns null if not found.
const users = Core.storage.get('followed_users');
// Reads localStorage key "xactions_followed_users"
// Returns: parsed JSON object, or null
storage.set(key, value)
Write a value. Returns true on success.
Core.storage.set('followed_users', { alice: { followedAt: Date.now() } });
// Writes JSON to "xactions_followed_users"
storage.remove(key)
Delete a key.
Core.storage.remove('followed_users');
storage.list()
List all XActions storage keys (without prefix).
Core.storage.list();
// Returns: ['followed_users', 'liked_tweets', 'session_log', ...]
storage.clear()
Delete all XActions data from localStorage.
Core.storage.clear();
// Removes everything with 'xactions_' prefix
Common Storage Keys
| Key | Written By | Contains |
|---|---|---|
followed_users |
keywordFollow, followTargetUsers, growthSuite | { username: { followedAt, followedBack, checkedAt } } |
liked_tweets |
autoLiker | [tweetId, ...] |
commented_tweets |
autoCommenter | [tweetId, ...] |
protected_users |
protectActiveUsers | [username, ...] |
my_followers |
smartUnfollow | [username, ...] |
ratelimit_* |
rateLimit system | { count, timestamp } |
session_log |
sessionLogger | Array of action entries |
engager_followed |
followEngagers | { username: { at, source, postUrl } } |
growth_followed |
growthSuite | { username: { at, source } } |
growth_liked |
growthSuite | [tweetId, ...] |
Inspecting All Stored Data
// View everything XActions has stored
Object.keys(localStorage)
.filter(k => k.startsWith('xactions_'))
.forEach(k => console.log(k, JSON.parse(localStorage.getItem(k))));
DOM Helpers
waitForElement(selector, timeout)
Poll the DOM until an element matching selector appears. Returns the element or null after timeout (default: 10 seconds).
const textarea = await Core.waitForElement('[data-testid="tweetTextarea_0"]');
if (textarea) {
// Element found — interact with it
}
waitForElements(selector, minCount, timeout)
Wait until at least minCount elements match the selector. Returns an array.
const tweets = await Core.waitForElements('[data-testid="tweet"]', 5, 15000);
// Wait up to 15s for at least 5 tweets to load
clickElement(element)
Scrolls the element into view, waits 300ms, then clicks. Returns true/false.
const btn = document.querySelector('[data-testid="like"]');
await Core.clickElement(btn);
typeText(element, text, delay)
Types text character-by-character into a contenteditable element, dispatching InputEvents for each character. Default character delay: 50ms.
const textarea = await Core.waitForElement('[data-testid="tweetTextarea_0"]');
await Core.typeText(textarea, 'Hello from XActions!');
Why character-by-character? X uses React controlled inputs that don't respond to
.value =or.textContent =directly. The synthetic InputEvents trigger React's state updates.
User Extraction
extractUsername(element)
Extract a username from any element containing a profile link.
const username = Core.extractUsername(tweetElement);
// Returns: 'elonmusk' (lowercase, no @)
extractTweetInfo(tweetElement)
Extract structured data from a tweet element.
const info = Core.extractTweetInfo(tweetElement);
// Returns: {
// text: 'Tweet text content...',
// links: ['https://external-link.com'],
// tweetLink: 'https://x.com/user/status/123',
// userName: 'Display Name'
// }
extractUserFromCell(cell)
The workhorse extractor — pulls full user data from a [data-testid="UserCell"] element using multiple fallback strategies per field.
const user = Core.extractUserFromCell(userCellElement);
// Returns:
// {
// username: 'alice', // @handle (lowercase)
// displayName: 'Alice', // Display name
// bio: 'Web3 builder...', // Bio text
// followers: 10500, // Parsed follower count
// isFollowing: false, // Are you following them?
// followsYou: true, // Do they follow you?
// isVerified: false, // Blue check?
// isProtected: false, // Private account?
// _meta: { // Debugging metadata
// bioStrategy: 'testid', // Which extraction method worked
// nameStrategy: 'testid'
// }
// }
Extraction Strategies (in order of priority)
Display Name:
data-testid="User-Name"— most stable[dir="ltr"] > span— common fallback- First non-@ span with 2-50 characters — last resort
Bio:
data-testid="UserDescription"— most reliable[dir="auto"]:not([data-testid])— catches variant DOMs[dir="auto"]:not([role])— another variant- Span iteration — last resort, skips links and count patterns
Follower Count:
- Regex match on cell text:
(\d[\d,]*\.?\d*[KMB]?)\s*Follower - Parsed by
parseCount():"10.5K"→10500
parseCount(str)
Parse human-readable numbers.
Core.parseCount('10.5K'); // 10500
Core.parseCount('1,234'); // 1234
Core.parseCount('2.3M'); // 2300000
Core.parseCount('1B'); // 1000000000
Extraction Diagnostics
Tracks which extraction strategies succeed, alerting when X changes their DOM.
extractionStats.report()
Print a strategy usage report.
Core.extractionStats.report();
// Output:
// 📊 Extraction Strategy Report:
// bio: 45 extractions
// testid: 40 (88.9%) ████████████████
// dir-auto: 5 (11.1%) ██
// name: 45 extractions
// testid: 43 (95.6%) ███████████████████
// dir-ltr: 2 (4.4%) █
If the primary testid strategy drops below 50%, it warns:
⚠️ WARNING: Primary bio selector (data-testid) working less than 50% of the time.
Twitter may have changed their DOM!
extractionStats.reset()
Clear accumulated statistics.
Rate Limiting
Per-action rate tracking with automatic period resets.
rateLimit.check(action, limit, period)
Check if an action is under the limit. Returns true if allowed.
if (Core.rateLimit.check('follow', 100, 'day')) {
// Under the daily follow limit — proceed
}
if (Core.rateLimit.check('like', 50, 'hour')) {
// Under the hourly like limit
}
rateLimit.increment(action, period)
Increment the counter after performing an action.
Core.rateLimit.increment('follow', 'day');
Core.rateLimit.increment('like', 'hour');
rateLimit.getRemaining(action, limit, period)
Get how many actions remain in the current period.
const remaining = Core.rateLimit.getRemaining('follow', 100, 'day');
console.log(`${remaining} follows left today`);
How It Works
- Counters are stored in localStorage as
xactions_ratelimit_{action}_{period} - Each entry stores
{ count, timestamp } - When the period expires (1 hour or 24 hours), the counter auto-resets to 0
- Periods:
'hour'(3,600,000ms) or'day'(86,400,000ms)
Action Queue
Sequential task processor with built-in delays.
actionQueue.add(asyncFunction, ...args)
Add an async function to the queue. If the queue isn't already processing, it starts automatically.
Core.actionQueue.add(followUser, 'alice');
Core.actionQueue.add(followUser, 'bob');
Core.actionQueue.add(followUser, 'charlie');
// Executes sequentially with randomDelay between each
actionQueue.clear()
Flush all pending actions.
actionQueue.length()
Get the number of pending actions.
Processing Behavior
- Actions execute one at a time (sequential, not parallel)
- After each action, waits
randomDelay(CONFIG.DELAY_BETWEEN_ACTIONS, CONFIG.DELAY_BETWEEN_ACTIONS * 1.5) - Errors are caught and logged — the queue continues processing
- The queue is self-starting and self-stopping (no separate start/stop calls needed)
Full Public API
Everything exposed by window.XActions.Core:
{
CONFIG, // Configuration object (mutable)
SELECTORS, // DOM selector map
sleep, // sleep(ms)
randomDelay, // randomDelay(min, max)
log, // log(message, type)
scrollToBottom, // scrollToBottom()
scrollToTop, // scrollToTop()
scrollBy, // scrollBy(pixels)
storage, // { get, set, remove, list, clear }
waitForElement, // waitForElement(selector, timeout)
waitForElements, // waitForElements(selector, minCount, timeout)
clickElement, // clickElement(element)
typeText, // typeText(element, text, delay)
extractUsername, // extractUsername(element)
extractTweetInfo, // extractTweetInfo(tweetElement)
extractUserFromCell, // extractUserFromCell(cellElement)
parseCount, // parseCount(str)
rateLimit, // { check, increment, getRemaining }
actionQueue, // { add, clear, length }
extractionStats, // { record, report, reset }
}