⚡ Advanced DM

Other Tools src
1053 lines by @nichxbt

src/advancedDM.js

How to Use

  1. Navigate to x.com and log in
  2. Open DevTools Console (F12 or Cmd+Option+I)
  3. Paste the script below and press Enter

Full Script

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

// src/advancedDM.js
// Advanced Direct Message features for X/Twitter
// by nichxbt
//
// 1. Go to https://x.com/messages (or open a DM conversation)
// 2. Open DevTools Console (F12)
// 3. Paste and run this script
// 4. Use window.XActions.advancedDM.<function>() to call features
//
// Features:
//   sendMedia(filePath)         - Send photo/video/GIF via the media button
//   sendGIF(searchTerm)         - Search and send a GIF in the current conversation
//   sendVoiceMessage()          - Record and send a voice message
//   startAudioCall()            - Initiate an audio call from the conversation
//   startVideoCall()            - Initiate a video call from the conversation
//   toggleEncryptedDM()         - Toggle encrypted DMs (lock icon)
//   pinConversation()           - Pin the current conversation
//   deleteMessage(messageEl)    - Delete a specific message (hover > menu)
//   deleteConversation()        - Delete the entire current conversation
//   leaveGroupDM()              - Leave a group DM conversation
//   renameGroupDM(newName)      - Rename a group DM
//   addGroupMember(username)    - Add a member to a group DM
//   removeGroupMember(username) - Remove a member from a group DM
//   toggleReadReceipts()        - Toggle read receipts in DM settings
//   sharePostViaDM(tweetUrl, username) - Share a tweet to a DM conversation
//
// Last Updated: 30 March 2026
(() => {
  'use strict';

  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
  const $ = (s) => document.querySelector(s);
  const $$ = (s) => [...document.querySelectorAll(s)];

  const SEL = {
    // Compose area
    composerInput:      '[data-testid="dmComposerTextInput"]',
    sendButton:         '[data-testid="dmComposerSendButton"]',
    mediaButton:        '[data-testid="fileInput"]',
    gifButton:          '[aria-label="GIF"]',
    gifSearchInput:     '[data-testid="gifSearchField"], input[aria-label="Search for GIFs"]',
    gifResult:          '[data-testid="gifSearchResult"], [role="option"] img',
    voiceButton:        '[aria-label="Voice message"], [data-testid="voiceMessageButton"]',
    voiceRecordStop:    '[aria-label="Stop recording"], [data-testid="voiceMessageStop"]',
    voiceRecordSend:    '[aria-label="Send voice message"], [data-testid="voiceMessageSend"]',

    // Calls
    audioCallButton:    '[aria-label="Start an audio call"], [data-testid="dmAudioCallButton"]',
    videoCallButton:    '[aria-label="Start a video call"], [data-testid="dmVideoCallButton"]',

    // Conversation header / info
    dmHeader:           '[data-testid="DmActivityContainer"]',
    conversationInfo:   '[data-testid="conversationInfoButton"], [aria-label="Conversation info"]',
    lockIcon:           '[data-testid="lockIcon"]',
    encryptedToggle:    '[data-testid="encryptedDM"]',

    // Conversation list
    conversation:       '[data-testid="conversation"]',
    newDM:              '[data-testid="NewDM_Button"]',
    searchPeople:       '[data-testid="searchPeople"]',
    typeaheadUser:      '[data-testid="TypeaheadUser"]',
    nextButton:         '[data-testid="nextButton"]',

    // Context menus & actions
    messageEntry:       '[data-testid="messageEntry"]',
    moreButton:         '[data-testid="caret"], [aria-label="More"]',
    deleteMenuItem:     '[data-testid="Dropdown-Item-Delete"], [role="menuitem"]',
    confirmButton:      '[data-testid="confirmationSheetConfirm"]',
    backButton:         '[data-testid="app-bar-back"]',

    // Group DM
    groupNameInput:     '[data-testid="groupDmName"], input[name="groupName"]',
    addMemberButton:    '[data-testid="addMember"], [aria-label="Add people"]',
    leaveButton:        '[data-testid="leaveConversation"]',
    removeMemberButton: '[data-testid="removeMember"]',

    // Settings
    settingsSwitch:     '[data-testid="settingsSwitch"]',
    toast:              '[data-testid="toast"]',
  };

  const waitForSelector = async (selector, timeout = 10000) => {
    const start = Date.now();
    while (Date.now() - start < timeout) {
      const el = $(selector);
      if (el) return el;
      await sleep(200);
    }
    return null;
  };

  const waitForAny = async (selectors, timeout = 10000) => {
    const start = Date.now();
    while (Date.now() - start < timeout) {
      for (const s of selectors) {
        const el = $(s);
        if (el) return el;
      }
      await sleep(200);
    }
    return null;
  };

  const typeText = async (element, text, charDelay = 50) => {
    element.focus();
    for (const char of text) {
      document.execCommand('insertText', false, char);
      element.dispatchEvent(new InputEvent('input', { bubbles: true, data: char, inputType: 'insertText' }));
      await sleep(charDelay);
    }
  };

  const clearInput = (element) => {
    element.focus();
    document.execCommand('selectAll', false, null);
    document.execCommand('delete', false, null);
    element.dispatchEvent(new Event('input', { bubbles: true }));
  };

  const ensureOnMessages = () => {
    if (!window.location.href.includes('/messages')) {
      console.log('❌ Navigate to https://x.com/messages first');
      return false;
    }
    return true;
  };

  const ensureInConversation = () => {
    if (!$(SEL.composerInput) && !$(SEL.dmHeader)) {
      console.log('❌ Open a DM conversation first');
      return false;
    }
    return true;
  };

  const openConversationInfo = async () => {
    const infoBtn = await waitForAny([
      SEL.conversationInfo,
      '[data-testid="conversationInfoButton"]',
      '[aria-label="Conversation info"]',
    ], 5000);
    if (!infoBtn) {
      console.log('❌ Could not find conversation info button');
      return false;
    }
    infoBtn.click();
    await sleep(2000);
    return true;
  };

  // ─────────────────────────────────────────────────────────
  // 1. Send media in DMs
  // ─────────────────────────────────────────────────────────
  const sendMedia = async () => {
    if (!ensureInConversation()) return;
    console.log('🔄 Opening media file picker...');
    console.log('💡 The file picker will open. Select your photo or video.');

    const fileInput = $(SEL.mediaButton);
    if (!fileInput) {
      // Try to find a hidden file input in the DM compose area
      const hiddenInput = $('input[type="file"][accept*="image"], input[type="file"][accept*="video"]');
      if (hiddenInput) {
        hiddenInput.click();
        console.log('✅ File picker opened. Select your media file.');
      } else {
        console.log('❌ Could not find media input. Make sure the DM compose bar is visible.');
      }
      return;
    }

    fileInput.click();
    console.log('✅ File picker opened. Select your photo/video to attach.');
    console.log('💡 After selecting, click the send button or call sendMessage().');
  };

  // ─────────────────────────────────────────────────────────
  // 2. Send GIF in DMs
  // ─────────────────────────────────────────────────────────
  const sendGIF = async (searchTerm = '') => {
    if (!ensureInConversation()) return;
    console.log('🔄 Opening GIF picker...');

    const gifBtn = await waitForAny([
      SEL.gifButton,
      '[aria-label="GIF"]',
      '[data-testid="gifButton"]',
      'button[aria-label*="GIF"]',
    ], 5000);

    if (!gifBtn) {
      console.log('❌ Could not find GIF button in DM compose area');
      return;
    }

    gifBtn.click();
    await sleep(2000);

    if (searchTerm) {
      console.log(`🔍 Searching for GIF: "${searchTerm}"`);
      const searchInput = await waitForAny([
        SEL.gifSearchInput,
        'input[placeholder*="Search"]',
        '[data-testid="gifSearchField"]',
      ], 5000);

      if (searchInput) {
        clearInput(searchInput);
        await typeText(searchInput, searchTerm);
        await sleep(2000);

        const firstGif = await waitForAny([
          SEL.gifResult,
          '[data-testid="gifSearchResult"]',
          '[role="option"] img',
        ], 5000);

        if (firstGif) {
          firstGif.click();
          await sleep(1500);
          console.log(`✅ GIF "${searchTerm}" sent!`);
        } else {
          console.log(`⚠️ No GIF results found for "${searchTerm}"`);
        }
      } else {
        console.log('⚠️ Could not find GIF search input');
      }
    } else {
      console.log('✅ GIF picker opened. Select a GIF to send.');
    }
  };

  // ─────────────────────────────────────────────────────────
  // 3. Send voice message
  // ─────────────────────────────────────────────────────────
  const sendVoiceMessage = async (durationMs = 5000) => {
    if (!ensureInConversation()) return;
    console.log('🔄 Starting voice message recording...');

    const voiceBtn = await waitForAny([
      SEL.voiceButton,
      '[aria-label="Voice message"]',
      '[data-testid="voiceMessageButton"]',
    ], 5000);

    if (!voiceBtn) {
      console.log('❌ Could not find voice message button');
      console.log('💡 Voice messages may require X Premium or may not be available in all regions');
      return;
    }

    voiceBtn.click();
    await sleep(1500);

    console.log(`🎙️ Recording for ${(durationMs / 1000).toFixed(1)}s...`);
    await sleep(durationMs);

    // Stop recording
    const stopBtn = await waitForAny([
      SEL.voiceRecordStop,
      '[aria-label="Stop recording"]',
      '[data-testid="voiceMessageStop"]',
    ], 3000);

    if (stopBtn) {
      stopBtn.click();
      await sleep(1000);
    }

    // Send the voice message
    const sendVoiceBtn = await waitForAny([
      SEL.voiceRecordSend,
      '[aria-label="Send voice message"]',
      '[data-testid="voiceMessageSend"]',
      SEL.sendButton,
    ], 3000);

    if (sendVoiceBtn) {
      sendVoiceBtn.click();
      await sleep(1500);
      console.log('✅ Voice message sent!');
    } else {
      console.log('⚠️ Could not find send button for voice message');
    }
  };

  // ─────────────────────────────────────────────────────────
  // 4. Audio call
  // ─────────────────────────────────────────────────────────
  const startAudioCall = async () => {
    if (!ensureInConversation()) return;
    console.log('🔄 Starting audio call...');

    const callBtn = await waitForAny([
      SEL.audioCallButton,
      '[aria-label="Start an audio call"]',
      '[data-testid="dmAudioCallButton"]',
      '[aria-label="Audio call"]',
    ], 5000);

    if (!callBtn) {
      console.log('❌ Could not find audio call button');
      console.log('💡 Audio calls must be enabled in DM settings and both users must allow them');
      return;
    }

    callBtn.click();
    await sleep(2000);
    console.log('✅ Audio call initiated!');
  };

  // ─────────────────────────────────────────────────────────
  // 5. Video call
  // ─────────────────────────────────────────────────────────
  const startVideoCall = async () => {
    if (!ensureInConversation()) return;
    console.log('🔄 Starting video call...');

    const callBtn = await waitForAny([
      SEL.videoCallButton,
      '[aria-label="Start a video call"]',
      '[data-testid="dmVideoCallButton"]',
      '[aria-label="Video call"]',
    ], 5000);

    if (!callBtn) {
      console.log('❌ Could not find video call button');
      console.log('💡 Video calls must be enabled in DM settings and both users must allow them');
      return;
    }

    callBtn.click();
    await sleep(2000);
    console.log('✅ Video call initiated!');
  };

  // ─────────────────────────────────────────────────────────
  // 6. Toggle encrypted DMs
  // ─────────────────────────────────────────────────────────
  const toggleEncryptedDM = async () => {
    if (!ensureInConversation()) return;
    console.log('🔄 Toggling encrypted DM mode...');

    // Check for lock icon in the conversation header area
    const lockIcon = $(SEL.lockIcon);
    const encryptedToggle = $(SEL.encryptedToggle);

    if (encryptedToggle) {
      encryptedToggle.click();
      await sleep(2000);
      console.log('✅ Encrypted DM mode toggled');
      return;
    }

    if (lockIcon) {
      lockIcon.click();
      await sleep(2000);
      console.log('✅ Encrypted DM lock toggled');
      return;
    }

    // Try opening conversation info to find the toggle
    const opened = await openConversationInfo();
    if (opened) {
      const toggle = await waitForAny([
        SEL.encryptedToggle,
        SEL.lockIcon,
        '[aria-label*="ncrypt"]',
      ], 5000);
      if (toggle) {
        toggle.click();
        await sleep(2000);
        console.log('✅ Encrypted DM mode toggled');
      } else {
        console.log('❌ Could not find encryption toggle');
        console.log('💡 Both users must have X Premium for encrypted DMs');
      }
    }
  };

  // ─────────────────────────────────────────────────────────
  // 7. Pin conversation
  // ─────────────────────────────────────────────────────────
  const pinConversation = async () => {
    if (!ensureOnMessages()) return;
    console.log('🔄 Pinning current conversation...');

    const opened = await openConversationInfo();
    if (!opened) return;

    // Look for pin option
    const pinBtn = await waitForAny([
      '[data-testid="pinConversation"]',
      '[aria-label="Pin conversation"]',
      'div[role="menuitem"]',
    ], 5000);

    if (pinBtn) {
      // If we got a generic menuitem, try to find the one with "Pin" text
      if (!pinBtn.matches('[data-testid="pinConversation"], [aria-label="Pin conversation"]')) {
        const menuItems = $$('[role="menuitem"]');
        const pinItem = menuItems.find(el => el.textContent.toLowerCase().includes('pin'));
        if (pinItem) {
          pinItem.click();
          await sleep(1500);
          console.log('✅ Conversation pinned!');
          return;
        }
      } else {
        pinBtn.click();
        await sleep(1500);
        console.log('✅ Conversation pinned!');
        return;
      }
    }

    // Alternative: try right-clicking or long-pressing the conversation in the list
    console.log('⚠️ Pin button not found in conversation info');
    console.log('💡 Try right-clicking the conversation in the list and selecting "Pin"');
  };

  // ─────────────────────────────────────────────────────────
  // 8. Delete specific message
  // ─────────────────────────────────────────────────────────
  const deleteMessage = async (messageIndex = -1) => {
    if (!ensureInConversation()) return;
    console.log('🔄 Deleting a specific message...');

    const messages = $$(SEL.messageEntry);
    if (messages.length === 0) {
      console.log('❌ No messages found in this conversation');
      return;
    }

    // Default to the last message (your most recent)
    const targetIndex = messageIndex >= 0 ? messageIndex : messages.length - 1;
    if (targetIndex >= messages.length) {
      console.log(`❌ Message index ${targetIndex} out of range (0-${messages.length - 1})`);
      return;
    }

    const targetMsg = messages[targetIndex];
    console.log(`🔄 Targeting message #${targetIndex}: "${targetMsg.textContent.substring(0, 50)}..."`);

    // Hover over the message to reveal the action menu
    targetMsg.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
    targetMsg.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
    await sleep(1000);

    // Click the more/caret button that appears on hover
    const moreBtn = targetMsg.querySelector(SEL.moreButton) ||
                    targetMsg.querySelector('[aria-label="More"]') ||
                    targetMsg.querySelector('[data-testid="caret"]');

    if (!moreBtn) {
      // Try finding it near the message
      const moreBtnGlobal = await waitForAny([
        SEL.moreButton,
        '[aria-label="More"]',
      ], 3000);
      if (!moreBtnGlobal) {
        console.log('❌ Could not find message action menu');
        console.log('💡 You can only delete your own messages');
        return;
      }
      moreBtnGlobal.click();
    } else {
      moreBtn.click();
    }

    await sleep(1500);

    // Find and click delete option
    const deleteItem = await waitForAny([
      SEL.deleteMenuItem,
      '[role="menuitem"]',
    ], 5000);

    if (deleteItem) {
      // If generic menuitem, find the delete one
      const menuItems = $$('[role="menuitem"]');
      const deleteOption = menuItems.find(el => el.textContent.toLowerCase().includes('delete'));
      if (deleteOption) {
        deleteOption.click();
        await sleep(1500);

        // Confirm deletion
        const confirmBtn = await waitForSelector(SEL.confirmButton, 5000);
        if (confirmBtn) {
          confirmBtn.click();
          await sleep(1500);
          console.log('✅ Message deleted!');
        } else {
          console.log('⚠️ Confirmation dialog not found. Message may already be deleted.');
        }
      } else {
        console.log('❌ Delete option not found in menu');
      }
    } else {
      console.log('❌ Could not find message action menu items');
    }
  };

  // ─────────────────────────────────────────────────────────
  // 9. Delete entire conversation
  // ─────────────────────────────────────────────────────────
  const deleteConversation = async () => {
    if (!ensureOnMessages()) return;
    console.log('🔄 Deleting entire conversation...');

    const opened = await openConversationInfo();
    if (!opened) return;

    await sleep(1000);

    // Look for delete conversation option
    const deleteBtn = await waitForAny([
      '[data-testid="deleteConversation"]',
      '[aria-label="Delete conversation"]',
    ], 5000);

    if (!deleteBtn) {
      // Try scrolling down in the info panel to find it
      const menuItems = $$('[role="menuitem"], [role="button"]');
      const deleteOption = menuItems.find(el =>
        el.textContent.toLowerCase().includes('delete conversation') ||
        el.textContent.toLowerCase().includes('delete')
      );
      if (deleteOption) {
        deleteOption.click();
      } else {
        console.log('❌ Could not find delete conversation option');
        return;
      }
    } else {
      deleteBtn.click();
    }

    await sleep(1500);

    // Confirm deletion
    const confirmBtn = await waitForSelector(SEL.confirmButton, 5000);
    if (confirmBtn) {
      confirmBtn.click();
      await sleep(2000);
      console.log('✅ Conversation deleted!');
    } else {
      console.log('⚠️ Confirmation dialog not found');
    }
  };

  // ─────────────────────────────────────────────────────────
  // 10. Leave group DM
  // ─────────────────────────────────────────────────────────
  const leaveGroupDM = async () => {
    if (!ensureInConversation()) return;
    console.log('🔄 Leaving group DM...');

    const opened = await openConversationInfo();
    if (!opened) return;

    await sleep(1000);

    const leaveBtn = await waitForAny([
      SEL.leaveButton,
      '[data-testid="leaveConversation"]',
      '[aria-label="Leave conversation"]',
    ], 5000);

    if (!leaveBtn) {
      // Try finding by text
      const buttons = $$('[role="button"], [role="menuitem"]');
      const leaveOption = buttons.find(el =>
        el.textContent.toLowerCase().includes('leave')
      );
      if (leaveOption) {
        leaveOption.click();
      } else {
        console.log('❌ Could not find "Leave conversation" option');
        console.log('💡 This feature is only available in group DMs');
        return;
      }
    } else {
      leaveBtn.click();
    }

    await sleep(1500);

    const confirmBtn = await waitForSelector(SEL.confirmButton, 5000);
    if (confirmBtn) {
      confirmBtn.click();
      await sleep(2000);
      console.log('✅ Left the group DM!');
    } else {
      console.log('⚠️ Confirmation dialog not found');
    }
  };

  // ─────────────────────────────────────────────────────────
  // 11. Rename group DM
  // ─────────────────────────────────────────────────────────
  const renameGroupDM = async (newName) => {
    if (!newName || typeof newName !== 'string') {
      console.log('❌ Provide a new name: renameGroupDM("My Group")');
      return;
    }
    if (!ensureInConversation()) return;
    console.log(`🔄 Renaming group DM to "${newName}"...`);

    const opened = await openConversationInfo();
    if (!opened) return;

    await sleep(1000);

    // Look for the group name edit area
    const nameInput = await waitForAny([
      SEL.groupNameInput,
      '[data-testid="groupDmName"]',
      'input[name="groupName"]',
      '[aria-label="Group name"]',
    ], 5000);

    if (!nameInput) {
      // Try clicking on the group name text to make it editable
      const headerTexts = $$('h2, [role="heading"]');
      const groupHeader = headerTexts.find(el => el.closest('[data-testid="DmActivityContainer"], [data-testid="conversationInfoButton"]'));
      if (groupHeader) {
        groupHeader.click();
        await sleep(1500);
        const editInput = await waitForAny([
          'input[name="groupName"]',
          '[data-testid="groupDmName"]',
          'input[aria-label="Group name"]',
        ], 5000);
        if (editInput) {
          clearInput(editInput);
          await typeText(editInput, newName);
          await sleep(1000);

          // Save / confirm
          const saveBtn = await waitForAny([
            '[data-testid="confirmationSheetConfirm"]',
            '[data-testid="Profile_Save_Button"]',
            'button[type="submit"]',
          ], 3000);
          if (saveBtn) saveBtn.click();
          await sleep(1500);
          console.log(`✅ Group DM renamed to "${newName}"!`);
          return;
        }
      }
      console.log('❌ Could not find group name input');
      console.log('💡 This feature is only available in group DMs');
      return;
    }

    clearInput(nameInput);
    await typeText(nameInput, newName);
    await sleep(1000);

    // Look for save/done button
    const saveBtn = await waitForAny([
      '[data-testid="confirmationSheetConfirm"]',
      '[data-testid="Profile_Save_Button"]',
      'button[type="submit"]',
    ], 3000);
    if (saveBtn) {
      saveBtn.click();
      await sleep(1500);
    }

    console.log(`✅ Group DM renamed to "${newName}"!`);
  };

  // ─────────────────────────────────────────────────────────
  // 12. Add member to group DM
  // ─────────────────────────────────────────────────────────
  const addGroupMember = async (username) => {
    if (!username || typeof username !== 'string') {
      console.log('❌ Provide a username: addGroupMember("username")');
      return;
    }
    if (!ensureInConversation()) return;
    console.log(`🔄 Adding @${username} to group DM...`);

    const opened = await openConversationInfo();
    if (!opened) return;

    await sleep(1000);

    const addBtn = await waitForAny([
      SEL.addMemberButton,
      '[data-testid="addMember"]',
      '[aria-label="Add people"]',
      '[aria-label="Add members"]',
    ], 5000);

    if (!addBtn) {
      console.log('❌ Could not find "Add people" button');
      console.log('💡 This feature is only available in group DMs');
      return;
    }

    addBtn.click();
    await sleep(2000);

    const searchInput = await waitForAny([
      SEL.searchPeople,
      '[data-testid="searchPeople"]',
      'input[placeholder*="Search"]',
    ], 5000);

    if (!searchInput) {
      console.log('❌ Could not find search input');
      return;
    }

    await typeText(searchInput, username);
    await sleep(2000);

    const userResult = await waitForAny([
      '[data-testid="TypeaheadUser"]',
      '[data-testid="UserCell"]',
    ], 5000);

    if (!userResult) {
      console.log(`⚠️ Could not find @${username} in search results`);
      return;
    }

    userResult.click();
    await sleep(1500);

    // Confirm adding
    const doneBtn = await waitForAny([
      SEL.nextButton,
      '[data-testid="nextButton"]',
      SEL.confirmButton,
    ], 3000);

    if (doneBtn) {
      doneBtn.click();
      await sleep(1500);
    }

    console.log(`✅ @${username} added to group DM!`);
  };

  // ─────────────────────────────────────────────────────────
  // 13. Remove member from group DM
  // ─────────────────────────────────────────────────────────
  const removeGroupMember = async (username) => {
    if (!username || typeof username !== 'string') {
      console.log('❌ Provide a username: removeGroupMember("username")');
      return;
    }
    if (!ensureInConversation()) return;
    console.log(`🔄 Removing @${username} from group DM...`);

    const opened = await openConversationInfo();
    if (!opened) return;

    await sleep(1000);

    // Find the member in the member list
    const memberCells = $$('[data-testid="UserCell"]');
    const memberCell = memberCells.find(cell =>
      cell.textContent.toLowerCase().includes(username.toLowerCase())
    );

    if (!memberCell) {
      console.log(`❌ Could not find @${username} in group members`);
      return;
    }

    // Click on the member to open their options
    memberCell.click();
    await sleep(1500);

    const removeBtn = await waitForAny([
      SEL.removeMemberButton,
      '[data-testid="removeMember"]',
      '[aria-label="Remove from conversation"]',
    ], 5000);

    if (!removeBtn) {
      // Try looking in a dropdown menu
      const menuItems = $$('[role="menuitem"]');
      const removeOption = menuItems.find(el =>
        el.textContent.toLowerCase().includes('remove')
      );
      if (removeOption) {
        removeOption.click();
      } else {
        console.log('❌ Could not find "Remove" option');
        console.log('💡 You may need to be the group admin to remove members');
        return;
      }
    } else {
      removeBtn.click();
    }

    await sleep(1500);

    const confirmBtn = await waitForSelector(SEL.confirmButton, 5000);
    if (confirmBtn) {
      confirmBtn.click();
      await sleep(1500);
    }

    console.log(`✅ @${username} removed from group DM!`);
  };

  // ─────────────────────────────────────────────────────────
  // 14. Toggle read receipts
  // ─────────────────────────────────────────────────────────
  const toggleReadReceipts = async () => {
    console.log('🔄 Toggling DM read receipts...');
    console.log('🔄 Navigating to DM settings...');

    // Navigate to DM settings
    window.location.href = 'https://x.com/settings/direct_messages';
    await sleep(3000);

    // Wait for the page to load
    const settingsPage = await waitForAny([
      SEL.settingsSwitch,
      'input[type="checkbox"]',
      '[role="switch"]',
    ], 10000);

    if (!settingsPage) {
      console.log('❌ Could not find DM settings controls');
      console.log('💡 Try navigating to Settings > Privacy and Safety > Direct Messages manually');
      return;
    }

    // Look for read receipts toggle
    const switches = $$('[role="switch"], input[type="checkbox"], [data-testid="settingsSwitch"]');
    const labels = $$('span, label');
    const readReceiptLabel = labels.find(el =>
      el.textContent.toLowerCase().includes('read receipt') ||
      el.textContent.toLowerCase().includes('show read')
    );

    if (readReceiptLabel) {
      // Find the closest toggle to this label
      const container = readReceiptLabel.closest('[role="listitem"], div[class]');
      if (container) {
        const toggle = container.querySelector('[role="switch"], input[type="checkbox"], [data-testid="settingsSwitch"]');
        if (toggle) {
          toggle.click();
          await sleep(2000);
          console.log('✅ Read receipts toggled!');
          return;
        }
      }

      // Fallback: click the label area itself
      readReceiptLabel.click();
      await sleep(2000);
      console.log('✅ Read receipts setting clicked');
      return;
    }

    // If we can't find by label, toggle the first switch (often read receipts)
    if (switches.length > 0) {
      console.log(`⚠️ Found ${switches.length} toggle(s). Clicking the first one (likely read receipts).`);
      switches[0].click();
      await sleep(2000);
      console.log('✅ Setting toggled. Verify it was the correct one.');
    } else {
      console.log('❌ Could not find read receipts toggle');
    }
  };

  // ─────────────────────────────────────────────────────────
  // 15. Share post via DM
  // ─────────────────────────────────────────────────────────
  const sharePostViaDM = async (tweetUrl, username) => {
    if (!tweetUrl || typeof tweetUrl !== 'string') {
      console.log('❌ Provide a tweet URL: sharePostViaDM("https://x.com/user/status/123", "recipient")');
      return;
    }
    if (!username || typeof username !== 'string') {
      console.log('❌ Provide a recipient username: sharePostViaDM("url", "username")');
      return;
    }

    console.log(`🔄 Sharing post to @${username} via DM...`);

    // Navigate to the tweet first
    if (!window.location.href.includes(tweetUrl.replace('https://x.com', ''))) {
      window.location.href = tweetUrl;
      await sleep(3000);
    }

    // Find the share button on the tweet
    const shareBtn = await waitForAny([
      '[data-testid="share"]',
      'button[aria-label="Share post"]',
      'button[aria-label="Share Tweet"]',
    ], 5000);

    if (!shareBtn) {
      // Alternative: try the first tweet's share button
      const tweets = $$('article[data-testid="tweet"]');
      if (tweets.length > 0) {
        const tweetShareBtn = tweets[0].querySelector('[data-testid="share"], [aria-label*="Share"]');
        if (tweetShareBtn) {
          tweetShareBtn.click();
          await sleep(1500);
        } else {
          console.log('❌ Could not find share button on the tweet');
          return;
        }
      } else {
        console.log('❌ Could not find the tweet');
        return;
      }
    } else {
      shareBtn.click();
      await sleep(1500);
    }

    // Click "Send via Direct Message" in the share menu
    const menuItems = $$('[role="menuitem"]');
    const dmOption = menuItems.find(el =>
      el.textContent.toLowerCase().includes('direct message') ||
      el.textContent.toLowerCase().includes('send via')
    );

    if (dmOption) {
      dmOption.click();
      await sleep(2000);
    } else {
      // Try looking for the DM icon option
      const sendDMBtn = await waitForAny([
        '[data-testid="sendViaDM"]',
        '[aria-label*="Direct Message"]',
        '[aria-label*="Send via"]',
      ], 3000);
      if (sendDMBtn) {
        sendDMBtn.click();
        await sleep(2000);
      } else {
        console.log('❌ Could not find "Send via Direct Message" option');
        console.log('💡 Alternatively, just paste the tweet URL in a DM');
        return;
      }
    }

    // Search for the recipient
    const searchInput = await waitForAny([
      SEL.searchPeople,
      'input[placeholder*="Search"]',
    ], 5000);

    if (searchInput) {
      await typeText(searchInput, username);
      await sleep(2000);

      const userResult = await waitForAny([
        '[data-testid="TypeaheadUser"]',
        '[data-testid="UserCell"]',
      ], 5000);

      if (userResult) {
        userResult.click();
        await sleep(1500);

        // Click send
        const sendBtn = await waitForAny([
          SEL.sendButton,
          SEL.nextButton,
          '[data-testid="dmComposerSendButton"]',
        ], 3000);

        if (sendBtn) {
          sendBtn.click();
          await sleep(2000);
          console.log(`✅ Post shared with @${username} via DM!`);
        } else {
          console.log('⚠️ Could not find send button');
        }
      } else {
        console.log(`⚠️ Could not find @${username} in search results`);
      }
    } else {
      console.log('❌ Could not find recipient search input');
    }
  };

  // ─────────────────────────────────────────────────────────
  // Expose on window.XActions.advancedDM
  // ─────────────────────────────────────────────────────────
  window.XActions = window.XActions || {};
  window.XActions.advancedDM = {
    sendMedia,
    sendGIF,
    sendVoiceMessage,
    startAudioCall,
    startVideoCall,
    toggleEncryptedDM,
    pinConversation,
    deleteMessage,
    deleteConversation,
    leaveGroupDM,
    renameGroupDM,
    addGroupMember,
    removeGroupMember,
    toggleReadReceipts,
    sharePostViaDM,
  };

  // ─────────────────────────────────────────────────────────
  // Print menu
  // ─────────────────────────────────────────────────────────
  const W = 62;
  console.log('╔' + '═'.repeat(W) + '╗');
  console.log('║  💬 ADVANCED DM FEATURES — XActions' + ' '.repeat(W - 38) + '║');
  console.log('║  by nichxbt — v1.0' + ' '.repeat(W - 21) + '║');
  console.log('╠' + '═'.repeat(W) + '╣');
  console.log('║  📎 Media & Content' + ' '.repeat(W - 21) + '║');
  console.log('║    sendMedia()' + ' '.repeat(W - 16) + '║');
  console.log('║    sendGIF("search term")' + ' '.repeat(W - 27) + '║');
  console.log('║    sendVoiceMessage(durationMs)' + ' '.repeat(W - 33) + '║');
  console.log('║    sharePostViaDM(tweetUrl, username)' + ' '.repeat(W - 39) + '║');
  console.log('╠' + '═'.repeat(W) + '╣');
  console.log('║  📞 Calls' + ' '.repeat(W - 12) + '║');
  console.log('║    startAudioCall()' + ' '.repeat(W - 21) + '║');
  console.log('║    startVideoCall()' + ' '.repeat(W - 21) + '║');
  console.log('╠' + '═'.repeat(W) + '╣');
  console.log('║  🔒 Privacy & Settings' + ' '.repeat(W - 25) + '║');
  console.log('║    toggleEncryptedDM()' + ' '.repeat(W - 24) + '║');
  console.log('║    toggleReadReceipts()' + ' '.repeat(W - 25) + '║');
  console.log('╠' + '═'.repeat(W) + '╣');
  console.log('║  📌 Conversation Management' + ' '.repeat(W - 30) + '║');
  console.log('║    pinConversation()' + ' '.repeat(W - 22) + '║');
  console.log('║    deleteMessage(index)' + ' '.repeat(W - 25) + '║');
  console.log('║    deleteConversation()' + ' '.repeat(W - 25) + '║');
  console.log('╠' + '═'.repeat(W) + '╣');
  console.log('║  👥 Group DM' + ' '.repeat(W - 15) + '║');
  console.log('║    leaveGroupDM()' + ' '.repeat(W - 19) + '║');
  console.log('║    renameGroupDM("New Name")' + ' '.repeat(W - 30) + '║');
  console.log('║    addGroupMember("username")' + ' '.repeat(W - 31) + '║');
  console.log('║    removeGroupMember("username")' + ' '.repeat(W - 33) + '║');
  console.log('╚' + '═'.repeat(W) + '╝');
  console.log('');
  console.log('💡 Usage: window.XActions.advancedDM.sendGIF("thumbs up")');
  console.log('💡 Open a DM conversation first, then call a function.');
})();

⚡ More XActions Scripts

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

Browse All Scripts