/ clis / twitter / delete.js
delete.js
  1  import { cli, Strategy } from '@jackwener/opencli/registry';
  2  import { CommandExecutionError } from '@jackwener/opencli/errors';
  3  function extractTweetId(url) {
  4      let pathname = '';
  5      try {
  6          pathname = new URL(url).pathname;
  7      }
  8      catch {
  9          throw new Error(`Invalid tweet URL: ${url}`);
 10      }
 11      const match = pathname.match(/\/status\/(\d+)/);
 12      if (!match?.[1]) {
 13          throw new Error(`Could not extract tweet ID from URL: ${url}`);
 14      }
 15      return match[1];
 16  }
 17  function buildDeleteScript(tweetId) {
 18      return `(async () => {
 19        try {
 20            const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
 21            const tweetId = ${JSON.stringify(tweetId)};
 22            const targetArticle = Array.from(document.querySelectorAll('article')).find((article) =>
 23                Array.from(article.querySelectorAll('a[href*="/status/"]')).some((link) => {
 24                    try {
 25                        return new URL(link.href, window.location.origin).pathname.includes('/status/' + tweetId);
 26                    } catch {
 27                        return false;
 28                    }
 29                })
 30            );
 31  
 32            if (!targetArticle) {
 33                return { ok: false, message: 'Could not find the tweet card matching the requested URL.' };
 34            }
 35  
 36            const buttons = Array.from(targetArticle.querySelectorAll('button,[role="button"]'));
 37            const moreMenu = buttons.find((el) => visible(el) && (el.getAttribute('aria-label') || '').trim() === 'More');
 38            if (!moreMenu) {
 39                return { ok: false, message: 'Could not find the "More" context menu on the matched tweet. Are you sure you are logged in and looking at a valid tweet?' };
 40            }
 41  
 42            moreMenu.click();
 43            await new Promise(r => setTimeout(r, 1000));
 44  
 45            const items = Array.from(document.querySelectorAll('[role="menuitem"]'));
 46            const deleteBtn = items.find((item) => {
 47                const text = (item.textContent || '').trim();
 48                return text.includes('Delete') && !text.includes('List');
 49            });
 50  
 51            if (!deleteBtn) {
 52                return { ok: false, message: 'The matched tweet menu did not contain Delete. This tweet may not belong to you.' };
 53            }
 54  
 55            deleteBtn.click();
 56            await new Promise(r => setTimeout(r, 1000));
 57  
 58            const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
 59            if (confirmBtn) {
 60                confirmBtn.click();
 61                return { ok: true, message: 'Tweet successfully deleted.' };
 62            } else {
 63                return { ok: false, message: 'Delete confirmation dialog did not appear.' };
 64            }
 65        } catch (e) {
 66            return { ok: false, message: e.toString() };
 67        }
 68    })()`;
 69  }
 70  cli({
 71      site: 'twitter',
 72      name: 'delete',
 73      description: 'Delete a specific tweet by URL',
 74      domain: 'x.com',
 75      strategy: Strategy.UI, // Utilizes internal DOM flows for interaction
 76      browser: true,
 77      args: [
 78          { name: 'url', type: 'string', required: true, positional: true, help: 'The URL of the tweet to delete' },
 79      ],
 80      columns: ['status', 'message'],
 81      func: async (page, kwargs) => {
 82          if (!page)
 83              throw new CommandExecutionError('Browser session required for twitter delete');
 84          let tweetId = '';
 85          try {
 86              tweetId = extractTweetId(kwargs.url);
 87          }
 88          catch (error) {
 89              const message = error instanceof Error ? error.message : String(error);
 90              throw new CommandExecutionError(message);
 91          }
 92          await page.goto(kwargs.url);
 93          await page.wait({ selector: '[data-testid="primaryColumn"]' }); // Wait for tweet to load completely
 94          const result = await page.evaluate(buildDeleteScript(tweetId));
 95          if (result.ok) {
 96              // Wait for the deletion request to be processed
 97              await page.wait(2);
 98          }
 99          return [{
100                  status: result.ok ? 'success' : 'failed',
101                  message: result.message
102              }];
103      }
104  });
105  export const __test__ = {
106      buildDeleteScript,
107      extractTweetId,
108  };