Fix Stefano PDF email delivery path

This commit is contained in:
2026-03-28 02:50:42 -05:00
parent 5cffd0edf9
commit c3e5f669ed
6 changed files with 148 additions and 18 deletions

View File

@@ -11,7 +11,6 @@
const fs = require('fs');
const path = require('path');
const { google } = require('googleapis');
const DEFAULT_SUBJECT = process.env.GW_IMPERSONATE || 'stefano@fiorinis.com';
const DEFAULT_KEY_CANDIDATES = [
@@ -45,7 +44,11 @@ function parseArgs(argv) {
if (!next || next.startsWith('--')) {
out[key] = true;
} else {
out[key] = next;
if (Object.hasOwn(out, key)) {
out[key] = Array.isArray(out[key]) ? out[key].concat(next) : [out[key], next];
} else {
out[key] = next;
}
i++;
}
} else {
@@ -63,7 +66,7 @@ Env (optional):
Commands:
whoami
send --to <email> --subject <text> --body <text> [--html]
send --to <email> --subject <text> --body <text> [--html] [--attach <file>]
search-mail --query <gmail query> [--max 10]
search-calendar --query <text> [--max 10] [--timeMin ISO] [--timeMax ISO] [--calendar primary]
create-event --summary <text> --start <ISO> --end <ISO> [--timeZone America/Chicago] [--description <text>] [--location <text>] [--calendar primary]
@@ -77,17 +80,82 @@ function assertRequired(opts, required) {
}
}
function makeRawEmail({ from, to, subject, body, isHtml = false }) {
function toArray(value) {
if (value == null || value === false) return [];
return Array.isArray(value) ? value : [value];
}
function getAttachmentContentType(filename) {
const ext = path.extname(filename).toLowerCase();
if (ext === '.pdf') return 'application/pdf';
if (ext === '.txt') return 'text/plain; charset="UTF-8"';
if (ext === '.html' || ext === '.htm') return 'text/html; charset="UTF-8"';
if (ext === '.json') return 'application/json';
return 'application/octet-stream';
}
function wrapBase64(base64) {
return base64.match(/.{1,76}/g)?.join('\r\n') || '';
}
function loadAttachments(attachArg) {
return toArray(attachArg).map((filePath) => {
const absolutePath = path.resolve(filePath);
if (!fs.existsSync(absolutePath)) {
throw new Error(`Attachment file not found: ${absolutePath}`);
}
return {
filename: path.basename(absolutePath),
contentType: getAttachmentContentType(absolutePath),
data: fs.readFileSync(absolutePath).toString('base64'),
};
});
}
function makeRawEmail({ from, to, subject, body, isHtml = false, attachments = [] }) {
const contentType = isHtml ? 'text/html; charset="UTF-8"' : 'text/plain; charset="UTF-8"';
const msg = [
`From: ${from}`,
`To: ${to}`,
`Subject: ${subject}`,
'MIME-Version: 1.0',
`Content-Type: ${contentType}`,
'',
body,
].join('\r\n');
const normalizedAttachments = attachments.filter(Boolean);
let msg;
if (normalizedAttachments.length === 0) {
msg = [
`From: ${from}`,
`To: ${to}`,
`Subject: ${subject}`,
'MIME-Version: 1.0',
`Content-Type: ${contentType}`,
'',
body,
].join('\r\n');
} else {
const boundary = `gw-boundary-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
msg = [
`From: ${from}`,
`To: ${to}`,
`Subject: ${subject}`,
'MIME-Version: 1.0',
`Content-Type: multipart/mixed; boundary="${boundary}"`,
'',
`--${boundary}`,
`Content-Type: ${contentType}`,
'Content-Transfer-Encoding: 7bit',
'',
body,
'',
...normalizedAttachments.flatMap((attachment) => [
`--${boundary}`,
`Content-Type: ${attachment.contentType}; name="${attachment.filename}"`,
'Content-Transfer-Encoding: base64',
`Content-Disposition: attachment; filename="${attachment.filename}"`,
'',
wrapBase64(attachment.data),
'',
]),
`--${boundary}--`,
'',
].join('\r\n');
}
return Buffer.from(msg)
.toString('base64')
@@ -97,6 +165,7 @@ function makeRawEmail({ from, to, subject, body, isHtml = false }) {
}
async function getClients() {
const { google } = require('googleapis');
const keyPath = resolveKeyPath();
if (!keyPath) {
throw new Error('Service account key not found. Set GW_KEY_PATH or place the file in ~/.openclaw/workspace/.clawdbot/credentials/google-workspace/service-account.json');
@@ -132,6 +201,7 @@ async function cmdSend(clients, opts) {
subject: opts.subject,
body: opts.body,
isHtml: !!opts.html,
attachments: loadAttachments(opts.attach),
});
const res = await clients.gmail.users.messages.send({
@@ -222,7 +292,7 @@ async function cmdCreateEvent(clients, opts) {
console.log(JSON.stringify({ ok: true, id: res.data.id, htmlLink: res.data.htmlLink }, null, 2));
}
(async function main() {
async function main() {
try {
const args = parseArgs(process.argv.slice(2));
const cmd = args._[0];
@@ -245,4 +315,19 @@ async function cmdCreateEvent(clients, opts) {
console.error(`ERROR: ${err.message}`);
process.exit(1);
}
})();
}
if (require.main === module) {
main();
}
module.exports = {
getAttachmentContentType,
loadAttachments,
main,
makeRawEmail,
parseArgs,
resolveKeyPath,
toArray,
wrapBase64,
};