I think now it works

This commit is contained in:
Zichao Lin 2024-07-23 23:57:09 +08:00
commit b94784dc83
Signed by: earthjasonlin
GPG Key ID: 406D9913DE2E42FB
6 changed files with 412 additions and 0 deletions

1
.gitignore vendored Normal file

@ -0,0 +1 @@
config.json

21
LICENSE Normal file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 earthjasonlin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

113
README.md Normal file

@ -0,0 +1,113 @@
# MailForwarder
**此README由 ChatGPT-4o 生成**
这个项目是一个邮件自动转发器,能够从指定的邮箱中读取未读邮件,并将其自动转发到配置文件中指定的收件人邮箱中。该项目支持使用代理服务器进行连接,并且可以处理包含附件的邮件。
## 功能特性
- 自动转发未读邮件
- 支持 HTML 和纯文本格式的邮件内容
- 支持邮件附件的转发
- 支持通过 SOCKS5 代理进行连接
- 支持多账户配置
- 可配置的检查间隔
## 安装与配置
### 环境要求
- Python 3.6+
- 需要安装以下 Python 库:
- json
- imaplib
- smtplib
- email
- socks
- logging
### 安装依赖
```sh
pip install pysocks
```
### 配置文件
请在项目根目录下创建一个名为 `config.json` 的配置文件,并根据以下格式进行配置(见`config-example.json`
```json
{
"check_interval": 60,
"accounts": [
{
"email": "your_email@example.com",
"password": "your_password",
"enabled": true,
"imap": {
"server": "imap.example.com",
"port": 993,
"use_ssl": true
},
"smtp": {
"server": "smtp.example.com",
"port": 587,
"use_ssl": false,
"use_starttls": true
},
"forward": {
"to": ["forward_to@example.com"]
},
"proxy": {
"enabled": false,
"server": "proxy.example.com",
"port": 1080
}
}
]
}
```
### 配置项说明
- `check_interval`: 检查邮件的时间间隔,单位为秒。
- `accounts`: 配置多个邮箱账户。
- `email`: 邮箱地址。
- `password`: 邮箱密码。
- `enabled`: 是否启用该账户。
- `imap`: IMAP 服务器配置。
- `server`: IMAP 服务器地址。
- `port`: IMAP 服务器端口。
- `use_ssl`: 是否使用 SSL 连接。
- `smtp`: SMTP 服务器配置。
- `server`: SMTP 服务器地址。
- `port`: SMTP 服务器端口。
- `use_ssl`: 是否使用 SSL 连接。
- `use_starttls`: 是否使用 STARTTLS 加密。
- `forward`: 转发配置。
- `to`: 转发邮件的收件人地址列表。
- `proxy`: 代理服务器配置。
- `enabled`: 是否启用代理。
- `server`: 代理服务器地址。
- `port`: 代理服务器端口。
## 使用说明
1. 确保已经安装了所有依赖项,并正确配置了 `config.json` 文件。
2. 运行脚本:
```sh
python main.py
```
## 日志
该脚本会在控制台输出日志信息,记录程序的运行状态、邮件处理情况以及错误信息。
## 注意事项
- 请确保配置文件中的邮箱账号和密码正确无误,并且邮箱服务支持 IMAP 和 SMTP 协议。
- 转发的邮件会包含一个自定义的头部和尾部,以提示收件人该邮件是自动转发的邮件。
## 许可证
本项目遵循 MIT 许可证。详细信息请参见 LICENSE 文件。

53
config-example.json Normal file

@ -0,0 +1,53 @@
{
"accounts": [
{
"enabled": false,
"email": "sample@example.com",
"password": "password",
"imap": {
"server": "imap.example.com",
"port": 993,
"use_ssl": true
},
"smtp": {
"server": "smtp.example.com",
"port": 465,
"use_ssl": true,
"use_starttls": false
},
"proxy": {
"enabled": true,
"server": "127.0.0.1",
"port": 7897
},
"forward": {
"to": ["forward@example.com", "forward2@example.com"]
}
},
{
"enabled": true,
"email": "active@example.com",
"password": "password",
"imap": {
"server": "imap.active.com",
"port": 993,
"use_ssl": true
},
"smtp": {
"server": "smtp.active.com",
"port": 465,
"use_ssl": false,
"use_starttls": true
},
"proxy": {
"enabled": false,
"server": "127.0.0.1",
"port": 7897
},
"forward": {
"to": ["forward@example.com"]
}
}
],
"check_interval": 10
}

152
maincopy.py Normal file

@ -0,0 +1,152 @@
import json
import imaplib
import smtplib
import email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import time
import logging
import socks
import socket
from email.header import decode_header
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
)
def add_mask(original_msg, content):
original_subject = decode_mime_words(original_msg['Subject'])
header = f"""<table align=center style="background:#3d3d3d;padding:8px 16px;margin-top:30px;margin-bottom:30px;width:96%;border-radius:6px;max-width:1200px"width=100% bgcolor=#3D3D3D id=relay-email-header><tr><td style=font-size:xx-large;font-weight:bolder;color:#fff width=50% align=left>Forwarded Email<td style=color:#fff;text-align:right width=50% align=right><p>From: {original_msg['From']}<p>To: {original_msg['To']}<p>Subject: {original_subject}</table><table align=center style=padding:0;max-width:850px width=100%><tr><td style=padding-left:15px;padding-right:15px width=100%>"""
footer = f"""<table align=center bgcolor=#3D3D3D style="background:#3d3d3d;padding:8px 16px;margin-top:30px;margin-bottom:30px;width:96%;border-radius:6px;max-width:1200px"width=100%><tr><td align=left style=color:#d22;font-size:xx-large;font-weight:bolder width=50%>FORWARDED<td align=right style=color:#fff;text-align:left width=50%><p style=font-size:x-large;font-weight:700;margin-block:0>Notice:<p style=margin-block:.1em>This is a automatically forwarded email, which means it may contains something bad.<p style=margin-block:.1em>You shouldn't reply directly to this email, it will never reach your destination!</table>"""
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():
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
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'])
if "163.com" in account_config['email'] or "126.com" in account_config['email']:
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])
if 'Forwarded' not in msg['Subject']:
emails.append((email_id, msg))
imap.logout()
logger.info(f"Retrieved {len(emails)} new emails from {account_config['email']}")
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']:
msg = MIMEMultipart('mixed')
msg['From'] = account_config['email']
msg['To'] = recipient
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
html_content = add_mask(original_msg, body)
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} to {recipient}")
smtp.quit()
def main():
logger = setup_logging()
config = load_config()
while True:
for account in config['accounts']:
if account['enabled']:
try:
emails = get_unforwarded_emails(account, logger)
if emails:
forward_emails(account, emails, logger)
else:
logger.info(f"No new emails to forward for {account['email']}.")
except Exception as e:
logger.error(f"Error processing account {account['email']}: {str(e)}")
else:
logger.info(f"Account {account['email']} is disabled.")
time.sleep(config.get('check_interval', 60))
if __name__ == "__main__":
main()

72
template.html Normal file

@ -0,0 +1,72 @@
<table
id="relay-email-header"
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-size: xx-large;
font-weight: bolder;
color: #ffffff;
">
Forwarded Email
</td>
<td width="50%" align="right" style="color: #ffffff; text-align: right;">
<p>From: {original_msg['From']}</p>
<p>To: {original_msg['To']}</p>
<p>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%">
<!-- content -->
</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: #dd2222; font-size: xx-large; font-weight: bolder">
FORWARDED
</td>
<td width="50%" align="right" style="color: #ffffff; text-align: left;">
<p style="font-size: x-large; font-weight: bold; margin-block: 0;">Notice:</p>
<p style="margin-block: 0.1em;">&emsp;This is a automatically forwarded email, which means it may contains something bad.</p>
<p style="margin-block: 0.1em;">&emsp;You shouldn't reply directly to this email, it will never reach your destination!</p>
</td>
</tr>
</tbody>
</table>