← back to AbramsOS

lib/gmail-fetcher.js

72 lines

const { google } = require('googleapis');
const { clientFor } = require('./google-oauth');

const RECEIPT_QUERY = 'newer_than:90d (receipt OR order OR invoice OR confirmation OR shipped OR delivered OR purchase) -category:promotions';

function gmailClient(refreshToken) {
  return google.gmail({ version: 'v1', auth: clientFor(refreshToken) });
}

async function listReceiptIds(refreshToken, { query = RECEIPT_QUERY, max = 200 } = {}) {
  const gmail = gmailClient(refreshToken);
  const ids = [];
  let pageToken;
  do {
    const resp = await gmail.users.messages.list({
      userId: 'me',
      q: query,
      maxResults: Math.min(100, max - ids.length),
      pageToken,
    });
    for (const m of resp.data.messages || []) ids.push(m.id);
    pageToken = resp.data.nextPageToken;
  } while (pageToken && ids.length < max);
  return ids;
}

async function getMessage(refreshToken, id) {
  const gmail = gmailClient(refreshToken);
  const resp = await gmail.users.messages.get({ userId: 'me', id, format: 'full' });
  return resp.data;
}

function header(payload, name) {
  const h = (payload.headers || []).find((x) => x.name.toLowerCase() === name.toLowerCase());
  return h ? h.value : null;
}

function decodeBase64Url(data) {
  if (!data) return '';
  return Buffer.from(data.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
}

function walkParts(payload, out = { text: '', html: '', attachments: [] }) {
  if (!payload) return out;
  const mime = payload.mimeType || '';
  if (mime === 'text/plain' && payload.body?.data) out.text += decodeBase64Url(payload.body.data);
  else if (mime === 'text/html' && payload.body?.data) out.html += decodeBase64Url(payload.body.data);
  else if (payload.body?.attachmentId && payload.filename) {
    out.attachments.push({
      attachmentId: payload.body.attachmentId,
      filename: payload.filename,
      mime,
      size: payload.body.size || 0,
    });
  }
  for (const p of payload.parts || []) walkParts(p, out);
  return out;
}

function summarize(message) {
  const headers = {
    subject: header(message.payload, 'Subject'),
    from: header(message.payload, 'From'),
    to: header(message.payload, 'To'),
    date: header(message.payload, 'Date'),
  };
  const body = walkParts(message.payload);
  return { id: message.id, threadId: message.threadId, headers, body, raw: message };
}

module.exports = { listReceiptIds, getMessage, summarize, RECEIPT_QUERY };