| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 | 
							
- #!/usr/bin/env python3
 
- # -*- coding: utf-8 -*-
 
- """
 
- efbeff 2024-04-16
 
- Dieses script läuft z.Zt. produktiv auf Duesseldorf blech02
 
- Freifunk Dortmund Terminerinnerungen aus dem Kalender erzeugen
 
- und über die Mailingliste(n) verschicken.
 
- Die Erinnerung für das Monatstreffen wird 5 Tage vorher verschickt,
 
- die restlichen (FF@HOME, ... 2 Tage vorher )
 
- Unterschieden wird es durch den ical tag SUMMARY.
 
- Aus der WEBseite lassen sich die Termine leider nur monatsweise lesen,
 
- deshalb werden 2 Monatskalender gelesen, falls das Prüfintervall über eine
 
- Monatsgrenze geht.
 
- Das alte script von FFdo als Basis erheblich überarbeitet
 
- Dies ist die zweite Überarbeitung mit erstellen von *.ics Anhängen
 
- Dies ist die dritte Überarbeitung mit Entfernen HTML Kommentar
 
- Es darf nur 1mal am Tag laufen, sonst gibt es mehrfache Erinnerungen
 
- Der Absender reminder@freifunk-dortmund.de ist keine echte mailadresse,
 
- sondern nur als Nichtmitglied aber sendeberechtigt zugelassen in der
 
-     DEFAULT_LISTE, VEREIN_LISTE und INFRA_LISTE
 
- Dafür in der Listenadministration jeweils unter
 
-   Abo-regeln und Adressfilter
 
-      Absender-filter
 
-         Anti-Spam-filter
 
-            reminder@freifunk-dortmund.de
 
- eintragen.
 
- Mails an reminder@freifunk-dortmund.de werden abgewiesen.
 
- """
 
- # testmode 0 setzen für Produktion
 
- # testmode 1 nur aufbereiten und in log schreiben
 
- # testmode 2 über smarthost an einzelnen Empfänger schicken
 
- #           setzt eventuell username PW und port voraus
 
- #           ACHTUNG PW im Klartext
 
- testmode = 0
 
- # bei testmode 1 wird keine mail versandt, die Erinnerungen werden
 
- # nur aufbereitet und ins log geschrieben
 
- import logging
 
- import logging.handlers
 
- import sys
 
- import traceback
 
- import os
 
- import shutil
 
- import smtplib
 
- import requests
 
- from email.message import EmailMessage
 
- from datetime import datetime, timedelta, date
 
- ########from icalendar import Calendar, Event
 
- import re
 
- ABSFILE=os.path.abspath(__file__)
 
- CALURL_TEMPLATE='http://www.freifunk-dortmund.de/termine/{}-{:02d}/?ical=1'
 
- # empfängerlisten
 
- DEFAULT_LISTE="freifunk-do@list.free.de"
 
- INFRA_LISTE="freifunk-do-infra@list.free.de"
 
- VEREIN_LISTE="freifunk-do-verein@list.free.de"
 
- # Absender
 
- MAIL_FROM="FF-DO Termine <reminder@freifunk-dortmund.de>" if testmode < 2 else\
 
- "fbeythien@gmx.de"
 
- MAIL_SUBJECT="Monatstreffen"  # wird angepasst
 
- if testmode < 2:
 
-     MAIL_HOST="list.free.de"
 
-     MAIL_STARTTLS=False
 
-     MAIL_USER=""
 
-     MAIL_PASSWORD=""
 
-     MAIL_PORT="25"
 
- else:
 
-     MAIL_HOST="mail.gmx.net"
 
-     MAIL_STARTTLS=True
 
-     MAIL_USER="xxxx"
 
-     MAIL_PASSWORD="xxxx"
 
-     MAIL_PORT="587"
 
-     DEFAULT_LISTE="efbe@prima.de"
 
-     INFRA_LISTE=DEFAULT_LISTE
 
-     VEREIN_LISTE=DEFAULT_LISTE
 
- # Tagesordnungspunkte Monatstreffen
 
- TOP_LINK="https://wiki.ffdo.de/Community/TOPs"
 
- TOP_LINK_PAGE= TOP_LINK +".page"
 
- WOTAGE = ("Montag","Dienstag","Mittwoch","Donnerstag","Freitag",
 
-         "Samstag","Sonntag")
 
- REM_TREFFEN_MONAT = 4   # Tage vorher erinnern Monatstreffen
 
- REM_TREFFEN_TOPS  = REM_TREFFEN_MONAT + 2 # Tage vorher erinnern TOP ergänzen
 
- REM_STANDARD = 2        # Tage vorher erinnern FF@home, ...
 
- REM_INTERVALL = 10      # heute + x Tage Termine prüfen
 
- # logging init
 
- logging.basicConfig(level=logging.DEBUG)
 
- mylog = logging.getLogger("remind")
 
- sysl = logging.handlers.SysLogHandler(address='/dev/log')
 
- #formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
- formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
 
- sysl.setFormatter(formatter)
 
- mylog.addHandler(sysl)
 
- def print_exc_plus():
 
-     """
 
-     Print the usual traceback information, followed by a listing of all the
 
-     local variables in each frame.
 
-     """
 
-     tb = sys.exc_info()[2]
 
-     while 1:
 
-         if not tb.tb_next:
 
-             break
 
-         tb = tb.tb_next
 
-     stack = []
 
-     f = tb.tb_frame
 
-     while f:
 
-         stack.append(f)
 
-         f = f.f_back
 
-     stack.reverse()
 
-     traceback.print_exc()
 
-     print("Locals by frame, innermost last")
 
-     for frame in stack:
 
-         print()
 
-         print("Frame %s in %s at line %s" % (frame.f_code.co_name,
 
-                                              frame.f_code.co_filename,
 
-                                              frame.f_lineno))
 
-         for key, value in frame.f_locals.items():
 
-             print("\t%20s =" % key,end=" ")
 
-             #We have to be careful not to cause a new error in our error
 
-             #printer! Calling str() on an unknown object could cause an
 
-             #error we don't want.
 
-             try:
 
-                 print(value)
 
-             except:
 
-                 print( "<ERROR WHILE PRINTING VALUE>")
 
- #falls remind nicht erfolgreich beim cron.daily Lauf (z.B. temporäre Netzproblem)
 
- #dann versuche es in cron.hourly bis es klappt
 
- def falls_daily_versuche_hourly(progfile):
 
-     (pfad, datei) = os.path.split(progfile)
 
-     if pfad[-6:] == ".daily":
 
-         hpfad = pfad[:-5] + "hourly/"
 
-         shutil.copy2(progfile, hpfad, follow_symlinks=False)
 
-         mylog.info("remind nach cron.hourly kopiert.")
 
- # evtl remind aus hourly wieder entfernen nach erfolgreichem Lauf
 
- def falls_hourly_loe(progfile):
 
-     if "hourly" in progfile:
 
-         os.remove(progfile)
 
-         mylog.info("remind in cron.hourly gelöscht")
 
- #   alle gefundenen Erinnerungen versenden wenn kein testmode gesetzt
 
- def send(msgs, sender, server, username=None, password=None):
 
-     if testmode == 0:
 
-         s = smtplib.SMTP(server, port=MAIL_PORT)
 
- #       s.set_debuglevel(2)
 
-         for m in msgs:
 
-             s.send_message(m)
 
-             mylog.info(str(m))
 
- #           print('\n', str(m), '\n')
 
-         s.quit()
 
-     elif testmode == 1:
 
-         for m in msgs:
 
-             mylog.info(str(m))
 
-     #        print('\n', str(m), '\n')
 
-     elif  testmode == 2:
 
-         with smtplib.SMTP(MAIL_HOST, port=MAIL_PORT) as s:
 
-             s.set_debuglevel(2)
 
-             s.starttls()
 
-             s.login(MAIL_USER, MAIL_PASSWORD, initial_response_ok=True)
 
-             for m in msgs:
 
-                 s.send_message(m)
 
-                 mylog.info(str(m))
 
- #               print('\n', str(m), '\n')
 
-             s.close()
 
-     else:
 
-         mylog.info(" testmode nicht 0 - 2 ungültig.")
 
- # Erinnerung für allgemeinen Termin aufbereiten
 
- def gen_mail_allg(sender, rcpt, subject, description, vorspann, event, dtstart):
 
-     body = "DESCRIPTION " + description + " " + "\n\nDein freundlicher FFDO Autoreminder\n.\n"
 
-     msg = EmailMessage()
 
-     msg["From"]=sender
 
-     msg["To"]=rcpt
 
-     msg["Subject"]="Reminder " + subject
 
-     msg.set_content(body)
 
-     att = vorspann + event + '\r\nEND:VCALENDAR'
 
-     evdat = dtstart.date()
 
-     msg.add_attachment(att, subtype="ics", filename=str(evdat)+".ics")
 
-     return msg
 
- # die mmarkdown Markierungen teilweise umsetzen
 
- #  --- bis ... markdown vorspann löschen
 
- # führende Leerstellen erhalten
 
- # dies ist rudimentär, kein markdown interpreter!!
 
- def zeilen_aufbereiten(tops):
 
- # html kommentar entfernen
 
- # beispiel für spamschutz entfernen
 
- # email = "tony@tiremove_thisger.net"
 
- # m = re.search("remove_this", email)
 
- # email[:m.start()] + email[m.end():]
 
- # Ergebnis: 'tony@tiger.net'
 
-     koma = re.search("<!--", tops)
 
-     kome = re.search("-->", tops)
 
-     topn = tops
 
-     if koma != None and kome != None:
 
-         if koma.end() < kome.start():
 
-             topn = tops[:koma.start()] + tops[kome.end():]
 
-     topsp = topn.partition("...")
 
-     if topsp[2]:
 
-         lines = topsp[2].split("\n")
 
-     else:
 
-         lines = topsp[0].split("\n")
 
-     body = ""
 
-     for l in lines:
 
- #        print(l)
 
-         if len(l.strip()) > 0:
 
-             if l.strip()[0] == "*":
 
-                 prefix = l.partition("*")[0]
 
-                 body += prefix + " - " + l.strip(' *\n') + "\n"
 
-             elif l.strip()[0] == "#":
 
-                 continue
 
-             elif l.strip()[0] == "-":
 
-                 prefix = l.partition("-")[0]
 
-                 body += prefix + "- " + l.strip(' -\n') + "\n"
 
-             else:
 
-                 body += l.rstrip(" \n") + "\n"
 
-     return body
 
- # Erinnerung für TOPs einstellen zum Monatstreffen aufbereiten
 
- def gen_mail_top(sender, rcpt, subject, tops, vorspann, event, dtstart):
 
-     body = "Hallo zusammen!\n\nIn zwei Tagen wird die Erinnerung für das nächste Monatstreffen verschickt. Bisher sind folgende TOPs eingetragen:\n\n"
 
-     body += zeilen_aufbereiten(tops)
 
-     body += "\nFalls du noch ein weiteres Thema besprechen möchtest, trag es bitte hier ein: " + TOP_LINK + "\n\nDein freundlicher FFDO Auto-Reminder\n.\n"
 
-     msg = EmailMessage()
 
-     msg["From"]=sender
 
-     msg["To"]=rcpt
 
-     msg.set_content(body)
 
-     att = vorspann + event + '\r\nEND:VCALENDAR'
 
-     evdat = dtstart.date()
 
-     msg.add_attachment(att, subtype="ics", filename=str(evdat)+".ics")
 
-     msg["Subject"]="Reminder TOPs für " + subject
 
-     return msg
 
- # Erinnerung für Monatstreffen aufbereiten
 
- def gen_mail_monat(sender, rcpt, subject, tops,  vorspann, event, dtstart):
 
-     wotag = WOTAGE[dtstart.weekday()]
 
-     stzeit = str(dtstart.time())[:5]
 
-     body = "Hallo zusammen! \n\nAm kommenden " + wotag + " um " + stzeit + " Uhr steht das nächste Monatstreffen auf dem Plan.  Bisher sind folgende TOPs eingetragen: \n\n"
 
-     body += zeilen_aufbereiten(tops)
 
-     body += "\nFalls du noch ein weiteres Thema besprechen möchtest, trag es bitte hier ein: " + TOP_LINK + " \n\nDein freundlicher Auto-Reminder\n.\n"
 
-     msg = EmailMessage()
 
-     msg["From"]=sender
 
-     msg["To"]=rcpt
 
-     msg.set_content(body)
 
-     att = vorspann + event + '\r\nEND:VCALENDAR'
 
-     evdat=dtstart.date()
 
-     msg.add_attachment(att, subtype="ics", filename=str(evdat)+".ics")
 
-     msg["Subject"]="Reminder " + subject
 
-     return msg
 
- # key in string suchen ende mit crlf
 
- def s_in_event(string,such):
 
-     ergebnis = ''
 
-     inda=string.find(such)
 
-     if inda > -1:
 
-         inda += len(such)
 
-         inde=string[inda:].find('\r\n')
 
-         ergebnis=string[inda:inda + inde]
 
-     return ergebnis
 
- def main():
 
-     start = date.today()
 
-     # Intervall für prüfungen festlegen
 
-     end = start + timedelta(days=REM_INTERVALL)
 
-     if testmode:        # bei test auch 2 vorige Tage prüfen
 
-         start -= timedelta(2)
 
-     mylog.info("Start remind testmode: " + str(testmode))
 
-     remstandard = start + timedelta(days=REM_STANDARD)
 
-     remmontreffen = start + timedelta(days=REM_TREFFEN_MONAT)
 
-     remmontops = start + timedelta(days=REM_TREFFEN_TOPS)
 
- #  jetzt die Kalenderdaten einsammeln und zerlegen
 
-     kals = []
 
-     msgs = []
 
-     msguids = []
 
-     tops = None
 
-     icss = []
 
-     calurls = []
 
- #              termin URL für lfd Monat erzeugen
 
-     calurls.append(CALURL_TEMPLATE.format(start.year,start.month))
 
-     if start.month != end.month:
 
- #              termin URL für nächsten Monat erzeugen
 
-         calurls.append(CALURL_TEMPLATE.format(end.year,end.month))
 
- # Termindaten holen (1 oder 2 Monate) und in Kalenderformat bringen
 
- # und auch als ics daten behalten
 
-     for calurl in calurls:
 
-         try:
 
-             ics = requests.get(calurl)
 
- #   gibt es Termine für den lfd / nächsten Monat
 
-             if ics:
 
-                 icss.append(ics.text)
 
-         except ConnectionError as ce:
 
-             mylog.error("Fehler bei Holen Kalender: " + str(ce))
 
-             continue
 
- #    Events isolieren und auswerten
 
-         veanf=0
 
-         voreins=True
 
-         icswk=ics.text
 
-         while veanf > -1:
 
-             veanf=icswk.find('BEGIN:VEVENT')
 
-             if veanf == -1: break       # keine events mehr Ende while
 
-             if voreins:                 # Kalendervorspann  retten
 
-                 voreins=False
 
-                 vorspann=icswk[:veanf]
 
-                 if testmode > 1:
 
-                     print(len(vorspann))
 
-                     print(vorspann)
 
-             veend=icswk.find('END:VEVENT')
 
-             vevent=icswk[veanf:veend+len('END:VEVENT')]
 
-             #icswk setzen damit ein continue später klappt
 
-             icswk=icswk[veend+len('END:VEVENT'):]
 
-             vstdat=vevent.find('DTSTART')
 
-             vstdat1=vevent[vstdat:].find(':')
 
-             vdatum=vevent[vstdat+vstdat1+1:vstdat+vstdat1+16]
 
-             dtstart=datetime.strptime(vdatum, '%Y%m%dT%H%M%S')
 
-             if testmode > 1:
 
-                 print(len(vevent))
 
-                 print(vevent)
 
-                 print(vdatum)
 
-                 print(dtstart)
 
-             if dtstart.date() >= start and ( dtstart.date() <= end or testmode):
 
-                 if testmode > 1:
 
-                     print('\n\n selektiert, im Intervall\n\n')
 
-                 subject = s_in_event(vevent,'SUMMARY:')
 
-                 subject += ' ' +str(dtstart)
 
-                 description = s_in_event(vevent,'DESCRIPTION:')
 
-                 for such in ('\\n', '\\,'):
 
-                     ix = description.find(such)
 
-                     while ix > 0:
 
-                         description = description[:ix] + description[ix+2]
 
-                         ix = description.find(such)
 
-                 location = s_in_event(vevent,'LOCATTION:')
 
-                 url = s_in_event(vevent,'URL:')
 
-                 description += '\n' + location + '\n' + url
 
-                 uid = s_in_event(vevent,'UID:')
 
-                 if uid in msguids:
 
-                     if testmode:
 
-                         print("doppelt ", uid)
 
-                     continue
 
- # an FF-DO liste per default
 
-                 liste = DEFAULT_LISTE
 
- #         subject/SUMMARY für Ermittlung der Vorwarnzeit prüfen
 
-                 if "Freifunktreffen" in subject or\
 
-                    "Monatstreffen" in subject or\
 
-                    "Orga-Treff" in subject:
 
-                     if testmode or dtstart.date() == remmontops:
 
- #          Aufforderung  TOPs einzustellen
 
-                         try:
 
-                             tops = requests.get(TOP_LINK_PAGE)
 
-                         except ConnectionError as ce:
 
-                             mylog.error("Fehler holen TOPs mit requests.get(): " + str(ce))
 
-                             continue
 
-                         m = gen_mail_top(MAIL_FROM, liste, subject, tops.text,
 
-                                 vorspann, vevent, dtstart)
 
-                         msgs.append(m)
 
-                         msguids.append(uid)
 
-                     elif testmode or dtstart.date() == remmontreffen:
 
- #          Tagesordnungspunkte für nächstes Monatstreffen
 
-                         try:
 
-                             tops = requests.get(TOP_LINK_PAGE)
 
-                         except ConnectionError as ce:
 
-                             mylog.error("Fehler holen TOPs mit requests.get(): " + str(ce))
 
-                             continue
 
-                         m = gen_mail_monat(MAIL_FROM, liste, subject, tops.text,
 
-                                 vorspann, vevent, dtstart)
 
-                         msgs.append(m)
 
-                         msguids.append(uid)
 
-                 elif "FF@home" in subject:
 
-                     if testmode or dtstart.date() == remstandard:
 
-                         liste = INFRA_LISTE
 
-                         m = gen_mail_allg(MAIL_FROM, liste, subject,
 
-                                 description, vorspann, vevent, dtstart)
 
-                         msgs.append(m)
 
-                         msguids.append(uid)
 
-                 elif "Öffentlichkeitsarbeit" in subject:
 
-                     if testmode or dtstart.date() == remstandard:
 
-                         liste = VEREIN_LISTE
 
-                         m = gen_mail_allg(MAIL_FROM, liste, subject,
 
-                                 description, vorspann, vevent, dtstart)
 
-                         msgs.append(m)
 
-                         msguids.append(uid)
 
-                 else:
 
-                     if testmode or dtstart.date() == remstandard:
 
-                         m = gen_mail_allg(MAIL_FROM, liste, subject,
 
-                                 description, vorspann, vevent, dtstart)
 
-                         msgs.append(m)
 
-                         msguids.append(uid)
 
- ### willi=willi ### für exception test
 
- #       Erinnerungen verschicken wenn kein testmode oder testmode 2
 
-     if len(msgs):
 
-         send(msgs, MAIL_FROM, MAIL_HOST, MAIL_USER, MAIL_PASSWORD)
 
- #    print("\n-------", len(msgs), "Erinnerung(en)\n")
 
-     mylog.info("Ende " + str(len(msgs)) + " Erinnerungen")
 
- if __name__ == '__main__':
 
-     try:
 
-         main()
 
-     except:
 
-         falls_daily_versuche_hourly(ABSFILE)
 
-         traceback.print_exc()
 
- ####    print_exc_plus() #für bessere Fehlersuche aktivieren
 
-         mylog.error(traceback.format_exc())
 
-     else:
 
-         falls_hourly_loe(ABSFILE)
 
-     finally:
 
-         logging.shutdown()
 
-     exit(0)
 
 
  |