Journal envoi d'emails automatisé dans le futur

Posté par  . Licence CC By‑SA.
8
5
avr.
2013

Sommaire

Voila, hier et aujourd'hui, n'ayant rien de particulier a faire, j'ai décidé de mettre un oeuvre une idée qui me trottait par la tête depuis quelque temps; Un envoi de rappel dans le futur automatisé par email

Le problème:

J'ai la politique, dans ma boite mail, de tout archiver, et de ne laisser que les mails auxquels je dois répondre, ou les mails qui contiennent des informations pour effectuer des actions en dehors (par exemple des billets d'avion, un mail qui dit "je dois 150€ a tata danielle", etc…)
Quand ma boite est vide, je n'ai rien a faire de spécial!
Problème! Ma boite n'est jamais vide! En effet, les mails du style "je dois 150€ a tata" sont présent, et imaginons que je sais que je vais manger chez elle le 01 juin, comme je ne relis absolument pas régulièrement mes vieux mails, j'oublie totalement les 150€ euros et la tata pas contente
Ou alors gilbert me demande par mail le prix du kilo de savon a marseille, et je ne saurais ce prix que dans 1 semaine, car dans une semaine je vais a marseille pour en acheter, du coup son email traine dans la boite de reception toute la semaine le temps que j'aille a marseille.

Solution:

Et si j'envoyais ce mail a une adresse specifiée, lui disant de me le reenvoyer le 31 mai, la veille du repas chez la tata ! Comme ca je n'oublierais pas les 150€! Et voila je peux archiver le mail "150€ a tata" ma boite de reception est plus propre que propre!
J'en profite pour envoyer le mail que m'a envoyé gilbert a cette meme adresse, en specifiant un reenvoi dans 7 jours, et le tour est joué!
Donc j'ai cree un nouvelle adresse mail sur internet qui m'offre un acces pop et smtp (j'ai fait ça chez yahoo, mais ca devrait marcher n'importe ou, disons que cette adresse est rappel@rappel.fr ), et j'ai fait un petit script qui lit les mails de cette adresse, determine quand ce message doit etre reexpedié, le stocke, et reexpedie le mail a la date demandée!

Exemple d'utilisation:

J'envoie un mail a rappel@rappel.fr, avec comme sujet "20 mai; balayer le placard" et dans le corps ce que je vais, et le 20 mai, entre 6 et 7 h du mail, je recevrais un mail de rappel@rappel.fr me disant "balayer le placard" et ce qui etait dans le corps egalement

Le detail:

j'ai fait un script rappels.py, grace a une entree dans le crontab, ce script est executé plusieurs fois par jours (2 suffisent je pense).
Dans ce script est specifié les adresse mail autorisées a utiliser le service de rappel
Ce script agit de la maniere suivante:
-Il telecharge via pop les messages recus sur rappel@rappel.fr
-Parmi les emails, si l'email vient d'une personne autorisée, il verifie si dans le sujet il arrive a lire pour quelle date l'utilisateur veut son rappel
-Si il n'y arrive pas, il envoie un mail a la personne pour lui preciser que ce n'est pas possible
-Si il y arrive, il stocke le mail dans un fichier a part, le moment prevu d'envoi dans un autre fichier, et cree une tache avec at (le programme doit etre installé) pour le moment voulu de l'envoi
-Il supprime tous les email sur le serveur POP
-Finalement, il lit la liste des tous les envois a faire, et realise ceux qui sont prevu dans le passé par SMTP

les formats de moment de rappel acceptes

Le moment ou le rappel doit être fait s’écrit dans le sujet du mail, se termine par un ";", et doit être le la forme suivante; Le mail sera envoyé le jour spécifié entre 6 et 7h du matin
demain; -> demain
1j; -> dans 1 jour, meme heure
21j; -> dans 23 jours, meme heure
1m; -> dans 1 mois, meme heure
25 juin; -> le 25 juin prochain
4 septembre 2015; -> le 4 septembre 2013

Le code est hautement crado, les emails renvoyés ne sont pas identique, et parfois difficiles a lire, ayant eu un peu de mal avec le parsing des mail en python, d'ailleurs n'ayant que des debian squeeze sous la main, je n'ai teste le programme qu'avec python2.6, mais j'ai vu que a partir de python3.2 le parsing des email a ete amelioré.

Voici le programme:

#!/usr/bin/python2.6


adresseAutorisees=["monAdressePerso@perso.fr"]
SMTPserver = 'smtp.mail.yahoo.com'
SMTPsender =      'rappel@example.com'

SMTPUSERNAME = "rappel@example.com"
SMTPPASSWORD = "motdepasse"

POPSERVER='pop.mail.yahoo.com'
POPUSER='rappel'
POPPASS='motdepasse'

import os
from subprocess import Popen

def ex(command):
    print(" on va executer: "+command)
    p = Popen(command, shell=True)
    sts = os.waitpid(p.pid, 0)[1]

class FichierDate:
    def __init__(self,nomFichier,dateEnvoi,sujet):
        self.nomFichier=nomFichier
        self.dateEnvoi=dateEnvoi
        self.sujet=sujet
    def __str__(self):
        return self.nomFichier+" SUJET: "+self.sujet


if not os.path.exists(".rappel"):
    os.makedirs(".rappel")
import shelve
d=shelve.open(".rappel/liste")
def innit(db,key,valeur):
    if key in db:
        return db[key]
    else:
        db[key]=valeur
        return valeur

f=innit(d,"fichierDate",[])

def enregistrerMail(mail,dateEnvoi):
    nomFichier=str(dateEnvoi)+" pour "+str(mail.adresse)
    fi=shelve.open(".rappel/"+nomFichier)
    fi["mail"]=mail
    fi.close()
    fichierDate=d["fichierDate"]
    fichierDate.append(FichierDate(nomFichier,dateEnvoi,mail.sujet))
    d["fichierDate"]=fichierDate


mois=["janvier","fevrier","mars","avril","mai","juin","juillet","aout","septembre","octobre","novembre","decembre"]
moisVersNum={}
for i in range(len(mois)):
    moisVersNum[mois[i]]=i+1
syntax="""
demain; -> demain 
1j; -> dans 1 jour, meme heure
21j; -> dans 23 jours, meme heure
1m; -> dans 1 mois, meme heure
25 juin; -> le 25 juin prochain
4 septembre 2015; -> le 4 septembre 2013
"""
def chopeNumeroDevant(st):
    i=0
    while st[i:i+1].isdigit():
        i+=1
    if i==0:
        return None,st
    nb=int(st[0:i])
    st=st[i:]
    return nb,st
from datetime import timedelta,datetime,date
def parseMoment(chaine):
    #print chaine
    avant,sep,apres=chaine.partition(";")
    aujourdhui=datetime.now()
    avant=avant.strip()
    if avant.startswith("demain"):
        return aujourdhui+timedelta(1)
    nbJour,reste=chopeNumeroDevant(avant)
    if nbJour==0:
        return None
    avant=reste.strip()
    numMois=None
    for m in mois:
        if avant.startswith(m):
            numMois=moisVersNum[m]
            avant=avant[len(m):]
            avant=avant.strip()
    if numMois:
        #print avant
        nb,reste=chopeNumeroDevant(avant)
        if nb:
        #   print nb
            return datetime(nb,numMois,nbJour,6,aujourdhui.minute,aujourdhui.second)
        else:
            annee=aujourdhui.year
            if aujourdhui.month==numMois:
                if aujourdhui.day<nbJour:
                    annee=aujourdhui.year
                elif aujourdhui.day>nbJour:
                    annee=aujourdhui.year+1
                else:
                    return None
            elif aujourdhui.month>numMois:
                annee=aujourdhui.year+1
            return datetime(annee,numMois,nbJour,6,aujourdhui.minute,aujourdhui.second)
        # c'est pas une date, c'est des jours relatifs
    if avant.startswith("j"):
        return aujourdhui+timedelta(nbJour)
    if avant.startswith("m"):
        return aujourdhui+timedelta(nbJour*30)


class MEMO:
    pass
def Envoyeur(memo):
# typical values for text_subtype are plain, html, xml
    text_subtype = 'plain'
    content=b"""\
            Ceci est un rappel comme demande
            """
    subject=memo.sujet
    import sys
    import os
    import re
    from smtplib import SMTP_SSL         # this invokes the secure SMTP protocol (port 465, uses SSL)
    from email.mime.text import MIMEText
    from email.mime.base import MIMEBase
    from email.mime.multipart import MIMEMultipart
    from email.message import Message
    msg=MIMEMultipart()
    msg['Subject']=      subject
    msg['From'] = SMTPsender # some SMTP servers will do this automatically, not all
    msg['To']=memo.adresse
    msg.preamble=b"hoho"
    texte=memo.body
    kiki=MIMEText(texte,'plain')
    msg.attach(kiki)
#       att = MIMEBase('application','octet-stream')
#       att.set_payload(memo.message)
#       att.add_header('Content-Disposition', 'attachment', filename="mess")

#       msg.attach(att)

    try:
        conn = SMTP_SSL(SMTPserver,465)
        conn.set_debuglevel(False)
        conn.login(SMTPUSERNAME, SMTPPASSWORD)
#       print (sender,destination,msg.as_string())
        conn.sendmail(SMTPsender, [memo.adresse], msg.as_string())
    except:
        return False
    finally:
        conn.close()
    return True



import email
import poplib
def extractEmail(chaine):
    deb=chaine.find(b"<")
    fin=chaine.find(b">")
    return chaine[deb+1:fin]
server= poplib.POP3_SSL(POPSERVER)

server.user(POPUSER)
server.pass_(POPPASS)
nbMess,size=server.stat()
print (nbMess)
memos=[]
for i in range(nbMess):
    memo=MEMO()
    reponse,lines,octets=server.retr(i+1)
    print( reponse,"HIHI",octets)
    adOk=False
    suOk=False
    for l in lines:
        if not suOk and l.startswith(b"Subject:"):
            memo.sujet=l[9:]
            suOk=True
            print(l[9:])
        if not adOk and l.startswith(b"From:"):
            memo.adresse=extractEmail(l[5:])
            adOk=True
            print(extractEmail(l[5:]))
    print( len(lines))


#   import email.parser.BytesParser
#   msg=BytesParser().parsebytes('\n'.join(lines))
    lll=""
    for l in lines:
        ll=l
        lll=lll+'\n'+ll
    msg = email.message_from_string(lll) # new statement
    #print(msg.get_payload())
    memo.body=msg.get_payload()
    memo.body=memo.body[memo.body.find("Content-Transfer")+32:]
    memo.message=lines
    memo.numero=i+1
#   memo.body=str(lines[len(lines)-1])
#   print (memo.message)
    memos.append(memo)
    server.dele(i+1)
def estAutorise(adresse):
    for a in adresseAutorisees:
        if adresse in adresseAutorisees:
            return True
    return False
for m in memos:
    moment=parseMoment(m.sujet)
    if estAutorise(m.adresse):
        if moment:
            avant,sep,apres=m.sujet.partition(";")
            m.sujet=apres
            enregistrerMail(m,moment)
            date=moment.strftime("%H:%M %m%d%y")
            ex("at -f /home/auberge/rappel.sh "+date)
        else:
            m.sujet="Nous pas pu identifier le moment du rappel "+m.sujet
            Envoyeur(m)
    else:
        print "ERREUR DESTINATAIRE NON AUTORISE"
        #e=Envoyeur(m)
        #print (m.sujet," POUR ",m.adresse)

def voyonsVoirSiOnPeutEnvoyerDesMails():
    print "voyons voir"
    fichierDate=d["fichierDate"]
    fichierDateASupp=[]
    for f in fichierDate:
        print(f)
        if f.dateEnvoi <= datetime.now():
            print "on doit envoyer un mail "+str(f)
            nomf=".rappel/"+f.nomFichier
            print nomf
            fmail=shelve.open(nomf)
            mail=fmail["mail"]
            if(Envoyeur(mail)):
                os.remove(nomf)
                fichierDateASupp.append(f)
    for f in fichierDateASupp:
        fichierDate.remove(f)
    d["fichierDate"]=fichierDate
voyonsVoirSiOnPeutEnvoyerDesMails()

server.quit()
d.close()

  • # rappel de calendrier

    Posté par  . Évalué à 9.

    Salut,

    En fait, tu as recréé la fonction "Rappel" d'un événement dans un calendrier?

    • [^] # Re: rappel de calendrier

      Posté par  . Évalué à 2.

      Exactement, mais sans être dépendant d'un calendrier, d'une application, qui souvent ont d'ailleurs des interfaces qui nécessitent au moins 3 clicks et un temps non négligeable pour spécifier la date et le rappel

  • # at ? cron ?

    Posté par  . Évalué à -1.

    La commande at ne te convient pas ? Et pour un mail cyclique comme un rappel d'anniversaire : il y a cron (ou mieux, la crontab de ton utilisateur pour ne pas polluer /etc/cron.d avec des petites commandes mail perso).

    • [^] # Re: at ? cron ?

      Posté par  . Évalué à 1.

      Salut,

      T'as lu le journal ?

      Il utilise at ET cron.

      • [^] # Re: at ? cron ?

        Posté par  . Évalué à -2.

        Salut, t'as lu mon message ?

        Déjà il ne parle pas de la commande at. Et deuxièmement, il se sert de la crontab pour aller relever une boîte mail.

        Là je parle d'utiliser at et/ou cron justement pour envoyer le mail, pas pour aller voir s'il y en a un à relever. Cela permet de se servir de cron (comme dans le journal donc) mais de rien de plus (enfin si, echo et mail…). Tu veux te rappeler de faire un truc demain, tu fais :

        at -t 04060700
        echo "Rappel" | mail -s "Vous avez reçu un rappel" phil@localhost
        ^D
        
        
  • # agenda

    Posté par  . Évalué à 9.

    En fait, tu viens de découvrir l'utilité d'un agenda. Donc utilises-en un, que ce soit papier ou logiciel.

    En logiciel, la plupart proposent également de gérer des listes de tâches.

    2 cas :

    • soit tu sait à quelle date dans le futur tu auras à faire une chose, dans ce cas tu le mets dans l'agenda.
    • soit tu as un truc à faire, tu ne sais pas trop quand, tu le mets dans la liste des tâches et tu mets dans l'agenda un moment où tu reliras toutes tes tâches (très important).

    Bonne chance … pour le futur.

    • [^] # Re: agenda

      Posté par  . Évalué à 6.

      Et ensuite tu utilises ton script pour que ça t'envoi un rappel pour regarder ton agenda !

  • # Le futur c'est bien

    Posté par  . Évalué à 9.

    Tu comptes faire évoluer ça pour gérer le passé aussi ?

    Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

  • # Dans Kmail

    Posté par  (site web personnel) . Évalué à 5.

    [je ne l'utilises pas, mais ça existe déjà]

    Bouton droit sur l'email dans la liste, menu "Créer une tache/un rappel".

    Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

    • [^] # Re: Dans Kmail

      Posté par  . Évalué à 4.

      pareil avec thunderbird+lightning

      1°) clic droit -> convertir en taches/evenements
      2°) activer le rappel
      3°) sauvegarder

      si tes divers appareils sont synchronisés pour les calendriers,
      tous les appareils te rappellerons la veille :
      1°) que tu dois aller manger chez tata daniel demain
      2°) que tu dois lui ramener 150euros

  • # Tu bosses pas chez ATOS au moins ?

    Posté par  . Évalué à 4.

    Ce serait marrant

  • # dredi

    Posté par  (site web personnel) . Évalué à 2.

    Le prix du kilo à Marseille m’intéresse aussi ;)

  • # Tu as couplé ça avec iPot en plus ?

    Posté par  . Évalué à 4.

    1j; -> dans 1 jour, meme heure
    21 j; -> dans 23 jours, meme heure
    1m; -> dans 1 mois, meme heure
    25 juin; -> le 25 juin prochain
    4 septembre 2015; -> le 4 septembre 2013

  • # Utilité

    Posté par  . Évalué à 1.

    Clairement ce n'est pas très utile vu qu'il existe pléthore d'agendas web/pas web/mobiles/synchronisés/cloud/… permettant de faire la même chose mais pour moi qui apprend le python ton code peut être intéressant.
    Si ploum< passait par là il te dirait d'utiliser gtg mais que c'est pas complètement au point parce pas de version mobile, sinon owncloud te propose un agenda avec caldav et rappels et en GTD proprio il y a doit.im qui n'est pas mal foutu (je rêve d'ailleurs d'une alternative libre).

  • # Enlarge your income

    Posté par  . Évalué à 6.

    Je vais facturer mes clients après leur avoir envoyé un email automatique environ chaque heure de la nuit pour montrer que je dépannais le serveur de sauvegarde.
    Mouahahaha, à moi l'argent, les femmes et l'huile solaire.

  • # Réaction typique ... ça existe déjà

    Posté par  (site web personnel) . Évalué à 1.

    J'ai envie de dire (ou plutôt d'écrire) : "Gna gna gna"

    Comme trop souvent ici, les commentaires sont peu constructif et trop agressif à mon goût.
    Oui, bien sur, ça existe déjà. On peut faire cela différemment avec Google Agenda, Owncloud, FaceBook, … (rayer la mention inutile).

    Je trouve le concept sympathique et même si j'aurai implémenter cela différemment (je pense en laissant les mails sur le serveur et en les consultant périodiquement afin de pouvoir les transférer à l'adresse de l'expéditeur le jour J) ta façon de faire est intéressante.

    Pourquoi réinventer la roue ??
    Le hollandais volant a expliqué son point de vue ici et je le partage.

    Après, si Torvalds avait pensé "ça existe déjà" au moment ou il a écrit un clone de Minix, nous ne serions pas ici à discuter le bout de gras.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.