You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
6.5 KiB
JavaScript
237 lines
6.5 KiB
JavaScript
#!/usr/bin/zx --quiet
|
|
|
|
var elparser = require("elparser");
|
|
import { resolve, basename } from "path";
|
|
|
|
const pwd = (await $`pwd`).stdout.replace("\n", "");
|
|
|
|
const sexpr = require("sexpr-plus").parse;
|
|
|
|
$`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 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 */ `<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
</head>
|
|
<body>
|
|
<pre style="white-space: pre-wrap">${mail_obj["body-txt"]}</pre>
|
|
</body>
|
|
</html>`;
|
|
|
|
if (!html.includes("<body")) {
|
|
html = /* HTML */ ` <head>
|
|
<meta charset="utf-8" />
|
|
</head>
|
|
<body>
|
|
${html}
|
|
</body>`;
|
|
}
|
|
|
|
for (const str_to_remove of [
|
|
`<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-2">`,
|
|
`<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2">`,
|
|
]) {
|
|
html = html.replace(str_to_remove, "");
|
|
}
|
|
|
|
if (!html.includes(`<meta charset="utf-8"`)) {
|
|
html = html.replace("</head>", `<meta charset="utf-8" /></head>`);
|
|
}
|
|
|
|
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) {
|
|
return await emacsEval(`(plist-get '${body_sexp} ':${header_name})`);
|
|
}
|
|
|
|
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(/</g, "<");
|
|
}
|
|
|
|
const to_download = attachments
|
|
.split("\n")
|
|
.slice(1)
|
|
.map((line) => {
|
|
//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] != "<none>")
|
|
.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 */ `<li>
|
|
${filename}<img style="width: 100%" src="data:${mime};base64,${base64}" />
|
|
</li>`
|
|
)
|
|
.join("\n");
|
|
|
|
html = html.replace(
|
|
/<\/body>/i,
|
|
/* HTML */ `<hr/><h3>Załączniki:</h3>
|
|
<table><tbody>${attachment_info.map(
|
|
(a) =>
|
|
`<tr><th>${a.name}</th><td>${a.size}</td><td><em>${a.type}</em></td></tr>`
|
|
)}<tbody></table>
|
|
<ul>
|
|
${attachments_items}
|
|
</ul></body>`
|
|
);
|
|
|
|
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(
|
|
/<body[^>]*>/i,
|
|
`<body>` +
|
|
/* HTML */ `<h1>${subject}</h1>
|
|
<table>
|
|
<tr>
|
|
<th>Temat wiadomości:</th>
|
|
<td>${subject}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Wysłano:</th>
|
|
<td>${escape(date)}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Od:</th>
|
|
<td>${escape(from)}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Do:</th>
|
|
<td>${escape(to)}</td>
|
|
</tr>
|
|
</table>
|
|
<hr />`
|
|
);
|
|
|
|
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"
|
|
)}`;
|
|
}
|