search.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 export const searchCommand = cli({ 3 site: 'discord-app', 4 name: 'search', 5 description: 'Search messages in the current Discord server/channel (Cmd+F)', 6 domain: 'localhost', 7 strategy: Strategy.UI, 8 browser: true, 9 args: [{ name: 'query', required: true, positional: true, help: 'Search query' }], 10 columns: ['Index', 'Author', 'Message'], 11 func: async (page, kwargs) => { 12 const query = kwargs.query; 13 // Open search with Cmd+F 14 const isMac = process.platform === 'darwin'; 15 await page.pressKey(isMac ? 'Meta+F' : 'Control+F'); 16 await page.wait(0.5); 17 // Type query into search box 18 await page.evaluate(` 19 (function(q) { 20 const input = document.querySelector('[aria-label*="Search"], [class*="searchBar"] input, [placeholder*="Search"]'); 21 if (!input) throw new Error('Search input not found'); 22 input.focus(); 23 const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; 24 setter.call(input, q); 25 input.dispatchEvent(new Event('input', { bubbles: true })); 26 })(${JSON.stringify(query)}) 27 `); 28 await page.pressKey('Enter'); 29 await page.wait(2); 30 // Scrape search results 31 const results = await page.evaluate(` 32 (function() { 33 const items = []; 34 const resultNodes = document.querySelectorAll('[class*="searchResult_"], [id*="search-result"]'); 35 36 resultNodes.forEach((node, i) => { 37 const author = node.querySelector('[class*="username"]')?.textContent?.trim() || '—'; 38 const content = node.querySelector('[id^="message-content-"], [class*="messageContent"]')?.textContent?.trim() || node.textContent?.trim(); 39 items.push({ 40 Index: i + 1, 41 Author: author, 42 Message: (content || '').substring(0, 200), 43 }); 44 }); 45 46 return items; 47 })() 48 `); 49 // Close search 50 await page.pressKey('Escape'); 51 if (results.length === 0) { 52 return [{ Index: 0, Author: 'System', Message: `No results for "${query}"` }]; 53 } 54 return results; 55 }, 56 });