#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- """alerter les adhérents de leur fin de connexion imminente Ce script devra aussi permettre d'alerter les cableurs sur les prochaines affluences en perm""" import sys import pytz import datetime from dateutil.parser import parse as parse_gtf import calendar from lc_ldap.shortcuts import lc_ldap_readonly from lc_ldap.variables import base_dn import ldap from affich_tools import coul import gestion.mail as mail_module #: Une journée (c'est plus pratique) DAY = datetime.timedelta(days=1) #: Format des dates dans la base LDAP FORMAT_LDAP = '%Y%m%d%H%M%S%z' #: Infos à oublier dans un datetime pour ne garder que le jour ERASE_DAY = { 'second': 0, 'minute': 0, 'microsecond': 0, 'hour': 0, } #: filtre ldap max(finConnexion) \in intervalle # NB: finConnexion est un attribut ldap multivalué, et on s'intéresse ici # à sa valeur max pour un adhérent. # Les filtres ldap recherchent, de manière existentielle, une valeur valide. # Ainsi, en notant F l'ensemble des valeurs : # max(F) >= v <=> \exists x\in F x>= v <=> finConnexion>=v # L'autre inégalité ( < ) est plus délicate : # max(F) < v <=> \forall x\in F x < v <=> \not( \exists x\in F x >= v ) # <=> \not( finConnexion>= v ) FILTRE_TPL_SIMPLE = u'(&(finConnexion>=%(debut)s)(!(finConnexion>=%(fin)s)))' # Le cas précédent était simplifié, en réalité, la connexion s'achève dès que # l'adhésion se termine. On regarde donc min(max(finConnexion), max(finAdhesion)) # min(a,b) >= v <=> a >= v /\ b >= v # min(a,b) < v <=> a < v \/ b < v FILTRE_TPL = u"""(& (aid=*) (&(finConnexion>=%(debut)s)(finAdhesion>=%(debut)s)) (|(!(finConnexion>=%(fin)s))(!(finAdhesion>=%(fin)s))) )""" # Calcul de la timezone locale #try: # with open('/etc/timezone', 'r') as f: # tz = pytz.timezone(f.read().strip()) #except: # tz = pytz.UTC # usage : # datetime.datetime.now(tz) c = lc_ldap_readonly() # Instant courant: on ne garde que le jour now = datetime.datetime.now() # Applique un delta, si spécifié for arg in sys.argv[1:]: if arg.startswith('+'): now += int(arg[1:])*DAY today = now.replace(**ERASE_DAY) print "Nous serons le %s" % today def warn(adh, delai): data = {'delai': delai} for l in ['adhesion', 'connexion']: fin = max(parse_gtf(v.value) for v in adh['fin' + l.capitalize()]) data['fin_%s' % l] = fin From = 'respbats@crans.org' To = adh.get_mail() if not To: print "No valid mail for %r" % adh return data.update({'To': To, 'From': From}) mailtxt = mail_module.generate('fin_connexion', data) print mailtxt def compute_fin_connexion(adh): return min( max(parse_gtf(v.value) for v in adh['fin' + l]) for l in ['Adhesion', 'Connexion']) def select(conn, begin, to, mode='r'): """Récupère les adhérents dont la connexion expire entre les datetimes ``begin`` (inclu) et ``to`` (exclu)""" # Nous avons besoin de dates avec timezone. if not begin.tzinfo: begin = begin.replace(tzinfo=pytz.UTC) if not to.tzinfo: to = to.replace(tzinfo=pytz.UTC) data = { 'debut': begin.strftime(FORMAT_LDAP), 'fin': to.strftime(FORMAT_LDAP), } filtre = FILTRE_TPL % data # NB: on ne prend que les adhérents, d'où SCOPE_ONELEVEL res = conn.search(filtre, scope=ldap.SCOPE_ONELEVEL, dn=base_dn, mode=mode) return res def brief(c, debut, fin): if not debut.tzinfo: debut = debut.replace(tzinfo=pytz.UTC) if not fin.tzinfo: fin = fin.replace(tzinfo=pytz.UTC) to_warn = select(c, debut, fin) print ("%d adhérents seront prévenus que leur connexion expire entre le %s " + \ "et le %s") % (len(to_warn), debut, fin) if "--list" in sys.argv: for adh in to_warn: valeurs = [max(parse_gtf(v.value) for v in adh[l]) \ for l in ['finConnexion', 'finAdhesion'] ] [f_con, f_adh] = [ coul(str(v), 'rouge' if v >= debut and v < fin else 'vert') \ for v in valeurs] print "%r %s %s;%s" % (adh, adh.dn.split(',', 1)[0], f_con, f_adh) return to_warn def prev(c, date): """Prévisualise l'expiration des connexions sur le mois courant""" month = date.month year = date.year cal = calendar.Calendar() first = datetime.datetime(day=1, month=month, year=year, tzinfo=pytz.UTC) last = first.replace(month=1+month%12, year=year+int(month==12)) disconnect = brief(c, first, last) by_day = {x: 0 for x in xrange(1,32)} for adh in disconnect: date = compute_fin_connexion(adh) by_day[date.day] += 1 spaces = 3 days = ['L', 'M', 'Me', 'J', 'V', 'S', 'D'] print " ".join(d + ' '*(spaces-len(d)) for d in days) l = [] for d in cal.itermonthdays(year, month): if not d: item = "" elif not by_day[d]: item = "." else: item = str(by_day[d]) l.append(item + ' '*(spaces-len(item))) if len(l) == 7: print " ".join(l) l = [] if l: print " ".join(l) # zone de test #warn(c.search(u'aid=4281')[0], 7) prev(c, today) # Plusieurs type d'execution: # * Manuel (préventif): avertit d'une déco dans moins d'un mois # select(c, today, today+30*DAY) # * Quotidien (préventif) : avertit d'une déco dans moins d'un mois # select(c, today+(30-1)*DAY, today+30*DAY) # * Quotidien (last chance): avertit d'une déco dans moins de 4 jours # (l'idée, c'est qu'il y a toujours une perm entre les deux) # select(c, today+(4-1)*DAY, today+4*DAY) # * Mensuel: avertit les cableurs des connexions à expiration dans le mois # prochain # select(c, first_day, last_day+DAY) # prev(c, today+28*DAY)