#!/usr/bin/zx var elparser = require("elparser"); import { resolve, basename } from "path"; const pwd = (await $`pwd`).stdout.replace("\n", ""); const sexpr = require("sexpr-plus").parse; const simpleParser = require("mailparser").simpleParser; $`echo $PWD`; const path = resolve(process.env.PWD, process.argv.slice(-1)[0]); const fs = require("fs").promises; const mail_body = (await $`mu view ${path} -o sexp`).stdout; function parse_mail(sexp_string) { const array = sexpr(sexp_string)[0].content; const result = {}; let i = 0; while (i < array.length) { result[array[i].content.slice(1)] = array[i + 1].content; i += 2; } return result; } const parsed = await simpleParser((await $`cat ${path}`).stdout); const mail_obj = parse_mail(mail_body); let html = mail_obj["body-html"] && mail_obj["body-html"].match(/body/i) !== null ? mail_obj["body-html"] : /* HTML */ `
${mail_obj["body-txt"] || parsed.text}
`; if (!html.includes(" ${html || parsed.html || parsed.text} `; } for (const str_to_remove of [ ``, ``, ``, ]) { html = html.replace(str_to_remove, ""); } if (!html.includes(`", ``); } const attachments = (await $`mu extract ${path}`).stdout; async function emacsEval(expr) { const filename = `/tmp/${Math.random()}${Math.random()}${Math.random()}${Math.random()}${Math.random()}.el`; await fs.writeFile(filename, expr); return ( await $`emacsclient --eval ${`(eval (ignore-errors (read-from-whole-string (with-temp-buffer (insert-file-contents "${filename}") (buffer-string)))))`}` ).stdout.slice(0, -1); // slice to remove trailing newline; } async function getSimpleHeader(body_sexp, header_name) { const result = await emacsEval(`(plist-get '${body_sexp} ':${header_name})`); console.log("getsimpleheader", header_name, result); return result; } async function formatAddress(sexp) { const [name, address] = await Promise.all([ emacsEval(`(car (car '${sexp}))`), emacsEval(`(cdr (car '${sexp}))`), ]); return `${name === "nil" ? "" : name} <${address}>`; } function escape(str) { return str.replace(/ { //someimtes images have the wrong mime type, like for example in photos from straż miejska confirmation emails if (line.includes(".jpg") && line.includes("application/octet-stream")) { return line.replace("application/octet-stream", "image/jpg"); } else { return line; } }) .filter((line) => line.includes("image")) .map((line) => { return { filename: line.match(/\d+ (.*) image\//)[1], mime: line.match(/image\/[^ ]+/)[0], }; }); const attachment_info = attachments .split("\n") .slice(1) .map((line) => line.split(" ").slice(3)) .filter((e) => e[0] != "") .slice(0, -1) .map(([name, type, role, size1, size2]) => ({ name, type, role, size: (size1 + " " + size2).replace(/[()]/g, ""), })) .filter(({ role }) => role == "[attach]"); const files = await Promise.all( to_download.map(async ({ filename, mime }) => { await $`mu extract --overwrite --target-dir=/tmp ${path} ${filename}`; const base64 = (await $`base64 -w 0 < ${`/tmp/${filename}`}`).stdout; return { filename, base64, mime }; }) ); const attachments_items = files .map( ({ filename, mime, base64 }) => /* HTML */ `
  • ${filename}
  • ` ) .join("\n"); html = html.replace( /<\/body>/i, /* HTML */ `

    Załączniki:

    ${attachment_info.map( (a) => `` )}
    ${a.name}${a.size}${a.type}
    ` ); const subject = (await getSimpleHeader(mail_body, "subject")).slice(1, -1); // slice to remove " and newlines const from = await formatAddress(await getSimpleHeader(mail_body, "from")); const to = await formatAddress(await getSimpleHeader(mail_body, "to")); const date = await formatDate(await getSimpleHeader(mail_body, "date")); const parts = elparser .parse1(`'${await getSimpleHeader(mail_body, "parts")}`) ?.qsexp?.list?.map((entry) => Object.fromEntries( entry.list.map((value, index, array) => { if (index % 2 == 0) { const key = array[index].symbol.slice(1); let value = array[index + 1]; for (const subkey of ["string", "symbol"]) { if (value[subkey] !== undefined) { value = value[subkey]; } } if (value.type && value.type?.type === "int") { value = parseInt(value.val); } return [key, value]; } else return []; }) ) ) || []; const inline_attachments = parts.filter((e) => e.cid !== undefined); for (const inline_attachment of inline_attachments) { await $`mu extract --overwrite ${path} ${inline_attachment.name} --target-dir=/tmp --overwrite`; html = html.replace( `cid:${inline_attachment.cid}`, `./${inline_attachment.name}` ); } html = html.replace( /]*>/i, `` + /* HTML */ `

    ${subject}

    Temat wiadomości: ${subject}
    Wysłano: ${escape(date)}
    Od: ${escape(from)}
    Do: ${escape(to)}

    ` ); const output = (await $`mktemp --suffix=.html`).stdout.replace("\n", ""); await fs.writeFile(output, html); const output_file = `${resolve(path, "../")}/${basename(path)}.pdf`.replace( ",", " " ); await $`wkhtmltopdf --enable-local-file-access ${output} ${output_file}`; // --enable-local-file-access is there so the inline images don't have to be included as base64. Probably not the safest approach and base64 would be better // console.log(pwd + "output.html"); // console.log(mail_body); async function formatDate(sexp) { const parsed = (await $`emacsclient --eval ${`(decode-time '${sexp})`}`) .stdout; const [, minutes, hours, date, month, year] = parsed.split(" "); return `${year}-${month.padStart(2, "0")}-${date} ${hours}:${minutes.padStart( 2, "0" )}`; }