From df4adbe93a861bc1a092c066dbb9f855475427f2 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Wed, 24 Nov 2021 08:49:34 +0100 Subject: commands/thread: move determining reply recipients to mail/reply --- alot/commands/thread.py | 96 +++---------------------------------------------- alot/mail/reply.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 92 deletions(-) diff --git a/alot/commands/thread.py b/alot/commands/thread.py index 9b3c56b9..5ec8eac4 100644 --- a/alot/commands/thread.py +++ b/alot/commands/thread.py @@ -7,7 +7,6 @@ import argparse import asyncio import email import email.policy -from email.utils import getaddresses, parseaddr import logging import mailcap import os @@ -42,35 +41,6 @@ from ..utils.mailcap import MailcapHandler MODE = 'thread' -def ensure_unique_address(recipients): - """ - clean up a list of name,address pairs so that - no address appears multiple times. - """ - res = dict() - for name, address in getaddresses(recipients): - res[address] = name - logging.debug(res) - urecipients = [formataddr((n, a)) for a, n in res.items()] - return sorted(urecipients) - -def clear_my_address(my_account, value): - """return recipient header without the addresses in my_account - - :param my_account: my account - :type my_account: :class:`Account` - :param value: a list of recipient or sender strings (with or without - real names as taken from email headers) - :type value: list(str) - :returns: a new, potentially shortend list - :rtype: list(str) - """ - new_value = [] - for name, address in getaddresses(value): - if not my_account.matches_address(address): - new_value.append(formataddr((name, address))) - return new_value - @registerCommand(MODE, 'reply', arguments=[ (['--all'], {'action': 'store_true', 'help': 'reply to all'}), (['--list'], {'action': cargparse.BooleanAction, 'default': None, @@ -105,66 +75,6 @@ class ReplyCommand(Command): return bool(self._force_list_reply) - def _determine_recipients(self, message, account): - # set To - sender = (message.headers.get(HDR.REPLY_TO) or - message.headers.get(HDR.FROM) or '') - sender_address = parseaddr(sender)[1] - cc = [] - - # check if reply is to self sent message - if account.matches_address(sender_address): - recipients = message.headers.get_all(HDR.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 = message.headers.get_all(HDR.MAIL_FOLLOWUP_TO) - followupto = clear_my_address(account, MFT) - if followupto and settings.get('honor_followup_to'): - logging.debug('honor followup to: %s', ', '.join(followupto)) - recipients = followupto - # since Mail-Followup-To was set, ignore the Cc header - else: - if sender != message.headers.get(HDR.FROM): - recipients.append(message.headers.get(HDR.FROM)) - - # append To addresses if not replying to self sent message - if not account.matches_address(sender_address): - cleared = clear_my_address(account, - message.headers.get_all(HDR.TO)) - recipients.extend(cleared) - - # copy cc for group-replies - if HDR.CC in message.headers: - cc = clear_my_address(account, message.headers.get_all(HDR.CC)) - - to = ', '.join(ensure_unique_address(recipients)) - cc = ', '.join(cc) - logging.debug('reply to: %s', to) - - if self._list_reply(message): - # 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 = (message.headers.get(HDR.REPLY_TO) or - message.headers.get(HDR.X_BEEN_THERE) or - message.headers.get(HDR.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 = message.headers[HDR.TO] - logging.debug('mail list reply to: %s', to) - - return to, cc - async def apply(self, ui): message = ui.current_buffer.get_selected_message() @@ -183,7 +93,11 @@ class ReplyCommand(Command): headers[HDR.FROM] = from_header # set the recipients - to, cc = self._determine_recipients(message, account) + mode = reply.ReplyMode.LIST if self._list_reply(message) else \ + reply.ReplyMode.GROUP if self.groupreply else \ + reply.ReplyMode.AUTHOR + + to, cc = reply.determine_recipients(message, account, mode) headers[HDR.TO] = to if cc: headers[HDR.CC] = cc diff --git a/alot/mail/reply.py b/alot/mail/reply.py index 0e7d897e..4a69ecee 100644 --- a/alot/mail/reply.py +++ b/alot/mail/reply.py @@ -2,7 +2,8 @@ # For further details see the COPYING file import email.policy -from email.utils import getaddresses +from email.utils import getaddresses, parseaddr +from enum import Enum, auto import logging from . import headers as HDR @@ -69,6 +70,99 @@ def determine_account(headers, action = ACT_REPLY): from_value = formataddr((realname, str(address))) return from_value, account +class ReplyMode(Enum): + AUTHOR = auto() + GROUP = auto() + LIST = auto() + +def _ensure_unique_address(recipients): + """ + clean up a list of name,address pairs so that + no address appears multiple times. + """ + res = dict() + for name, address in getaddresses(recipients): + res[address] = name + logging.debug(res) + urecipients = [formataddr((n, a)) for a, n in res.items()] + return sorted(urecipients) + +def _clear_my_address(my_account, value): + """return recipient header without the addresses in my_account + + :param my_account: my account + :type my_account: :class:`Account` + :param value: a list of recipient or sender strings (with or without + real names as taken from email headers) + :type value: list(str) + :returns: a new, potentially shortend list + :rtype: list(str) + """ + new_value = [] + for name, address in getaddresses(value): + if not my_account.matches_address(address): + new_value.append(formataddr((name, address))) + return new_value + +def determine_recipients(message, account, mode): + # set To + sender = (message.headers.get(HDR.REPLY_TO) or + message.headers.get(HDR.FROM) or '') + sender_address = parseaddr(sender)[1] + cc = [] + + # check if reply is to self sent message + if account.matches_address(sender_address): + recipients = message.headers.get_all(HDR.TO) + emsg = 'Replying to own message, set recipients to: %s' \ + % recipients + logging.debug(emsg) + else: + recipients = [sender] + + if mode == ReplyMode.GROUP: + # make sure that our own address is not included + # if the message was self-sent, then our address is not included + MFT = message.headers.get_all(HDR.MAIL_FOLLOWUP_TO) + followupto = _clear_my_address(account, MFT) + if followupto and settings.get('honor_followup_to'): + logging.debug('honor followup to: %s', ', '.join(followupto)) + recipients = followupto + # since Mail-Followup-To was set, ignore the Cc header + else: + if sender != message.headers.get(HDR.FROM): + recipients.append(message.headers.get(HDR.FROM)) + + # append To addresses if not replying to self sent message + if not account.matches_address(sender_address): + cleared = _clear_my_address(account, + message.headers.get_all(HDR.TO)) + recipients.extend(cleared) + + # copy cc for group-replies + if HDR.CC in message.headers: + cc = _clear_my_address(account, message.headers.get_all(HDR.CC)) + + to = ', '.join(_ensure_unique_address(recipients)) + cc = ', '.join(cc) + logging.debug('reply to: %s', to) + + if mode == ReplyMode.LIST: + # 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 = (message.headers.get(HDR.REPLY_TO) or + message.headers.get(HDR.X_BEEN_THERE) or + message.headers.get(HDR.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 = message.headers[HDR.TO] + logging.debug('mail list reply to: %s', to) + + return to, cc def subject(message): subject = message.headers.get(HDR.SUBJECT) or '' -- cgit v1.2.3