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/mail/reply.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) (limited to 'alot/mail/reply.py') 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