Ship the receipt pipeline (Livin/Grab/BRI), PocketBase spendings, PM2 ecosystem, and a proper .gitignore that excludes secrets, SQLite data, and media dumps. Co-authored-by: Cursor <cursoragent@cursor.com>
141 lines
3.9 KiB
JavaScript
141 lines
3.9 KiB
JavaScript
#!/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 <q> Gmail search (default: newer_than:7d)
|
|
* --user <email> Mailbox (default: first user with tokens)
|
|
* --max <n> 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);
|
|
});
|