import { Command, EnumType } from '@cliffy/command/mod.ts' import { Input } from '@cliffy/prompt/mod.ts' 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) => JSX.Element, Record > > = 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) } } //TODO completions for "--from" //TODO require sudo for "--from !== !me" const templateType = new EnumType([...templates.keys()]) export const cmd = new Command() .name('send') .description('Send a mail.') .type('template', templateType) .option( '-f, --from ', 'From mail account or short name from config.', { default: '!me' }, ) .option('-r, --recipient ', 'Recipient (to) of the mail.', { required: true, collect: true, }) .option('--cc ', 'Copy carbon.', { collect: true }) .option('--cci ', 'Copy carbon invisible.', { collect: true, }) .option('-a, --attachments ', 'Attachments.', { collect: true }) .option('-t, --template ', 'HTML template from config', { default: 'message', }) .arguments('') .action( async ({ from, recipient, cc, cci, attachments, template }, subject) => { 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({ name: `${name} de Cohabit`, address: `_${name}_@cohabit.fr`, }) } 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 = {} 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 ?? [], }, } await send(mail) }, )