- 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 <[email protected]>
185 lines
5.9 KiB
JavaScript
185 lines
5.9 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/consumers/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-oauth.json");
|
||
const creds = JSON.parse(fs.readFileSync(credsPath, "utf8"));
|
||
|
||
console.log("🎮 Analyzing Steam spending...\n");
|
||
const accessToken = await getAccessToken(creds);
|
||
|
||
// Get messages in batches
|
||
let allMessages = [];
|
||
let nextLink = `https://graph.microsoft.com/v1.0/me/messages?$top=100&$select=subject,from,receivedDateTime,bodyPreview&$orderby=receivedDateTime DESC`;
|
||
|
||
while (nextLink && allMessages.length < 500) {
|
||
const data = await httpsRequest(nextLink, {
|
||
headers: { Authorization: `Bearer ${accessToken}` },
|
||
});
|
||
|
||
if (data.value) {
|
||
allMessages.push(...data.value);
|
||
}
|
||
|
||
nextLink = data["@odata.nextLink"];
|
||
console.log(`📧 Fetched ${allMessages.length} messages...`);
|
||
}
|
||
|
||
// Filter for Steam
|
||
const steamMessages = allMessages.filter((msg) => {
|
||
const from = (msg.from?.emailAddress?.address || "").toLowerCase();
|
||
const subject = (msg.subject || "").toLowerCase();
|
||
const preview = (msg.bodyPreview || "").toLowerCase();
|
||
|
||
return (
|
||
from.includes("steam") ||
|
||
subject.includes("steam") ||
|
||
(from.includes("paypal") && (preview.includes("steam") || subject.includes("steam")))
|
||
);
|
||
});
|
||
|
||
console.log(`\n🎮 Found ${steamMessages.length} Steam-related emails\n`);
|
||
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||
|
||
let totalEUR = 0;
|
||
let totalDKK = 0;
|
||
const purchases = [];
|
||
|
||
for (const msg of steamMessages) {
|
||
const date = new Date(msg.receivedDateTime).toLocaleDateString("da-DK");
|
||
const subject = msg.subject;
|
||
const preview = msg.bodyPreview || "";
|
||
const from = msg.from?.emailAddress?.address || "";
|
||
|
||
// Look for purchase indicators
|
||
const isPurchase =
|
||
subject.toLowerCase().includes("purchase") ||
|
||
subject.toLowerCase().includes("kvittering") ||
|
||
subject.toLowerCase().includes("betaling") ||
|
||
subject.toLowerCase().includes("receipt") ||
|
||
subject.toLowerCase().includes("thank you") ||
|
||
preview.toLowerCase().includes("betalt") ||
|
||
preview.toLowerCase().includes("paid");
|
||
|
||
if (!isPurchase) continue;
|
||
|
||
// Extract EUR amounts
|
||
const eurMatches = [
|
||
...(subject + " " + preview).matchAll(
|
||
/€\s*(\d+[,.]?\d*)|(\d+[,.]?\d*)\s*€|EUR\s*(\d+[,.]?\d*)|(\d+[,.]?\d*)\s*EUR/gi,
|
||
),
|
||
];
|
||
if (eurMatches.length > 0) {
|
||
const amounts = eurMatches
|
||
.map((m) => {
|
||
const numStr = (m[1] || m[2] || m[3] || m[4] || "0").replace(",", ".");
|
||
return parseFloat(numStr);
|
||
})
|
||
.filter((n) => !isNaN(n) && n > 0 && n < 10000);
|
||
|
||
if (amounts.length > 0) {
|
||
const amount = Math.max(...amounts);
|
||
totalEUR += amount;
|
||
purchases.push({ date, subject: subject.substring(0, 80), amount, currency: "EUR", from });
|
||
console.log(`💰 ${date} - €${amount.toFixed(2)} EUR`);
|
||
console.log(` ${subject.substring(0, 80)}`);
|
||
console.log("");
|
||
}
|
||
}
|
||
|
||
// Extract DKK amounts
|
||
const dkkMatches = [
|
||
...(subject + " " + preview).matchAll(
|
||
/(\d+[,.]?\d*)\s*kr|kr\s*(\d+[,.]?\d*)|DKK\s*(\d+[,.]?\d*)|(\d+[,.]?\d*)\s*DKK/gi,
|
||
),
|
||
];
|
||
if (dkkMatches.length > 0) {
|
||
const amounts = dkkMatches
|
||
.map((m) => {
|
||
const numStr = (m[1] || m[2] || m[3] || m[4] || "0").replace(",", ".");
|
||
return parseFloat(numStr);
|
||
})
|
||
.filter((n) => !isNaN(n) && n > 0 && n < 100000);
|
||
|
||
if (amounts.length > 0) {
|
||
const amount = Math.max(...amounts);
|
||
totalDKK += amount;
|
||
purchases.push({ date, subject: subject.substring(0, 80), amount, currency: "DKK", from });
|
||
console.log(`💰 ${date} - ${amount.toFixed(2)} DKK`);
|
||
console.log(` ${subject.substring(0, 80)}`);
|
||
console.log("");
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||
|
||
if (totalEUR > 0) {
|
||
console.log(`💸 TOTAL BRUGT PÅ STEAM (EUR): €${totalEUR.toFixed(2)}`);
|
||
}
|
||
|
||
if (totalDKK > 0) {
|
||
console.log(`💸 TOTAL BRUGT PÅ STEAM (DKK): ${totalDKK.toFixed(2)} kr`);
|
||
}
|
||
|
||
if (totalEUR > 0 || totalDKK > 0) {
|
||
console.log(`\n📊 Antal køb: ${purchases.length}`);
|
||
|
||
if (totalEUR > 0) {
|
||
const eurToDkk = totalEUR * 7.46; // Approximate exchange rate
|
||
console.log(`📊 EUR omregnet til DKK: ~${eurToDkk.toFixed(2)} kr`);
|
||
console.log(`📊 Total cirka: ~${(totalDKK + eurToDkk).toFixed(2)} kr`);
|
||
}
|
||
} else {
|
||
console.log("ℹ️ Ingen Steam-køb fundet i de seneste beskeder");
|
||
}
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error("❌ Error:", err.message);
|
||
process.exit(1);
|
||
});
|