- 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>
173 lines
5.0 KiB
JavaScript
173 lines
5.0 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,
|
|
client_secret: creds.client_secret,
|
|
refresh_token: creds.refresh_token,
|
|
grant_type: "refresh_token",
|
|
}).toString();
|
|
|
|
const data = await httpsRequest("https://oauth2.googleapis.com/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 query = process.argv[2] || "Reservedele.nu FAKTURA 668926";
|
|
|
|
const credsPath = join(homedir(), ".openclaw/secrets/google-oauth.json");
|
|
const creds = JSON.parse(fs.readFileSync(credsPath, "utf8"));
|
|
|
|
const accessToken = await getAccessToken(creds);
|
|
|
|
console.log(`🔍 Searching for: "${query}"\n`);
|
|
|
|
// Search for messages
|
|
const searchUrl = `https://gmail.googleapis.com/gmail/v1/users/me/messages?q=${encodeURIComponent(query)}&maxResults=1`;
|
|
|
|
const searchData = await httpsRequest(searchUrl, {
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
});
|
|
|
|
if (!searchData.messages || searchData.messages.length === 0) {
|
|
console.log("❌ No messages found");
|
|
return;
|
|
}
|
|
|
|
const messageId = searchData.messages[0].id;
|
|
console.log(`📧 Found message ID: ${messageId}\n`);
|
|
|
|
// Get full message with attachments
|
|
const msgUrl = `https://gmail.googleapis.com/gmail/v1/users/me/messages/${messageId}?format=full`;
|
|
|
|
const msg = await httpsRequest(msgUrl, {
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
});
|
|
|
|
// Parse headers
|
|
const headers = {};
|
|
for (const h of msg.payload.headers) {
|
|
headers[h.name.toLowerCase()] = h.value;
|
|
}
|
|
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
console.log(`From: ${headers.from || "Unknown"}`);
|
|
console.log(`Subject: ${headers.subject || "No subject"}`);
|
|
console.log(`Date: ${headers.date || "Unknown"}`);
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
|
|
// Get body
|
|
let body = "";
|
|
|
|
function extractBody(part) {
|
|
if (part.mimeType === "text/plain" && part.body?.data) {
|
|
body += Buffer.from(part.body.data, "base64").toString("utf8");
|
|
} else if (part.mimeType === "text/html" && !body && part.body?.data) {
|
|
// Fallback to HTML if no plain text
|
|
const html = Buffer.from(part.body.data, "base64").toString("utf8");
|
|
// Strip HTML tags for basic display
|
|
body += html
|
|
.replace(/<[^>]*>/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
if (part.parts) {
|
|
for (const subpart of part.parts) {
|
|
extractBody(subpart);
|
|
}
|
|
}
|
|
}
|
|
|
|
extractBody(msg.payload);
|
|
|
|
if (body) {
|
|
console.log("📄 MESSAGE BODY:\n");
|
|
console.log(body);
|
|
console.log("\n");
|
|
}
|
|
|
|
// Check for attachments
|
|
const attachments = [];
|
|
|
|
function findAttachments(part) {
|
|
if (part.filename && part.body?.attachmentId) {
|
|
attachments.push({
|
|
filename: part.filename,
|
|
mimeType: part.mimeType,
|
|
attachmentId: part.body.attachmentId,
|
|
size: part.body.size,
|
|
});
|
|
}
|
|
|
|
if (part.parts) {
|
|
for (const subpart of part.parts) {
|
|
findAttachments(subpart);
|
|
}
|
|
}
|
|
}
|
|
|
|
findAttachments(msg.payload);
|
|
|
|
if (attachments.length > 0) {
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
console.log(`📎 ATTACHMENTS (${attachments.length}):\n`);
|
|
|
|
for (const att of attachments) {
|
|
const sizeKB = (att.size / 1024).toFixed(2);
|
|
console.log(` ${att.filename} (${sizeKB} KB)`);
|
|
console.log(` Type: ${att.mimeType}`);
|
|
|
|
// Download attachment
|
|
const attUrl = `https://gmail.googleapis.com/gmail/v1/users/me/messages/${messageId}/attachments/${att.attachmentId}`;
|
|
|
|
const attData = await httpsRequest(attUrl, {
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
});
|
|
|
|
const buffer = Buffer.from(attData.data, "base64");
|
|
const outputPath = `/tmp/${att.filename}`;
|
|
fs.writeFileSync(outputPath, buffer);
|
|
|
|
console.log(` ✅ Downloaded to: ${outputPath}\n`);
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("❌ Error:", err.message);
|
|
process.exit(1);
|
|
});
|