← back to Ventura Claw

server/connectors/gmail.js

48 lines

// Gmail — REAL via Steve's "george" gmail-agent proxy on Mac (per memory).
// george endpoint: http://100.107.67.67:9850 (tailnet) OR http://127.0.0.1:9850 (local Mac).
// Field stored is just the bearer/admin password for george (admin:DWSecure2024! by default).
function url(c)   { return c?.GEORGE_URL  || process.env.GEORGE_URL  || "http://127.0.0.1:9850"; }
function basic(c) {
  // Accept either pre-encoded GEORGE_BASIC_AUTH ("user:pass" plain or "Basic ..." or already base64) or split user/pass
  const combo = c?.GEORGE_BASIC_AUTH || process.env.GEORGE_BASIC_AUTH;
  if (combo) {
    if (combo.startsWith("Basic ")) return combo;
    if (/^[A-Za-z0-9+/=]+$/.test(combo) && combo.length > 12 && !combo.includes(":")) return "Basic " + combo;
    return "Basic " + Buffer.from(combo).toString("base64");
  }
  const u = c?.GEORGE_USER || process.env.GEORGE_USER || "admin";
  const p = c?.GEORGE_PASS || process.env.GEORGE_PASS;
  if (!p) throw new Error("GEORGE_BASIC_AUTH or GEORGE_PASS not set");
  return "Basic " + Buffer.from(`${u}:${p}`).toString("base64");
}
async function call(method, path, body, c) {
  const r = await fetch(`${url(c)}${path}`, {
    method,
    headers: { "Authorization": basic(c), "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(10000)
  });
  const d = r.status === 204 ? {} : await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(`gmail(george) ${path} ${r.status}: ${d.error || JSON.stringify(d).slice(0,200)}`);
  return d;
}
module.exports = {
  meta: { id: "gmail", name: "Gmail", category: "email", docsUrl: "http://127.0.0.1:9850", auth: "basic", realImpl: true },
  fields: [
    { key: "GEORGE_URL",        label: "George Endpoint",      type: "text",     required: false, hint: "default http://127.0.0.1:9850" },
    { key: "GEORGE_BASIC_AUTH", label: "Basic auth (user:pass)", type: "password", required: true,  hint: "e.g. admin:DWSecure2024! — plain or base64" }
  ],
  configured(c) { return !!(c?.GEORGE_BASIC_AUTH || c?.GEORGE_PASS || process.env.GEORGE_BASIC_AUTH || process.env.GEORGE_PASS); },
  async health(c) {
    if (!this.configured(c)) return { ok: false, reason: "no_token" };
    try { const d = await call("GET", "/api/health", null, c); return { ok: true, status: d.status || "ok", endpoint: url(c) }; }
    catch (e) { return { ok: false, reason: e.message }; }
  },
  actions: {
    async "message.send"({ to, subject, body, account }, c) {
      if (!to || !subject || !body) throw new Error("to, subject, body required");
      return call("POST", "/api/send", { to, subject, body, account }, c);
    }
  }
};