Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/clis/chatwise/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();
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;
},
});
54 changes: 33 additions & 21 deletions src/clis/discord-app/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: <a> 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;
Comment on lines +39 to +40

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;
})()
`);
Expand Down
Loading