๐Ÿค– Automation Tutorials

๐ŸŽ‰ XActions is 100% Free & Open Source Star on GitHub โญ

โš ๏ธ Prerequisites

These automation scripts require core.js to be loaded first. The core module provides shared utilities like rate limiting, storage, and selectors. Paste core.js before running any automation script.

โค๏ธ Auto Liker

Automatically like tweets in your timeline or on any profile. Filter by keywords, users, or like everything.

Step 1: Go to your timeline or any profile

Navigate to x.com/home or any user's profile page.

Step 2: Open Developer Console

Press F12 (Windows) or Cmd+Option+J (Mac) and click the Console tab.

Step 3: First paste core.js (required)

core.js (paste this first)
// XActions Core Module - Required for automation scripts
// https://github.com/nirholas/XActions
window.XActions = window.XActions || {};
window.XActions.Core = (() => {
  const SELECTORS = {
    tweet: 'article[data-testid="tweet"]',
    likeButton: '[data-testid="like"]',
    unlikeButton: '[data-testid="unlike"]',
    retweetButton: '[data-testid="retweet"]',
    replyButton: '[data-testid="reply"]',
    tweetText: '[data-testid="tweetText"]',
    userCell: '[data-testid="UserCell"]',
  };

  const log = (msg, type = 'info') => {
    const icons = { info: 'โ„น๏ธ', success: 'โœ…', warning: 'โš ๏ธ', error: 'โŒ', action: '๐ŸŽฏ' };
    console.log(`${icons[type] || 'โ€ข'} [XActions] ${msg}`);
  };

  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
  const randomDelay = (min = 1000, max = 3000) => sleep(min + Math.random() * (max - min));
  const scrollBy = (px = 500) => window.scrollBy({ top: px, behavior: 'smooth' });
  const scrollToTop = () => window.scrollTo({ top: 0, behavior: 'smooth' });

  const clickElement = async (el) => {
    if (!el) return false;
    el.scrollIntoView({ behavior: 'smooth', block: 'center' });
    await sleep(300);
    el.click();
    return true;
  };

  const waitForElement = (selector, timeout = 5000) => {
    return new Promise((resolve) => {
      const el = document.querySelector(selector);
      if (el) return resolve(el);
      const observer = new MutationObserver(() => {
        const el = document.querySelector(selector);
        if (el) { observer.disconnect(); resolve(el); }
      });
      observer.observe(document.body, { childList: true, subtree: true });
      setTimeout(() => { observer.disconnect(); resolve(null); }, timeout);
    });
  };

  const storage = {
    get: (key) => { try { return JSON.parse(localStorage.getItem(`xactions_${key}`)); } catch { return null; } },
    set: (key, val) => localStorage.setItem(`xactions_${key}`, JSON.stringify(val)),
    remove: (key) => localStorage.removeItem(`xactions_${key}`),
  };

  const rateLimit = {
    _counts: {},
    check: (action, limit, period) => {
      const key = `${action}_${period}`;
      const count = rateLimit._counts[key] || 0;
      return count < limit;
    },
    increment: (action, period) => {
      const key = `${action}_${period}`;
      rateLimit._counts[key] = (rateLimit._counts[key] || 0) + 1;
    },
  };

  log('Core module loaded!', 'success');
  return { SELECTORS, log, sleep, randomDelay, scrollBy, scrollToTop, clickElement, waitForElement, storage, rateLimit };
})();

Step 4: Then paste the Auto Liker script

autoLiker.js
// XActions Auto Liker - by nichxbt
// https://github.com/nirholas/XActions
// Requires core.js to be loaded first!

(() => {
  if (!window.XActions?.Core) {
    console.error('โŒ Core module not loaded! Paste core.js first.');
    return;
  }

  const { log, sleep, randomDelay, scrollBy, clickElement, storage, SELECTORS } = window.XActions.Core;

  // ============================================
  // CONFIGURATION - Edit these options!
  // ============================================
  const OPTIONS = {
    LIKE_ALL: false,                    // true = like everything, false = use filters
    KEYWORDS: ['web3', 'crypto', 'AI'], // Only like posts with these words (if LIKE_ALL is false)
    FROM_USERS: [],                     // Only like from these users (empty = all users)
    MAX_LIKES: 20,                      // Stop after this many likes
    MIN_DELAY: 2000,                    // Minimum delay between likes (ms)
    MAX_DELAY: 5000,                    // Maximum delay between likes (ms)
    SKIP_REPLIES: true,                 // Skip reply tweets
  };

  let likeCount = 0;
  let scrollCount = 0;
  const likedIds = new Set(storage.get('liked_tweets') || []);

  const getTweetId = (tweet) => {
    const link = tweet.querySelector('a[href*="/status/"]');
    const match = link?.href?.match(/status\/(\d+)/);
    return match ? match[1] : null;
  };

  const matchesKeywords = (text) => {
    if (OPTIONS.LIKE_ALL) return true;
    if (OPTIONS.KEYWORDS.length === 0) return true;
    const lower = text.toLowerCase();
    return OPTIONS.KEYWORDS.some(kw => lower.includes(kw.toLowerCase()));
  };

  const isAlreadyLiked = (tweet) => !!tweet.querySelector(SELECTORS.unlikeButton);

  const likeTweet = async (tweet) => {
    const btn = tweet.querySelector(SELECTORS.likeButton);
    if (!btn) return false;
    await clickElement(btn);
    likeCount++;
    const id = getTweetId(tweet);
    if (id) { likedIds.add(id); storage.set('liked_tweets', [...likedIds]); }
    log(`Liked tweet #${likeCount}`, 'success');
    return true;
  };

  const run = async () => {
    log(`Starting Auto Liker (max: ${OPTIONS.MAX_LIKES} likes)`, 'info');

    while (likeCount < OPTIONS.MAX_LIKES && scrollCount < 50) {
      const tweets = document.querySelectorAll(SELECTORS.tweet);

      for (const tweet of tweets) {
        if (likeCount >= OPTIONS.MAX_LIKES) break;

        const id = getTweetId(tweet);
        if (id && likedIds.has(id)) continue;
        if (isAlreadyLiked(tweet)) continue;

        const text = tweet.querySelector(SELECTORS.tweetText)?.textContent || '';
        if (!matchesKeywords(text)) continue;

        await likeTweet(tweet);
        await sleep(OPTIONS.MIN_DELAY + Math.random() * (OPTIONS.MAX_DELAY - OPTIONS.MIN_DELAY));
      }

      scrollBy(600);
      scrollCount++;
      await sleep(2000);
    }

    log(`Done! Liked ${likeCount} tweets.`, 'success');
  };

  run();
})();

โš ๏ธ Important Notes

  • Edit the OPTIONS object to customize behavior
  • Set KEYWORDS to only like relevant tweets
  • Keep delays reasonable (2-5 seconds) to avoid rate limits
  • Start with small MAX_LIKES values to test

๐Ÿ’ฌ Auto Commenter

Monitor a user's profile and automatically reply to their new posts. Great for engagement and building relationships.

Step 1: Go to the target user's profile

Navigate to x.com/USERNAME (replace with the account you want to engage with).

Step 2: Paste core.js first (same as above)

If you haven't already, paste the core.js script from the Auto Liker section.

Step 3: Paste the Auto Commenter script

autoCommenter.js
// XActions Auto Commenter - by nichxbt
// https://github.com/nirholas/XActions
// Monitors a profile and auto-comments on new posts
// Requires core.js to be loaded first!

(() => {
  if (!window.XActions?.Core) {
    console.error('โŒ Core module not loaded! Paste core.js first.');
    return;
  }

  const { log, sleep, clickElement, waitForElement, storage, SELECTORS } = window.XActions.Core;

  // ============================================
  // CONFIGURATION - Customize your comments!
  // ============================================
  const OPTIONS = {
    // Random comments to choose from
    COMMENTS: [
      '๐Ÿ”ฅ',
      'Great point!',
      'This is so true ๐Ÿ‘',
      'Interesting take!',
      'Love this perspective',
      '๐Ÿ’ฏ',
    ],
    CHECK_INTERVAL: 60,       // Check for new posts every X seconds
    MAX_COMMENTS: 5,          // Max comments per session
    ONLY_ORIGINAL: true,      // Skip replies and retweets
  };

  let commentCount = 0;
  let isRunning = true;
  const commented = new Set(storage.get('commented_tweets') || []);

  const getUsername = () => window.location.pathname.split('/')[1];
  const getRandomComment = () => OPTIONS.COMMENTS[Math.floor(Math.random() * OPTIONS.COMMENTS.length)];

  const getTweetId = (tweet) => {
    const link = tweet.querySelector('a[href*="/status/"]');
    const match = link?.href?.match(/status\/(\d+)/);
    return match ? match[1] : null;
  };

  const postComment = async (tweet) => {
    const id = getTweetId(tweet);
    if (!id || commented.has(id)) return false;

    // Click reply button
    const replyBtn = tweet.querySelector(SELECTORS.replyButton);
    if (!replyBtn) return false;

    await clickElement(replyBtn);
    await sleep(1000);

    // Wait for reply input
    const input = await waitForElement('[data-testid="tweetTextarea_0"]', 5000);
    if (!input) {
      document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
      return false;
    }

    // Type comment
    input.focus();
    await sleep(300);
    document.execCommand('insertText', false, getRandomComment());
    await sleep(500);

    // Click post
    const postBtn = await waitForElement('[data-testid="tweetButton"]', 3000);
    if (postBtn) {
      await clickElement(postBtn);
      commentCount++;
      commented.add(id);
      storage.set('commented_tweets', [...commented]);
      log(`Posted comment #${commentCount}`, 'success');
      return true;
    }

    return false;
  };

  const checkForPosts = async () => {
    if (!isRunning || commentCount >= OPTIONS.MAX_COMMENTS) {
      log(`Session complete. Posted ${commentCount} comments.`, 'success');
      return;
    }

    log(`Checking for new posts from @${getUsername()}...`, 'info');

    const tweets = document.querySelectorAll(SELECTORS.tweet);
    for (const tweet of tweets) {
      if (commentCount >= OPTIONS.MAX_COMMENTS) break;

      const id = getTweetId(tweet);
      if (commented.has(id)) continue;

      // Skip retweets if configured
      if (OPTIONS.ONLY_ORIGINAL && tweet.textContent?.includes('reposted')) continue;

      await postComment(tweet);
      await sleep(3000);
    }

    // Schedule next check
    log(`Next check in ${OPTIONS.CHECK_INTERVAL} seconds...`, 'info');
    setTimeout(checkForPosts, OPTIONS.CHECK_INTERVAL * 1000);
  };

  // Stop function
  window.stopAutoComment = () => { isRunning = false; log('Stopping...', 'warning'); };

  log(`Auto Commenter started for @${getUsername()}`, 'info');
  log('Type stopAutoComment() to stop', 'info');
  checkForPosts();
})();

โš ๏ธ Use Responsibly

  • Customize comments to be genuine and relevant
  • Don't spam - use reasonable limits
  • Type stopAutoComment() in console to stop
  • Spammy behavior can get your account limited

๐Ÿ‘ฅ Follow Engagers

Find and follow users who liked or retweeted specific posts. Great for growing your network with engaged users.

Step 1: Go to any tweet

Navigate to a popular tweet: x.com/user/status/123456

Step 2: Paste core.js first

If you haven't already, paste the core.js script.

Step 3: Paste the Follow Engagers script

followEngagers.js
// XActions Follow Engagers - by nichxbt
// https://github.com/nirholas/XActions
// Follow users who liked/retweeted a post
// Requires core.js to be loaded first!

(() => {
  if (!window.XActions?.Core) {
    console.error('โŒ Core module not loaded! Paste core.js first.');
    return;
  }

  const { log, sleep, scrollBy, clickElement, storage, SELECTORS } = window.XActions.Core;

  // ============================================
  // CONFIGURATION
  // ============================================
  const CONFIG = {
    MODE: 'likers',           // 'likers' or 'retweeters'
    MAX_FOLLOWS: 20,          // Max follows per session
    DELAY_BETWEEN: 3000,      // Delay between follows (ms)
    SKIP_VERIFIED: false,     // Skip verified accounts
    SKIP_PROTECTED: true,     // Skip private accounts
  };

  let followCount = 0;
  const followed = new Set(storage.get('engager_followed') || []);

  const extractPostId = () => {
    const match = window.location.href.match(/status\/(\d+)/);
    return match ? match[1] : null;
  };

  const followUser = async (cell) => {
    const followBtn = cell.querySelector('[data-testid$="-follow"]');
    if (!followBtn) return false;

    // Check if already following
    if (followBtn.textContent?.includes('Following')) return false;

    // Get username
    const link = cell.querySelector('a[href^="/"]');
    const username = link?.getAttribute('href')?.replace('/', '') || 'unknown';

    if (followed.has(username)) return false;

    await clickElement(followBtn);
    followCount++;
    followed.add(username);
    storage.set('engager_followed', [...followed]);
    log(`Followed @${username} (#${followCount})`, 'success');
    return true;
  };

  const run = async () => {
    const postId = extractPostId();
    if (!postId) {
      log('Please navigate to a tweet first!', 'error');
      return;
    }

    // Navigate to likers/retweeters
    const targetUrl = CONFIG.MODE === 'likers' 
      ? `${window.location.href}/likes`
      : `${window.location.href}/retweets`;

    log(`Opening ${CONFIG.MODE} list...`, 'info');
    window.location.href = targetUrl;

    // Wait for page load
    await sleep(3000);

    log(`Starting to follow ${CONFIG.MODE} (max: ${CONFIG.MAX_FOLLOWS})`, 'info');

    let scrolls = 0;
    while (followCount < CONFIG.MAX_FOLLOWS && scrolls < 30) {
      const cells = document.querySelectorAll(SELECTORS.userCell);

      for (const cell of cells) {
        if (followCount >= CONFIG.MAX_FOLLOWS) break;

        // Skip protected if configured
        if (CONFIG.SKIP_PROTECTED && cell.querySelector('[data-testid="icon-lock"]')) continue;

        // Skip verified if configured
        if (CONFIG.SKIP_VERIFIED && cell.querySelector('[data-testid="icon-verified"]')) continue;

        if (await followUser(cell)) {
          await sleep(CONFIG.DELAY_BETWEEN);
        }
      }

      scrollBy(500);
      scrolls++;
      await sleep(2000);
    }

    log(`Done! Followed ${followCount} ${CONFIG.MODE}.`, 'success');
  };

  run();
})();

โš ๏ธ Follow Limits

  • X has daily follow limits - don't exceed ~400/day
  • Following too fast can trigger rate limits
  • Keep MAX_FOLLOWS reasonable per session
  • Use DELAY_BETWEEN of at least 2-3 seconds