/ clis / notion / favorites.js
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  });