Untitled

 avatar
unknown
plain_text
a month ago
14 kB
3
Indexable
import time
import pandas as pd
import smtplib
import tkinter as tk
import os
import threading
import re
import subprocess
import queue
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from docx import Document
from tkinter import filedialog
from tkinter import messagebox
from dotenv import load_dotenv
from email.mime.base import MIMEBase
from email import encoders

def convert_docx_to_html(docx_file_path):
    html_file_path = docx_file_path.replace(".docx", ".html")
    try:
        # Run the `mammoth` command to convert .docx to .html
        subprocess.run(["mammoth", docx_file_path, html_file_path], check=True)
        with open(html_file_path, 'r', encoding='utf-8') as file:
            html_content = file.read()
        return html_content
    except subprocess.CalledProcessError as e:
        raise Exception(f"Error converting DOCX to HTML: {e}")

def extract_html_source_code(html_file_path):
    with open(html_file_path, 'r', encoding='utf-8') as file:
        html_content = file.read()
    return html_content

def email_to_env_var(email):
    return email.replace("@", "_").replace(".", "_")

def get_sender_credentials(sender_email):
    sender_env_var = email_to_env_var(sender_email)
    sender_email = os.getenv(f"{sender_env_var}_EMAIL")
    sender_password = os.getenv(f"{sender_env_var}_PASSWORD")
    return sender_email, sender_password

def dataframe_to_styled_html(df, merge_first_row=False):
    table_style = (
        "border-collapse: collapse; width: 65%; font-family: Arial, sans-serif; "
        "font-size: 14px; text-align: center;"
    )
    header_style = (
        "background-color: #6A0DAD; color: white; padding: 10px; border: 1px solid black;"
    )
    cell_style = "padding: 10px; border: 1px solid black;"
    row_alt_style = "background-color: #f2f2f2;"

    html_table = f'<table style="{table_style}">'

    if merge_first_row:
        html_table += f'<tr><td colspan="{len(df.columns)}" style="{header_style}">{df.columns[0]}</td></tr>'
    else:
        html_table += "<tr>"
        for col in df.columns:
            html_table += f'<th style="{header_style}">{col}</th>'
        html_table += "</tr>"

    for i, row in df.iterrows():
        row_style = row_alt_style if i % 2 == 1 else ""
        html_table += f'<tr style="{row_style}">'
        for cell in row:
            if pd.isna(cell):
                cell_content = ""
            else:
                if isinstance(cell, float):
                    cell_content = f"{cell:.2%}" if 0 <= cell <= 1 else cell
                else:
                    cell_content = str(cell)  # Ensure cell content is always a string
            html_table += f'<td style="{cell_style}">{cell_content}</td>'
        html_table += "</tr>"

    html_table += "</table>"
    return html_table

def send_bulk_emails_with_text_and_tables(
    smtp_server,
    smtp_port,
    email_list_file,
    table1_file,
    table2_file,
    table3_file,
    table4_file,
    table5_file,
    log_queue,
    log_file
):
    log_queue.put("The email-sending process has started. Please wait...")
    log_serial_number = 1

    with open(log_file, 'a') as log_file_obj:
        try:
            email_data = pd.read_excel(email_list_file)
            if "Email" not in email_data.columns or "Salutations" not in email_data.columns or "CC" not in email_data.columns or "From" not in email_data.columns or "File Name" not in email_data.columns or "Sender Name" not in email_data.columns or "Signature File" not in email_data.columns:
                log_queue.put("The email list file must contain columns: 'Email', 'Salutations', 'CC', 'From', 'File Name', 'Sender Name', and 'Signature File'.")
                return

            email_details = email_data[["Email", "Salutations", "CC", "From", "File Name", "Sender Name", "Attachments", "Signature File"]]

            tables = [
                (table1_file, "{{ TABLE_1 }}"),
                (table2_file, "{{ TABLE_2 }}"),
                (table3_file, "{{ TABLE_3 }}"),
                (table4_file, "{{ TABLE_4 }}"),
                (table5_file, "{{ TABLE_5 }}")
            ]
            
            # Read all tables as dataframes if they are valid files
            tables_html = {}
            for table_file, placeholder in tables:
                if table_file and os.path.isfile(table_file):
                    try:
                        table_df = pd.read_excel(table_file)
                        tables_html[placeholder] = dataframe_to_styled_html(table_df)
                    except Exception as e:
                        log_entry = f"Error reading table from {table_file}: {e}"
                        log_queue.put(log_entry)
                        log_file_obj.write(log_entry + "\n")
            
            for _, row in email_details.iterrows():
                try:
                    recipients = row["Email"].split(",")
                    salutation = row["Salutations"]
                    cc_list = row["CC"].split(",") if pd.notna(row["CC"]) else []
                    from_email = row["From"]
                    text_file = row["File Name"]
                    sender_name = row["Sender Name"]

                    signature_file = row["Signature File"]
                    signature_html = ""
                    if pd.notna(signature_file) and os.path.isfile(signature_file):
                        signature_html = convert_docx_to_html(signature_file)

                    attachment_file = row.get("Attachments", None)

                    if not os.path.isfile(text_file):
                        log_entry = f"{log_serial_number}. The file {text_file} does not exist for {from_email}."
                        log_queue.put(log_entry)
                        log_file_obj.write(log_entry + "\n")
                        continue

                    # html_file_path = convert_docx_to_html(text_file)
                    path = "Reports/mfReportsBody.html"
                    email_text = extract_html_source_code(path)

                    # Replace placeholders for tables dynamically
                    for placeholder, table_html in tables_html.items():
                        if placeholder in email_text:
                            email_text = email_text.replace(placeholder, table_html)
                        else:
                            log_queue.put(f"Placeholder {placeholder} not found in the text.")

                    # Replace the {{ Sender_Name }} and {{ Signature }} placeholders
                    email_text = email_text.replace("{{ Sender_Name }}", sender_name)
                    email_text = email_text.replace("{{ Signature }}", signature_html)

                    email_body_html = f"""
                    <html>
                        <body>
                            {email_text}
                        </body>
                    </html>
                    """

                    # Fetch sender email and password based on the 'From' column
                    sender_email, sender_password = get_sender_credentials(from_email)
                    if not sender_email or not sender_password:
                        log_entry = f"{log_serial_number}. Missing credentials for sender email {from_email}."
                        log_queue.put(log_entry)
                        log_file_obj.write(log_entry + "\n")
                        continue

                    with smtplib.SMTP(smtp_server, smtp_port) as server:
                        server.starttls()
                        server.login(sender_email, sender_password)

                        personalized_text = email_body_html.replace("{{ Salutations }}", salutation)

                        msg = MIMEMultipart()
                        msg["From"] = sender_email
                        msg["To"] = ", ".join(recipients)
                        msg["Cc"] = ", ".join(cc_list)
                        msg["Subject"] = "Mutual Fund Investment Snapshot - Dated as of Dec 31, 2024"

                        msg.attach(MIMEText(personalized_text, "html"))

                        # Handle attachments (if provided)
                        if attachment_file and os.path.isfile(attachment_file):
                            with open(attachment_file, "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={os.path.basename(attachment_file)}"
                                )
                                msg.attach(part)
                                log_entry = f"Attachment {os.path.basename(attachment_file)} added."
                                log_queue.put(log_entry)
                                log_file_obj.write(log_entry + "\n")

                        all_recipients = recipients + cc_list
                        server.sendmail(sender_email, all_recipients, msg.as_string())

                        log_entry = f"{log_serial_number}. Email sent successfully from {sender_email} to {', '.join(recipients)}, CC: {', '.join(cc_list)}"
                        log_queue.put(log_entry)
                        log_file_obj.write(log_entry + "\n")

                except Exception as e:
                    log_entry = f"{log_serial_number}. Failed to send email from {from_email} to {', '.join(recipients)}: {e}"
                    log_queue.put(log_entry)
                    log_file_obj.write(log_entry + "\n")

                log_serial_number += 1

        except Exception as e:
            log_entry = f"Failed to connect to the SMTP server: {e}"
            log_queue.put(log_entry)
            log_file_obj.write(log_entry + "\n")

    log_queue.put("The email-sending process has completed successfully.")



# to show logs in the ui box
def update_log_ui(log_queue, log_text_widget):
    try:
        while not log_queue.empty():
            log_entry = log_queue.get_nowait()
            log_text_widget.insert(tk.END, log_entry + "\n")
            log_text_widget.yview(tk.END)
    except queue.Empty:
        pass
    finally:
        # Schedule the next update
        root.after(100, update_log_ui, log_queue, log_text_widget)

# threading is used to send emails slowly so it doesnt go into spam
def send_email_threaded():
    smtp_server = "smtp.gmail.com"
    smtp_port = 587
    email_list_file = email_list_entry.get()
    table_files = [
        table1_entry.get(),
        table2_entry.get(),
        table3_entry.get(),
        table4_entry.get(),
        table5_entry.get()
    ]
    log_file = "email_log.txt"

    if not email_list_file:
        messagebox.showerror("Missing Information", "Please provide the email list file.")
    else:
        log_queue = queue.Queue()
        email_thread = threading.Thread(
            target=send_bulk_emails_with_text_and_tables,
            args=(
                smtp_server,
                smtp_port,
                email_list_file,
                *table_files,  # Pass the table files dynamically
                log_queue,
                log_file,
            ),
        )
        email_thread.start()
        update_log_ui(log_queue, log_text_widget)


#upload file from ui
def browse_file(entry):
    file_path = filedialog.askopenfilename(
        title="Select a file", 
        filetypes=[("Excel files", ".xlsx")]
    )
    if file_path:
        entry.delete(0, tk.END)
        entry.insert(0, file_path)

load_dotenv()

# UI Setup
# UI Setup
root = tk.Tk()
root.title("Pygmalion Wealth")

tk.Label(root, text="Sender Email:").grid(row=0, column=0, padx=10, pady=5)
sender_email_entry = tk.Entry(root, width=40)
sender_email_entry.grid(row=0, column=1, padx=10, pady=5)

tk.Label(root, text="Email List File:").grid(row=2, column=0, padx=10, pady=5)
email_list_entry = tk.Entry(root, width=40)
email_list_entry.grid(row=2, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(email_list_entry)).grid(row=2, column=2, padx=10, pady=5)

tk.Label(root, text="Table 1 File (Optional):").grid(row=3, column=0, padx=10, pady=5)
table1_entry = tk.Entry(root, width=40)
table1_entry.grid(row=3, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table1_entry)).grid(row=3, column=2, padx=10, pady=5)

tk.Label(root, text="Table 2 File (Optional):").grid(row=4, column=0, padx=10, pady=5)
table2_entry = tk.Entry(root, width=40)
table2_entry.grid(row=4, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table2_entry)).grid(row=4, column=2, padx=10, pady=5)

tk.Label(root, text="Table 3 File (Optional):").grid(row=5, column=0, padx=10, pady=5)
table3_entry = tk.Entry(root, width=40)
table3_entry.grid(row=5, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table3_entry)).grid(row=5, column=2, padx=10, pady=5)

tk.Label(root, text="Table 4 File (Optional):").grid(row=6, column=0, padx=10, pady=5)
table4_entry = tk.Entry(root, width=40)
table4_entry.grid(row=6, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table4_entry)).grid(row=6, column=2, padx=10, pady=5)

tk.Label(root, text="Table 5 File (Optional):").grid(row=7, column=0, padx=10, pady=5)
table5_entry = tk.Entry(root, width=40)
table5_entry.grid(row=7, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table5_entry)).grid(row=7, column=2, padx=10, pady=5)

log_text_widget = tk.Text(root, height=15, width=80)
log_text_widget.grid(row=8, column=0, columnspan=3, padx=10, pady=10)

tk.Button(root, text="Send Emails", command=send_email_threaded).grid(row=9, column=1, padx=10, pady=5)

root.mainloop()
Leave a Comment