Going back to what works.

Manifest v3 and its consequences have been a disaster for the human race.
This commit is contained in:
Jordan Wages 2025-07-07 21:46:21 -05:00
commit 6a85dbb2eb
3 changed files with 144 additions and 153 deletions

View file

@ -126,7 +126,7 @@ function buildEmailText(full) {
const attachments = []; const attachments = [];
collectText(full, bodyParts, attachments); collectText(full, bodyParts, attachments);
const headers = Object.entries(full.headers || {}) const headers = Object.entries(full.headers || {})
.map(([k,v]) => `${k}: ${v.join(' ')}`) .map(([k, v]) => `${k}: ${v.join(' ')}`)
.join('\n'); .join('\n');
const attachInfo = `Attachments: ${attachments.length}` + (attachments.length ? "\n" + attachments.map(a => ` - ${a}`).join('\n') : ""); const attachInfo = `Attachments: ${attachments.length}` + (attachments.length ? "\n" + attachments.map(a => ` - ${a}`).join('\n') : "");
const combined = `${headers}\n${attachInfo}\n\n${bodyParts.join('\n')}`.trim(); const combined = `${headers}\n${attachInfo}\n\n${bodyParts.join('\n')}`.trim();
@ -369,35 +369,14 @@ async function clearCacheForMessages(idsInput) {
icons: { "16": "resources/img/brain.png" } icons: { "16": "resources/img/brain.png" }
}); });
//for the love of god work please
browser.messageDisplayAction.onClicked.addListener(async (tab, info) => {
try {
let header = await browser.messageDisplay.getDisplayedMessages(tab.id);
if (!header) {
logger.aiLog("No header, no message loaded?", { debug: true });
return;
}
const url = browser.runtime.getURL(`details.html?mid=${header.id}`);
await browser.messageDisplayAction.setPopup({ tabId: tab.id, popup: url });
await browser.messageDisplayAction.openPopup({ tabId: tab.id });
} catch (err) {
logger.aiLog("Failed to open details popup", { debug: true });
}
});
browser.messageDisplay.onMessagesDisplayed.addListener(async (tab, displayedMessages) => {
logger.aiLog("Messages displayed!", { debug: true }, displayedMessages);
});
browser.menus.onClicked.addListener(async (info, tab) => { browser.menus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === "apply-ai-rules-list" || info.menuItemId === "apply-ai-rules-display") { if (info.menuItemId === "apply-ai-rules-list" || info.menuItemId === "apply-ai-rules-display") {
const ids = info.selectedMessages?.messages?.map(m => m.id) || const ids = info.selectedMessages?.messages?.map(m => m.id) ||
(info.messageId ? [info.messageId] : []); (info.messageId ? [info.messageId] : []);
await applyAiRules(ids); await applyAiRules(ids);
} else if (info.menuItemId === "clear-ai-cache-list" || info.menuItemId === "clear-ai-cache-display") { } else if (info.menuItemId === "clear-ai-cache-list" || info.menuItemId === "clear-ai-cache-display") {
const ids = info.selectedMessages?.messages?.map(m => m.id) || const ids = info.selectedMessages?.messages?.map(m => m.id) ||
(info.messageId ? [info.messageId] : []); (info.messageId ? [info.messageId] : []);
await clearCacheForMessages(ids); await clearCacheForMessages(ids);
} else if (info.menuItemId === "view-ai-reason-list" || info.menuItemId === "view-ai-reason-display") { } else if (info.menuItemId === "view-ai-reason-list" || info.menuItemId === "view-ai-reason-display") {
const [header] = await browser.messageDisplay.getDisplayedMessages(tab.id); const [header] = await browser.messageDisplay.getDisplayedMessages(tab.id);
@ -417,141 +396,143 @@ async function clearCacheForMessages(idsInput) {
} }
if (msg?.type === "sortana:test") { if (msg?.type === "sortana:test") {
const { text = "", criterion = "" } = msg; const { text = "", criterion = "" } = msg;
logger.aiLog("sortana:test text", {debug: true}, text); logger.aiLog("sortana:test text", { debug: true }, text);
logger.aiLog("sortana:test criterion", {debug: true}, criterion); logger.aiLog("sortana:test criterion", { debug: true }, criterion);
try { try {
logger.aiLog("Calling AiClassifier.classifyText()", {debug: true}); logger.aiLog("Calling AiClassifier.classifyText()", { debug: true });
const result = await AiClassifier.classifyText(text, criterion); const result = await AiClassifier.classifyText(text, criterion);
logger.aiLog("classify() returned", {debug: true}, result); logger.aiLog("classify() returned", { debug: true }, result);
return { match: result }; return { match: result };
}
catch (err) {
logger.aiLog("Error in classify()", {level: 'error'}, err);
// rethrow so the caller sees the failure
throw err;
}
} else if (msg?.type === "sortana:clearCacheForDisplayed") {
try {
const msgs = await browser.messageDisplay.getDisplayedMessages();
const ids = msgs.map(m => m.id);
await clearCacheForMessages(ids);
} catch (e) {
logger.aiLog("failed to clear cache from message script", { level: 'error' }, e);
}
} else if (msg?.type === "sortana:getReasons") {
try {
const id = msg.id;
const hdr = await messenger.messages.get(id);
const subject = hdr?.subject || "";
if (!aiRules.length) {
const { aiRules: stored } = await storage.local.get("aiRules");
aiRules = Array.isArray(stored) ? stored.map(r => {
if (r.actions) return r;
const actions = [];
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
const rule = { criterion: r.criterion, actions };
if (r.stopProcessing) rule.stopProcessing = true;
return rule;
}) : [];
} }
const reasons = []; catch (err) {
for (const rule of aiRules) { logger.aiLog("Error in classify()", { level: 'error' }, err);
const key = await AiClassifier.buildCacheKey(id, rule.criterion); // rethrow so the caller sees the failure
const reason = AiClassifier.getReason(key); throw err;
if (reason) { }
reasons.push({ criterion: rule.criterion, reason }); } else if (msg?.type === "sortana:clearCacheForDisplayed") {
try {
const msgs = await browser.messageDisplay.getDisplayedMessages();
const ids = msgs.map(m => m.id);
await clearCacheForMessages(ids);
} catch (e) {
logger.aiLog("failed to clear cache from message script", { level: 'error' }, e);
}
} else if (msg?.type === "sortana:getReasons") {
try {
const id = msg.id;
const hdr = await messenger.messages.get(id);
const subject = hdr?.subject || "";
if (!aiRules.length) {
const { aiRules: stored } = await storage.local.get("aiRules");
aiRules = Array.isArray(stored) ? stored.map(r => {
if (r.actions) return r;
const actions = [];
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
const rule = { criterion: r.criterion, actions };
if (r.stopProcessing) rule.stopProcessing = true;
return rule;
}) : [];
} }
} const reasons = [];
return { subject, reasons }; for (const rule of aiRules) {
} catch (e) { const key = await AiClassifier.buildCacheKey(id, rule.criterion);
logger.aiLog("failed to collect reasons", { level: 'error' }, e); const reason = AiClassifier.getReason(key);
return { subject: '', reasons: [] }; if (reason) {
} reasons.push({ criterion: rule.criterion, reason });
} else if (msg?.type === "sortana:getDetails") { }
try {
const id = msg.id;
const hdr = await messenger.messages.get(id);
const subject = hdr?.subject || "";
if (!aiRules.length) {
const { aiRules: stored } = await storage.local.get("aiRules");
aiRules = Array.isArray(stored) ? stored.map(r => {
if (r.actions) return r;
const actions = [];
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
const rule = { criterion: r.criterion, actions };
if (r.stopProcessing) rule.stopProcessing = true;
return rule;
}) : [];
}
const results = [];
for (const rule of aiRules) {
const key = await AiClassifier.buildCacheKey(id, rule.criterion);
const matched = AiClassifier.getCachedResult(key);
const reason = AiClassifier.getReason(key);
if (matched !== null || reason) {
results.push({ criterion: rule.criterion, matched, reason });
} }
return { subject, reasons };
} catch (e) {
logger.aiLog("failed to collect reasons", { level: 'error' }, e);
return { subject: '', reasons: [] };
} }
return { subject, results }; } else if (msg?.type === "sortana:getDetails") {
} catch (e) { try {
logger.aiLog("failed to collect details", { level: 'error' }, e); const id = msg.id;
return { subject: '', results: [] }; const hdr = await messenger.messages.get(id);
} const subject = hdr?.subject || "";
} else if (msg?.type === "sortana:getDisplayedMessages") { if (!aiRules.length) {
try { const { aiRules: stored } = await storage.local.get("aiRules");
const [tab] = await browser.tabs.query({ active: true, currentWindow: true }); aiRules = Array.isArray(stored) ? stored.map(r => {
const messages = await browser.messageDisplay.getDisplayedMessages(tab?.id); if (r.actions) return r;
return { messages }; const actions = [];
} catch (e) { if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
logger.aiLog("failed to get displayed messages", { level: 'error' }, e); if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
return { messages: [] }; const rule = { criterion: r.criterion, actions };
} if (r.stopProcessing) rule.stopProcessing = true;
} else if (msg?.type === "sortana:clearCacheForMessage") { return rule;
try { }) : [];
await clearCacheForMessages([msg.id]); }
return { ok: true }; const results = [];
} catch (e) { for (const rule of aiRules) {
logger.aiLog("failed to clear cache for message", { level: 'error' }, e); const key = await AiClassifier.buildCacheKey(id, rule.criterion);
return { ok: false }; const matched = AiClassifier.getCachedResult(key);
} const reason = AiClassifier.getReason(key);
} else if (msg?.type === "sortana:getQueueCount") { if (matched !== null || reason) {
return { count: queuedCount + (processing ? 1 : 0) }; results.push({ criterion: rule.criterion, matched, reason });
} else if (msg?.type === "sortana:getTiming") { }
const t = timingStats; }
const std = t.count > 1 ? Math.sqrt(t.m2 / (t.count - 1)) : 0; return { subject, results };
return { } catch (e) {
count: queuedCount + (processing ? 1 : 0), logger.aiLog("failed to collect details", { level: 'error' }, e);
current: currentStart ? Date.now() - currentStart : -1, return { subject: '', results: [] };
last: t.last, }
runs: t.count, } else if (msg?.type === "sortana:getDisplayedMessages") {
average: t.mean, try {
total: t.total, const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
stddev: std const messages = await browser.messageDisplay.getDisplayedMessages(tab?.id);
}; const ids = messages.map(hdr => hdr.id);
} else {
logger.aiLog("Unknown message type, ignoring", {level: 'warn'}, msg?.type);
}
});
// Automatically classify new messages return { ids };
if (typeof messenger !== "undefined" && messenger.messages?.onNewMailReceived) { } catch (e) {
messenger.messages.onNewMailReceived.addListener(async (folder, messages) => { logger.aiLog("failed to get displayed messages", { level: 'error' }, e);
logger.aiLog("onNewMailReceived", {debug: true}, messages); return { messages: [] };
const ids = (messages?.messages || messages || []).map(m => m.id ?? m); }
await applyAiRules(ids); } else if (msg?.type === "sortana:clearCacheForMessage") {
try {
await clearCacheForMessages([msg.id]);
return { ok: true };
} catch (e) {
logger.aiLog("failed to clear cache for message", { level: 'error' }, e);
return { ok: false };
}
} else if (msg?.type === "sortana:getQueueCount") {
return { count: queuedCount + (processing ? 1 : 0) };
} else if (msg?.type === "sortana:getTiming") {
const t = timingStats;
const std = t.count > 1 ? Math.sqrt(t.m2 / (t.count - 1)) : 0;
return {
count: queuedCount + (processing ? 1 : 0),
current: currentStart ? Date.now() - currentStart : -1,
last: t.last,
runs: t.count,
average: t.mean,
total: t.total,
stddev: std
};
} else {
logger.aiLog("Unknown message type, ignoring", { level: 'warn' }, msg?.type);
}
}); });
} else {
logger.aiLog("messenger.messages API unavailable, skipping new mail listener", { level: 'warn' });
}
// Catch any unhandled rejections // Automatically classify new messages
window.addEventListener("unhandledrejection", ev => { if (typeof messenger !== "undefined" && messenger.messages?.onNewMailReceived) {
logger.aiLog("Unhandled promise rejection", {level: 'error'}, ev.reason); messenger.messages.onNewMailReceived.addListener(async (folder, messages) => {
}); logger.aiLog("onNewMailReceived", { debug: true }, messages);
const ids = (messages?.messages || messages || []).map(m => m.id ?? m);
await applyAiRules(ids);
});
} else {
logger.aiLog("messenger.messages API unavailable, skipping new mail listener", { level: 'warn' });
}
// Catch any unhandled rejections
window.addEventListener("unhandledrejection", ev => {
logger.aiLog("Unhandled promise rejection", { level: 'error' }, ev.reason);
});
browser.runtime.onInstalled.addListener(async ({ reason }) => { browser.runtime.onInstalled.addListener(async ({ reason }) => {
if (reason === "install") { if (reason === "install") {

View file

@ -10,7 +10,16 @@ if (!isNaN(qMid)) {
if (messages && messages[0]) { if (messages && messages[0]) {
loadMessage(messages[0].id); loadMessage(messages[0].id);
} else { } else {
aiLog("Details popup: no displayed message found"); const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const tabId = tabs[0]?.id;
const msgs = tabId ? await browser.messageDisplay.getDisplayedMessages(tabId) : [];
let id = msgs[0]?.id;
if (id) {
loadMessage(id);
}
else {
aiLog("Details popup: no displayed message found");
}
} }
} }

View file

@ -24,7 +24,8 @@
"message_display_action": { "message_display_action": {
"default_icon": "resources/img/brain.png", "default_icon": "resources/img/brain.png",
"default_title": "Details", "default_title": "Details",
"default_label": "Details" "default_label": "Details",
"default_popup": "details.html"
}, },
"background": { "scripts": [ "background.js" ] }, "background": { "scripts": [ "background.js" ] },
"options_ui": { "options_ui": {