diff --git a/src/clis/chatwise/history.ts b/src/clis/chatwise/history.ts index 4cda47c..46cb252 100644 --- a/src/clis/chatwise/history.ts +++ b/src/clis/chatwise/history.ts @@ -42,6 +42,20 @@ export const historyCommand = cli({ return [{ Index: 0, Title: 'No history found. Ensure the sidebar is visible.' }]; } - return items; + const dateHeaders = /^(today|yesterday|last week|last month|last year|this week|this month|older|previous \d+ days|\d+ days ago)$/i; + const numericOnly = /^[\d\s]+$/; + const modelPath = /^[\w.-]+\/[\w.-]/; + const seen = new Set(); + const deduped = items.filter((item: { Index: number; Title: string }) => { + const t = item.Title.trim(); + if (dateHeaders.test(t)) return false; + if (numericOnly.test(t)) return false; + if (modelPath.test(t)) return false; + if (seen.has(t)) return false; + seen.add(t); + return true; + }).map((item: { Index: number; Title: string }, i: number) => ({ Index: i + 1, Title: item.Title })); + + return deduped; }, }); diff --git a/src/clis/discord-app/channels.ts b/src/clis/discord-app/channels.ts index ac5f940..a908ec6 100644 --- a/src/clis/discord-app/channels.ts +++ b/src/clis/discord-app/channels.ts @@ -14,28 +14,40 @@ export const channelsCommand = cli({ const channels = await page.evaluate(` (function() { const results = []; - // Discord channel list items - const items = document.querySelectorAll('[data-list-item-id*="channels___"], [class*="containerDefault_"]'); - - items.forEach((item, i) => { - const nameEl = item.querySelector('[class*="name_"], [class*="channelName"]'); - const name = nameEl ? nameEl.textContent.trim() : (item.textContent || '').trim().substring(0, 50); - - if (!name || name.length < 1) return; - - // Detect channel type from icon or aria-label - const iconEl = item.querySelector('[class*="icon"]'); - let type = 'Text'; - if (iconEl) { - const cls = iconEl.className || ''; - if (cls.includes('voice') || cls.includes('speaker')) type = 'Voice'; - else if (cls.includes('forum')) type = 'Forum'; - else if (cls.includes('announcement')) type = 'Announcement'; - } - - results.push({ Index: i + 1, Channel: name, Type: type }); + + // Discord channel links: tags with href like /channels/GUILD/CHANNEL + const links = document.querySelectorAll('a[href*="/channels/"][data-list-item-id^="channels___"]'); + + links.forEach(function(el) { + var label = el.getAttribute('aria-label') || ''; + if (!label) return; + + // Skip categories + if (/[((]category[))]/i.test(label)) return; + + // Strip any leading status prefix before the first comma (e.g. "unread, ", locale-agnostic) + var commaIdx = label.search(/[,,]/); + var cleaned = commaIdx !== -1 ? label.slice(commaIdx + 1).trimStart() : label; + + // Extract name and type from "name (type)" or "name(type)" + var m = cleaned.match(/^(.+?)\s*[((](.+?)[))]\s*$/); + // If no type annotation found, skip — real channels always have "(Type channel)" in aria-label + if (!m) return; + var name = m[1].trim(); + var rawType = m[2].toLowerCase(); + + // Discord channel names are ASCII-only; skip placeholder entries (e.g. locked channels) + if (!name || !/^[\x20-\x7E]+$/.test(name)) return; + + var type = 'Text'; + if (rawType.includes('voice')) type = 'Voice'; + else if (rawType.includes('forum')) type = 'Forum'; + else if (rawType.includes('announcement')) type = 'Announcement'; + else if (rawType.includes('stage')) type = 'Stage'; + + results.push({ Index: results.length + 1, Channel: name, Type: type }); }); - + return results; })() `);