delete.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { CommandExecutionError } from '@jackwener/opencli/errors'; 3 4 function buildDeleteScript(messageId) { 5 return `(async () => { 6 try { 7 const messageId = ${JSON.stringify(messageId)}; 8 9 // Find the message element by its ID attribute (format: chat-messages-{channelId}-{messageId}) 10 const msgEl = document.querySelector('[id$="-' + messageId + '"]'); 11 if (!msgEl) { 12 return { ok: false, message: 'Could not find a message with ID ' + messageId + ' in the current channel.' }; 13 } 14 15 // Find the closest list item wrapper that Discord uses for messages 16 const listItem = msgEl.closest('[id^="chat-messages-"]') || msgEl; 17 18 // Hover over the message to reveal the action toolbar 19 listItem.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); 20 listItem.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); 21 await new Promise(r => setTimeout(r, 500)); 22 23 // Look for the "More" button in the message toolbar 24 // Discord shows a toolbar with buttons when hovering over a message 25 const toolbar = listItem.querySelector('[class*="toolbar"]') || 26 document.querySelector('[id^="message-actions-"]'); 27 if (!toolbar) { 28 return { ok: false, message: 'Could not find the message action toolbar. Try scrolling so the message is fully visible.' }; 29 } 30 31 const buttons = Array.from(toolbar.querySelectorAll('button, [role="button"], div[class*="button"]')); 32 const moreBtn = buttons.find(btn => { 33 const label = (btn.getAttribute('aria-label') || '').toLowerCase(); 34 return label === 'more' || label.includes('more'); 35 }); 36 if (!moreBtn) { 37 return { ok: false, message: 'Could not find the "More" button on the message toolbar.' }; 38 } 39 40 moreBtn.click(); 41 await new Promise(r => setTimeout(r, 500)); 42 43 // Find "Delete Message" in the context menu 44 const menuItems = Array.from(document.querySelectorAll('[role="menuitem"], [id*="message-actions"]')); 45 const deleteItem = menuItems.find(item => { 46 const text = (item.textContent || '').trim().toLowerCase(); 47 return text.includes('delete message') || text === 'delete'; 48 }); 49 50 if (!deleteItem) { 51 // Close the menu by pressing Escape 52 document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); 53 return { ok: false, message: 'No "Delete Message" option found. You may not have permission to delete this message.' }; 54 } 55 56 deleteItem.click(); 57 await new Promise(r => setTimeout(r, 500)); 58 59 // Confirm deletion in the modal dialog 60 const confirmBtn = document.querySelector('[type="submit"], button[class*="colorRed"], button[class*="danger"]'); 61 if (!confirmBtn) { 62 return { ok: false, message: 'Delete confirmation dialog did not appear.' }; 63 } 64 65 confirmBtn.click(); 66 return { ok: true, message: 'Message ' + messageId + ' deleted successfully.' }; 67 } catch (e) { 68 return { ok: false, message: e.toString() }; 69 } 70 })()`; 71 } 72 73 cli({ 74 site: 'discord-app', 75 name: 'delete', 76 description: 'Delete a message by its ID in the active Discord channel', 77 domain: 'localhost', 78 strategy: Strategy.UI, 79 browser: true, 80 args: [ 81 { 82 name: 'message_id', 83 type: 'string', 84 required: true, 85 positional: true, 86 help: 'The ID of the message to delete (visible via Developer Mode or the read command)', 87 }, 88 ], 89 columns: ['status', 'message'], 90 func: async (page, kwargs) => { 91 if (!page) 92 throw new CommandExecutionError('Browser session required for discord-app delete'); 93 const messageId = kwargs.message_id; 94 if (!/^\d+$/.test(messageId)) { 95 throw new CommandExecutionError( 96 `Invalid message ID: "${messageId}". A Discord message ID is a numeric snowflake (e.g. 1234567890123456789).` 97 ); 98 } 99 // Wait a moment for the chat to be fully loaded 100 await page.wait(0.5); 101 const result = await page.evaluate(buildDeleteScript(messageId)); 102 if (result.ok) { 103 await page.wait(1); 104 } 105 return [{ 106 status: result.ok ? 'success' : 'failed', 107 message: result.message, 108 }]; 109 }, 110 }); 111 112 export const __test__ = { 113 buildDeleteScript, 114 };