MailForwarder/main.py

182 lines
8.5 KiB
Python
Raw Normal View History

2024-07-23 23:57:09 +08:00
import json
import imaplib
import smtplib
import email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import parseaddr
2024-07-23 23:57:09 +08:00
import time
import datetime
2024-07-23 23:57:09 +08:00
import logging
import socks
import socket
2024-07-24 11:55:17 +08:00
import re
2024-07-23 23:57:09 +08:00
from email.header import decode_header
2024-07-24 14:02:54 +08:00
def provider_in(address, provider_list):
for provider in provider_list:
if address.endswith(provider):
return True
return False
2024-07-23 23:57:09 +08:00
def decode_mime_words(s):
decoded_fragments = decode_header(s)
return ''.join(
str(fragment, encoding or 'utf-8') if isinstance(fragment, bytes) else fragment
for fragment, encoding in decoded_fragments
)
2024-07-24 11:55:17 +08:00
def add_mask(original_msg, content, is_html):
2024-07-23 23:57:09 +08:00
original_subject = decode_mime_words(original_msg['Subject'])
from_name, from_address = parseaddr(original_msg['From'])
2024-07-24 07:16:33 +08:00
from_name = decode_mime_words(from_name)
to_name, to_address = parseaddr(original_msg['To'])
2024-07-24 07:16:33 +08:00
to_name = decode_mime_words(to_name)
header = f"""<html><head></head><body><table style="background:#3d3d3d;padding:8px 16px;margin-top:30px;margin-bottom:30px;width:96%;border-radius:6px;max-width:1200px" width="100%" bgcolor="#3D3D3D" align="center"><tbody><tr><td width="50%" align="left" style="font-weight:bolder;color:#fff"><p style="font-size:x-large;margin-block:.2em">Forwarded Email</p>{'' if is_html else '<p style="font-size: medium; margin-block: 0.2em;">This email is plain text, it may have display issues</p>'}</td><td width="50%" align="right" style="color:#fff;text-align:right"><p style="margin-block: 0.1em">From: {from_name} &lt;<a href="mailto:{from_address}">{from_address}</a>&gt;</p><p style="margin-block: 0.1em">To: {to_name} &lt;<a href="mailto:{to_address}">{to_address}</a>&gt;</p><p style="margin-block:.1em">Subject: {original_subject}</p></td></tr></tbody></table><table style="padding:0;max-width:850px" width="100%" align="center"><tbody><tr><td style="padding-left:15px;padding-right:15px" width="100%">"""
2024-07-24 11:55:17 +08:00
footer = f"""</td></tr></tbody></table><table style="background:#3d3d3d;padding:8px 16px;margin-top:30px;margin-bottom:30px;width:96%;border-radius:6px;max-width:1200px" width="100%" bgcolor="#3D3D3D" align="center"><tbody><tr><td width="50%" align="left" style="color:#d22;font-size:xx-large;font-weight:bolder">FORWARDED</td><td width="50%" align="right" style="color:#fff;text-align:left"><p style="font-size:x-large;font-weight:700;margin-block:0">Notice:</p><p style="margin-block:.1em">&emsp;This is a automatically forwarded email, which means it may contains something bad.</p><p style="margin-block:.1em">&emsp;You shouldn't reply directly to this email, it will never reach your destination!</p></td></tr></tbody></table></body></html>"""
2024-07-23 23:57:09 +08:00
return header + content + footer
def load_config(config_file='config.json'):
with open(config_file, 'r') as file:
config = json.load(file)
return config
def set_proxy(proxy_config):
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, proxy_config['server'], proxy_config['port'])
socket.socket = socks.socksocket
def setup_logging(filename):
2024-07-23 23:57:09 +08:00
logger = logging.getLogger()
logger.setLevel('DEBUG')
BASIC_FORMAT = "%(asctime)s >> %(levelname)s - %(message)s"
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(BASIC_FORMAT, DATE_FORMAT)
chlr = logging.StreamHandler()
chlr.setFormatter(formatter)
chlr.setLevel('INFO')
2024-07-24 11:57:39 +08:00
fhlr = logging.FileHandler(filename, encoding='utf-8')
fhlr.setFormatter(formatter)
logger.addHandler(chlr)
logger.addHandler(fhlr)
2024-07-23 23:57:09 +08:00
return logger
def get_unforwarded_emails(account_config, logger):
if account_config['proxy']['enabled']:
set_proxy(account_config['proxy'])
if account_config['imap'].get('use_ssl', True):
imap = imaplib.IMAP4_SSL(account_config['imap']['server'], account_config['imap']['port'])
else:
imap = imaplib.IMAP4(account_config['imap']['server'], account_config['imap']['port'])
imap.login(account_config['email'], account_config['password'])
2024-07-24 14:02:54 +08:00
if provider_in(account_config['email'], ["163.com", "126.com"]):
2024-07-23 23:57:09 +08:00
imaplib.Commands['ID'] = ('AUTH')
args = ("name","XXXX","contact","XXXX@163.com","version","1.0.0","vendor","myclient")
typ, dat = imap._simple_command('ID', '("' + '" "'.join(args) + '")')
imap.select()
status, messages = imap.search(None, 'UNSEEN')
email_ids = messages[0].split()
emails = []
for email_id in email_ids:
status, data = imap.fetch(email_id, '(RFC822)')
for response_part in data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1])
2024-07-24 10:56:02 +08:00
emails.append((email_id, msg))
2024-07-23 23:57:09 +08:00
imap.logout()
if len(emails) > 0:
logger.info(f"Retrieved {len(emails)} new emails from {account_config['email']}")
2024-07-23 23:57:09 +08:00
return emails
def forward_emails(account_config, emails, logger):
if account_config['proxy']['enabled']:
set_proxy(account_config['proxy'])
smtp = None
if account_config['smtp'].get('use_ssl', False):
smtp = smtplib.SMTP_SSL(account_config['smtp']['server'], account_config['smtp']['port'])
else:
smtp = smtplib.SMTP(account_config['smtp']['server'], account_config['smtp']['port'])
if account_config['smtp'].get('use_starttls', False):
smtp.starttls()
smtp.login(account_config['email'], account_config['password'])
for email_id, original_msg in emails:
for recipient in account_config['forward']['to']:
from_name, from_address = parseaddr(original_msg['From'])
from_name = decode_mime_words(from_name)
to_name, to_address = parseaddr(original_msg['To'])
to_name = decode_mime_words(to_name)
2024-07-23 23:57:09 +08:00
msg = MIMEMultipart('mixed')
2024-07-24 16:07:24 +08:00
msg['From'] = f'"{from_name} ({from_address}) via Forwarder" <{account_config["email"]}>'
msg['To'] = f'"{to_name} ({to_address}) via Forwarder" <{recipient}>'
2024-07-23 23:57:09 +08:00
original_subject = decode_mime_words(original_msg['Subject'])
msg['Subject'] = original_subject
body = ""
attachments = []
if original_msg.is_multipart():
for part in original_msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
if "attachment" in content_disposition or part.get_filename():
attachments.append(part)
elif content_type == 'text/html':
body = part.get_payload(decode=True).decode()
elif content_type == 'text/plain' and not body:
body = part.get_payload(decode=True).decode()
else:
body = original_msg.get_payload(decode=True).decode()
if not body:
logger.error(f"Failed to extract body from email {email_id}")
continue
2024-07-24 11:55:17 +08:00
is_html = bool(re.compile(r'<[^>]+>').search(body))
if not is_html:
body = body.replace('\n', '<br>')
html_content = add_mask(original_msg, body, is_html)
2024-07-23 23:57:09 +08:00
msg.attach(MIMEText(html_content, 'html'))
for attachment in attachments:
filename = attachment.get_filename()
if decode_header(filename):
filename = decode_mime_words(filename)
attachment.add_header('Content-Disposition', 'attachment', filename=filename)
msg.attach(attachment)
smtp.sendmail(account_config['email'], recipient, msg.as_string())
logger.info(f"Forwarded email {original_subject} from {account_config['email']} to {recipient}")
2024-07-23 23:57:09 +08:00
smtp.quit()
def main():
config = load_config()
logger = setup_logging(config['log'])
2024-07-23 23:57:09 +08:00
while True:
for account in config['accounts']:
if account['enabled']:
try:
emails = get_unforwarded_emails(account, logger)
if emails:
forward_emails(account, emails, logger)
except Exception as e:
logger.error(f"Error processing account {account['email']}: {str(e)}")
logger.info(datetime.datetime.now().strftime("Check finished at %Y-%m-%d %H:%M:%S"))
2024-07-23 23:57:09 +08:00
time.sleep(config.get('check_interval', 60))
if __name__ == "__main__":
main()