def apply(self, ui): if self.envelope is None: self.envelope = Envelope() if self.template is not None: #get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception, e: ui.notify(str(e), priority='error') return
def _make_ui_mock(): """Create a mock for the ui and envelope and return them.""" envelope = Envelope() envelope['From'] = 'foo <*****@*****.**>' envelope.sign = mock.sentinel.default envelope.sign_key = mock.sentinel.default ui = mock.Mock(current_buffer=mock.Mock(envelope=envelope)) return envelope, ui
def _make_ui_mock(): """Create a mock for the ui and envelope and return them.""" envelope = Envelope() envelope['From'] = 'foo <*****@*****.**>' envelope.sign = mock.sentinel.default envelope.sign_key = mock.sentinel.default ui = utilities.make_ui(current_buffer=mock.Mock(envelope=envelope)) return envelope, ui
def apply(self, ui): # look if this makes sense: do we have any accounts set up? my_accounts = settings.get_accounts() if not my_accounts: ui.notify('no accounts set', priority='error') return # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() envelope = Envelope() if self.inline: # inline mode # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('forward_prefix') if qf: quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quote = 'Forwarded message from %s (%s):\n' % (name or address, timestamp) mailcontent = quote quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope.body = mailcontent else: # attach original mode # attach original msg mail.set_type('message/rfc822') mail['Content-Disposition'] = 'attachment' envelope.attach(Attachment(mail)) # copy subject subject = decode_header(mail.get('Subject', '')) subject = 'Fwd: ' + subject forward_subject_hook = settings.get_hook('forward_subject') if forward_subject_hook: subject = forward_subject_hook(subject) else: fsp = settings.get('forward_subject_prefix') if not subject.startswith(('Fwd:', fsp)): subject = fsp + subject envelope.add('Subject', subject) # set From realname, address = recipient_to_from(mail, my_accounts) envelope.add('From', '%s <%s>' % (realname, address)) # continue to compose ui.apply_command( ComposeCommand(envelope=envelope, spawn=self.force_spawn))
async def test_encrypt_to_self_false(self): ui = utilities.make_ui() envelope = Envelope() envelope['From'] = '*****@*****.**' envelope['To'] = '*****@*****.**' gpg_key = crypto.get_key(FPR) account = _Account(encrypt_to_self=False, gpg_key=gpg_key) envelope.account = account await utils.update_keys(ui, envelope) self.assertTrue(envelope.encrypt) self.assertNotIn(FPR, envelope.encrypt_keys)
def apply(self, ui): # look if this makes sense: do we have any accounts set up? my_accounts = settings.get_accounts() if not my_accounts: ui.notify('no accounts set', priority='error') return # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() envelope = Envelope() if self.inline: # inline mode # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('forward_prefix') if qf: quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quote = 'Forwarded message from %s (%s):\n' % ( name or address, timestamp) mailcontent = quote quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope.body = mailcontent else: # attach original mode # attach original msg mail.set_type('message/rfc822') mail['Content-Disposition'] = 'attachment' envelope.attach(Attachment(mail)) # copy subject subject = decode_header(mail.get('Subject', '')) subject = 'Fwd: ' + subject forward_subject_hook = settings.get_hook('forward_subject') if forward_subject_hook: subject = forward_subject_hook(subject) else: fsp = settings.get('forward_subject_prefix') if not subject.startswith(('Fwd:', fsp)): subject = fsp + subject envelope.add('Subject', subject) # set From realname, address = recipient_to_from(mail, my_accounts) envelope.add('From', '%s <%s>' % (realname, address)) # continue to compose ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn))
def test_get_no_keys(self): ui = utilities.make_ui() envelope = Envelope() envelope['To'] = '*****@*****.**' yield utils.set_encrypt(ui, envelope) self.assertFalse(envelope.encrypt) self.assertEqual(envelope.encrypt_keys, {})
def apply(self, ui): if self.envelope == None: self.envelope = Envelope() if self.template is not None: #get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception, e: ui.notify(str(e), priority='error') return
async def test_get_no_keys(self): ui = utilities.make_ui() envelope = Envelope() envelope['To'] = '*****@*****.**' await utils.update_keys(ui, envelope) self.assertFalse(envelope.encrypt) self.assertEqual(envelope.encrypt_keys, {})
def apply(self, ui): if self.envelope is None: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get("template_dir") tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) tempdir = os.path.join(xdgdir, "alot", "templates") path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify("no templates directory: %s" % tempdir, priority="error") return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify("could not find template: %s" % path, priority="error") return try: self.envelope.parse_template(open(path).read()) except Exception, e: ui.notify(str(e), priority="error") return
def mailto_to_envelope(mailto_str): """ Interpret mailto-string into a :class:`alot.db.envelope.Envelope` """ from alot.db.envelope import Envelope headers, body = parse_mailto(mailto_str) return Envelope(bodytext=body, headers=headers)
async def test_get_partial_keys(self): ui = utilities.make_ui() envelope = Envelope() envelope['Cc'] = '[email protected], [email protected]' await utils.update_keys(ui, envelope) self.assertTrue(envelope.encrypt) self.assertCountEqual([f.fpr for f in envelope.encrypt_keys.values()], [crypto.get_key(FPR).fpr])
def test_get_partial_keys(self): ui = mock.Mock() envelope = Envelope() envelope['Cc'] = '[email protected], [email protected]' yield utils.set_encrypt(ui, envelope) self.assertTrue(envelope.encrypt) self.assertEqual([f.fpr for f in envelope.encrypt_keys.itervalues()], [crypto.get_key(FPR).fpr])
def test_get_keys_from_cc(self): ui = utilities.make_ui() envelope = Envelope() envelope['Cc'] = '[email protected], [email protected]' yield utils.set_encrypt(ui, envelope) self.assertTrue(envelope.encrypt) self.assertEqual( [f.fpr for f in envelope.encrypt_keys.itervalues()], [crypto.get_key(FPR).fpr, crypto.get_key(EXTRA_FPRS[0]).fpr])
async def test_get_keys_from_cc(self): ui = utilities.make_ui() envelope = Envelope() envelope['Cc'] = '[email protected], [email protected]' await utils.update_keys(ui, envelope) self.assertTrue(envelope.encrypt) self.assertCountEqual( [f.fpr for f in envelope.encrypt_keys.values()], [crypto.get_key(FPR).fpr, crypto.get_key(EXTRA_FPRS[0]).fpr])
def test_encrypt_to_self_false(self): ui = utilities.make_ui() envelope = Envelope() envelope['From'] = '*****@*****.**' envelope['To'] = '*****@*****.**' gpg_key = crypto.get_key(FPR) account = _Account(encrypt_to_self=False, gpg_key=gpg_key) with mock.patch('alot.commands.thread.settings.get_account_by_address', mock.Mock(return_value=account)): yield utils.set_encrypt(ui, envelope) self.assertTrue(envelope.encrypt) self.assertNotIn(FPR, envelope.encrypt_keys)
async def spam(ui): message = ui.current_buffer.get_selected_message() message.add_tags(['spam']) message.remove_tags(['unread', 'flagged']) if 'ugent' in message.get_tags(): envelope = Envelope(headers=dict( From=['Felix Van der Jeugt <*****@*****.**>'], To=['*****@*****.**'], Subject=['spam'], References=['<' + message.get_message_id() + '>']), bodytext='spam', tags=['spam'], account=settings.account_matching_address( '*****@*****.**')) else: envelope = Envelope(headers=dict( From=['Felix Van der Jeugt <*****@*****.**>'], To=['*****@*****.**'], Subject=['spam'], References=['<' + message.get_message_id() + '>']), bodytext='', tags=['spam'], account=settings.account_matching_address( '*****@*****.**')) envelope.attach(message.get_filename()) ui.buffer_open(EnvelopeBuffer(ui, envelope))
def apply(self, ui): if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() mailcontent = self.message.accumulate_body() envelope = Envelope(bodytext=mailcontent) # copy selected headers to_copy = [ 'Subject', 'From', 'To', 'Cc', 'Bcc', 'In-Reply-To', 'References' ] for key in to_copy: value = decode_header(mail.get(key, '')) if value: envelope.add(key, value) # copy attachments for b in self.message.get_attachments(): envelope.attach(b) ui.apply_command( ComposeCommand(envelope=envelope, spawn=self.force_spawn, omit_signature=True))
async def test_encrypt_to_self_true(self): ui = utilities.make_ui() envelope = Envelope() envelope['From'] = '*****@*****.**' envelope['To'] = '*****@*****.**' gpg_key = crypto.get_key(FPR) account = _Account(encrypt_to_self=True, gpg_key=gpg_key) with mock.patch('alot.commands.thread.settings.get_account_by_address', mock.Mock(return_value=account)): await utils.update_keys(ui, envelope) self.assertTrue(envelope.encrypt) self.assertIn(FPR, envelope.encrypt_keys) self.assertEqual(gpg_key, envelope.encrypt_keys[FPR])
def apply(self, ui): if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() mailcontent = self.message.accumulate_body() envelope = Envelope(bodytext=mailcontent) # copy selected headers to_copy = ['Subject', 'From', 'To', 'Cc', 'Bcc', 'In-Reply-To', 'References'] for key in to_copy: value = decode_header(mail.get(key, '')) if value: envelope.add(key, value) # copy attachments for b in self.message.get_attachments(): envelope.attach(b) ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn, omit_signature=True))
def _test(self, tagstring, action, expected): """Common steps for envelope.TagCommand tests :param tagstring: the string to pass to the TagCommand :type tagstring: str :param action: the action to pass to the TagCommand :type action: str :param expected: the expected output to assert in the test :type expected: list(str) """ env = Envelope(tags=['one', 'two', 'three']) ui = mock.Mock() ui.current_buffer = mock.Mock() ui.current_buffer.envelope = env cmd = envelope.TagCommand(tags=tagstring, action=action) cmd.apply(ui) actual = env.tags self.assertListEqual(sorted(actual), sorted(expected))
def apply(self, ui): # look if this makes sense: do we have any accounts set up? my_accounts = settings.get_accounts() if not my_accounts: ui.notify('no accounts set', priority='error') return # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name, timestamp) mailcontent = quotestring for line in self.message.accumulate_body().splitlines(): mailcontent += '>' + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) if not subject.startswith('Re:'): subject = 'Re: ' + subject envelope.add('Subject', subject) # set From realname, address = recipient_to_from(mail, my_accounts) envelope.add('From', '%s <%s>' % (realname, address)) # set To sender = mail['Reply-To'] or mail['From'] recipients = [sender] my_addresses = settings.get_addresses() if self.groupreply: if sender != mail['From']: recipients.append(mail['From']) cleared = self.clear_my_address(my_addresses, mail.get('To', '')) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address(my_addresses, mail['Cc']) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) envelope.add('To', decode_header(to)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn))
def apply(self, ui): if self.envelope is None: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: raise CommandCanceled() self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = filter(lambda x: x, tagsstring.split(',')) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.lower().startswith(('re:', rsp.lower())): subject = rsp + subject envelope.add('Subject', subject) # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] my_addresses = settings.get_addresses() sender_address = parseaddr(sender)[1] cc = '' # check if reply is to self sent message if sender_address in my_addresses: recipients = [mail['To']] emsg = 'Replying to own message, set recipients to: %s' \ % recipients logging.debug(emsg) else: recipients = [sender] if self.groupreply: # make sure that our own address is not included # if the message was self-sent, then our address is not included MFT = mail.get_all('Mail-Followup-To', []) followupto = self.clear_my_address(my_addresses, MFT) if followupto and settings.get('honor_followup_to'): logging.debug('honor followup to: %s', followupto) recipients = [followupto] # since Mail-Followup-To was set, ignore the Cc header else: if sender != mail['From']: recipients.append(mail['From']) # append To addresses if not replying to self sent message if sender_address not in my_addresses: cleared = self.clear_my_address( my_addresses, mail.get_all('To', [])) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address( my_addresses, mail.get_all('Cc', [])) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) envelope.add('To', decode_header(to)) # if any of the recipients is a mailinglist that we are subscribed to, # set Mail-Followup-To header so that duplicates are avoided if settings.get('followup_to'): # to and cc are already cleared of our own address allrecipients = [to] + [cc] lists = settings.get('mailinglists') # check if any recipient address matches a known mailing list if any([addr in lists for n, addr in getaddresses(allrecipients)]): followupto = ', '.join(allrecipients) logging.debug('mail followup to: %s' % followupto) envelope.add('Mail-Followup-To', decode_header(followupto)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn))
class ComposeCommand(Command): """compose a new email""" def __init__( self, envelope=None, headers={}, template=None, sender=u"", subject=u"", to=[], cc=[], bcc=[], attach=None, omit_signature=False, spawn=None, rest=[], **kwargs ): """ :param envelope: use existing envelope :type envelope: :class:`~alot.db.envelope.Envelope` :param headers: forced header values :type headers: dict (str->str) :param template: name of template to parse into the envelope after creation. This should be the name of a file in your template_dir :type template: str :param sender: From-header value :type sender: str :param subject: Subject-header value :type subject: str :param to: To-header value :type to: str :param cc: Cc-header value :type cc: str :param bcc: Bcc-header value :type bcc: str :param attach: Path to files to be attached (globable) :type attach: str :param omit_signature: do not attach/append signature :type omit_signature: bool :param spawn: force spawning of editor in a new terminal :type spawn: bool :param rest: remaining parameters. These can start with 'mailto' in which case it is interpreted as mailto string. Otherwise it will be interpreted as recipients (to) header :type rest: list(str) """ Command.__init__(self, **kwargs) self.envelope = envelope self.template = template self.headers = headers self.sender = sender self.subject = subject self.to = to self.cc = cc self.bcc = bcc self.attach = attach self.omit_signature = omit_signature self.force_spawn = spawn self.rest = " ".join(rest) @inlineCallbacks def apply(self, ui): if self.envelope is None: if self.rest: if self.rest.startswith("mailto"): self.envelope = mailto_to_envelope(self.rest) else: self.envelope = Envelope() self.envelope.add("To", self.rest) else: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get("template_dir") tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) tempdir = os.path.join(xdgdir, "alot", "templates") path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify("no templates directory: %s" % tempdir, priority="error") return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify("could not find template: %s" % path, priority="error") return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority="error") return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add("From", self.sender) if self.subject: self.envelope.add("Subject", self.subject) if self.to: self.envelope.add("To", ",".join(self.to)) if self.cc: self.envelope.add("Cc", ",".join(self.cc)) if self.bcc: self.envelope.add("Bcc", ",".join(self.bcc)) # get missing From header if not "From" in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add("From", fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt("From", completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add("From", fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope["From"]) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug("has signature") sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug("is file") if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug("attached") else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith("text"): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += "\n" + sigcontent else: ui.notify("could not locate signature: %s" % sig, priority="error") if (yield ui.choice("send without signature?", "yes", "no")) == "no": return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get("From") name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if "To" not in self.envelope.headers: allbooks = not settings.get("complete_matching_abook_only") logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt("To", completer=completer) if to is None: raise CommandCanceled() self.envelope.add("To", to.strip(" \t\n,")) if settings.get("ask_subject") and not "Subject" in self.envelope.headers: subject = yield ui.prompt("Subject") logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add("Subject", subject) if settings.get("compose_ask_tags"): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt("Tags", completer=comp) tags = filter(lambda x: x, tagsstring.split(",")) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug("attaching: " + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def apply(self, ui): if self.envelope is None: if self.rest: if self.rest.startswith("mailto"): self.envelope = mailto_to_envelope(self.rest) else: self.envelope = Envelope() self.envelope.add("To", self.rest) else: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get("template_dir") tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) tempdir = os.path.join(xdgdir, "alot", "templates") path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify("no templates directory: %s" % tempdir, priority="error") return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify("could not find template: %s" % path, priority="error") return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority="error") return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add("From", self.sender) if self.subject: self.envelope.add("Subject", self.subject) if self.to: self.envelope.add("To", ",".join(self.to)) if self.cc: self.envelope.add("Cc", ",".join(self.cc)) if self.bcc: self.envelope.add("Bcc", ",".join(self.bcc)) # get missing From header if not "From" in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add("From", fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt("From", completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add("From", fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope["From"]) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug("has signature") sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug("is file") if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug("attached") else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith("text"): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += "\n" + sigcontent else: ui.notify("could not locate signature: %s" % sig, priority="error") if (yield ui.choice("send without signature?", "yes", "no")) == "no": return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get("From") name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if "To" not in self.envelope.headers: allbooks = not settings.get("complete_matching_abook_only") logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt("To", completer=completer) if to is None: raise CommandCanceled() self.envelope.add("To", to.strip(" \t\n,")) if settings.get("ask_subject") and not "Subject" in self.envelope.headers: subject = yield ui.prompt("Subject") logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add("Subject", subject) if settings.get("compose_ask_tags"): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt("Tags", completer=comp) tags = filter(lambda x: x, tagsstring.split(",")) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug("attaching: " + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.startswith(('Re:', rsp)): subject = rsp + subject envelope.add('Subject', subject) # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] recipients = [sender] my_addresses = settings.get_addresses() if self.groupreply: if sender != mail['From']: recipients.append(mail['From']) cleared = self.clear_my_address(my_addresses, mail.get('To', '')) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address(my_addresses, mail['Cc']) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) envelope.add('To', decode_header(to)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn))
def apply(self, ui): if self.envelope is None: if self.rest: if self.rest.startswith('mailto'): self.envelope = mailto_to_envelope(self.rest) else: self.envelope = Envelope() self.envelope.add('To', self.rest) else: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode( sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: raise CommandCanceled() self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = filter(lambda x: x, tagsstring.split(',')) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
class ComposeCommand(Command): """compose a new email""" def __init__(self, envelope=None, headers={}, template=None, sender=u'', subject=u'', to=[], cc=[], bcc=[], attach=None, omit_signature=False, spawn=None, **kwargs): """ :param envelope: use existing envelope :type envelope: :class:`~alot.db.envelope.Envelope` :param headers: forced header values :type header: doct (str->str) :param template: name of template to parse into the envelope after creation. This should be the name of a file in your template_dir :type template: str :param sender: From-header value :type sender: str :param subject: Subject-header value :type subject: str :param to: To-header value :type to: str :param cc: Cc-header value :type cc: str :param bcc: Bcc-header value :type bcc: str :param attach: Path to files to be attached (globable) :type attach: str :param omit_signature: do not attach/append signature :type omit_signature: bool :param spawn: force spawning of editor in a new terminal :type spawn: bool """ Command.__init__(self, **kwargs) self.envelope = envelope self.template = template self.headers = headers self.sender = sender self.subject = subject self.to = to self.cc = cc self.bcc = bcc self.attach = attach self.omit_signature = omit_signature self.force_spawn = spawn @inlineCallbacks def apply(self, ui): if self.envelope == None: self.envelope = Envelope() if self.template is not None: #get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception, e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: ui.notify('canceled') return a = settings.get_account_by_address(fromaddress) if a is not None: fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature', select='yes', cancel='no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to == None: ui.notify('canceled') return self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject == None: ui.notify('canceled') return self.envelope.add('Subject', subject) if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() envelope = Envelope() if self.inline: # inline mode # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('forward_prefix') if qf: quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quote = 'Forwarded message from %s (%s):\n' % ( name or address, timestamp) mailcontent = quote quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope.body = mailcontent for a in self.message.get_attachments(): envelope.attach(a) else: # attach original mode # attach original msg original_mail = Message() original_mail.set_type('message/rfc822') original_mail['Content-Disposition'] = 'attachment' original_mail.set_payload(email_as_string(mail)) envelope.attach(Attachment(original_mail)) # copy subject subject = decode_header(mail.get('Subject', '')) subject = 'Fwd: ' + subject forward_subject_hook = settings.get_hook('forward_subject') if forward_subject_hook: subject = forward_subject_hook(subject) else: fsp = settings.get('forward_subject_prefix') if not subject.startswith(('Fwd:', fsp)): subject = fsp + subject envelope.add('Subject', subject) # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # continue to compose ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn))
class ComposeCommand(Command): """compose a new email""" def __init__(self, envelope=None, headers={}, template=None, sender=u'', subject=u'', to=[], cc=[], bcc=[], attach=None, omit_signature=False, spawn=None, **kwargs): """ :param envelope: use existing envelope :type envelope: :class:`~alot.db.envelope.Envelope` :param headers: forced header values :type header: doct (str->str) :param template: name of template to parse into the envelope after creation. This should be the name of a file in your template_dir :type template: str :param sender: From-header value :type sender: str :param subject: Subject-header value :type subject: str :param to: To-header value :type to: str :param cc: Cc-header value :type cc: str :param bcc: Bcc-header value :type bcc: str :param attach: Path to files to be attached (globable) :type attach: str :param omit_signature: do not attach/append signature :type omit_signature: bool :param spawn: force spawning of editor in a new terminal :type spawn: bool """ Command.__init__(self, **kwargs) self.envelope = envelope self.template = template self.headers = headers self.sender = sender self.subject = subject self.to = to self.cc = cc self.bcc = bcc self.attach = attach self.omit_signature = omit_signature self.force_spawn = spawn @inlineCallbacks def apply(self, ui): if self.envelope is None: self.envelope = Envelope() if self.template is not None: #get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception, e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: ui.notify('canceled') return self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode( sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: ui.notify('canceled') return self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: ui.notify('canceled') return self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = filter(lambda x: x, tagsstring.split(',')) if tags is None: ui.notify('canceled') return self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def apply(self, ui): # look if this makes sense: do we have any accounts set up? my_accounts = settings.get_accounts() if not my_accounts: ui.notify('no accounts set', priority='error') return # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.startswith(('Re:', rsp)): subject = rsp + subject envelope.add('Subject', subject) # set From realname, address = recipient_to_from(mail, my_accounts) envelope.add('From', '%s <%s>' % (realname, address)) # set To sender = mail['Reply-To'] or mail['From'] recipients = [sender] my_addresses = settings.get_addresses() if self.groupreply: if sender != mail['From']: recipients.append(mail['From']) cleared = self.clear_my_address(my_addresses, mail.get('To', '')) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address(my_addresses, mail['Cc']) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) envelope.add('To', decode_header(to)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose ui.apply_command( ComposeCommand(envelope=envelope, spawn=self.force_spawn))
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.lower().startswith(('re:', rsp.lower())): subject = rsp + subject envelope.add('Subject', subject) # Auto-detect ML auto_replyto_mailinglist = settings.get('auto_replyto_mailinglist') if mail['List-Id'] and self.listreply is None: # mail['List-Id'] is need to enable reply-to-list self.listreply = auto_replyto_mailinglist elif mail['List-Id'] and self.listreply is True: self.listreply = True elif self.listreply is False: # In this case we only need the sender self.listreply = False # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] my_addresses = settings.get_addresses() sender_address = parseaddr(sender)[1] cc = '' # check if reply is to self sent message if sender_address in my_addresses: recipients = [mail['To']] emsg = 'Replying to own message, set recipients to: %s' \ % recipients logging.debug(emsg) else: recipients = [sender] if self.groupreply: # make sure that our own address is not included # if the message was self-sent, then our address is not included MFT = mail.get_all('Mail-Followup-To', []) followupto = self.clear_my_address(my_addresses, MFT) if followupto and settings.get('honor_followup_to'): logging.debug('honor followup to: %s', followupto) recipients = [followupto] # since Mail-Followup-To was set, ignore the Cc header else: if sender != mail['From']: recipients.append(mail['From']) # append To addresses if not replying to self sent message if sender_address not in my_addresses: cleared = self.clear_my_address( my_addresses, mail.get_all('To', [])) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address( my_addresses, mail.get_all('Cc', [])) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) if self.listreply: # To choose the target of the reply --list # Reply-To is standart reply target RFC 2822:, RFC 1036: 2.2.1 # X-BeenThere is needed by sourceforge ML also winehq # X-Mailing-List is also standart and is used by git-send-mail to = mail['Reply-To'] or mail['X-BeenThere'] or mail['X-Mailing-List'] # Some mail server (gmail) will not resend you own mail, so you have # to deal with the one in sent if to is None: to = mail['To'] logging.debug('mail list reply to: %s' % to) # Cleaning the 'To' in this case if envelope.get('To') is not None: envelope.__delitem__('To') # Finally setup the 'To' header envelope.add('To', decode_header(to)) # if any of the recipients is a mailinglist that we are subscribed to, # set Mail-Followup-To header so that duplicates are avoided if settings.get('followup_to'): # to and cc are already cleared of our own address allrecipients = [to] + [cc] lists = settings.get('mailinglists') # check if any recipient address matches a known mailing list if any([addr in lists for n, addr in getaddresses(allrecipients)]): followupto = ', '.join(allrecipients) logging.debug('mail followup to: %s' % followupto) envelope.add('Mail-Followup-To', decode_header(followupto)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose encrypt = mail.get_content_subtype() == 'encrypted' ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn, encrypt=encrypt))
class ComposeCommand(Command): """compose a new email""" def __init__(self, envelope=None, headers={}, template=None, sender='', subject='', to=[], cc=[], bcc=[], attach=None, omit_signature=False, spawn=None, rest=[], encrypt=False, **kwargs): """ :param envelope: use existing envelope :type envelope: :class:`~alot.db.envelope.Envelope` :param headers: forced header values :type headers: dict (str->str) :param template: name of template to parse into the envelope after creation. This should be the name of a file in your template_dir :type template: str :param sender: From-header value :type sender: str :param subject: Subject-header value :type subject: str :param to: To-header value :type to: str :param cc: Cc-header value :type cc: str :param bcc: Bcc-header value :type bcc: str :param attach: Path to files to be attached (globable) :type attach: str :param omit_signature: do not attach/append signature :type omit_signature: bool :param spawn: force spawning of editor in a new terminal :type spawn: bool :param rest: remaining parameters. These can start with 'mailto' in which case it is interpreted as mailto string. Otherwise it will be interpreted as recipients (to) header :type rest: list(str) :param encrypt: if the email should be encrypted :type encrypt: bool """ Command.__init__(self, **kwargs) self.envelope = envelope self.template = template self.headers = headers self.sender = sender self.subject = subject self.to = to self.cc = cc self.bcc = bcc self.attach = attach self.omit_signature = omit_signature self.force_spawn = spawn self.rest = ' '.join(rest) self.encrypt = encrypt @inlineCallbacks def apply(self, ui): if self.envelope is None: if self.rest: if self.rest.startswith('mailto'): self.envelope = mailto_to_envelope(self.rest) else: self.envelope = Envelope() self.envelope.add('To', self.rest) else: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority='error') return # set forced headers for key, value in list(self.headers.items()): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if 'From' not in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: raise CommandCanceled() self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ 'Subject' not in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = [x for x in tagsstring.split(',') if x] if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) # set encryption if needed if self.encrypt or account.encrypt_by_default: yield self._set_encrypt(ui, self.envelope) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd) @inlineCallbacks def _set_encrypt(self, ui, envelope): """Find and set the encryption keys in an envolope. :param ui: the main user interface object :type ui: alot.ui.UI :param envolope: the envolope buffer object :type envolope: alot.buffers.EnvelopeBuffer """ encrypt_keys = [] for recipient in envelope.headers['To'][0].split(','): if not recipient: continue match = re.search("<(.*@.*)>", recipient) if match: recipient = match.group(0) encrypt_keys.append(recipient) logging.debug("encryption keys: " + str(encrypt_keys)) keys = yield get_keys(ui, encrypt_keys, block_error=self.encrypt) if keys: envelope.encrypt_keys.update(keys) envelope.encrypt = True else: envelope.encrypt = False
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.lower().startswith(('re:', rsp.lower())): subject = rsp + subject envelope.add('Subject', subject) # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] my_addresses = settings.get_addresses() sender_address = parseaddr(sender)[1] cc = '' # check if reply is to self sent message if sender_address in my_addresses: recipients = [mail['To']] emsg = 'Replying to own message, set recipients to: %s' \ % recipients logging.debug(emsg) else: recipients = [sender] if self.groupreply: # make sure that our own address is not included # if the message was self-sent, then our address is not included MFT = mail.get_all('Mail-Followup-To', []) followupto = self.clear_my_address(my_addresses, MFT) if followupto and settings.get('honor_followup_to'): logging.debug('honor followup to: %s', followupto) recipients = [followupto] # since Mail-Followup-To was set, ignore the Cc header else: if sender != mail['From']: recipients.append(mail['From']) # append To addresses if not replying to self sent message if sender_address not in my_addresses: cleared = self.clear_my_address(my_addresses, mail.get_all('To', [])) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address(my_addresses, mail.get_all('Cc', [])) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) envelope.add('To', decode_header(to)) # if any of the recipients is a mailinglist that we are subscribed to, # set Mail-Followup-To header so that duplicates are avoided if settings.get('followup_to'): # to and cc are already cleared of our own address allrecipients = [to] + [cc] lists = settings.get('mailinglists') # check if any recipient address matches a known mailing list if any([addr in lists for n, addr in getaddresses(allrecipients)]): followupto = ', '.join(allrecipients) logging.debug('mail followup to: %s' % followupto) envelope.add('Mail-Followup-To', decode_header(followupto)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose ui.apply_command( ComposeCommand(envelope=envelope, spawn=self.force_spawn))