]> git.parisson.com Git - teleforma.git/commitdiff
Initial shot of POP email answer
authorGael Le Mignot <gael@pilotsystems.net>
Thu, 20 Jun 2024 13:20:45 +0000 (15:20 +0200)
committerGael Le Mignot <gael@pilotsystems.net>
Thu, 20 Jun 2024 13:20:45 +0000 (15:20 +0200)
teleforma/management/commands/teleforma-get-postman-replies.py [new file with mode: 0644]
teleforma/templates/postman/email_user_subject.txt
teleforma/templatetags/teleforma_tags.py
teleforma/utils.py

diff --git a/teleforma/management/commands/teleforma-get-postman-replies.py b/teleforma/management/commands/teleforma-get-postman-replies.py
new file mode 100644 (file)
index 0000000..f858827
--- /dev/null
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from postman.models import *
+import re
+import logging
+import datetime
+import time
+from django.contrib.sites.models import Site
+from django.core.exceptions import ObjectDoesNotExist
+import email
+
+from teleforma import utils
+
+import poplib
+
+def decode_header(header, charset):
+    """
+    Decode a header field
+    """        
+    header = email.header.decode_header(header)
+    res = []
+    for value, encoding in header:
+        res.append(value)
+    return " ".join(res)
+
+def smart_walk(msg, in_alternative = False):
+    """
+    Like the walk method, but ignores html in multipart/alternative
+    """
+    ctype = msg.get_content_type()
+    if msg.is_multipart():
+        if ctype == "multipart/alternative":
+            in_alternative = True
+        yield msg
+        subparts = msg.get_payload()
+        for subpart in subparts:
+            for subsubpart in smart_walk(subpart, in_alternative):
+                yield subsubpart
+    elif not in_alternative or not "text/html" in ctype:
+        yield msg    
+
+class Logger:
+    """A logging object"""
+
+    def __init__(self, file):
+        self.logger = logging.getLogger('teleforma')
+        self.hdlr = logging.FileHandler(file)
+        self.formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+        self.hdlr.setFormatter(self.formatter)
+        self.logger.addHandler(self.hdlr)
+        self.logger.setLevel(logging.INFO)
+
+
+class Command(BaseCommand):
+    help = "Fetch mails from a POP account to feed postman"
+    language_code = 'fr_FR'
+
+    def add_arguments(self, parser):
+        parser.add_argument('log_file')
+
+    def process(self, message):
+        mail = email.message_from_bytes(message)
+        title = mail.get("subject", "").strip()
+        title = decode_header(title, 'utf-8')
+        
+        parts = title.split('[')
+        if len(parts) == 1:
+            self.logger.warning("Invalid title (no start delimiter): {%s}" % title)
+            return
+        subject = '['.join(parts[:-1])
+        part = parts[-1]
+        if not part.endswith("]"):
+            self.logger.warning("Invalid title (no start delimiter): {%s}" % title)
+            return
+        msghash = part[:-1]
+        msgid = msghash[:-4]
+        msgid = int(msgid, 16)
+        if utils.generate_hash(msgid) != msghash:
+            self.logger.warning("Invalid hash {%s} for {%s}" % (msghash, msgid))
+            return
+        msg = Message.objects.get(pk = msgid)
+
+        self.logger.info("Found reply to message %s" % msgid)
+        
+        body = ""
+        for part in smart_walk(mail):
+            payload = part.get_payload(decode = True)
+            if not isinstance(payload, bytes):
+                # ignore multipart here
+                continue
+            filename = part.get_filename()
+            content_type = part.get_content_type()
+            if not filename and content_type.startswith("text/"):
+                body += payload.decode(part.get_content_charset() or "ascii")
+               
+        # Purge quotes and signatures from body
+        lines = []
+        for line in body.split("\n"):
+            stripped = line.strip()
+            if stripped.startswith(">"):
+                continue
+            if stripped.startswith("----"):
+                break
+            if stripped.startswith("--") and not line[2:].strip():
+                break
+            lines.append(stripped)
+
+        while lines and not lines[0]:
+            del lines[0]
+        while lines and not lines[-1]:
+            del lines[-1]
+        body = '\n'.join(lines)
+
+        mess = Message(sender=msg.recipient, recipient=msg.sender,
+                       subject=subject, body=body)
+        mess.moderation_status = 'a'
+        mess.save()
+        site = Site.objects.all()[0]
+        notify_user(mess, 'acceptance', site)
+        
+    def handle(self, *args, **options):
+        log_file = options['log_file']
+        logger = Logger(log_file)
+        logger.logger.info('########### Processing #############')
+        self.logger = logger.logger
+        
+        pop = poplib.POP3_SSL(settings.REPLY_POP_SERVER)
+        pop.user(settings.REPLY_POP_LOGIN)
+        pop.pass_(settings.REPLY_POP_PASSWORD)
+        _, messages, _ = pop.list()
+        self.logger.info("%d messages available" % len(messages))
+        try:
+            for msg in messages:
+                msgid, _ = msg.split()
+                msgid = msgid.decode('ascii')
+                _, msg, _ = pop.retr(msgid)
+                msg = b"\r\n".join(msg)
+                try:
+                    self.process(msg)
+                    pop.dele(msgid)
+                except Exception as e:
+                    self.logger.warning("Error in message %s: %s" % (msgid, str(e)))
+                    raise
+        finally:
+            pop.quit()
+
+        self.logger.info('############## Done #################')
index cace60c0bcfb30f65bf18bdcc0b9216a1c3b2b33..6c72d09c3d9eab2304265c6aac5a3f9f5f03d10b 100644 (file)
@@ -1 +1 @@
-{% load i18n %}{% autoescape off %}{% blocktrans with object.subject as subject and site.name as sitename %}Message "{{ subject }}" on the site {{ sitename }}{% endblocktrans %}{% endautoescape %}
\ No newline at end of file
+{% load teleforma_tags %}{% load i18n %}{% autoescape off %}{% blocktrans with object.subject as subject and site.name as sitename %}Message "{{ subject }}" on the site {{ sitename }}{% endblocktrans %} [{% generate_msg_id object %}]{% endautoescape %}
index 73930a2f0682f1261ecd6d7a599905e97aa8499b..f34f1d647d1eabbcc6e62a53ee5ba4d06b068b59 100644 (file)
@@ -55,6 +55,7 @@ from ..exam.models import Quota, Script
 from ..models.core import Document, Professor
 from ..models.crfpa import IEJ, Course, NewsItem, Training
 from ..views import get_courses
+from ..utils import generate_hash
 
 from collections import defaultdict
 
@@ -495,6 +496,11 @@ def conference_publication(context, conference):
     period = context['period']
     return conference.publication_info(period)
 
+@register.simple_tag(takes_context=True)
+def generate_msg_id(context, message):
+    mid = message.id
+    return generate_hash(mid)
+
 
 # @register.simple_tag(takes_context=True)
 # def course_media(context):
index 6a21d79e630d12d80a89373f2623f65d490decde..c26835d8f3152eaefdface0a3d26bcefeddf6351 100644 (file)
@@ -1,5 +1,16 @@
 import mimetypes
+import hashlib
+from django.conf import settings
 
 def guess_mimetypes(url):
     url = url.split("?")[0]
-    return mimetypes.guess_type(url)[0]
\ No newline at end of file
+    return mimetypes.guess_type(url)[0]
+
+def generate_hash(mid):
+    """
+    Generate a hash for messages
+    """
+    mid = "%x" % mid
+    msg = '%s-%s' % (settings.SECRET_KEY, mid)
+    md5 = hashlib.md5(msg.encode('ascii')).hexdigest()
+    return '%s%s' % (mid, md5[:4])