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 };