// @ts-nocheck import { executeScript, getActiveTab, getAliases, isScriptableUrl, resolveTabForDirectAction, tabInfo } from '../core'; export async function tabsList() { const windows = await chrome.windows.getAll({ populate: true }); const aliases = await getAliases(); const tabs = []; for (const w of windows) { for (const t of w.tabs) { tabs.push({ ...tabInfo(t), windowAlias: aliases[t.windowId] || null, pinned: t.pinned, favIconUrl: t.favIconUrl, }); } } return tabs; } export async function tabsClose({ tabId, inactive, duplicates }) { let toClose = []; if (duplicates) { const all = await chrome.tabs.query({}); const seen = new Set(); for (const t of all) { if (!t.url) continue; if (seen.has(t.url)) toClose.push(t.id); else seen.add(t.url); } } else if (inactive) { const all = await chrome.tabs.query({}); toClose = all.filter(t => !t.active).map(t => t.id); } else if (tabId) { toClose = [tabId]; } if (toClose.length) await chrome.tabs.remove(toClose); return { closed: toClose.length }; } export async function tabsMove({ tabId, groupId, windowId, index, forward, backward }) { const moveProps = {}; if (windowId != null) moveProps.windowId = windowId; if (forward || backward) { const tab = await chrome.tabs.get(tabId); if (forward) moveProps.index = tab.index + 2; // +2 because Chrome shifts after removal else moveProps.index = Math.max(0, tab.index - 1); } else if (index != null) { moveProps.index = index; } else { moveProps.index = -1; } await chrome.tabs.move(tabId, moveProps); if (groupId != null) { await chrome.tabs.group({ tabIds: [tabId], groupId }); } return { tabId }; } export async function tabsActive({ tabId }) { const tab = await chrome.tabs.get(tabId); await chrome.windows.update(tab.windowId, { focused: true }); await chrome.tabs.update(tabId, { active: true }); return { tabId }; } export async function tabsActiveInWindow({ windowId }) { const activeTabs = await chrome.tabs.query({ windowId, active: true }); const tab = activeTabs[0]; if (!tab) { throw new Error(`No active tab found for window ${windowId}`); } return tabInfo(tab); } export async function tabsStatus({ tabId }) { const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); return tabInfo(tab); } export async function tabsFilter({ pattern }) { const all = await chrome.tabs.query({}); return all.filter(t => t.url && t.url.includes(pattern)).map(tabInfo); } export async function tabsCount({ pattern }) { const all = await chrome.tabs.query({}); if (pattern) return all.filter(t => t.url && t.url.includes(pattern)).length; return all.length; } export async function tabsQuery({ search }) { const q = search.toLowerCase(); const all = await chrome.tabs.query({}); return all.filter(t => (t.url && t.url.toLowerCase().includes(q)) || (t.title && t.title.toLowerCase().includes(q)) ).map(tabInfo); } export async function tabsHtml({ tabId }) { for (let i = 0; i < 3; i++) { const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); if (!isScriptableUrl(tab.url || tab.pendingUrl || "")) { throw new Error(`Cannot get HTML of ${tab.url || tab.pendingUrl} — navigate to a regular web page first`); } try { const results = await executeScript({ target: { tabId: tab.id }, func: () => document.documentElement.outerHTML, }); return results[0]?.result || ""; } catch (e) { const transient = e.message && (e.message.includes("Frame with ID") || e.message.includes("No tab with id")) && !e.message.includes("error page"); if (i < 2 && transient) { await new Promise(r => setTimeout(r, 300)); continue; } throw e; } } } export async function tabsDedupe() { return tabsClose({ duplicates: true }); } export async function tabsSort({ by }) { const windows = await chrome.windows.getAll({ populate: true }); let moved = 0; for (const w of windows) { const sorted = [...w.tabs].sort((a, b) => { if (by === "title") return (a.title || "").localeCompare(b.title || ""); if (by === "time") return a.id - b.id; // lower id = opened earlier // domain (default) const da = new URL(a.url || a.pendingUrl || "about:blank").hostname; const db = new URL(b.url || b.pendingUrl || "about:blank").hostname; return da.localeCompare(db); }); for (let i = 0; i < sorted.length; i++) { await chrome.tabs.move(sorted[i].id, { index: i }); moved++; } } return { moved }; } export async function tabsMergeWindows() { const current = await chrome.windows.getCurrent(); const all = await chrome.windows.getAll({ populate: true }); let moved = 0; for (const w of all) { if (w.id === current.id) continue; const ids = w.tabs.map(t => t.id); await chrome.tabs.move(ids, { windowId: current.id, index: -1 }); moved += ids.length; } return { moved }; } export async function tabsPin({ tabId }) { const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); await chrome.tabs.update(tab.id, { pinned: true }); return { tabId: tab.id, pinned: true }; } export async function tabsUnpin({ tabId }) { const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); await chrome.tabs.update(tab.id, { pinned: false }); return { tabId: tab.id, pinned: false }; } export async function tabsScreenshot({ tabId, format = "png", quality } = {}) { let windowId; if (tabId) { const tab = await chrome.tabs.get(tabId); await chrome.tabs.update(tabId, { active: true }); windowId = tab.windowId; } else { const tab = await getActiveTab(); windowId = tab.windowId; } const opts = { format }; if (format === "jpeg" && quality != null) opts.quality = quality; const dataUrl = await chrome.tabs.captureVisibleTab(windowId, opts); return { dataUrl, format }; } export async function tabsWatchUrl({ pattern, timeout = 30000, tabId } = {}) { const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); const deadline = Date.now() + timeout; const regex = new RegExp(pattern); while (Date.now() < deadline) { const t = await chrome.tabs.get(tab.id); const url = t.url || t.pendingUrl || ""; if (regex.test(url)) return tabInfo(t); await new Promise(r => setTimeout(r, 200)); } throw new Error(`Tab ${tab.id} URL did not match '${pattern}' within ${timeout}ms`); } export async function tabsMute({ tabId }) { const tab = await resolveTabForDirectAction(tabId, "mute"); await chrome.tabs.update(tab.id, { muted: true }); return { tabId: tab.id, muted: true }; } export async function tabsUnmute({ tabId }) { const tab = await resolveTabForDirectAction(tabId, "unmute"); await chrome.tabs.update(tab.id, { muted: false }); return { tabId: tab.id, muted: false }; }