Files
Clawd BotandClaude Opus 4.6 ca9b510922 chore: align with upstream openclaw/openclaw and overlay local additions
- 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]>
2026-03-03 07:40:46 +01:00

185 lines
5.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
});