Added first version of the script
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import imaplib
|
||||
import email
|
||||
from email.header import decode_header
|
||||
import os
|
||||
from pathlib import Path
|
||||
import paramiko
|
||||
|
||||
# =========================
|
||||
# EMAIL CONFIG
|
||||
# =========================
|
||||
IMAP_SERVER = "outlook.office365.com"
|
||||
EMAIL_ACCOUNT = "mailbox@company.com"
|
||||
PASSWORD = "your_password"
|
||||
|
||||
MAIL_FOLDERS = ["Inbox", "Orders"]
|
||||
|
||||
# =========================
|
||||
# SFTP CONFIG
|
||||
# =========================
|
||||
SFTP_HOST = "sftp.company.com"
|
||||
SFTP_PORT = 22
|
||||
SFTP_USERNAME = "sftpuser"
|
||||
SSH_KEY_PATH = "/home/youruser/.ssh/id_rsa"
|
||||
SSH_KEY_PASSPHRASE = None
|
||||
|
||||
# =========================
|
||||
# RULES
|
||||
# =========================
|
||||
RULES = [
|
||||
{
|
||||
"from_contains": "supplier1@company.com",
|
||||
"to_contains": "orders@yourcompany.com",
|
||||
"remote_path": "/incoming/orders"
|
||||
},
|
||||
{
|
||||
"from_contains": "reports@vendor.com",
|
||||
"to_contains": "reports@yourcompany.com",
|
||||
"remote_path": "/incoming/reports"
|
||||
}
|
||||
]
|
||||
|
||||
TEMP_DOWNLOAD = "/tmp/mail_attachments"
|
||||
|
||||
# =========================
|
||||
# HELPERS
|
||||
# =========================
|
||||
def ensure_folder(path):
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def clean_subject(subject):
|
||||
if not subject:
|
||||
return "No Subject"
|
||||
parts = decode_header(subject)
|
||||
decoded_subject = ""
|
||||
for part, encoding in parts:
|
||||
if isinstance(part, bytes):
|
||||
decoded_subject += part.decode(encoding or "utf-8", errors="ignore")
|
||||
else:
|
||||
decoded_subject += part
|
||||
return decoded_subject
|
||||
|
||||
def decode_filename(filename):
|
||||
if not filename:
|
||||
return "unknown"
|
||||
parts = decode_header(filename)
|
||||
decoded_name = ""
|
||||
for part, encoding in parts:
|
||||
if isinstance(part, bytes):
|
||||
decoded_name += part.decode(encoding or "utf-8", errors="ignore")
|
||||
else:
|
||||
decoded_name += part
|
||||
return decoded_name
|
||||
|
||||
# =========================
|
||||
# SFTP CONNECTION
|
||||
# =========================
|
||||
def create_sftp_connection():
|
||||
try:
|
||||
key = paramiko.RSAKey.from_private_key_file(
|
||||
SSH_KEY_PATH,
|
||||
password=SSH_KEY_PASSPHRASE
|
||||
)
|
||||
transport = paramiko.Transport((SFTP_HOST, SFTP_PORT))
|
||||
transport.connect(username=SFTP_USERNAME, pkey=key)
|
||||
sftp = paramiko.SFTPClient.from_transport(transport)
|
||||
return sftp, transport
|
||||
except Exception as e:
|
||||
print(f"SFTP connection failed: {e}")
|
||||
return None, None
|
||||
|
||||
# =========================
|
||||
# UPLOAD FILE
|
||||
# =========================
|
||||
def upload_to_sftp(local_file, remote_folder):
|
||||
sftp, transport = create_sftp_connection()
|
||||
if not sftp:
|
||||
print("Skipping upload due to SFTP connection error")
|
||||
return
|
||||
try:
|
||||
filename = os.path.basename(local_file)
|
||||
remote_path = f"{remote_folder}/{filename}"
|
||||
print(f"Uploading {filename} -> {remote_path}")
|
||||
sftp.put(local_file, remote_path)
|
||||
print("Upload successful")
|
||||
except Exception as e:
|
||||
print(f"Failed to upload {filename}: {e}")
|
||||
finally:
|
||||
sftp.close()
|
||||
transport.close()
|
||||
|
||||
# =========================
|
||||
# PROCESS EMAIL
|
||||
# =========================
|
||||
def process_email(msg):
|
||||
from_addr = msg.get("From", "").lower()
|
||||
to_addr = msg.get("To", "").lower()
|
||||
subject = clean_subject(msg.get("Subject"))
|
||||
|
||||
print("\n====================")
|
||||
print(f"FROM: {from_addr}")
|
||||
print(f"TO: {to_addr}")
|
||||
print(f"SUBJECT: {subject}")
|
||||
|
||||
matching_rule = None
|
||||
for rule in RULES:
|
||||
if rule["from_contains"].lower() in from_addr and rule["to_contains"].lower() in to_addr:
|
||||
matching_rule = rule
|
||||
break
|
||||
|
||||
if not matching_rule:
|
||||
print("No matching rule")
|
||||
return
|
||||
|
||||
remote_folder = matching_rule["remote_path"]
|
||||
|
||||
for part in msg.walk():
|
||||
content_disposition = str(part.get("Content-Disposition"))
|
||||
if "attachment" not in content_disposition.lower():
|
||||
continue
|
||||
|
||||
filename = decode_filename(part.get_filename())
|
||||
ensure_folder(TEMP_DOWNLOAD)
|
||||
local_path = os.path.join(TEMP_DOWNLOAD, filename)
|
||||
with open(local_path, "wb") as f:
|
||||
f.write(part.get_payload(decode=True))
|
||||
print(f"Saved attachment locally: {local_path}")
|
||||
|
||||
upload_to_sftp(local_path, remote_folder)
|
||||
|
||||
# =========================
|
||||
# MAIN
|
||||
# =========================
|
||||
def main():
|
||||
try:
|
||||
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
|
||||
mail.login(EMAIL_ACCOUNT, PASSWORD)
|
||||
print("Connected to mailbox")
|
||||
except Exception as e:
|
||||
print(f"Failed to connect/login: {e}")
|
||||
return
|
||||
|
||||
for folder in MAIL_FOLDERS:
|
||||
print(f"\nChecking folder: {folder}")
|
||||
status, _ = mail.select(folder)
|
||||
if status.decode() != "OK":
|
||||
print(f"Cannot access folder: {folder}")
|
||||
continue
|
||||
|
||||
status, messages = mail.search(None, "UNSEEN")
|
||||
if status.decode() != "OK":
|
||||
continue
|
||||
|
||||
email_ids = messages[0].split()
|
||||
print(f"Found {len(email_ids)} unread emails")
|
||||
|
||||
for email_id in email_ids:
|
||||
status, msg_data = mail.fetch(email_id, "(RFC822)")
|
||||
if status.decode() != "OK":
|
||||
continue
|
||||
raw_email = msg_data[0][1]
|
||||
msg = email.message_from_bytes(raw_email)
|
||||
process_email(msg)
|
||||
mail.store(email_id, '+FLAGS', '\\Seen')
|
||||
|
||||
mail.logout()
|
||||
print("\nFinished")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user