diff options
author | Matěj Cepl <mcepl@cepl.eu> | 2023-05-25 10:15:47 +0200 |
---|---|---|
committer | Matěj Cepl <mcepl@cepl.eu> | 2023-05-25 11:01:18 +0200 |
commit | ec4b49d843e67b31b33ac81bef55346353f1d04c (patch) | |
tree | 5f60ffae4d6ebe180c5ee4d51d468bf154535251 /src/mail2news.py | |
parent | 8006d981ce26fe8c1140e33b9476c08470d59f30 (diff) | |
download | pyg-ec4b49d843e67b31b33ac81bef55346353f1d04c.tar.gz |
refactor: rearrange the project to the src/ layout.0.10.3
Fix also pyproject.toml to generate what seems right.
Add the explicit dependency on nntplib for Python >= 3.12
(gh#python/cpython!104894).
Fixes: https://todo.sr.ht/~mcepl/pygn/7
Diffstat (limited to 'src/mail2news.py')
-rw-r--r-- | src/mail2news.py | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/src/mail2news.py b/src/mail2news.py new file mode 100644 index 0000000..6e8fe53 --- /dev/null +++ b/src/mail2news.py @@ -0,0 +1,195 @@ +"""Mail to news gateway script. Copyright 2000 Cosimo Alfarano + +Author: Cosimo Alfarano +Date: September 16 2000 + +mail2news.py - (C) 2000 by Cosimo Alfarano <Alfarano@Students.CS.UniBo.It> +You can use this software under the terms of the GPL. If we meet some day, +and you think this stuff is worth it, you can buy me a beer in return. + +Thanks to md for this useful formula. Beer is beer. + +Gets news email and sends it via SMTP. + +class mail2news is hopefully conform to rfc850. + +""" +import io +from collections import OrderedDict +import email +import email.policy +import logging +import nntplib +import os +from re import findall +from socket import gethostbyaddr, gethostname +import sys +import tempfile + + +#logging.basicConfig(level=logging.DEBUG) +# This is the single source of Truth +# Yes, it is awkward to have it assymetrically here +# and not in news2mail as well. +__version__ = '0.10.3' +__description__ = 'The Python Gateway Script: news2mail mail2news gateway' + + +class mail2news(object): + """news to mail gateway class""" + + def __init__(self, options): + # newsgroups = None # Newsgroups: local.test,local.moderated... + # approved = None # Approved: kame@aragorn.lorien.org + if 'NNTPHOST' in os.environ: + self.newsserver = os.environ['NNTPHOST'] + else: + self.newsserver = 'localhost' + + self.port = 119 + self.user = None + self.password = None + self.verbose = options.verbose + logging.debug('self.verbose = %s', self.verbose) + + self.hostname = gethostbyaddr(gethostname())[0] + + self.heads_dict, self.smtpheads, self.nntpheads = {}, {}, {} + self.message = self.__readfile(options) + + self.message['X-Gateway'] = 'pyg {0} {1}'.format(VERSION, DESC) + + def __add_header(self, header, value, msg=None): + if msg is None: + msg = self.message + if value: + msg[header] = value.strip() + + def __readfile(self, opt): + message = email.message_from_file(sys.stdin, policy=email.policy.SMTP) + + if (len(message) == 0) \ + and message.get_payload().startswith('/'): + msg_file_name = message.get_payload().strip() + del message + with open(msg_file_name, 'r') as msg_file: + message = email.message_from_file(msg_file, policy=email.policy.SMTP) + + # introduce nntpheads + self.__add_header('Newsgroups', opt.newsgroup, message) + self.__add_header('Approved', opt.approver, message) + + return message + + def renameheads(self): + """rename headers such as Resent-*: to X-Resent-*: + + headers renamed are useless or not rfc 977/850 copliant + handles References/In-Reply-To headers + """ + try: + + for key in list(self.message.keys()): + if key.startswith('Resent-'): + if ('X-' + key) in self.message: + self.message['X-Original-' + key] = \ + self.message['X-' + key] + self.message['X-' + key] = self.message[key] + del self.message[key] + + # In rfc822 References: is considered, but many MUA doen't put it. + if ('References' not in self.message) and \ + ('In-Reply-To' in self.message): + print(self.message['In-Reply-To']) + + # some MUA uses msgid without '<' '>' +# ref = findall('([^\s<>\']+@[^\s<>;:\']+)', \ + # but I prefer use RFC standards + ref = findall('(<[^<>]+@[^<>]+>)', + self.message['In-Reply-To']) + + # if found, keep first element that seems a Msg-ID. + if (ref and len(ref)): + self.message['References'] = '%s\n' % ref[0] + + except KeyError as message: + print(message) + + def removeheads(self, heads=None): + """remove headers like Xref: Path: Lines: + """ + + try: + # removing some others useless headers .... (From is not From:) + + rmheads = ['Received', 'From ', 'NNTP-Posting-Host', + 'X-Trace', 'X-Compliants-To', 'NNTP-Posting-Date'] + if heads: + rmheads.append(heads) + + for head in rmheads: + if head in self.message: + del self.message[head] + + if 'Message-Id' in self.message: + msgid = self.message['Message-Id'] + del self.message['Message-Id'] + self.message['Message-Id'] = msgid + else: + msgid = '<pyg.%d@tuchailepuppapera.org>\n' % (os.getpid()) + self.message['Message-Id'] = msgid + + except KeyError as message: + print(message) + + def sortheads(self): + """make list sorted by heads: From: To: Subject: first, + others, X-*, X-Resent-* last""" + + heads_dict = OrderedDict(self.message) + for hdr in list(self.message.keys()): + del self.message[hdr] + + # put at top + head_set = ('Newsgroups', 'From', 'To', 'X-To', 'Cc', 'Subject', + 'Date', 'Approved', 'References', 'Message-Id') + + logging.debug('heads_dict = %s', heads_dict) + for k in head_set: + if k in heads_dict: + self.__add_header(k, heads_dict[k]) + + for k in heads_dict: + if not k.startswith('X-') and not k.startswith('X-Resent-') \ + and k not in head_set: + self.__add_header(k, heads_dict[k]) + + for k in heads_dict: + if k.startswith('X-'): + self.__add_header(k, heads_dict[k]) + + for k in heads_dict: + if k.startswith('X-Resent-'): + self.__add_header(k, heads_dict[k]) + + def sendemail(self): + "Talk to NNTP server and try to send email." + # readermode must be True, otherwise we don't have POST command. + server = nntplib.NNTP(self.newsserver, self.port, self.user, + self.password, readermode=True) + + logging.debug('self.verbose = %s', self.verbose) + if self.verbose: + server.set_debuglevel(2) + + msg_bytes = self.message.as_bytes() + try: + server.post(io.BytesIO(msg_bytes)) + except UnicodeEncodeError: + with tempfile.NamedTemporaryFile(suffix="eml", prefix="failed_msg", + delete=False) as tmpf: + tmpf.write(msg_bytes) + logging.info(f"failed file name = {tmpf.name}") + logging.exception("Failed to convert message!") + + server.quit() |