#!/usr/bin/python3 # Libre en Communs's mecene control program # Copyright (C) 2022-2024 Libre en Communs # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import os import requests import datetime import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders from requests.auth import HTTPBasicAuth VERSION = "1.0.0" GESTION_SECRET_FILE = "/home/tresorier/.secret/gestion_api_password" WORKDIR = "/srv/validation_don.d" MODALITY_MAIL = "mail_instructions.txt" MODALITY_MAIL_HEADERS = "mail_instructions_headers.txt" RECEPT_MAIL = "mail_recu.txt" RECEPT_MAIL_HEADERS = "mail_recu_headers.txt" RECEPT_MAIL_ATTACHMENT = "mail_recu_attachment.txt" SUMMARY_MAIL = "mail_recap_header.txt" # SMTP settings (adjust as necessary) SMTP_SERVER = 'mail.a-lec.org' SMTP_PORT = 587 SMTP_USER = 'tresorier@a-lec.org' SMTP_SECRET_FILE = "/home/tresorier/.secret/smtp_api_password" BUF = [] # Utility functions def gestion_get_secret(): with open(GESTION_SECRET_FILE) as sfile: return sfile.readline().strip() def smtp_get_secret(): with open(SMTP_SECRET_FILE) as sfile: return sfile.readline().strip() def get_file_content(filename): with open(filename) as sfile: return sfile.readlines() def get_file_content_all(filename): with open(filename) as sfile: return sfile.read() def set_file_content(filename, lines): with open(filename, "x") as sfile: return sfile.writelines(lines) def gestion_read(req): response = requests.post('https://gestion.a-lec.org/api/sql/', auth=HTTPBasicAuth('api666', gestion_get_secret()), data=req) return response.json() def setup_workdir(): for subdir in ['transition', 'nouveau', 'validé']: path = os.path.join(WORKDIR, subdir) if not os.path.exists(path): os.makedirs(path) # Send email with attachment via SMTP, password fetched from file def sendmail_with_attachment(headers, body, attachment_path, attachment_filename): msg = MIMEMultipart() # Add headers for header in headers.split("\n"): if ": " in header: key, value = header.split(": ", 1) msg[key] = value.strip() # Email body msg.attach(MIMEText(body, 'plain', 'utf-8')) # Attach PDF file if attachment_path: with open(attachment_path, "rb") as attachment: part = MIMEBase('application', 'octet-stream') part.set_payload(attachment.read()) encoders.encode_base64(part) part.add_header('Content-Disposition', f'attachment; filename={attachment_filename}') msg.attach(part) # Send email try: with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server: server.starttls() # Secure the connection server.login(SMTP_USER, smtp_get_secret()) # Fetch SMTP password from file server.sendmail(SMTP_USER, msg['To'], msg.as_string()) print(f"Email sent to {msg['To']}") except Exception as e: print(f"Error sending email: {e}") # Notify unpaid donors def notify_unpaid(record): content = get_file_content_all(os.path.join(WORKDIR, "transition", record)).split("\n") if len(content) > 8 and "notified" in content[8]: return # Already notified # Parse donor information name, surname, address, postal_code, city, email, amount, mode = content[:8] BUF.append(f"* {record} {name}, {amount} €") BUF.append(" NOTIFICATION DONATEUR") BUF.append("") mailtext = get_file_content_all(MODALITY_MAIL) mailtext = mailtext.replace("NOM_DONNEUR", f"{surname} {name}") mailtext = mailtext.replace("NUMERO_DON", str(record)) mailtext = mailtext.replace("MONTANT_DON", amount) mailtext = mailtext.replace("MODE_DON", mode) mailheaders = get_file_content_all(MODALITY_MAIL_HEADERS) mailheaders = mailheaders.replace("COURRIEL_DONNEUR", email) # Notify sendmail_with_attachment(mailheaders, mailtext, None, None) # Mark as notified with open(os.path.join(WORKDIR, "transition", record), "a") as sfile: sfile.write("notified\n") # Validate donation records def validate(record): request = f"SELECT * FROM acc_transactions tr INNER JOIN acc_transactions_lines l " \ f"ON tr.id = l.id_transaction WHERE tr.notes LIKE '%{record}%' AND id_account = 469" try: answer = gestion_read(request)["results"][-1] except IndexError: return False # If no result, donation not validated # Parse donor information name, surname, address, postal_code, city, email, amount, mode = \ get_file_content_all(os.path.join(WORKDIR, "transition", record)).split("\n")[:8] date = datetime.datetime.strptime(answer["date"], '%Y-%m-%d').strftime("%d/%m/%Y") filename = f"{name}_reçu_{record}_{date.replace('/', '.')}" BUF.append(f"* {record} {name}, {amount} €") BUF.append(" VALIDATION DON") BUF.append("") # Generate receipt latexfile = get_file_content_all("RECU_FISCAL.tex") latexfile = latexfile.replace("NUMERO-DON", record) latexfile = latexfile.replace("ANNEE-CIVILE", answer["date"][:4]) latexfile = latexfile.replace("NOM-DONATEUR", name) latexfile = latexfile.replace("STATUT-DONATEUR", "Personne physique") latexfile = latexfile.replace("ADRESSE-DONATEUR", f"{address}, {postal_code} {city}") latexfile = latexfile.replace("SOMME", f"{str(answer['credit'])[:-2]},{str(answer['credit'])[-2:]}") latexfile = latexfile.replace("DATE-VERSEMENT", date) latexfile = latexfile.replace("MODE-VERSEMENT", answer["reference"]) latexfile = latexfile.replace("FORME-DON", "Déclaration de don manuel") latexfile = latexfile.replace("NATURE-DON", "Numéraire") # Save the LaTeX file and generate the PDF tex_file_path = os.path.join(WORKDIR, "validé", f"{filename}.tex") set_file_content(tex_file_path, latexfile) os.system(f"cd {WORKDIR}/validé/ && pdflatex {filename}.tex") # Prepare and send email with attachment mailheaders = get_file_content_all(RECEPT_MAIL_HEADERS).replace("COURRIEL-DON", email) mailtext = get_file_content_all(RECEPT_MAIL).replace("DATE-DON", date) mailtattach = get_file_content_all(RECEPT_MAIL_ATTACHMENT).replace("DATE-DON", f"{record}_{date.replace('/', '.')}") pdf_path = os.path.join(WORKDIR, "validé", f"{filename}.pdf") sendmail_with_attachment(mailheaders, mailtext, pdf_path, f"{filename}.pdf") # Clean up os.remove(os.path.join(WORKDIR, "transition", record)) return True # Process new donation records def check_record(intent): numero, content = get_file_content_all(intent).split("|") name, surname, address, postal_code, city, email, amount, mode = content.split(";")[:8] BUF.append(f"* {numero} {name} {surname}, {amount} €") BUF.append(" NOUVEAU DON") BUF.append("") lines = [f"{name}\n{surname}\n{address}\n{postal_code}\n{city}\n{email}\n{amount}\n{mode}\n"] set_file_content(os.path.join(WORKDIR, "transition", numero), lines) os.remove(intent) # Clean up processed intent file # Main donation validation workflow def validate_donors(): # Process new donation intents for new_intent in os.listdir(os.path.join(WORKDIR, "nouveau")): check_record(os.path.join(WORKDIR, "nouveau", new_intent)) # Validate existing records or notify unpaid for record in os.listdir(os.path.join(WORKDIR, "transition")): if not validate(record): notify_unpaid(record) # Main function def main(): setup_workdir() validate_donors() # Send summary email mailheaders = get_file_content_all(SUMMARY_MAIL) + "\n" mailtext = "" if BUF: mailtext = "\n".join(BUF) sendmail_with_attachment(mailheaders, mailtext, None, None) if __name__ == '__main__': main()