import smtplib import requests import ssl import json from email.message import EmailMessage import pymongo from pymongo.errors import ConnectionFailure, OperationFailure, PyMongoError from bson.objectid import ObjectId # Import ObjectId for querying by _id # --- Configuration Loading --- def load_config_from_json(file_path="config.json"): """Loads configuration from a JSON file.""" try: with open(file_path, "r", encoding="utf-8") as f: config = json.load(f) print(f"Configuration loaded from {file_path}") return config except FileNotFoundError: print(f"Error: Configuration file '{file_path}' not found. Please create it.") exit(1) except json.JSONDecodeError: print(f"Error: Could not decode JSON from '{file_path}'. Check file format for errors.") exit(1) except Exception as e: print(f"An unexpected error occurred while loading config: {e}") exit(1) # Load configuration at the start CONFIG = load_config_from_json() # Access variables from the nested configuration SMPT_CONFIG = CONFIG.get("SMPT", {}) SMTP_SERVER = SMPT_CONFIG.get("SMPTSERVER") SMTP_PORT = int(SMPT_CONFIG.get("PORT")) if SMPT_CONFIG.get("PORT") is not None else None SENDER_EMAIL = SMPT_CONFIG.get("SENDER_EMAIL") SENDER_PASSWORD = SMPT_CONFIG.get("SENDER_PASSWORD") API_CONFIG = CONFIG.get("API", {}) BASE_URL = API_CONFIG.get("BASE_URL") API_USERNAME = API_CONFIG.get("USERNAME") API_PASSWORD = API_CONFIG.get("PASSWORD") MONGODB_CONFIG = CONFIG.get("MONGODB", {}) MONGO_CONNECTION_STRING = MONGODB_CONFIG.get("MONGO_CONNECTION_STRING") DATABASE_NAME = MONGODB_CONFIG.get("DATABASE_NAME") COLLECTION_NAME = MONGODB_CONFIG.get("COLLECTION_NAME") # Validate essential configuration (more robust check) if not (SMTP_SERVER and SMTP_PORT and SENDER_EMAIL and SENDER_PASSWORD and BASE_URL and API_USERNAME and API_PASSWORD and MONGO_CONNECTION_STRING and DATABASE_NAME and COLLECTION_NAME): print("Error: One or more essential configuration variables are missing or invalid in config.json.") # Print which specific parts are missing for easier debugging missing_configs = [] if not SMTP_SERVER: missing_configs.append("SMPT.SMPTSERVER") if not SMTP_PORT: missing_configs.append("SMPT.PORT") if not SENDER_EMAIL: missing_configs.append("SMPT.SENDER_EMAIL") if not SENDER_PASSWORD: missing_configs.append("SMPT.SENDER_PASSWORD") if not BASE_URL: missing_configs.append("API.BASE_URL") if not API_USERNAME: missing_configs.append("API.USERNAME") if not API_PASSWORD: missing_configs.append("API.PASSWORD") if not MONGO_CONNECTION_STRING: missing_configs.append("MONGODB.MONGO_CONNECTION_STRING") if not DATABASE_NAME: missing_configs.append("MONGODB.DATABASE_NAME") if not COLLECTION_NAME: missing_configs.append("MONGODB.COLLECTION_NAME") print(f"Missing/Invalid: {', '.join(missing_configs)}") exit(1) # Create a default SSL context once for secure connections SSL_CONTEXT = ssl.create_default_context() # --- API Functions --- def login_api(): """Logs into the API and returns the JWT token.""" payload = {"username": API_USERNAME, "password": API_PASSWORD} headers = {"Content-Type": "application/json"} try: response = requests.post(f"{BASE_URL}/auth/login", json=payload, headers=headers, timeout=10) # Add timeout response.raise_for_status() data = response.json().get("data") # Use .get() for safer access jwt = data.get("token") if data else None # Safely get token if jwt: print("API login successful.") return jwt else: print("API response missing 'data' or 'token' key.") return None except requests.exceptions.HTTPError as e: print(f"HTTP Error during API login: {e} - Response: {getattr(e.response, 'text', 'N/A')}") return None except requests.exceptions.Timeout: print("Timeout Error during API login: The server did not respond in time.") return None except requests.exceptions.ConnectionError: print("Connection Error during API login: Could not connect to the API server. Check URL and network.") return None except requests.exceptions.RequestException as e: print(f"An unexpected error occurred during API login: {e}") return None except json.JSONDecodeError: print(f"API response is not valid JSON for login: {response.text}") return None def get_email_bodies_from_api(jwt): """Retrieves email body data from the API.""" if not jwt: print("No JWT token available to get email bodies.") return [] headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"} try: response = requests.get(f"{BASE_URL}/report/report-mail", headers=headers, timeout=10) # Add timeout response.raise_for_status() data = response.json().get("data") # Safely get data if isinstance(data, list): print(f"Successfully retrieved {len(data)} email bodies from API.") return data else: print("API response for report-mail is missing 'data' key or it's not a list.") return [] except requests.exceptions.HTTPError as e: print(f"HTTP Error when getting email bodies: {e} - Response: {getattr(e.response, 'text', 'N/A')}") return [] except requests.exceptions.Timeout: print("Timeout Error when getting email bodies: The server did not respond in time.") return [] except requests.exceptions.ConnectionError: print("Connection Error when getting email bodies: Could not connect to the API server. Check URL and network.") return [] except requests.exceptions.RequestException as e: print(f"An error occurred when getting email bodies: {e}") return [] except json.JSONDecodeError: print(f"API response for getting email bodies is not valid JSON: {response.text}") return [] # --- SMTP Functions --- def login_smtp(): """Logs into the SMTP server and returns the server object.""" server = None try: server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) server.starttls(context=SSL_CONTEXT) print("TLS connection established.") server.login(SENDER_EMAIL, SENDER_PASSWORD) print("Logged in to SMTP server successfully.") return server except smtplib.SMTPAuthenticationError as e: print(f"SMTP Authentication Error: {e}") print("Please check your email address and password/app password.") print("For Gmail, ensure 'Less secure app access' is OFF and you're using an App Password.") print("For Outlook, you might need to generate an App Password.") if server: server.quit() return None except smtplib.SMTPServerDisconnected as e: print(f"SMTP Server Disconnected Error: {e}") print("The SMTP server unexpectedly disconnected. Check network or server status.") if server: server.quit() return None except smtplib.SMTPException as e: print(f"SMTP Error: {e}") print("An error occurred during the SMTP transaction.") if server: server.quit() return None except Exception as e: print(f"An unexpected error occurred during SMTP login: {e}") if server: server.quit() return None def send_email(smtp_server, receiver_email, subject, body): """Sends an HTML email using a pre-logged-in SMTP server.""" if not smtp_server: print("SMTP server not initialized. Cannot send email.") return False msg = EmailMessage() msg["From"] = SENDER_EMAIL msg["Subject"] = subject msg.add_alternative(body, subtype="html") if isinstance(receiver_email, str): receiver_email = [receiver_email] msg["To"] = ", ".join(receiver_email) recipients_for_log = ", ".join(receiver_email) print(f"Attempting to send email from {SENDER_EMAIL} to {recipients_for_log}...") try: smtp_server.send_message(msg, from_addr=SENDER_EMAIL, to_addrs=receiver_email) print(f"Email sent successfully to {recipients_for_log}!") return True except smtplib.SMTPRecipientsRefused as e: print(f"Recipient Refused Error: {e.recipients}") print(f"Email not sent to some recipients: {recipients_for_log}. Check recipient addresses.") return False except smtplib.SMTPException as e: print(f"SMTP Error during sending: {e}") print(f"Could not send email to {recipients_for_log}.") return False except Exception as e: print(f"An unexpected error occurred while sending email: {e}") print(f"Could not send email to {recipients_for_log}.") return False # --- MongoDB Functions --- def update_mongodb_document(query, new_values, upsert=False, multi=False): """ Connects to MongoDB and updates documents in a specified collection. """ client = None # Initialize client to None for finally block try: # Establish a connection to MongoDB # The serverSelectionTimeoutMS helps prevent long waits if the server is unreachable client = pymongo.MongoClient( MONGO_CONNECTION_STRING, serverSelectionTimeoutMS=5000 ) # The ping command is cheap and does not require auth. # This will raise an exception if the connection fails. client.admin.command("ping") print("MongoDB connection successful!") # Access the specified database and collection db = client[DATABASE_NAME] collection = db[COLLECTION_NAME] print( f"Attempting to update document(s) in '{DATABASE_NAME}.{COLLECTION_NAME}'..." ) print(f"Query: {query}") print(f"New Values: {new_values}") if multi: # Update multiple documents result = collection.update_many(query, new_values, upsert=upsert) print( f"Matched {result.matched_count} document(s) and modified {result.modified_count} document(s)." ) if result.upserted_id: print(f"Upserted a new document with _id: {result.upserted_id}") else: # Update a single document result = collection.update_one(query, new_values, upsert=upsert) print( f"Matched {result.matched_count} document(s) and modified {result.modified_count} document(s)." ) if result.upserted_id: print(f"Upserted a new document with _id: {result.upserted_id}") return result except ConnectionFailure as e: print( f"MongoDB Connection Error: Could not connect to server. Please check connection string and server status. Error: {e}" ) except OperationFailure as e: print( f"MongoDB Operation Error: An error occurred during a database operation. Error: {e}" ) except PyMongoError as e: print(f"PyMongo Error: An unexpected PyMongo error occurred: {e}") except Exception as e: print(f"An unexpected error occurred: {e}") finally: # Ensure the client connection is closed if client: client.close() print("MongoDB connection closed.") return None # --- Main execution logic --- if __name__ == "__main__": jwt_token = login_api() if jwt_token: # Call add_email_body_to_api only if necessary, e.g., if you're populating the DB # For a regular report sending script, you might only need to get_email_bodies_from_api email_objects = get_email_bodies_from_api(jwt_token) if email_objects: smtp_connection = login_smtp() if smtp_connection: for item in email_objects: receivers = item.get("receivers") subject = item.get("subject") body = item.get("body") if receivers and subject and body: send_success = send_email( smtp_connection, receivers, subject, body ) if send_success: try: # Ensure you have an actual _id from your database for this to work document_id_string = item.get("id") query_by_id = {"_id": ObjectId(document_id_string)} update_by_id_values = {"$set": {"IsSent": True}} update_mongodb_document( query_by_id, update_by_id_values ) except Exception as e: print( f"Error in Example 5: Make sure the _id string is valid and exists. {e}" ) else: print( f"Skipping email due to missing data in API response: {item}" ) smtp_connection.quit() # Ensure SMTP connection is closed after all emails are sent print("SMTP connection closed.") else: print("Failed to establish SMTP connection. Cannot send emails.") else: print("No email bodies retrieved from API to send.") else: print("Failed to obtain JWT token. Cannot proceed with email operations.")