| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 | 
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""efbeff 2024-04-16Dieses script läuft z.Zt. produktiv auf Duesseldorf blech02Freifunk Dortmund Terminerinnerungen aus dem Kalender erzeugenund ü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 eineMonatsgrenze geht.Das alte script von FFdo als Basis erheblich überarbeitetDies ist die zweite Überarbeitung mit erstellen von *.ics AnhängenDies ist die dritte Überarbeitung mit Entfernen HTML KommentarEs darf nur 1mal am Tag laufen, sonst gibt es mehrfache ErinnerungenDer Absender reminder@freifunk-dortmund.de ist keine echte mailadresse,sondern nur als Nichtmitglied aber sendeberechtigt zugelassen in der    DEFAULT_LISTE, VEREIN_LISTE und INFRA_LISTEDafür in der Listenadministration jeweils unter  Abo-regeln und Adressfilter     Absender-filter        Anti-Spam-filter           reminder@freifunk-dortmund.deeintragen.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 Klartexttestmode = 0# bei testmode 1 wird keine mail versandt, die Erinnerungen werden# nur aufbereitet und ins log geschriebenimport loggingimport logging.handlersimport sysimport tracebackimport osimport shutilimport smtplibimport requestsfrom email.message import EmailMessagefrom datetime import datetime, timedelta, date########from icalendar import Calendar, Eventimport reABSFILE=os.path.abspath(__file__)CALURL_TEMPLATE='http://www.freifunk-dortmund.de/termine/{}-{:02d}/?ical=1'# empfängerlistenDEFAULT_LISTE="freifunk-do@list.free.de"INFRA_LISTE="freifunk-do-infra@list.free.de"VEREIN_LISTE="freifunk-do-verein@list.free.de"# AbsenderMAIL_FROM="FF-DO Termine <reminder@freifunk-dortmund.de>" if testmode < 2 else\"fbeythien@gmx.de"MAIL_SUBJECT="Monatstreffen"  # wird angepasstif 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 MonatstreffenTOP_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 MonatstreffenREM_TREFFEN_TOPS  = REM_TREFFEN_MONAT + 2 # Tage vorher erinnern TOP ergänzenREM_STANDARD = 2        # Tage vorher erinnern FF@home, ...REM_INTERVALL = 10      # heute + x Tage Termine prüfen# logging initlogging.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 klapptdef 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 Laufdef 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 gesetztdef 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 aufbereitendef 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 aufbereitendef 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 aufbereitendef 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 crlfdef 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 ergebnisdef 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)
 |