- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
179 lines
5.3 KiB
JavaScript
179 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require("fs");
|
|
const https = require("https");
|
|
const { homedir } = require("os");
|
|
const { join } = require("path");
|
|
|
|
function httpsRequest(url, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = https.request(url, options, (res) => {
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => {
|
|
if (res.statusCode >= 400) {
|
|
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
} else {
|
|
resolve(JSON.parse(data));
|
|
}
|
|
});
|
|
});
|
|
req.on("error", reject);
|
|
if (options.body) req.write(options.body);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function getAccessToken(creds) {
|
|
const body = new URLSearchParams({
|
|
client_id: creds.client_id,
|
|
refresh_token: creds.refresh_token,
|
|
grant_type: "refresh_token",
|
|
scope: "https://graph.microsoft.com/Mail.Read offline_access",
|
|
}).toString();
|
|
|
|
const data = await httpsRequest(
|
|
"https://login.microsoftonline.com/organizations/oauth2/v2.0/token",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Content-Length": Buffer.byteLength(body),
|
|
},
|
|
body,
|
|
},
|
|
);
|
|
|
|
return data.access_token;
|
|
}
|
|
|
|
async function main() {
|
|
const credsPath = join(homedir(), ".openclaw/secrets/outlook-o365-oauth.json");
|
|
const creds = JSON.parse(fs.readFileSync(credsPath, "utf8"));
|
|
|
|
console.log("📧 Analyzing spam on alexander@warme.dk...\n");
|
|
const accessToken = await getAccessToken(creds);
|
|
|
|
// Get recent messages
|
|
let allMessages = [];
|
|
let nextLink = `https://graph.microsoft.com/v1.0/me/messages?$top=100&$select=subject,from,receivedDateTime,isRead&$orderby=receivedDateTime DESC`;
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
// Get up to 500 messages
|
|
const data = await httpsRequest(nextLink, {
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
});
|
|
|
|
if (data.value) {
|
|
allMessages.push(...data.value);
|
|
}
|
|
|
|
nextLink = data["@odata.nextLink"];
|
|
if (!nextLink) break;
|
|
}
|
|
|
|
console.log(`✅ Fetched ${allMessages.length} messages\n`);
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
|
|
// Analyze senders
|
|
const senderCounts = {};
|
|
const spamKeywords = [
|
|
"casino",
|
|
"bonus",
|
|
"free spin",
|
|
"velkomst",
|
|
"tilbud",
|
|
"mcafee",
|
|
"antivirus",
|
|
"security alert",
|
|
];
|
|
const spamMessages = [];
|
|
|
|
for (const msg of allMessages) {
|
|
const from = msg.from?.emailAddress?.address || "unknown";
|
|
const subject = msg.subject || "";
|
|
const domain = from.split("@")[1] || from;
|
|
|
|
// Count by domain
|
|
if (!senderCounts[domain]) {
|
|
senderCounts[domain] = { count: 0, unread: 0, examples: [] };
|
|
}
|
|
senderCounts[domain].count++;
|
|
if (!msg.isRead) {
|
|
senderCounts[domain].unread++;
|
|
}
|
|
if (senderCounts[domain].examples.length < 3) {
|
|
senderCounts[domain].examples.push(subject);
|
|
}
|
|
|
|
// Detect spam
|
|
const isSpam = spamKeywords.some(
|
|
(kw) => subject.toLowerCase().includes(kw) || from.toLowerCase().includes(kw),
|
|
);
|
|
|
|
if (isSpam) {
|
|
spamMessages.push({ from, subject, date: msg.receivedDateTime });
|
|
}
|
|
}
|
|
|
|
// Sort by frequency
|
|
const sorted = Object.entries(senderCounts)
|
|
.sort((a, b) => b[1].count - a[1].count)
|
|
.slice(0, 20);
|
|
|
|
console.log("📊 TOP 20 AFSENDERE (hyppighed):\n");
|
|
|
|
for (const [domain, data] of sorted) {
|
|
const unreadText = data.unread > 0 ? ` (${data.unread} ulæste)` : "";
|
|
console.log(`${data.count.toString().padStart(3)} emails from ${domain}${unreadText}`);
|
|
if (data.examples.length > 0) {
|
|
console.log(` Eksempler: ${data.examples[0]}`);
|
|
if (data.examples.length > 1) {
|
|
console.log(` ${data.examples[1]}`);
|
|
}
|
|
}
|
|
console.log("");
|
|
}
|
|
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
|
|
if (spamMessages.length > 0) {
|
|
console.log(`🚫 SPAM FUNDET: ${spamMessages.length} beskeder\n`);
|
|
for (const spam of spamMessages.slice(0, 10)) {
|
|
console.log(`From: ${spam.from}`);
|
|
console.log(`Subject: ${spam.subject}`);
|
|
console.log(`Date: ${new Date(spam.date).toLocaleDateString("da-DK")}`);
|
|
console.log("");
|
|
}
|
|
|
|
if (spamMessages.length > 10) {
|
|
console.log(`... og ${spamMessages.length - 10} mere spam\n`);
|
|
}
|
|
}
|
|
|
|
// Recommendations
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
console.log("💡 ANBEFALINGER:\n");
|
|
|
|
const highVolume = sorted.filter(([_, data]) => data.count > 10);
|
|
if (highVolume.length > 0) {
|
|
console.log("📌 Høj-volumen afsendere (overvej at afmelde):");
|
|
for (const [domain, data] of highVolume.slice(0, 5)) {
|
|
console.log(` - ${domain} (${data.count} emails)`);
|
|
}
|
|
console.log("");
|
|
}
|
|
|
|
console.log("✅ Mulige løsninger:");
|
|
console.log(" 1. Opret inbox rules i Outlook til at filtrere spam");
|
|
console.log(' 2. Afmeld fra nyhedsbreve (klik "Unsubscribe")');
|
|
console.log(" 3. Marker spam og rapporter til Microsoft");
|
|
console.log(' 4. Brug "Focused Inbox" funktion i Outlook');
|
|
console.log("");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("❌ Error:", err.message);
|
|
process.exit(1);
|
|
});
|