favorites.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 export const favoritesCommand = cli({ 3 site: 'notion', 4 name: 'favorites', 5 description: 'List pages from the Notion Favorites section in the sidebar', 6 domain: 'localhost', 7 strategy: Strategy.UI, 8 browser: true, 9 args: [], 10 columns: ['Index', 'Title', 'Icon'], 11 func: async (page) => { 12 const items = await page.evaluate(` 13 (function() { 14 const results = []; 15 16 // Strategy 1: Use Notion's own class 'notion-outliner-bookmarks-header-container' 17 const headerContainer = document.querySelector('.notion-outliner-bookmarks-header-container'); 18 if (headerContainer) { 19 // Walk up to the section parent that wraps header + items 20 let section = headerContainer.parentElement; 21 if (section && section.children.length === 1) section = section.parentElement; 22 23 if (section) { 24 const treeItems = section.querySelectorAll('[role="treeitem"]'); 25 treeItems.forEach((item) => { 26 // Title text is in a div.notranslate sibling of the icon area 27 const titleEl = item.querySelector('div.notranslate:not(.notion-record-icon)'); 28 const title = titleEl 29 ? titleEl.textContent.trim() 30 : (item.textContent || '').trim().substring(0, 80); 31 32 // Icon/emoji is in the notion-record-icon element 33 const iconEl = item.querySelector('.notion-record-icon'); 34 const icon = iconEl ? iconEl.textContent.trim().substring(0, 4) : ''; 35 36 if (title && title.length > 0) { 37 results.push({ Index: results.length + 1, Title: title, Icon: icon || '๐' }); 38 } 39 }); 40 } 41 } 42 43 // Strategy 2: Fallback โ find "Favorites" text node and walk DOM 44 if (results.length === 0) { 45 const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null); 46 let node; 47 let favEl = null; 48 while (node = walker.nextNode()) { 49 const text = node.textContent.trim(); 50 if (text === 'Favorites' || text === 'ๆถ่' || text === 'ๆถ่ๅคน') { 51 favEl = node.parentElement; 52 break; 53 } 54 } 55 56 if (favEl) { 57 let section = favEl; 58 for (let i = 0; i < 6; i++) { 59 const p = section.parentElement; 60 if (!p || p === document.body) break; 61 const treeItems = p.querySelectorAll(':scope > [role="treeitem"]'); 62 if (treeItems.length > 0) { section = p; break; } 63 section = p; 64 } 65 66 const treeItems = section.querySelectorAll('[role="treeitem"]'); 67 treeItems.forEach((item) => { 68 const text = (item.textContent || '').trim().substring(0, 120); 69 if (text && text.length > 1 && !text.match(/^(Favorites|ๆถ่ๅคน?)$/)) { 70 results.push({ Index: results.length + 1, Title: text, Icon: '๐' }); 71 } 72 }); 73 } 74 } 75 76 return results; 77 })() 78 `); 79 if (items.length === 0) { 80 return [{ Index: 0, Title: 'No favorites found. Make sure sidebar is visible and you have favorites.', Icon: 'โ ๏ธ' }]; 81 } 82 return items; 83 }, 84 });