💬 Auto Commenter

Engagement src/automation
288 lines by @nichxbt

XActions Automation - Auto Commenter

How to Use

  1. Go to the user's profile: x.com/USERNAME
  2. Paste core.js, then paste this script
  3. Configure your comments and let it run!

Note: This script requires pasting src/automation/core.js first for shared utilities.

Configuration Options

OptionDefaultDescription
CHECK_INTERVAL_SECONDS60How often to check for new posts
MAX_COMMENTS_PER_SESSION5Stop after this many comments
ONLY_ORIGINAL_TWEETStrueSkip replies and retweets
REQUIRE_KEYWORDfalseOnly comment if post contains keyword
KEYWORDS[]Keywords to match (if REQUIRE_KEYWORD is true)
MIN_POST_AGE_SECONDS30Don't comment on posts younger than this
MAX_POST_AGE_MINUTES30Don't comment on posts older than this

Default Configuration

const OPTIONS = {
    // Comments to randomly choose from
    COMMENTS: [
      '🔥',
      'Great point!',
      'This is so true 👏',
      'Interesting take!',
      'Love this perspective',
      '💯',
      'Facts!',
    ],
    
    // Monitoring settings
    CHECK_INTERVAL_SECONDS: 60,   // How often to check for new posts
    MAX_COMMENTS_PER_SESSION: 5,  // Stop after this many comments
    
    // Behavior
    ONLY_ORIGINAL_TWEETS: true,   // Skip replies and retweets
    REQUIRE_KEYWORD: false,       // Only comment if post contains keyword
    KEYWORDS: [],                 // Keywords to match (if REQUIRE_KEYWORD is true)
    
    // Safety
    MIN_POST_AGE_SECONDS: 30,     // Don't comment on posts younger than this
    MAX_POST_AGE_MINUTES: 30,     // Don't comment on posts older than this
  };

Full Script

Copy and paste this entire script into your browser DevTools console on x.com.

// XActions Automation - Auto Commenter
// https://github.com/nirholas/XActions
//
// REQUIRES: Paste core.js first!
//
// Monitors a user's profile for new posts and auto-comments.
// Great for engagement, building relationships, never missing a post.
//
// HOW TO USE:
// 1. Go to the user's profile: x.com/USERNAME
// 2. Paste core.js, then paste this script
// 3. Configure your comments and let it run!
//
// ⚠️ WARNING: Use responsibly! Spammy comments can get you limited.

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

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

  // ============================================
  // CONFIGURATION
  // ============================================
  const OPTIONS = {
    // Comments to randomly choose from
    COMMENTS: [
      '🔥',
      'Great point!',
      'This is so true 👏',
      'Interesting take!',
      'Love this perspective',
      '💯',
      'Facts!',
    ],
    
    // Monitoring settings
    CHECK_INTERVAL_SECONDS: 60,   // How often to check for new posts
    MAX_COMMENTS_PER_SESSION: 5,  // Stop after this many comments
    
    // Behavior
    ONLY_ORIGINAL_TWEETS: true,   // Skip replies and retweets
    REQUIRE_KEYWORD: false,       // Only comment if post contains keyword
    KEYWORDS: [],                 // Keywords to match (if REQUIRE_KEYWORD is true)
    
    // Safety
    MIN_POST_AGE_SECONDS: 30,     // Don't comment on posts younger than this
    MAX_POST_AGE_MINUTES: 30,     // Don't comment on posts older than this
  };

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

  // ============================================
  // HELPERS
  // ============================================
  const getUsername = () => {
    const match = window.location.pathname.match(/^\/([^/]+)/);
    return match ? match[1] : 'unknown';
  };

  const getRandomComment = () => {
    return OPTIONS.COMMENTS[Math.floor(Math.random() * OPTIONS.COMMENTS.length)];
  };

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

  const getTweetAge = (tweetElement) => {
    // Try to find the time element
    const timeEl = tweetElement.querySelector('time');
    if (timeEl) {
      const datetime = timeEl.getAttribute('datetime');
      if (datetime) {
        return Date.now() - new Date(datetime).getTime();
      }
    }
    return null;
  };

  const isReply = (tweetElement) => {
    return !!tweetElement.textContent?.includes('Replying to');
  };

  const isRetweet = (tweetElement) => {
    return !!tweetElement.closest('article')?.textContent?.includes('reposted');
  };

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

  // ============================================
  // COMMENT ACTION
  // ============================================
  const postComment = async (tweetElement) => {
    const tweetId = getTweetId(tweetElement);
    if (!tweetId) return false;

    // Click reply button
    const replyBtn = tweetElement.querySelector(SELECTORS.replyButton);
    if (!replyBtn) {
      log('Reply button not found', 'warning');
      return false;
    }

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

    // Wait for reply modal/input
    const replyInput = await waitForElement('[data-testid="tweetTextarea_0"]', 5000);
    if (!replyInput) {
      log('Reply input not found', 'warning');
      // Press Escape to close any modal
      document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
      return false;
    }

    // Type the comment
    const comment = getRandomComment();
    
    // Focus and type
    replyInput.focus();
    await sleep(300);
    
    // Use execCommand for contenteditable
    document.execCommand('insertText', false, comment);
    await sleep(500);

    // Find and click the Reply/Post button
    const postBtn = await waitForElement('[data-testid="tweetButton"]', 3000);
    if (!postBtn) {
      log('Post button not found', 'warning');
      document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
      return false;
    }

    // Check if button is enabled
    if (postBtn.disabled || postBtn.getAttribute('aria-disabled') === 'true') {
      log('Post button is disabled', 'warning');
      await sleep(500);
    }

    await clickElement(postBtn);
    await sleep(1000);

    // Save that we commented
    commentedTweets.add(tweetId);
    storage.set('commented_tweets', Array.from(commentedTweets));
    commentCount++;

    log(`💬 Commented "${comment}" on tweet ${tweetId}`, 'success');
    return true;
  };

  // ============================================
  // FIND NEW TWEETS
  // ============================================
  const findNewTweets = () => {
    const tweets = document.querySelectorAll(SELECTORS.tweet);
    const newTweets = [];

    for (const tweet of tweets) {
      const tweetId = getTweetId(tweet);
      if (!tweetId) continue;
      
      // Skip if already commented
      if (commentedTweets.has(tweetId)) continue;

      // Skip replies/retweets if configured
      if (OPTIONS.ONLY_ORIGINAL_TWEETS) {
        if (isReply(tweet) || isRetweet(tweet)) continue;
      }

      // Check age
      const ageMs = getTweetAge(tweet);
      if (ageMs !== null) {
        const ageSeconds = ageMs / 1000;
        const ageMinutes = ageSeconds / 60;
        
        if (ageSeconds < OPTIONS.MIN_POST_AGE_SECONDS) continue;
        if (ageMinutes > OPTIONS.MAX_POST_AGE_MINUTES) continue;
      }

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

      newTweets.push({ element: tweet, tweetId, text: text.substring(0, 50) });
    }

    return newTweets;
  };

  // ============================================
  // MAIN LOOP
  // ============================================
  const check = async () => {
    if (!isRunning) return;
    if (commentCount >= OPTIONS.MAX_COMMENTS_PER_SESSION) {
      log(`Reached max comments (${OPTIONS.MAX_COMMENTS_PER_SESSION}). Stopping.`, 'warning');
      return;
    }

    checkCount++;
    const time = new Date().toLocaleTimeString();
    log(`[${time}] Check #${checkCount}...`, 'info');

    // Refresh the page content
    scrollToTop();
    await sleep(2000);

    // Find new tweets
    const newTweets = findNewTweets();

    if (newTweets.length > 0) {
      log(`Found ${newTweets.length} new tweets to comment on!`, 'success');
      
      for (const { element, tweetId, text } of newTweets) {
        if (commentCount >= OPTIONS.MAX_COMMENTS_PER_SESSION) break;
        
        log(`Commenting on: "${text}..."`, 'action');
        await postComment(element);
        await randomDelay(3000, 6000);
      }
    } else {
      log('No new tweets found', 'info');
    }

    // Schedule next check
    if (isRunning && commentCount < OPTIONS.MAX_COMMENTS_PER_SESSION) {
      log(`Next check in ${OPTIONS.CHECK_INTERVAL_SECONDS} seconds...`, 'info');
      setTimeout(check, OPTIONS.CHECK_INTERVAL_SECONDS * 1000);
    }
  };

  // ============================================
  // START
  // ============================================
  const run = async () => {
    const username = getUsername();
    
    console.log(`
╔═══════════════════════════════════════════════════════════╗
║  💬 XActions Auto Commenter                              ║
║                                                           ║
║  Watching: @${username.padEnd(20)}                        ║
║  Interval: Every ${OPTIONS.CHECK_INTERVAL_SECONDS} seconds                         ║
║  Max comments: ${OPTIONS.MAX_COMMENTS_PER_SESSION}                                    ║
║                                                           ║
║  Keep this tab open!                                      ║
║  Run stopAutoComment() to stop.                           ║
╚═══════════════════════════════════════════════════════════╝
    `);

    log('Starting auto-commenter...', 'info');
    log(`Comments pool: ${OPTIONS.COMMENTS.join(', ')}`, 'info');
    log(`Previously commented on ${commentedTweets.size} tweets`, 'info');

    await check();
  };

  run();

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

⚡ More XActions Scripts

Browse 300+ free browser scripts for X/Twitter automation. No API keys, no fees.

Browse All Scripts