#!/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 */ ` <html>
< head >
< meta charset = "utf-8" / >
< / h e a d >
< body >
< pre style = "white-space: pre-wrap" >
$ { mail _obj [ "body-txt" ] || parsed . text } < / p r e
>
< / b o d y >
< / h t m l > ` ;
if ( ! html . includes ( "<body" ) ) {
html = /* HTML */ ` <head>
< meta charset = "utf-8" / >
< / h e a d >
< body >
$ { html || parsed . html || parsed . text }
< / b o d y > ` ;
}
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"> ` ,
` <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 ) {
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 ( /</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}" / >
< / l i > `
)
. 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 > < / t a b l e >
< ul >
$ { attachments _items }
< / u l > < / b o d y > `
) ;
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 : < / t h >
< td > $ { subject } < / t d >
< / t r >
< tr >
< th > Wysłano : < / t h >
< td > $ { escape ( date ) } < / t d >
< / t r >
< tr >
< th > Od : < / t h >
< td > $ { escape ( from ) } < / t d >
< / t r >
< tr >
< th > Do : < / t h >
< td > $ { escape ( to ) } < / t d >
< / t r >
< / t a b l e >
< 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"
) } ` ;
}