cohabit_mail/cli/send.ts

136 lines
3.7 KiB
TypeScript
Raw Normal View History

import { Command, EnumType } from '@cliffy/command/mod.ts'
import { Input } from '@cliffy/prompt/mod.ts'
2024-03-28 22:32:46 +01:00
import { Contact } from '../src/contact.ts'
import { send } from '../src/send.ts'
import type { Mail, Template } from '../types.ts'
import type { JSX } from 'preact'
import { fromFileUrl } from '@std/path'
const templates: Map<
string,
Template<
(props: Record<string, unknown>) => JSX.Element,
Record<string, unknown>
>
> = new Map()
const templatesDirUrl = import.meta.resolve('../templates')
const templatesDir = fromFileUrl(templatesDirUrl)
//Load templates dynamicaly
for await (
const template of Deno.readDir(templatesDir)
) {
if (template.isFile && template.name.endsWith('.tsx')) {
const modPath = new URL(
template.name,
`${import.meta.resolve('../templates')}/`,
)
const mod = await import(modPath.href)
templates.set(mod.default.name, mod.default)
}
}
2024-04-03 12:49:00 +02:00
//TODO completions for "--from"
//TODO require sudo for "--from !== !me"
const templateType = new EnumType([...templates.keys()])
2024-03-28 22:32:46 +01:00
export const cmd = new Command()
.name('send')
.description('Send a mail.')
.type('template', templateType)
2024-03-28 22:32:46 +01:00
.option(
'-f, --from <account:string>',
'From mail account or short name from config.',
{ default: '!me' },
)
.option('-r, --recipient <recipient:string>', 'Recipient (to) of the mail.', {
required: true,
collect: true,
})
2024-03-28 22:32:46 +01:00
.option('--cc <recipient:string>', 'Copy carbon.', { collect: true })
.option('--cci <recipient:string>', 'Copy carbon invisible.', {
collect: true,
})
.option('-a, --attachments <file:file>', 'Attachments.', { collect: true })
.option('-t, --template <name:template>', 'HTML template from config', {
default: 'message',
})
.arguments('<subject:string>')
2024-03-28 22:32:46 +01:00
.action(
async ({ from, recipient, cc, cci, attachments, template }, subject) => {
2024-03-28 22:32:46 +01:00
const fromContact: Contact = await (async () => {
if (from === '!me') {
const whoami = new Deno.Command('whoami', {
stderr: 'inherit',
})
const { stdout } = await whoami.output()
const rawName = new TextDecoder().decode(stdout).trim()
const name = encodeURIComponent(rawName)
return new Contact({
2024-03-28 22:32:46 +01:00
name: `${name} de Cohabit`,
address: `_${name}_@cohabit.fr`,
})
2024-03-28 22:32:46 +01:00
}
if (from.startsWith('!')) {
//@ts-ignore try expand
return expand(from.slice(1))
}
return Contact.fromString(from)
})()
const selectedTemplate = templates.get(template)!
type Props = typeof selectedTemplate
const props: Partial<Props> = {}
for (const prop of selectedTemplate.props) {
if (prop.multiline) {
console.log(
`%c?%c ${prop.description} %c[end input with "!EOL" on a new line] %c`,
'color: yellow;',
'font-weight: bold',
'color: white',
'color: blue',
)
const decoder = new TextDecoder()
for await (const chunk of Deno.stdin.readable) {
const text = decoder.decode(chunk)
if (text.startsWith('!EOF\n')) {
break
}
//@ts-ignore TODO fix type inference
props[prop.tag] += text
}
//@ts-ignore TODO fix type inference
if (props[prop.tag].startsWith('undefined')) {
//@ts-ignore TODO fix type inference
props[prop.tag] = props[prop.tag].slice('undefined'.length)
}
} else {
const value = await Input.prompt({
message: prop.description,
})
//@ts-ignore TODO fix type inference
props[prop.tag] = value
}
}
const mail: Mail = {
from: fromContact,
to: recipient.map((to) => Contact.fromString(to)),
subject,
body: selectedTemplate.builder(props)!,
options: {
cc: cc?.map(Contact.fromString) ?? [],
cci: cci?.map(Contact.fromString) ?? [],
attachments: attachments ?? [],
},
}
2024-03-28 22:32:46 +01:00
await send(mail)
},
)