#!/usr/bin/env node /** * List Gmail messages: id, from, subject (metadata only). * * Usage: * npm run list-messages -w watcher * npm run list-messages -w watcher -- --query 'newer_than:14d has:attachment filename:eml' * npm run list-messages -w watcher -- --max 100 --json * * Options: * --query Gmail search (default: newer_than:7d) * --user Mailbox (default: first user with tokens) * --max Max messages (default 50) * --json Print JSON lines instead of a table */ import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from "../src/config.js"; import { getUserByEmail, listUsersWithTokens } from "../src/db.js"; import { gmailClientForUser } from "../src/google.js"; import { header } from "../src/mime.js"; const DEFAULT_QUERY = "newer_than:7d"; function parseArgs(argv) { const out = { query: null, user: null, max: 50, json: false, help: false, }; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a === "--help" || a === "-h") out.help = true; else if (a === "--json") out.json = true; else if (a === "--query") out.query = argv[++i]; else if (a === "--user") out.user = argv[++i]; else if (a === "--max") out.max = Math.max(1, Number(argv[++i]) || 50); else if (a.startsWith("--query=")) out.query = a.slice("--query=".length); else if (a.startsWith("--user=")) out.user = a.slice("--user=".length); else if (a.startsWith("--max=")) { out.max = Math.max(1, Number(a.slice("--max=".length)) || 50); } } return out; } async function listMessageIds(gmail, query, max) { const ids = []; let pageToken; while (ids.length < max) { const { data } = await gmail.users.messages.list({ userId: "me", q: query, maxResults: Math.min(50, max - ids.length), pageToken, }); for (const m of data.messages || []) { if (m.id) ids.push(m.id); if (ids.length >= max) break; } pageToken = data.nextPageToken; if (!pageToken) break; } return ids; } function metaFromPayload(payload) { return { from: header(payload, "From") || "", subject: header(payload, "Subject") || "(no subject)", date: header(payload, "Date") || "", }; } async function main() { const args = parseArgs(process.argv.slice(2)); if (args.help) { console.log(`Usage: list-messages [--query q] [--user email] [--max n] [--json] Default query: ${DEFAULT_QUERY}`); process.exit(0); } if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) { console.error("Missing GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET in .env"); process.exit(1); } const users = listUsersWithTokens(); if (!users.length) { console.error("No signed-in users with tokens. Sign in via the web UI first."); process.exit(1); } let user = users[0]; if (args.user) { const byEmail = getUserByEmail(args.user); const byId = users.find((u) => String(u.id) === String(args.user)); user = byEmail || byId || null; if (!user) { console.error(`User not found: ${args.user}`); process.exit(1); } } const gmail = await gmailClientForUser(user.id); const query = args.query || DEFAULT_QUERY; const ids = await listMessageIds(gmail, query, args.max); if (!args.json) { console.error(`# mailbox=${user.email} query=${JSON.stringify(query)} count=${ids.length}`); } for (const id of ids) { const { data } = await gmail.users.messages.get({ userId: "me", id, format: "metadata", metadataHeaders: ["From", "Subject", "Date"], }); const meta = metaFromPayload(data.payload || {}); const row = { id, from: meta.from, subject: meta.subject, date: meta.date, }; if (args.json) { console.log(JSON.stringify(row)); } else { console.log(`${row.id}\t${row.from}\t${row.subject}`); } } } main().catch((err) => { console.error(err); process.exit(1); });