mirror of
https://github.com/draga79/NotiMail.git
synced 2024-11-09 23:51:07 +01:00
Compare commits
2 commits
ad8f8be049
...
34a3a70318
Author | SHA1 | Date | |
---|---|---|---|
34a3a70318 | |||
7602b06224 |
5 changed files with 188 additions and 168 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -1,3 +1,29 @@
|
||||||
|
### Version 1.0
|
||||||
|
|
||||||
|
#### New Features:
|
||||||
|
|
||||||
|
- **Log Rotation**: Added support for log rotation. You can now configure log rotation based on size or time. This helps in managing log file sizes and ensures that old log files are archived.
|
||||||
|
- **Thread-Safe Email Processing**: Improved the email processing to be thread-safe, ensuring that multiple email accounts can be processed simultaneously without conflicts.
|
||||||
|
- **Enhanced Configuration Validation**: Added a `validate_config` function to ensure required sections are present in the `config.ini` file before proceeding.
|
||||||
|
- **Improved Logging**: Introduced more robust logging mechanisms with options for size-based or time-based log rotation.
|
||||||
|
|
||||||
|
#### Changes:
|
||||||
|
|
||||||
|
- **Logging Enhancements**:
|
||||||
|
- Introduced `RotatingFileHandler` and `TimedRotatingFileHandler` for better log management.
|
||||||
|
- Updated the logging configuration to use parameters from `config.ini` for log rotation.
|
||||||
|
- **Codebase**:
|
||||||
|
- Refactored the `DatabaseHandler` class to use context management for better resource handling.
|
||||||
|
- Added a `Lock` mechanism in the `MultiIMAPHandler` class to ensure thread-safe email processing.
|
||||||
|
- Improved error handling and logging across the codebase to provide clearer insights and more robust operations.
|
||||||
|
- **Configuration (`config.ini`)**:
|
||||||
|
- Introduced new configuration keys under `[GENERAL]`: `LogRotationType`, `LogRotationSize`, `LogRotationInterval`, and `LogBackupCount` for log rotation settings.
|
||||||
|
|
||||||
|
#### Upgrade Notes:
|
||||||
|
|
||||||
|
- To use the new log rotation feature, update the `config.ini` file to include the new `[GENERAL]` configuration keys related to log rotation.
|
||||||
|
- Ensure that the `config.ini` file is properly validated using the new validation mechanism.
|
||||||
|
|
||||||
### Version 0.13
|
### Version 0.13
|
||||||
|
|
||||||
#### New Features:
|
#### New Features:
|
||||||
|
|
231
NotiMail.py
Executable file → Normal file
231
NotiMail.py
Executable file → Normal file
|
@ -1,13 +1,13 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
NotiMail
|
NotiMail
|
||||||
Version: 0.13
|
Version: 1.0
|
||||||
Author: Stefano Marinelli <stefano@dragas.it>
|
Author: Stefano Marinelli <stefano@dragas.it>
|
||||||
License: BSD 3-Clause License
|
License: BSD 3-Clause License
|
||||||
|
|
||||||
NotiMail is a script designed to monitor one or more email inbox(es) using the IMAP IDLE feature
|
NotiMail is a script designed to monitor one or more email inbox(es) using the IMAP IDLE feature
|
||||||
and send notifications via HTTP POST requests when a new email arrives. This version includes
|
and send notifications via HTTP POST requests when a new email arrives. This version includes
|
||||||
additional features to store processed email UIDs in a SQLite3 database and ensure they are not
|
additional features to store processed email UIDs in a SQLite3 database and ensure they are not
|
||||||
processed repeatedly.
|
processed repeatedly.
|
||||||
|
|
||||||
The script uses:
|
The script uses:
|
||||||
|
@ -30,12 +30,12 @@ Python Dependencies:
|
||||||
- apprise: for apprise notifications
|
- apprise: for apprise notifications
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
The script reads configuration data from a file named config.ini. Ensure it is properly
|
The script reads configuration data from a file named config.ini. Ensure it is properly
|
||||||
configured before running the script.
|
configured before running the script.
|
||||||
|
|
||||||
BSD 3-Clause License:
|
BSD 3-Clause License:
|
||||||
|
|
||||||
Copyright (c) 2023, Stefano Marinelli
|
Copyright (c) 2023-2024, Stefano Marinelli
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
@ -78,6 +78,8 @@ import threading
|
||||||
import apprise
|
import apprise
|
||||||
from email import policy
|
from email import policy
|
||||||
from email.parser import BytesParser
|
from email.parser import BytesParser
|
||||||
|
from threading import Lock
|
||||||
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||||
|
|
||||||
# Argument parsing to get the config file
|
# Argument parsing to get the config file
|
||||||
parser = argparse.ArgumentParser(description='NotiMail Notification Service.')
|
parser = argparse.ArgumentParser(description='NotiMail Notification Service.')
|
||||||
|
@ -91,11 +93,36 @@ args = parser.parse_args()
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(args.config)
|
config.read(args.config)
|
||||||
|
|
||||||
|
def validate_config(config):
|
||||||
|
required_sections = ['GENERAL', 'EMAIL:account1']
|
||||||
|
for section in required_sections:
|
||||||
|
if section not in config:
|
||||||
|
raise ValueError(f"Missing required section: {section}")
|
||||||
|
# Add more validation as needed
|
||||||
|
|
||||||
|
validate_config(config)
|
||||||
|
|
||||||
# Logging setup using config (or default if not set)
|
# Logging setup using config (or default if not set)
|
||||||
log_file_location = config.get('GENERAL', 'LogFileLocation', fallback='notimail.log')
|
log_file_location = config.get('GENERAL', 'LogFileLocation', fallback='notimail.log')
|
||||||
logging.basicConfig(filename=log_file_location,
|
log_rotation_type = config.get('GENERAL', 'LogRotationType', fallback='size')
|
||||||
level=logging.INFO,
|
log_rotation_size = config.getint('GENERAL', 'LogRotationSize', fallback=10485760) # 10MB
|
||||||
format='%(asctime)s - %(threadName)s - %(levelname)s - %(message)s')
|
log_rotation_interval = config.getint('GENERAL', 'LogRotationInterval', fallback=1) # 1 day
|
||||||
|
log_backup_count = config.getint('GENERAL', 'LogBackupCount', fallback=5)
|
||||||
|
|
||||||
|
# Logger configuration
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
if log_rotation_type == 'size':
|
||||||
|
handler = RotatingFileHandler(log_file_location, maxBytes=log_rotation_size, backupCount=log_backup_count)
|
||||||
|
elif log_rotation_type == 'time':
|
||||||
|
handler = TimedRotatingFileHandler(log_file_location, when='midnight', interval=log_rotation_interval, backupCount=log_backup_count)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid LogRotationType: {log_rotation_type}")
|
||||||
|
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(threadName)s - %(levelname)s - %(message)s')
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
class DatabaseHandler:
|
class DatabaseHandler:
|
||||||
def __init__(self, db_name=None):
|
def __init__(self, db_name=None):
|
||||||
|
@ -106,6 +133,12 @@ class DatabaseHandler:
|
||||||
self.create_table()
|
self.create_table()
|
||||||
self.update_schema_if_needed()
|
self.update_schema_if_needed()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.close()
|
||||||
|
|
||||||
def create_table(self):
|
def create_table(self):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS processed_emails (
|
CREATE TABLE IF NOT EXISTS processed_emails (
|
||||||
|
@ -128,14 +161,12 @@ class DatabaseHandler:
|
||||||
self.cursor.execute("CREATE UNIQUE INDEX idx_email_account_uid ON processed_emails(email_account, uid)")
|
self.cursor.execute("CREATE UNIQUE INDEX idx_email_account_uid ON processed_emails(email_account, uid)")
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|
||||||
|
|
||||||
def add_email(self, email_account, uid, notified):
|
def add_email(self, email_account, uid, notified):
|
||||||
date_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
date_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
self.cursor.execute("INSERT OR REPLACE INTO processed_emails (email_account, uid, notified, processed_date) VALUES (?, ?, ?, ?)",
|
self.cursor.execute("INSERT OR REPLACE INTO processed_emails (email_account, uid, notified, processed_date) VALUES (?, ?, ?, ?)",
|
||||||
(email_account, uid, notified, date_str))
|
(email_account, uid, notified, date_str))
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|
||||||
|
|
||||||
def is_email_notified(self, email_account, uid):
|
def is_email_notified(self, email_account, uid):
|
||||||
self.cursor.execute("SELECT * FROM processed_emails WHERE email_account = ? AND uid = ? AND notified = 1", (email_account, uid))
|
self.cursor.execute("SELECT * FROM processed_emails WHERE email_account = ? AND uid = ? AND notified = 1", (email_account, uid))
|
||||||
return bool(self.cursor.fetchone())
|
return bool(self.cursor.fetchone())
|
||||||
|
@ -152,7 +183,6 @@ class EmailProcessor:
|
||||||
def __init__(self, mail, email_account):
|
def __init__(self, mail, email_account):
|
||||||
self.mail = mail
|
self.mail = mail
|
||||||
self.email_account = email_account
|
self.email_account = email_account
|
||||||
self.db_handler = DatabaseHandler() # Create a new db_handler for each instance
|
|
||||||
|
|
||||||
def fetch_unseen_emails(self):
|
def fetch_unseen_emails(self):
|
||||||
status, messages = self.mail.uid('search', None, "UNSEEN")
|
status, messages = self.mail.uid('search', None, "UNSEEN")
|
||||||
|
@ -163,24 +193,24 @@ class EmailProcessor:
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
logging.info("Fetching the latest email...")
|
logging.info("Fetching the latest email...")
|
||||||
for message in self.fetch_unseen_emails():
|
with DatabaseHandler() as db_handler:
|
||||||
uid = message.decode('utf-8')
|
for message in self.fetch_unseen_emails():
|
||||||
if self.db_handler.is_email_notified(self.email_account, uid): # Added email_account here
|
uid = message.decode('utf-8')
|
||||||
logging.info(f"Email UID {uid} already processed and notified, skipping...")
|
if db_handler.is_email_notified(self.email_account, uid):
|
||||||
continue
|
logging.info(f"Email UID {uid} already processed and notified, skipping...")
|
||||||
|
continue
|
||||||
|
|
||||||
_, msg = self.mail.uid('fetch', message, '(BODY.PEEK[])')
|
_, msg = self.mail.uid('fetch', message, '(BODY.PEEK[])')
|
||||||
for response_part in msg:
|
for response_part in msg:
|
||||||
if isinstance(response_part, tuple):
|
if isinstance(response_part, tuple):
|
||||||
email_message = self.parse_email(response_part[1])
|
email_message = self.parse_email(response_part[1])
|
||||||
sender = email_message.get('From')
|
sender = email_message.get('From')
|
||||||
subject = email_message.get('Subject')
|
subject = email_message.get('Subject')
|
||||||
logging.info(f"Processing Email - UID: {uid}, Sender: {sender}, Subject: {subject}")
|
logging.info(f"Processing Email - UID: {uid}, Sender: {sender}, Subject: {subject}")
|
||||||
notifier.send_notification(email_message.get('From'), email_message.get('Subject'))
|
notifier.send_notification(email_message.get('From'), email_message.get('Subject'))
|
||||||
self.db_handler.add_email(self.email_account, uid, 1)
|
db_handler.add_email(self.email_account, uid, 1)
|
||||||
|
|
||||||
# Delete entries older than 7 days
|
db_handler.delete_old_emails()
|
||||||
self.db_handler.delete_old_emails()
|
|
||||||
|
|
||||||
class NotificationProvider:
|
class NotificationProvider:
|
||||||
def send_notification(self, mail_from, mail_subject):
|
def send_notification(self, mail_from, mail_subject):
|
||||||
|
@ -188,35 +218,25 @@ class NotificationProvider:
|
||||||
|
|
||||||
class AppriseNotificationProvider(NotificationProvider):
|
class AppriseNotificationProvider(NotificationProvider):
|
||||||
def __init__(self, apprise_config):
|
def __init__(self, apprise_config):
|
||||||
# Initialize the apprise object
|
|
||||||
self.apprise = apprise.Apprise()
|
self.apprise = apprise.Apprise()
|
||||||
# Add all the services by the configuration provided
|
|
||||||
for service_url in apprise_config:
|
for service_url in apprise_config:
|
||||||
self.apprise.add(service_url)
|
self.apprise.add(service_url)
|
||||||
|
|
||||||
def send_notification(self, mail_from, mail_subject):
|
def send_notification(self, mail_from, mail_subject):
|
||||||
# Prepare the notification message
|
|
||||||
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
||||||
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
||||||
message = f"{mail_from}"
|
message = f"{mail_from}"
|
||||||
|
|
||||||
# Send the notification
|
|
||||||
if not self.apprise.notify(title=mail_subject, body=message):
|
if not self.apprise.notify(title=mail_subject, body=message):
|
||||||
# If notification fails, log the failure
|
|
||||||
logging.error(f"Failed to send notification via Apprise.")
|
logging.error(f"Failed to send notification via Apprise.")
|
||||||
print(f"Failed to send notification via Apprise.")
|
|
||||||
|
|
||||||
|
|
||||||
class NTFYNotificationProvider(NotificationProvider):
|
class NTFYNotificationProvider(NotificationProvider):
|
||||||
def __init__(self, ntfy_data):
|
def __init__(self, ntfy_data):
|
||||||
#self.ntfy_urls = ntfy_urls # Expecting a list of URLs
|
self.ntfy_data = ntfy_data
|
||||||
self.ntfy_data = ntfy_data # Expecting a list of (URL, Token) tuples
|
|
||||||
|
|
||||||
def send_notification(self, mail_from, mail_subject):
|
def send_notification(self, mail_from, mail_subject):
|
||||||
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
||||||
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
||||||
|
|
||||||
# Encode the strings in UTF-8
|
|
||||||
encoded_from = mail_from.encode('utf-8')
|
encoded_from = mail_from.encode('utf-8')
|
||||||
encoded_subject = mail_subject.encode('utf-8')
|
encoded_subject = mail_subject.encode('utf-8')
|
||||||
|
|
||||||
|
@ -226,35 +246,25 @@ class NTFYNotificationProvider(NotificationProvider):
|
||||||
headers["Authorization"] = f"Bearer {token}"
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post(ntfy_url, data=encoded_from, headers=headers)
|
||||||
ntfy_url,
|
|
||||||
data=encoded_from,
|
|
||||||
headers=headers
|
|
||||||
)
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
print(f"Notification sent successfully to {ntfy_url}!")
|
|
||||||
logging.info(f"Notification sent successfully to {ntfy_url} via ntfy")
|
logging.info(f"Notification sent successfully to {ntfy_url} via ntfy")
|
||||||
else:
|
else:
|
||||||
print(f"Failed to send notification to {ntfy_url}. Status Code:", response.status_code)
|
|
||||||
logging.error(f"Failed to send notification to {ntfy_url} via NTFY. Status Code: {response.status_code}")
|
logging.error(f"Failed to send notification to {ntfy_url} via NTFY. Status Code: {response.status_code}")
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
print(f"An error occurred while sending notification to {ntfy_url}: {str(e)}")
|
|
||||||
logging.error(f"An error occurred while sending notification to {ntfy_url} via NTFY: {str(e)}")
|
logging.error(f"An error occurred while sending notification to {ntfy_url} via NTFY: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
time.sleep(2) # Ensure a delay between notifications
|
time.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PushoverNotificationProvider(NotificationProvider):
|
class PushoverNotificationProvider(NotificationProvider):
|
||||||
def __init__(self, api_token, user_key):
|
def __init__(self, api_token, user_key):
|
||||||
self.api_token = api_token
|
self.api_token = api_token
|
||||||
self.user_key = user_key
|
self.user_key = user_key
|
||||||
self.pushover_url = "https://api.pushover.net/1/messages.json"
|
self.pushover_url = "https://api.pushover.net/1/messages.json"
|
||||||
|
|
||||||
def send_notification(self, mail_from, mail_subject):
|
def send_notification(self, mail_from, mail_subject):
|
||||||
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
||||||
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
||||||
|
|
||||||
message = f"From: {mail_from}\nSubject: {mail_subject}"
|
message = f"From: {mail_from}\nSubject: {mail_subject}"
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -266,48 +276,38 @@ class PushoverNotificationProvider(NotificationProvider):
|
||||||
try:
|
try:
|
||||||
response = requests.post(self.pushover_url, data=data)
|
response = requests.post(self.pushover_url, data=data)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
print("Notification sent successfully via Pushover!")
|
|
||||||
logging.info(f"Notification sent successfully via Pushover")
|
logging.info(f"Notification sent successfully via Pushover")
|
||||||
else:
|
else:
|
||||||
print(f"Failed to send notification via Pushover. Status Code:", response.status_code)
|
logging.error(f"Failed to send notification via Pushover. Status Code: {response.status_code}")
|
||||||
logging.error(f"Failed to send notification to via Pushover. Status Code: {response.status_code}")
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
print(f"An error occurred while sending notification via Pushover: {str(e)}")
|
|
||||||
logging.error(f"An error occurred while sending notification via Pushover: {str(e)}")
|
logging.error(f"An error occurred while sending notification via Pushover: {str(e)}")
|
||||||
|
|
||||||
class GotifyNotificationProvider(NotificationProvider):
|
class GotifyNotificationProvider(NotificationProvider):
|
||||||
def __init__(self, gotify_url, gotify_token):
|
def __init__(self, gotify_url, gotify_token):
|
||||||
self.gotify_url = gotify_url
|
self.gotify_url = gotify_url
|
||||||
self.gotify_token = gotify_token
|
self.gotify_token = gotify_token
|
||||||
|
|
||||||
def send_notification(self, mail_from, mail_subject):
|
def send_notification(self, mail_from, mail_subject):
|
||||||
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
mail_subject = mail_subject if mail_subject is not None else "No Subject"
|
||||||
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
mail_from = mail_from if mail_from is not None else "Unknown Sender"
|
||||||
|
|
||||||
message = f"From: {mail_from}\nSubject: {mail_subject}"
|
message = f"From: {mail_from}\nSubject: {mail_subject}"
|
||||||
|
|
||||||
# Include the token in the URL
|
|
||||||
url_with_token = f"{self.gotify_url}?token={self.gotify_token}"
|
url_with_token = f"{self.gotify_url}?token={self.gotify_token}"
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"title": mail_subject,
|
"title": mail_subject,
|
||||||
"message": message,
|
"message": message,
|
||||||
"priority": 5 # Adjust priority as needed
|
"priority": 5
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url_with_token, json=payload)
|
response = requests.post(url_with_token, json=payload)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
print("Notification sent successfully via Gotify!")
|
|
||||||
logging.info(f"Notification sent successfully via Gotify")
|
logging.info(f"Notification sent successfully via Gotify")
|
||||||
else:
|
else:
|
||||||
print(f"Failed to send notification via Gotify. Status Code: {response.status_code}")
|
|
||||||
logging.error(f"Failed to send notification via Gotify. Status Code: {response.status_code}")
|
logging.error(f"Failed to send notification via Gotify. Status Code: {response.status_code}")
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
print(f"An error occurred while sending notification via Gotify: {str(e)}")
|
|
||||||
logging.error(f"An error occurred while sending notification via Gotify: {str(e)}")
|
logging.error(f"An error occurred while sending notification via Gotify: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
class Notifier:
|
class Notifier:
|
||||||
def __init__(self, providers):
|
def __init__(self, providers):
|
||||||
self.providers = providers
|
self.providers = providers
|
||||||
|
@ -316,7 +316,6 @@ class Notifier:
|
||||||
for provider in self.providers:
|
for provider in self.providers:
|
||||||
provider.send_notification(mail_from, mail_subject)
|
provider.send_notification(mail_from, mail_subject)
|
||||||
|
|
||||||
|
|
||||||
class IMAPHandler:
|
class IMAPHandler:
|
||||||
def __init__(self, host, email_user, email_pass, folder="inbox"):
|
def __init__(self, host, email_user, email_pass, folder="inbox"):
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -331,12 +330,11 @@ class IMAPHandler:
|
||||||
self.mail.login(self.email_user, self.email_pass)
|
self.mail.login(self.email_user, self.email_pass)
|
||||||
self.mail.select(self.folder)
|
self.mail.select(self.folder)
|
||||||
except imaplib.IMAP4.error as e:
|
except imaplib.IMAP4.error as e:
|
||||||
print(f"Cannot connect: {str(e)}")
|
logging.error(f"Cannot connect: {str(e)}")
|
||||||
notifier.send_notification("Script Error", f"Cannot connect: {str(e)}")
|
notifier.send_notification("Script Error", f"Cannot connect: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
print("IDLE mode started. Waiting for new email...")
|
|
||||||
logging.info(f"[{self.email_user} - {self.folder}] IDLE mode started. Waiting for new email...")
|
logging.info(f"[{self.email_user} - {self.folder}] IDLE mode started. Waiting for new email...")
|
||||||
try:
|
try:
|
||||||
tag = self.mail._new_tag().decode()
|
tag = self.mail._new_tag().decode()
|
||||||
|
@ -345,7 +343,6 @@ class IMAPHandler:
|
||||||
while True:
|
while True:
|
||||||
line = self.mail.readline()
|
line = self.mail.readline()
|
||||||
if line:
|
if line:
|
||||||
print(line.decode('utf-8'))
|
|
||||||
if b'BYE' in line:
|
if b'BYE' in line:
|
||||||
raise ConnectionAbortedError("Received BYE from server. Trying to reconnect...")
|
raise ConnectionAbortedError("Received BYE from server. Trying to reconnect...")
|
||||||
if b'EXISTS' in line:
|
if b'EXISTS' in line:
|
||||||
|
@ -353,80 +350,70 @@ class IMAPHandler:
|
||||||
self.mail.send(b'DONE\r\n')
|
self.mail.send(b'DONE\r\n')
|
||||||
self.mail.readline()
|
self.mail.readline()
|
||||||
except imaplib.IMAP4.abort as e:
|
except imaplib.IMAP4.abort as e:
|
||||||
print(f"Connection closed by server: {str(e)}")
|
|
||||||
logging.error(f"[{self.email_user}] Connection closed by server: {str(e)}")
|
logging.error(f"[{self.email_user}] Connection closed by server: {str(e)}")
|
||||||
notifier.send_notification("Script Error", f"Connection closed by server: {str(e)}")
|
notifier.send_notification("Script Error", f"Connection closed by server: {str(e)}")
|
||||||
raise ConnectionAbortedError("Connection lost. Trying to reconnect...")
|
raise ConnectionAbortedError("Connection lost. Trying to reconnect...")
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
print("Socket timeout during IDLE, re-establishing connection...")
|
|
||||||
logging.info(f"[{self.email_user}] Socket timeout during IDLE, re-establishing connection...")
|
logging.info(f"[{self.email_user}] Socket timeout during IDLE, re-establishing connection...")
|
||||||
raise ConnectionAbortedError("Socket timeout. Trying to reconnect...")
|
raise ConnectionAbortedError("Socket timeout. Trying to reconnect...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"An error occurred: {str(e)}")
|
logging.error(f"[{self.email_user}] An error occurred: {str(e)}")
|
||||||
logging.info(f"[{self.email_user}] An error occurred: {str(e)}")
|
|
||||||
notifier.send_notification("Script Error", f"An error occurred: {str(e)}")
|
notifier.send_notification("Script Error", f"An error occurred: {str(e)}")
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
print("IDLE mode stopped.")
|
|
||||||
logging.info(f"[{self.email_user}] IDLE mode stopped.")
|
logging.info(f"[{self.email_user}] IDLE mode stopped.")
|
||||||
|
|
||||||
def process_emails(self):
|
def process_emails(self):
|
||||||
processor = EmailProcessor(self.mail, self.email_user) # Pass the email_user (account) to the processor
|
processor = EmailProcessor(self.mail, self.email_user)
|
||||||
processor.process()
|
processor.process()
|
||||||
|
|
||||||
|
|
||||||
class MultiIMAPHandler:
|
class MultiIMAPHandler:
|
||||||
def __init__(self, accounts):
|
def __init__(self, accounts):
|
||||||
self.accounts = accounts
|
self.accounts = accounts
|
||||||
self.handlers = [IMAPHandler(account['Host'], account['EmailUser'], account['EmailPass'], account['Folder']) for account in accounts]
|
self.handlers = [IMAPHandler(account['Host'], account['EmailUser'], account['EmailPass'], account['Folder']) for account in accounts]
|
||||||
|
self.lock = Lock()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
threads = []
|
threads = []
|
||||||
for handler in self.handlers:
|
for handler in self.handlers:
|
||||||
#thread = threading.Thread(target=self.monitor_account, args=(handler,))
|
|
||||||
thread = threading.Thread(target=self.monitor_account, args=(handler,), name=handler.email_user)
|
thread = threading.Thread(target=self.monitor_account, args=(handler,), name=handler.email_user)
|
||||||
thread.daemon = True # Set thread as daemon
|
thread.daemon = True
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
for thread in threads:
|
for thread in threads:
|
||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
@staticmethod
|
def monitor_account(self, handler):
|
||||||
def monitor_account(handler):
|
|
||||||
print(f"Monitoring {handler.email_user} - Folder: {handler.folder}")
|
|
||||||
logging.info(f"Monitoring {handler.email_user} - Folder: {handler.folder}")
|
logging.info(f"Monitoring {handler.email_user} - Folder: {handler.folder}")
|
||||||
while True: # Add a loop to keep retrying on connection loss
|
while True:
|
||||||
try:
|
try:
|
||||||
handler.connect()
|
handler.connect()
|
||||||
while True:
|
while True:
|
||||||
handler.idle()
|
handler.idle()
|
||||||
handler.process_emails()
|
with self.lock:
|
||||||
|
handler.process_emails()
|
||||||
except ConnectionAbortedError as e:
|
except ConnectionAbortedError as e:
|
||||||
print(str(e))
|
logging.error(str(e))
|
||||||
time.sleep(30) # Sleep for 30 seconds before retrying
|
time.sleep(30)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"An unexpected error occurred: {str(e)}")
|
|
||||||
logging.error(f"An unexpected error occurred: {str(e)}")
|
logging.error(f"An unexpected error occurred: {str(e)}")
|
||||||
notifier.send_notification("Script Error", f"An unexpected error occurred: {str(e)}")
|
notifier.send_notification("Script Error", f"An unexpected error occurred: {str(e)}")
|
||||||
break
|
break
|
||||||
|
|
||||||
def shutdown_handler(signum, frame):
|
def shutdown_handler(signum, frame):
|
||||||
print("Shutdown signal received. Cleaning up...")
|
|
||||||
logging.info(f"Shutdown signal received. Cleaning up...")
|
logging.info(f"Shutdown signal received. Cleaning up...")
|
||||||
try:
|
try:
|
||||||
for handler in MultiIMAPHandler.handlers:
|
for handler in MultiIMAPHandler.handlers:
|
||||||
handler.mail.logout()
|
handler.mail.logout()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
print("Cleanup complete. Exiting.")
|
|
||||||
logging.info(f"Cleanup complete. Exiting.")
|
logging.info(f"Cleanup complete. Exiting.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def multi_account_main():
|
def multi_account_main():
|
||||||
accounts = []
|
accounts = []
|
||||||
|
|
||||||
# Check for the old format [EMAIL] section
|
|
||||||
if 'EMAIL' in config.sections():
|
if 'EMAIL' in config.sections():
|
||||||
old_account = {
|
old_account = {
|
||||||
'EmailUser': config['EMAIL']['EmailUser'],
|
'EmailUser': config['EMAIL']['EmailUser'],
|
||||||
|
@ -435,7 +422,6 @@ def multi_account_main():
|
||||||
}
|
}
|
||||||
accounts.append(old_account)
|
accounts.append(old_account)
|
||||||
|
|
||||||
# Check for new format [EMAIL:accountX]
|
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
if section.startswith("EMAIL:"):
|
if section.startswith("EMAIL:"):
|
||||||
folders = config[section].get('Folders', 'inbox').split(', ')
|
folders = config[section].get('Folders', 'inbox').split(', ')
|
||||||
|
@ -451,17 +437,16 @@ def multi_account_main():
|
||||||
providers = []
|
providers = []
|
||||||
|
|
||||||
if 'APPRISE' in config:
|
if 'APPRISE' in config:
|
||||||
apprise_urls = config['APPRISE']['urls'].split(',') # Assuming urls is a comma-separated list in the config
|
apprise_urls = config['APPRISE']['urls'].split(',')
|
||||||
providers.append(AppriseNotificationProvider(apprise_urls))
|
providers.append(AppriseNotificationProvider(apprise_urls))
|
||||||
|
|
||||||
|
|
||||||
if 'NTFY' in config:
|
if 'NTFY' in config:
|
||||||
ntfy_data = []
|
ntfy_data = []
|
||||||
for key in config['NTFY']:
|
for key in config['NTFY']:
|
||||||
if key.startswith("url"):
|
if key.startswith("url"):
|
||||||
url = config['NTFY'][key]
|
url = config['NTFY'][key]
|
||||||
token_key = "token" + key[3:]
|
token_key = "token" + key[3:]
|
||||||
token = config['NTFY'].get(token_key, None) # Retrieve the token if it exists, else default to None
|
token = config['NTFY'].get(token_key, None)
|
||||||
ntfy_data.append((url, token))
|
ntfy_data.append((url, token))
|
||||||
providers.append(NTFYNotificationProvider(ntfy_data))
|
providers.append(NTFYNotificationProvider(ntfy_data))
|
||||||
|
|
||||||
|
@ -483,13 +468,11 @@ def multi_account_main():
|
||||||
signal.signal(signal.SIGTERM, shutdown_handler)
|
signal.signal(signal.SIGTERM, shutdown_handler)
|
||||||
signal.signal(signal.SIGINT, shutdown_handler)
|
signal.signal(signal.SIGINT, shutdown_handler)
|
||||||
|
|
||||||
print("Script started. Press Ctrl+C to stop it anytime.")
|
|
||||||
logging.info(f"Script started. Press Ctrl+C to stop it anytime.")
|
logging.info(f"Script started. Press Ctrl+C to stop it anytime.")
|
||||||
|
|
||||||
multi_handler = MultiIMAPHandler(accounts)
|
multi_handler = MultiIMAPHandler(accounts)
|
||||||
multi_handler.run()
|
multi_handler.run()
|
||||||
|
|
||||||
print("Logging out and closing the connection...")
|
|
||||||
logging.info(f"Logging out and closing the connection...")
|
logging.info(f"Logging out and closing the connection...")
|
||||||
try:
|
try:
|
||||||
for handler in MultiIMAPHandler.handlers:
|
for handler in MultiIMAPHandler.handlers:
|
||||||
|
@ -498,9 +481,6 @@ def multi_account_main():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def print_config():
|
def print_config():
|
||||||
"""
|
|
||||||
Function to print the configuration options from config.ini
|
|
||||||
"""
|
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
print(f"[{section}]")
|
print(f"[{section}]")
|
||||||
for key, value in config[section].items():
|
for key, value in config[section].items():
|
||||||
|
@ -508,24 +488,19 @@ def print_config():
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def test_config():
|
def test_config():
|
||||||
"""
|
|
||||||
Function to test the configuration options
|
|
||||||
"""
|
|
||||||
# Test Email accounts
|
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
if section.startswith("EMAIL:"):
|
if section.startswith("EMAIL:"):
|
||||||
print(f"Testing {section}...")
|
logging.info(f"Testing {section}...")
|
||||||
handler = IMAPHandler(config[section]['Host'], config[section]['EmailUser'], config[section]['EmailPass'])
|
handler = IMAPHandler(config[section]['Host'], config[section]['EmailUser'], config[section]['EmailPass'])
|
||||||
try:
|
try:
|
||||||
handler.connect()
|
handler.connect()
|
||||||
print(f"Connection successful for {section}")
|
logging.info(f"Connection successful for {section}")
|
||||||
handler.mail.logout() # Explicitly logging out after testing
|
handler.mail.logout()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Connection failed for {section}. Reason: {str(e)}")
|
logging.error(f"Connection failed for {section}. Reason: {str(e)}")
|
||||||
|
|
||||||
# Testing NTFY Notification Provider
|
|
||||||
if 'NTFY' in config:
|
if 'NTFY' in config:
|
||||||
print("Testing NTFY Notification Provider...")
|
logging.info("Testing NTFY Notification Provider...")
|
||||||
ntfy_data = []
|
ntfy_data = []
|
||||||
for key in config['NTFY']:
|
for key in config['NTFY']:
|
||||||
if key.startswith("url"):
|
if key.startswith("url"):
|
||||||
|
@ -536,50 +511,43 @@ def test_config():
|
||||||
ntfy_provider = NTFYNotificationProvider(ntfy_data)
|
ntfy_provider = NTFYNotificationProvider(ntfy_data)
|
||||||
try:
|
try:
|
||||||
ntfy_provider.send_notification("Test Sender", "Test Notification from NotiMail")
|
ntfy_provider.send_notification("Test Sender", "Test Notification from NotiMail")
|
||||||
print("Test notification sent successfully via NTFY!")
|
logging.info("Test notification sent successfully via NTFY!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to send test notification via NTFY. Reason: {str(e)}")
|
logging.error(f"Failed to send test notification via NTFY. Reason: {str(e)}")
|
||||||
|
|
||||||
# Testing Pushover Notification Provider
|
|
||||||
if 'PUSHOVER' in config:
|
if 'PUSHOVER' in config:
|
||||||
print("Testing Pushover Notification Provider...")
|
logging.info("Testing Pushover Notification Provider...")
|
||||||
pushover_provider = PushoverNotificationProvider(config['PUSHOVER']['ApiToken'], config['PUSHOVER']['UserKey'])
|
pushover_provider = PushoverNotificationProvider(config['PUSHOVER']['ApiToken'], config['PUSHOVER']['UserKey'])
|
||||||
try:
|
try:
|
||||||
pushover_provider.send_notification("Test Sender", "Test Notification from NotiMail")
|
pushover_provider.send_notification("Test Sender", "Test Notification from NotiMail")
|
||||||
print("Test notification sent successfully via Pushover!")
|
logging.info("Test notification sent successfully via Pushover!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to send test notification via Pushover. Reason: {str(e)}")
|
logging.error(f"Failed to send test notification via Pushover. Reason: {str(e)}")
|
||||||
|
|
||||||
# Testing Gotify Notification Provider
|
|
||||||
if 'GOTIFY' in config:
|
if 'GOTIFY' in config:
|
||||||
print("Testing Gotify Notification Provider...")
|
logging.info("Testing Gotify Notification Provider...")
|
||||||
gotify_provider = GotifyNotificationProvider(config['GOTIFY']['Url'], config['GOTIFY']['Token'])
|
gotify_provider = GotifyNotificationProvider(config['GOTIFY']['Url'], config['GOTIFY']['Token'])
|
||||||
try:
|
try:
|
||||||
gotify_provider.send_notification("Test Sender", "Test Notification from NotiMail")
|
gotify_provider.send_notification("Test Sender", "Test Notification from NotiMail")
|
||||||
print("Test notification sent successfully via Gotify!")
|
logging.info("Test notification sent successfully via Gotify!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to send test notification via Gotify. Reason: {str(e)}")
|
logging.error(f"Failed to send test notification via Gotify. Reason: {str(e)}")
|
||||||
|
|
||||||
print("Testing done!")
|
|
||||||
|
|
||||||
|
logging.info("Testing done!")
|
||||||
|
|
||||||
def list_imap_folders():
|
def list_imap_folders():
|
||||||
"""
|
|
||||||
Function to list all IMAP folders of the configured mailboxes
|
|
||||||
"""
|
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
if section.startswith("EMAIL:"):
|
if section.startswith("EMAIL:"):
|
||||||
print(f"Listing folders for {section}...")
|
logging.info(f"Listing folders for {section}...")
|
||||||
handler = IMAPHandler(config[section]['Host'], config[section]['EmailUser'], config[section]['EmailPass'])
|
handler = IMAPHandler(config[section]['Host'], config[section]['EmailUser'], config[section]['EmailPass'])
|
||||||
try:
|
try:
|
||||||
handler.connect()
|
handler.connect()
|
||||||
typ, folders = handler.mail.list()
|
typ, folders = handler.mail.list()
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
print(folder.decode())
|
print(folder.decode())
|
||||||
handler.mail.logout() # Explicitly logging out after listing folders
|
handler.mail.logout()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to list folders for {section}. Reason: {str(e)}")
|
logging.error(f"Failed to list folders for {section}. Reason: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if args.print_config:
|
if args.print_config:
|
||||||
|
@ -590,4 +558,3 @@ if __name__ == "__main__":
|
||||||
list_imap_folders()
|
list_imap_folders()
|
||||||
else:
|
else:
|
||||||
multi_account_main()
|
multi_account_main()
|
||||||
|
|
||||||
|
|
60
README.md
60
README.md
|
@ -1,6 +1,6 @@
|
||||||
# NotiMail 📧
|
# NotiMail 📧
|
||||||
|
|
||||||
**Version 0.13 is here, read the changelog for more information!**
|
**Version 1.0 is here, read the changelog for more information!**
|
||||||
|
|
||||||
Stay connected without the constant drain on your battery. Introducing **NotiMail** - the future of server-side email notifications supporting multiple push providers and multiple email accounts!
|
Stay connected without the constant drain on your battery. Introducing **NotiMail** - the future of server-side email notifications supporting multiple push providers and multiple email accounts!
|
||||||
|
|
||||||
|
@ -8,21 +8,28 @@ Mobile devices often use IMAP IDLE, maintaining a persistent connection to ensur
|
||||||
|
|
||||||
## Features 🌟
|
## Features 🌟
|
||||||
|
|
||||||
- **Monitors Multiple Emails on the Server**: From version 0.9 onwards, NotiMail can monitor multiple email accounts. Ensure you never miss an email regardless of which account it's sent to.
|
- **Monitors Multiple Emails on the Server**: From version 0.9 onwards, NotiMail can monitor multiple email accounts. Ensure you never miss an email regardless of which account it's sent to.
|
||||||
|
|
||||||
- **Monitors Multiple Folders per Account**: With version 0.12, you can configure NotiMail to monitor multiple folders for each email account. Whether it's your 'inbox', 'junk', or any other folder, stay informed with NotiMail.
|
- **Monitors Multiple Folders per Account**: With version 0.12, you can configure NotiMail to monitor multiple folders for each email account. Whether it's your 'inbox', 'junk', or any other folder, stay informed with NotiMail.
|
||||||
|
|
||||||
- **Processes and Notifies**: Once a new email is detected, NotiMail swiftly processes its details.
|
- **Processes and Notifies**: Once a new email is detected, NotiMail swiftly processes its details.
|
||||||
|
|
||||||
- **Leverages Multiple Push Providers for Alerts**: Rather than having your device always on alert, NotiMail sends notifications via multiple push providers, ensuring you're promptly informed.
|
- **Leverages Multiple Push Providers for Alerts**: Rather than having your device always on alert, NotiMail sends notifications via multiple push providers, ensuring you're promptly informed.
|
||||||
|
|
||||||
- **Expanded Notification Capabilities**: Version 0.13 introduces support for Apprise, allowing for an extensive array of notification services through a single interface.
|
- **Expanded Notification Capabilities**: Version 0.13 introduced support for Apprise, allowing for an extensive array of notification services through a single interface.
|
||||||
|
|
||||||
- **Database Integration**: NotiMail uses an SQLite3 database to store and manage processed email UIDs, preventing repeated processing.
|
- **Log Rotation**: Version 1.0 introduces log rotation, which can be configured based on size or time, ensuring efficient log management.
|
||||||
|
|
||||||
- **Built for Resilience**: With connectivity hiccups in mind, NotiMail ensures you're always the first to know.
|
- **Thread-Safe Email Processing**: Improved thread safety in email processing, ensuring that multiple email accounts can be processed simultaneously without conflicts.
|
||||||
|
|
||||||
- **Multiple And Different Push providers supported**: You can use one or more of the supported Push providers - all support authentication, which now includes ntfy, Gotify, Pushover, and a wide range through Apprise.
|
- **Enhanced Configuration Validation**: Added configuration validation to ensure all required settings are present before running the script.
|
||||||
|
|
||||||
|
- **Database Integration**: NotiMail uses an SQLite3 database to store and manage processed email UIDs, preventing repeated processing.
|
||||||
|
|
||||||
|
- **Built for Resilience**: With connectivity hiccups in mind, NotiMail ensures you're always the first to know.
|
||||||
|
|
||||||
|
- **Multiple and Different Push Providers Supported**: You can use one or more of the supported push providers - all support authentication, which now includes NTFY, Gotify, Pushover, and a wide range through Apprise.
|
||||||
|
|
||||||
|
|
||||||
## Benefits 🚀
|
## Benefits 🚀
|
||||||
|
|
||||||
|
@ -50,7 +57,6 @@ Mobile devices often use IMAP IDLE, maintaining a persistent connection to ensur
|
||||||
|
|
||||||
Contributions, feedback, and stars ⭐ are always welcome.
|
Contributions, feedback, and stars ⭐ are always welcome.
|
||||||
|
|
||||||
|
|
||||||
## NotiMail Installation Walkthrough
|
## NotiMail Installation Walkthrough
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
@ -67,36 +73,36 @@ Ensure you have Python installed on your machine. NotiMail is written in Python,
|
||||||
|
|
||||||
If you've hosted `NotiMail` on a platform like GitHub, provide the link and the command. For this example, I'll use a placeholder link:
|
If you've hosted `NotiMail` on a platform like GitHub, provide the link and the command. For this example, I'll use a placeholder link:
|
||||||
|
|
||||||
`git clone https://github.com/draga79/NotiMail.git`
|
`git clone https://github.com/draga79/NotiMail.git`
|
||||||
|
|
||||||
If you're not using version control, ensure users have a link to download the `.zip` or `.tar.gz` of the project and then extract it.
|
If you're not using version control, ensure users have a link to download the `.zip` or `.tar.gz` of the project and then extract it.
|
||||||
|
|
||||||
**2. Navigate to the NotiMail Directory:**
|
**2. Navigate to the NotiMail Directory:**
|
||||||
|
|
||||||
`cd NotiMail`
|
`cd NotiMail`
|
||||||
|
|
||||||
**3. Set Up a Virtual Environment (Optional but Recommended):**
|
**3. Set Up a Virtual Environment (Optional but Recommended):**
|
||||||
|
|
||||||
A virtual environment ensures that the dependencies for the project don't interfere with your other Python projects or system libraries.
|
A virtual environment ensures that the dependencies for the project don't interfere with your other Python projects or system libraries.
|
||||||
|
|
||||||
`python -m venv notimail-env`
|
`python -m venv notimail-env`
|
||||||
|
|
||||||
Activate the virtual environment:
|
Activate the virtual environment:
|
||||||
|
|
||||||
- On macOS and Linux:
|
- On macOS and Linux:
|
||||||
|
|
||||||
- `source notimail-env/bin/activate`
|
`source notimail-env/bin/activate`
|
||||||
|
|
||||||
- On Windows:
|
- On Windows:
|
||||||
|
|
||||||
- `.\notimail-env\Scripts\activate`
|
`.\notimail-env\Scripts\activate`
|
||||||
|
|
||||||
|
|
||||||
**4. Install the Required Libraries:**
|
**4. Install the Required Libraries:**
|
||||||
|
|
||||||
Install the necessary Python libraries using `pip`, for example:
|
Install the necessary Python libraries using `pip`, for example:
|
||||||
|
|
||||||
`pip install requests`
|
`pip install requests configparser sqlite3 datetime signal logging argparse threading apprise`
|
||||||
|
|
||||||
**5. Configure NotiMail:**
|
**5. Configure NotiMail:**
|
||||||
|
|
||||||
|
@ -104,7 +110,7 @@ Open the `config.ini` file in a text editor. From version 0.9, you can configure
|
||||||
|
|
||||||
**6. Run NotiMail:**
|
**6. Run NotiMail:**
|
||||||
|
|
||||||
`python NotiMail.py`
|
`python NotiMail.py`
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -112,7 +118,7 @@ Open the `config.ini` file in a text editor. From version 0.9, you can configure
|
||||||
|
|
||||||
1. **Python Not Found**: Ensure Python is installed and added to your system's PATH.
|
1. **Python Not Found**: Ensure Python is installed and added to your system's PATH.
|
||||||
|
|
||||||
3. **Dependencies Missing**: If the script raises an error about missing modules, ensure you've activated your virtual environment and installed all necessary libraries.
|
2. **Dependencies Missing**: If the script raises an error about missing modules, ensure you've activated your virtual environment and installed all necessary libraries.
|
||||||
|
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
[GENERAL]
|
[GENERAL]
|
||||||
LogFileLocation = /var/log/notimail/notimail.log
|
LogFileLocation = /var/log/notimail/notimail.log
|
||||||
DataBaseLocation = /var/cache/notimail/processed_emails.db
|
DataBaseLocation = /var/cache/notimail/processed_emails.db
|
||||||
|
#Logorotation can be "size" or "time"
|
||||||
|
LogRotationType = size
|
||||||
|
#LogRotationSize - Only if size is selected - default is 10 MB
|
||||||
|
LogRotationSize = 10485760
|
||||||
|
#LogRotationInterval - Only if time is selected - in days
|
||||||
|
LogRotationInterval = 7
|
||||||
|
LogBackupCount = 5
|
||||||
|
|
||||||
[EMAIL:account1]
|
[EMAIL:account1]
|
||||||
EmailUser = your@address.com
|
EmailUser = your@address.com
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
.\" Man page for NotiMail
|
.\" Man page for NotiMail
|
||||||
.TH MAN 1 "24 October 2023"
|
.TH MAN 1 "2 July 2024"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
NotiMail \- Monitor email inbox(es) and send notifications upon new email arrivals using various providers.
|
NotiMail \- Monitor email inbox(es) and send notifications upon new email arrivals using various providers.
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B notimail
|
.B notimail
|
||||||
[\fB-c\fR \fICONFIG\fR]
|
[\fB-c\fR \fICONFIG\fR]
|
||||||
[\fB--check-config\fR]
|
[\fB--print-config\fR]
|
||||||
[\fB--test-config\fR]
|
[\fB--test-config\fR]
|
||||||
[\fB--list-folders\fR]
|
[\fB--list-folders\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
NotiMail is a script designed to monitor one or more email inboxes using the IMAP IDLE feature. It sends notifications when a new email arrives. The script can connect to multiple email servers and send notifications to different platforms, including NTFY, Pushover, and Gotify.
|
NotiMail is a script designed to monitor one or more email inboxes using the IMAP IDLE feature. It sends notifications when a new email arrives. The script can connect to multiple email servers and send notifications to different platforms, including NTFY, Pushover, Gotify, and Apprise.
|
||||||
.P
|
.P
|
||||||
Key features:
|
Key features:
|
||||||
.RS
|
.RS
|
||||||
|
@ -22,7 +22,7 @@ Sends notifications containing the sender and subject of new emails.
|
||||||
.IP "*"
|
.IP "*"
|
||||||
Maintains a SQLite database to ensure that emails are not processed repeatedly.
|
Maintains a SQLite database to ensure that emails are not processed repeatedly.
|
||||||
.IP "*"
|
.IP "*"
|
||||||
Supports multiple notification providers: NTFY, Pushover, and Gotify.
|
Supports multiple notification providers: NTFY, Pushover, Gotify, and Apprise.
|
||||||
.IP "*"
|
.IP "*"
|
||||||
Provides command-line functionalities to validate, test, and list IMAP folders.
|
Provides command-line functionalities to validate, test, and list IMAP folders.
|
||||||
.RE
|
.RE
|
||||||
|
@ -31,11 +31,11 @@ Provides command-line functionalities to validate, test, and list IMAP folders.
|
||||||
\fB-c\fR, \fB--config\fR \fICONFIG\fR
|
\fB-c\fR, \fB--config\fR \fICONFIG\fR
|
||||||
Specify the path to the configuration file. Defaults to \fIconfig.ini\fR.
|
Specify the path to the configuration file. Defaults to \fIconfig.ini\fR.
|
||||||
.TP
|
.TP
|
||||||
\fB--check-config\fR
|
\fB--print-config\fR
|
||||||
Check and print the configurations set in the `config.ini` file.
|
Print the configurations set in the \fIconfig.ini\fR file.
|
||||||
.TP
|
.TP
|
||||||
\fB--test-config\fR
|
\fB--test-config\fR
|
||||||
Test if the configurations set in the `config.ini` file are working properly.
|
Test if the configurations set in the \fIconfig.ini\fR file are working properly.
|
||||||
.TP
|
.TP
|
||||||
\fB--list-folders\fR
|
\fB--list-folders\fR
|
||||||
List all the IMAP folders of the configured mailboxes.
|
List all the IMAP folders of the configured mailboxes.
|
||||||
|
@ -48,6 +48,14 @@ Configuration data is read from a file named \fIconfig.ini\fR. Ensure it's corre
|
||||||
Path to the log file.
|
Path to the log file.
|
||||||
.IP DataBaseLocation:
|
.IP DataBaseLocation:
|
||||||
Path to the SQLite3 database for storing processed emails.
|
Path to the SQLite3 database for storing processed emails.
|
||||||
|
.IP LogRotationType:
|
||||||
|
Type of log rotation (\fIsize\fR or \fItime\fR).
|
||||||
|
.IP LogRotationSize:
|
||||||
|
Size threshold for log rotation (only used if \fILogRotationType\fR is \fIsize\fR).
|
||||||
|
.IP LogRotationInterval:
|
||||||
|
Time interval for log rotation in days (only used if \fILogRotationType\fR is \fItime\fR).
|
||||||
|
.IP LogBackupCount:
|
||||||
|
Number of backup log files to retain.
|
||||||
.IP "[EMAIL:accountX]:"
|
.IP "[EMAIL:accountX]:"
|
||||||
Defines an email account. Replace \fIaccountX\fR with a unique identifier for each email account.
|
Defines an email account. Replace \fIaccountX\fR with a unique identifier for each email account.
|
||||||
.IP EmailUser:
|
.IP EmailUser:
|
||||||
|
@ -76,6 +84,10 @@ Configuration for the Gotify notification provider.
|
||||||
The Gotify URL for sending messages.
|
The Gotify URL for sending messages.
|
||||||
.IP Token:
|
.IP Token:
|
||||||
Your Gotify token.
|
Your Gotify token.
|
||||||
|
.IP "[APPRISE]:"
|
||||||
|
Configuration for the Apprise notification provider.
|
||||||
|
.IP urls:
|
||||||
|
Comma-separated list of Apprise service URLs for sending notifications.
|
||||||
.RE
|
.RE
|
||||||
.SH DEPENDENCIES
|
.SH DEPENDENCIES
|
||||||
Python libraries:
|
Python libraries:
|
||||||
|
@ -104,6 +116,8 @@ argparse
|
||||||
threading
|
threading
|
||||||
.IP "*"
|
.IP "*"
|
||||||
BytesParser from email.parser
|
BytesParser from email.parser
|
||||||
|
.IP "*"
|
||||||
|
apprise
|
||||||
.RE
|
.RE
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
1. Running NotiMail with the default configuration file:
|
1. Running NotiMail with the default configuration file:
|
||||||
|
@ -119,7 +133,7 @@ notimail \-c /path/to/custom_config.ini
|
||||||
3. Checking the current configuration:
|
3. Checking the current configuration:
|
||||||
.RS
|
.RS
|
||||||
.IP ""
|
.IP ""
|
||||||
notimail --check-config
|
notimail --print-config
|
||||||
.RE
|
.RE
|
||||||
4. Testing the configuration settings:
|
4. Testing the configuration settings:
|
||||||
.RS
|
.RS
|
||||||
|
@ -136,4 +150,4 @@ Stefano Marinelli <stefano@dragas.it>
|
||||||
.SH LICENSE
|
.SH LICENSE
|
||||||
BSD 3-Clause License. See the source distribution for details.
|
BSD 3-Clause License. See the source distribution for details.
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
IMAP IDLE, SQLite3, NTFY, Pushover, Gotify.
|
IMAP IDLE, SQLite3, NTFY, Pushover, Gotify, Apprise.
|
||||||
|
|
Loading…
Reference in a new issue