announce.py 4.99 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Relays news entries to the announce mailing list.
# To be run with minimal privileges shortly after every full hour.

# Initial version, 10/2010:
#     B. Hof <hof@stusta.net>
# Made a little bit less unbelievably insane, 04/2012:
#     B. Braun <benjamin.braun@stusta.net>,
#     T. Klenze <tobias.klenze@stusta.net>
# Fixed stuff, 06/2016:
#     J. Schmidt <js@stusta.net>
# Fixed AM/PM handling, 02/2017
#     C. Winter <christian.winter@stusta.net>
# Complete rewrite in python3 using the API, 04/2018
#     J. Schmidt <js@stusta.net>

Jan Fecht's avatar
Jan Fecht committed
18
import re
Julien Schmidt's avatar
Julien Schmidt committed
19
import smtplib
20
import sys
Julien Schmidt's avatar
Julien Schmidt committed
21
import urllib.parse
Jan Fecht's avatar
Jan Fecht committed
22
from datetime import datetime, timedelta
Michael Loipführer's avatar
Michael Loipführer committed
23
from email.utils import make_msgid, formatdate, formataddr
Jan Fecht's avatar
Jan Fecht committed
24 25
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
26 27

import mwclient
Jan Fecht's avatar
Jan Fecht committed
28
import pytz
29
from bs4 import BeautifulSoup
Jan Fecht's avatar
Jan Fecht committed
30
from ics import Event, Calendar
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47


def format_news(entry, page):
    content = page.text()
    content = content.replace("{{!}}", "")
    content = content.split("\n}}\n")

    text = ""
    body = ""
    for item in content:
        if item.startswith("{{StuStaNet-News"):
            continue
        if item.startswith("{{Termin"):
            start = ""
            end = ""
            location = ""
            lines = item.splitlines()
Julien Schmidt's avatar
Julien Schmidt committed
48 49 50 51 52 53 54
            for line in lines:
                if line.startswith("|von="):
                    start = line[5:]
                elif line.startswith("|bis="):
                    end = line[5:]
                elif line.startswith("|Ort="):
                    location = line[5:]
55 56 57 58 59 60 61
            if start != "":
                body += "Datum: " + start
                if end != "":
                    body += " bis " + end
                body += "\n"
            if location != "":
                body += "Ort: " + location + "\n"
Jan Fecht's avatar
Jan Fecht committed
62

63
        else:
Julien Schmidt's avatar
Julien Schmidt committed
64
            text += item + "\n"
65

Jan Fecht's avatar
Jan Fecht committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
    calendar = None
    try:
        tz = pytz.timezone('Europe/Berlin')
        starttime = tz.localize(datetime.strptime(start, "%Y/%m/%d %H:%M:%S"))
        event = Event()
        event.name = entry['Titel']
        event.begin = starttime
        try:
            endtime = tz.localize(datetime.strptime(end, "%Y/%m/%d %H:%M:%S"))
        except ValueError:
            # default to 2 hours duration
            endtime = starttime + timedelta(hours=2)
        event.end = endtime
        if location != "":
            event.location = location
Jan Fecht's avatar
Jan Fecht committed
81
        tmp = page.text()
Jan Fecht's avatar
Jan Fecht committed
82
        ms = [re.search(r"\|Zusammenfassung=(.*)", line)
Jan Fecht's avatar
Jan Fecht committed
83
              for line in tmp.split('\n')]
Jan Fecht's avatar
Jan Fecht committed
84 85 86 87 88 89 90 91 92 93
        ms = [m for m in ms if m is not None]
        if len(ms) > 0:
            event.description = ms[0].groups()[0]

        calendar = Calendar()
        calendar.events.add(event)
    except ValueError:
        print(f"Value error: couldn't parse date {start}")
        calendar = None

94 95 96 97
    body += "Zusammenfassung:\n"
    body += entry['Zusammenfassung']
    body += "\n\n\n"

Julien Schmidt's avatar
Julien Schmidt committed
98 99
    text = ''.join(BeautifulSoup(
        text, "html.parser").findAll(text=True)).strip()
100 101 102
    body += text

    body += "\n\n\n"
Julien Schmidt's avatar
Julien Schmidt committed
103 104
    body += "Quelle: https://wiki.stusta.de/" + \
        urllib.parse.quote(entry['Page'].replace(" ", "_"))
105
    body += "\n\n-- \nMehr Informationen: https://info.stusta.de\n"
Jan Fecht's avatar
Jan Fecht committed
106 107 108 109 110 111 112 113 114
    return (body, calendar)


def attach_calendar(msg, calendar):
    ical_atch = MIMEText('text', 'calendar', 'utf-8')
    ical_atch.set_payload(str(calendar).encode("utf-8"), 'utf-8')
    ical_atch.add_header('Content-Disposition',
                         'attachment; filename="invite.ics"')
    msg.attach(ical_atch)
115 116 117


# send mail to announce list
Jan Fecht's avatar
Jan Fecht committed
118
def send_mail(subject, author, body, calendar=None):
Michael Loipführer's avatar
Michael Loipführer committed
119 120 121
    from_addr = "no-reply@stusta.mhn.de"
    from_domain = from_addr.split('@')[1]
    to_addr = "announce@lists.stusta.mhn.de"
Michael Loipführer's avatar
Michael Loipführer committed
122

Michael Loipführer's avatar
Michael Loipführer committed
123 124 125
    msg = MIMEMultipart()
    msg['date'] = formatdate(localtime=True)

Julien Schmidt's avatar
Julien Schmidt committed
126
    msg['subject'] = subject
Michael Loipführer's avatar
Michael Loipführer committed
127
    msg['from'] = formataddr((author, from_addr))
Jan Fecht's avatar
Jan Fecht committed
128
    msg['to'] = to_addr
Julien Schmidt's avatar
Julien Schmidt committed
129
    msg['reply-to'] = "StuStaNet e. V. Admins <admins@lists.stusta.de>"
Michael Loipführer's avatar
Michael Loipführer committed
130
    msg['Message-ID'] = make_msgid(domain=from_domain)
Michael Loipführer's avatar
Michael Loipführer committed
131

Michael Loipführer's avatar
Michael Loipführer committed
132
    msg.attach(MIMEText(body, 'plain'))
133

Jan Fecht's avatar
Jan Fecht committed
134 135 136
    if calendar:
        attach_calendar(msg, calendar)

Julien Schmidt's avatar
Julien Schmidt committed
137
    smtp = smtplib.SMTP('mail.stusta.de')
Jan Fecht's avatar
Jan Fecht committed
138
    smtp.sendmail(from_addr, to_addr, msg.as_string())
Julien Schmidt's avatar
Julien Schmidt committed
139 140 141 142
    smtp.quit()


def main():
143 144 145
    site = mwclient.Site('wiki.stusta.de', path='/')

    results = site.get('cargoquery',
Julien Schmidt's avatar
Julien Schmidt committed
146 147
                       tables='News',
                       fields='_pageName=Page,Titel,Autor,Zusammenfassung,Datum',
148
                       where='Infoseite=1 AND Kategorie="StuStaNet" AND TIMESTAMPDIFF(HOUR,NOW(),Datum)=0',
Julien Schmidt's avatar
Julien Schmidt committed
149 150 151
                       order_by='Datum ASC',
                       format='json',
                       )
152 153 154 155 156 157 158

    for res in results['cargoquery']:
        entry = res['title']
        author = entry['Autor']
        if author == "":
            author = "Infoseite"
        subject = entry['Titel']
Jan Fecht's avatar
Jan Fecht committed
159
        send_mail(subject, author, *format_news(
Julien Schmidt's avatar
Julien Schmidt committed
160 161
            entry, site.pages[entry['Page']]))

162 163 164

if __name__ == '__main__':
    sys.exit(main())