Initial commit: Zoho Mail Service v1.0.0
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email import encoders
|
||||
import secrets
|
||||
import os
|
||||
import shutil
|
||||
import string
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# ==================== 配置 ====================
|
||||
SMTP_SERVER = "smtp.zoho.com.cn"
|
||||
SMTP_PORT = 465
|
||||
SENDER_EMAIL = "yourname@yourname"
|
||||
|
||||
# ==================== 邮件密码(用户自己填写) ====================
|
||||
EMAIL_PASSWORD = "不能看."
|
||||
|
||||
# ==================== 硬编码账户 ====================
|
||||
AUTH_ACCOUNTS = "不能看"
|
||||
# ==================== 存储 Cookie(永久有效) ====================
|
||||
COOKIE_STORE = {}
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
|
||||
def generate_high_entropy_cookie():
|
||||
"""生成 4096 位高熵 Cookie"""
|
||||
entropy = secrets.token_bytes(512)
|
||||
cookie = hashlib.sha3_512(entropy).hexdigest() + secrets.token_hex(256)
|
||||
return cookie[:4096]
|
||||
|
||||
|
||||
def generate_token():
|
||||
"""生成 32 位令牌(随机数+字母+特殊字符)"""
|
||||
chars = string.ascii_letters + string.digits + "!@#$%^&*"
|
||||
token = ''.join(secrets.choice(chars) for _ in range(32))
|
||||
return token
|
||||
|
||||
|
||||
def validate_cookie(cookie):
|
||||
"""验证 Cookie 是否有效(永久有效)"""
|
||||
if cookie not in COOKIE_STORE:
|
||||
return None
|
||||
return COOKIE_STORE[cookie]
|
||||
|
||||
|
||||
def get_upload_dir(token):
|
||||
"""获取令牌对应的上传目录"""
|
||||
return os.path.join(os.path.dirname(__file__), 'uploads', token)
|
||||
|
||||
|
||||
def delete_upload_dir(token):
|
||||
"""删除令牌对应的上传目录"""
|
||||
upload_dir = get_upload_dir(token)
|
||||
if os.path.exists(upload_dir):
|
||||
shutil.rmtree(upload_dir)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# ==================== API 接口 ====================
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
"""
|
||||
登录接口
|
||||
请求体: {
|
||||
"aid": "w9M3nR8...",
|
||||
"username": "dvsadmin",
|
||||
"password": "G7#kLp$..."
|
||||
}
|
||||
返回: {"code": 200, "cookie": "xxx"}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"code": 400, "message": "请求体不能为空"}), 400
|
||||
|
||||
aid = data.get('aid')
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
|
||||
if not aid:
|
||||
return jsonify({"code": 400, "message": "缺少 aid"}), 400
|
||||
if not username:
|
||||
return jsonify({"code": 400, "message": "缺少 username"}), 400
|
||||
if not password:
|
||||
return jsonify({"code": 400, "message": "缺少 password"}), 400
|
||||
|
||||
if aid not in AUTH_ACCOUNTS:
|
||||
return jsonify({"code": 401, "message": "aid 不存在"}), 401
|
||||
|
||||
account = AUTH_ACCOUNTS[aid]
|
||||
if account['username'] != username:
|
||||
return jsonify({"code": 401, "message": "用户名错误"}), 401
|
||||
if account['password'] != password:
|
||||
return jsonify({"code": 401, "message": "密码错误"}), 401
|
||||
|
||||
if not EMAIL_PASSWORD:
|
||||
return jsonify({"code": 500, "message": "系统未配置邮件密码,请联系管理员"}), 500
|
||||
|
||||
cookie = generate_high_entropy_cookie()
|
||||
|
||||
COOKIE_STORE[cookie] = {
|
||||
'aid': aid,
|
||||
'username': username
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
"code": 200,
|
||||
"message": "登录成功",
|
||||
"cookie": cookie
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"code": 500, "message": f"服务器错误: {str(e)}"}), 500
|
||||
|
||||
|
||||
@app.route('/upload', methods=['POST'])
|
||||
def upload_file():
|
||||
"""
|
||||
上传附件接口
|
||||
请求: multipart/form-data
|
||||
- file: 要上传的文件
|
||||
返回: {"code": 200, "token": "xxx", "filename": "xxx"}
|
||||
"""
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
return jsonify({"code": 400, "message": "缺少 file 字段"}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({"code": 400, "message": "未选择文件"}), 400
|
||||
|
||||
token = generate_token()
|
||||
|
||||
upload_dir = get_upload_dir(token)
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
filename = file.filename
|
||||
file_path = os.path.join(upload_dir, filename)
|
||||
file.save(file_path)
|
||||
|
||||
return jsonify({
|
||||
"code": 200,
|
||||
"message": "上传成功",
|
||||
"token": token,
|
||||
"filename": filename
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"code": 500, "message": f"上传失败: {str(e)}"}), 500
|
||||
|
||||
|
||||
@app.route('/send', methods=['POST'])
|
||||
def send_email():
|
||||
"""
|
||||
发件接口
|
||||
请求体: {
|
||||
"cookie": "xxx",
|
||||
"to": "dvs6666@163.com",
|
||||
"subject": "邮件主题",
|
||||
"content": "邮件内容(支持HTML)",
|
||||
"token": "xxx" # 可选
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"code": 400, "message": "请求体不能为空"}), 400
|
||||
|
||||
cookie = data.get('cookie')
|
||||
to_email = data.get('to')
|
||||
subject = data.get('subject')
|
||||
content = data.get('content')
|
||||
token = data.get('token')
|
||||
|
||||
if not cookie:
|
||||
return jsonify({"code": 400, "message": "缺少 cookie"}), 400
|
||||
if not to_email:
|
||||
return jsonify({"code": 400, "message": "缺少收件人"}), 400
|
||||
if not subject:
|
||||
return jsonify({"code": 400, "message": "缺少主题"}), 400
|
||||
if not content:
|
||||
return jsonify({"code": 400, "message": "缺少内容"}), 400
|
||||
|
||||
# 验证 Cookie(永久有效)
|
||||
session_data = validate_cookie(cookie)
|
||||
if not session_data:
|
||||
return jsonify({"code": 401, "message": "Cookie 无效,请重新登录"}), 401
|
||||
|
||||
if not EMAIL_PASSWORD:
|
||||
return jsonify({"code": 500, "message": "系统未配置邮件密码,请联系管理员"}), 500
|
||||
|
||||
# 获取附件列表(如果有令牌)
|
||||
attachments = []
|
||||
if token:
|
||||
upload_dir = get_upload_dir(token)
|
||||
if os.path.exists(upload_dir):
|
||||
for filename in os.listdir(upload_dir):
|
||||
file_path = os.path.join(upload_dir, filename)
|
||||
if os.path.isfile(file_path):
|
||||
attachments.append(file_path)
|
||||
|
||||
# 发送邮件
|
||||
success, result = send_mail(to_email, subject, content, attachments, EMAIL_PASSWORD)
|
||||
|
||||
if success:
|
||||
# 发送成功后删除令牌目录(一次性使用)
|
||||
if token:
|
||||
delete_upload_dir(token)
|
||||
trigger_callback(to_email, subject, "成功")
|
||||
return jsonify({"code": 200, "message": result})
|
||||
else:
|
||||
trigger_callback(to_email, subject, f"失败: {result}")
|
||||
return jsonify({"code": 500, "message": result}), 500
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"code": 500, "message": f"服务器错误: {str(e)}"}), 500
|
||||
|
||||
|
||||
def send_mail(to_email, subject, content, attachments, email_password):
|
||||
"""发送邮件核心函数"""
|
||||
try:
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = SENDER_EMAIL
|
||||
msg['To'] = to_email
|
||||
msg['Subject'] = subject
|
||||
|
||||
if content.strip().startswith('<') or '</html>' in content.lower():
|
||||
msg.attach(MIMEText(content, 'html', 'utf-8'))
|
||||
else:
|
||||
msg.attach(MIMEText(content, 'plain', 'utf-8'))
|
||||
|
||||
if attachments:
|
||||
for file_path in attachments:
|
||||
if not os.path.exists(file_path):
|
||||
continue
|
||||
with open(file_path, 'rb') as f:
|
||||
part = MIMEBase('application', 'octet-stream')
|
||||
part.set_payload(f.read())
|
||||
encoders.encode_base64(part)
|
||||
filename = os.path.basename(file_path)
|
||||
part.add_header('Content-Disposition', f'attachment; filename="{filename}"')
|
||||
msg.attach(part)
|
||||
|
||||
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
|
||||
server.login(SENDER_EMAIL, email_password)
|
||||
server.send_message(msg)
|
||||
|
||||
return True, "邮件发送成功"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"发送失败: {str(e)}"
|
||||
|
||||
|
||||
def trigger_callback(to_email, subject, status):
|
||||
"""回调函数"""
|
||||
print("=" * 60)
|
||||
print(f"[回调] 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"[回调] 收件人: {to_email}")
|
||||
print(f"[回调] 主题: {subject}")
|
||||
print(f"[回调] 状态: {status}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
# ==================== 启动服务 ====================
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.makedirs(os.path.join(os.path.dirname(__file__), 'uploads'), exist_ok=True)
|
||||
|
||||
print("=" * 60)
|
||||
print("Zoho 邮件发送 API 服务")
|
||||
print("=" * 60)
|
||||
print(f"SMTP 服务器: {SMTP_SERVER}:{SMTP_PORT}")
|
||||
print(f"发件邮箱: {SENDER_EMAIL}")
|
||||
print(f"邮件密码已配置: {'✅ 是' if EMAIL_PASSWORD else '❌ 否(请先配置)'}")
|
||||
print(f"有效账户数: {len(AUTH_ACCOUNTS)}")
|
||||
print(f"Cookie 有效期: 永久")
|
||||
print("=" * 60)
|
||||
|
||||
if not EMAIL_PASSWORD:
|
||||
print("\n⚠️ 警告: 请先在代码中设置 EMAIL_PASSWORD 变量!")
|
||||
print(" 位置: 第 18 行 EMAIL_PASSWORD = '你的邮箱密码'")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n🚀 服务启动中...")
|
||||
print(f"📍 登录接口: POST http://localhost:5000/login")
|
||||
print(f"📍 上传接口: POST http://localhost:5000/upload")
|
||||
print(f"📍 发件接口: POST http://localhost:5000/send")
|
||||
print("=" * 60)
|
||||
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
Reference in New Issue
Block a user