diff --git a/app.py b/app.py index 6ec21dc..a3d9c29 100755 --- a/app.py +++ b/app.py @@ -1045,34 +1045,34 @@ def generate_captcha(): from PIL import Image, ImageDraw, ImageFont import io - # 创建图片 - width, height = 120, 40 + # 创建图片 - 增大尺寸以便显示更大的字体 + width, height = 160, 60 image = Image.new('RGB', (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(image) # 添加干扰线 - for _ in range(5): + for _ in range(6): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) - draw.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200))) + draw.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)), width=1) # 添加干扰点 - for _ in range(50): + for _ in range(80): x = random.randint(0, width) y = random.randint(0, height) draw.point((x, y), fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200))) - # 绘制验证码文字 + # 绘制验证码文字 - 增大字体 try: - font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 28) + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 42) except: font = ImageFont.load_default() for i, char in enumerate(code): - x = 10 + i * 25 + random.randint(-3, 3) - y = random.randint(2, 8) + x = 12 + i * 35 + random.randint(-3, 3) + y = random.randint(5, 12) color = (random.randint(0, 150), random.randint(0, 150), random.randint(0, 150)) draw.text((x, y), char, font=font, fill=color) @@ -2169,7 +2169,8 @@ def run_task(user_id, account_id, browse_type, enable_screenshot=True, source="m # 发送任务完成邮件通知(不截图时在此发送) try: user_info = database.get_user_by_id(user_id) - if user_info and user_info.get('email'): + # 检查用户是否开启了邮件通知 + if user_info and user_info.get('email') and database.get_user_email_notify(user_id): account_name = account.remark if account.remark else account.username email_service.send_task_complete_email_async( user_id=user_id, @@ -2538,7 +2539,8 @@ def take_screenshot_for_account(user_id, account_id, browse_type="应读", sourc # 发送任务完成邮件通知 try: user_info = database.get_user_by_id(user_id) - if user_info and user_info.get('email'): + # 检查用户是否开启了邮件通知 + if user_info and user_info.get('email') and database.get_user_email_notify(user_id): screenshot_path = None if result and result.get('success') and result.get('filename'): screenshot_path = os.path.join(SCREENSHOTS_DIR, result['filename']) @@ -2967,6 +2969,27 @@ def unbind_user_email(): return jsonify({"error": "解绑失败"}), 500 +@app.route('/api/user/email-notify', methods=['GET']) +@login_required +def get_user_email_notify(): + """获取用户邮件通知偏好""" + enabled = database.get_user_email_notify(current_user.id) + return jsonify({"enabled": enabled}) + + +@app.route('/api/user/email-notify', methods=['POST']) +@login_required +def update_user_email_notify(): + """更新用户邮件通知偏好""" + data = request.get_json() + enabled = data.get('enabled', True) + + if database.update_user_email_notify(current_user.id, enabled): + return jsonify({"success": True}) + else: + return jsonify({"error": "更新失败"}), 500 + + @app.route('/api/run_stats', methods=['GET']) @login_required def get_run_stats(): diff --git a/database.py b/database.py index 99a2bc5..1b38dbe 100755 --- a/database.py +++ b/database.py @@ -834,6 +834,13 @@ def update_user_email(user_id, email, verified=False): """更新用户邮箱""" with db_pool.get_db() as conn: cursor = conn.cursor() + # 先检查email_verified字段是否存在,不存在则添加 + try: + cursor.execute('SELECT email_verified FROM users LIMIT 1') + except: + cursor.execute('ALTER TABLE users ADD COLUMN email_verified INTEGER DEFAULT 0') + conn.commit() + cursor.execute(''' UPDATE users SET email = ?, email_verified = ? @@ -843,6 +850,41 @@ def update_user_email(user_id, email, verified=False): return cursor.rowcount > 0 +def update_user_email_notify(user_id, enabled): + """更新用户邮件通知偏好""" + with db_pool.get_db() as conn: + cursor = conn.cursor() + # 先检查字段是否存在 + try: + cursor.execute('SELECT email_notify_enabled FROM users LIMIT 1') + except: + cursor.execute('ALTER TABLE users ADD COLUMN email_notify_enabled INTEGER DEFAULT 1') + conn.commit() + + cursor.execute(''' + UPDATE users + SET email_notify_enabled = ? + WHERE id = ? + ''', (int(enabled), user_id)) + conn.commit() + return cursor.rowcount > 0 + + +def get_user_email_notify(user_id): + """获取用户邮件通知偏好(默认开启)""" + with db_pool.get_db() as conn: + cursor = conn.cursor() + # 先检查字段是否存在 + try: + cursor.execute('SELECT email_notify_enabled FROM users WHERE id = ?', (user_id,)) + row = cursor.fetchone() + if row is None: + return True + return bool(row[0]) if row[0] is not None else True + except: + return True # 字段不存在时默认开启 + + def get_all_users(): """获取所有用户""" with db_pool.get_db() as conn: diff --git a/email_service.py b/email_service.py index a4289cf..4890021 100644 --- a/email_service.py +++ b/email_service.py @@ -100,7 +100,7 @@ def init_email_tables(): password TEXT DEFAULT '', use_ssl INTEGER DEFAULT 1, use_tls INTEGER DEFAULT 0, - sender_name TEXT DEFAULT '知识管理平台', + sender_name TEXT DEFAULT '自动化学习', sender_email TEXT DEFAULT '', daily_limit INTEGER DEFAULT 0, daily_sent INTEGER DEFAULT 0, @@ -411,7 +411,7 @@ def create_smtp_config(data: Dict[str, Any]) -> int: password, int(data.get('use_ssl', True)), int(data.get('use_tls', False)), - data.get('sender_name', '知识管理平台'), + data.get('sender_name', '自动化学习'), data.get('sender_email', ''), data.get('daily_limit', 0) )) @@ -829,7 +829,7 @@ def test_smtp_config(config_id: int, test_email: str) -> Dict[str, Any]: sender.connect() sender.send( test_email, - '知识管理平台 - SMTP配置测试', + '自动化学习 - SMTP配置测试', f'这是一封测试邮件。\n\n配置名称: {config["name"]}\nSMTP服务器: {config["host"]}:{config["port"]}\n\n如果您收到此邮件,说明SMTP配置正确。', None, None @@ -1211,7 +1211,7 @@ def send_register_verification_email( text_body = f""" 您好,{username}! -感谢您注册知识管理平台。请点击下面的链接验证您的邮箱地址: +感谢您注册自动化学习。请点击下面的链接验证您的邮箱地址: {verify_url} @@ -1223,7 +1223,7 @@ def send_register_verification_email( # 发送邮件 result = send_email( to_email=email, - subject='【知识管理平台】邮箱验证', + subject='【自动化学习】邮箱验证', body=text_body, html_body=html_body, email_type=EMAIL_TYPE_REGISTER, @@ -1355,7 +1355,7 @@ def send_password_reset_email( # 发送邮件 result = send_email( to_email=email, - subject='【知识管理平台】密码重置', + subject='【自动化学习】密码重置', body=text_body, html_body=html_body, email_type=EMAIL_TYPE_RESET, @@ -1515,7 +1515,7 @@ def send_bind_email_verification( # 发送邮件 result = send_email( to_email=email, - subject='【知识管理平台】邮箱绑定验证', + subject='【自动化学习】邮箱绑定验证', body=text_body, html_body=html_body, email_type=EMAIL_TYPE_BIND, @@ -1832,7 +1832,7 @@ def send_task_complete_email( result = send_email( to_email=email, - subject=f'【知识管理平台】任务完成 - {account_name}', + subject=f'【自动化学习】任务完成 - {account_name}', body=text_body, html_body=html_body, email_type=EMAIL_TYPE_TASK_COMPLETE, @@ -1851,7 +1851,7 @@ def send_task_complete_email( attachment = [{'filename': screenshot_filename, 'data': screenshot_data}] result2 = send_email( to_email=email, - subject=f'【知识管理平台】任务截图 - {account_name}', + subject=f'【自动化学习】任务截图 - {account_name}', body=f'这是 {account_name} 的任务截图。', attachments=attachment, email_type=EMAIL_TYPE_TASK_COMPLETE, @@ -1898,7 +1898,7 @@ def send_task_complete_email( result = send_email( to_email=email, - subject=f'【知识管理平台】任务完成 - {account_name}', + subject=f'【自动化学习】任务完成 - {account_name}', body=text_body, html_body=html_body, attachments=attachments, diff --git a/templates/index.html b/templates/index.html index 73a7714..54e6521 100644 --- a/templates/index.html +++ b/templates/index.html @@ -768,6 +768,20 @@ + + @@ -2081,6 +2095,7 @@ .then(data => { const bindSection = document.getElementById('emailBindSection'); const boundSection = document.getElementById('emailBoundSection'); + const notifySection = document.getElementById('emailNotifySection'); const statusSpan = document.getElementById('emailStatus'); const boundEmail = document.getElementById('boundEmail'); const bindInput = document.getElementById('bindEmail'); @@ -2088,16 +2103,21 @@ if (data.email && data.email_verified) { bindSection.style.display = 'none'; boundSection.style.display = 'block'; + notifySection.style.display = 'block'; boundEmail.textContent = data.email; statusSpan.innerHTML = '已验证'; + // 加载邮件通知偏好 + loadEmailNotify(); } else if (data.email) { bindSection.style.display = 'block'; boundSection.style.display = 'none'; + notifySection.style.display = 'none'; bindInput.value = data.email; statusSpan.innerHTML = '待验证'; } else { bindSection.style.display = 'block'; boundSection.style.display = 'none'; + notifySection.style.display = 'none'; bindInput.value = ''; statusSpan.innerHTML = '未绑定'; } @@ -2105,6 +2125,63 @@ .catch(() => {}); } + function loadEmailNotify() { + fetch('/api/user/email-notify') + .then(r => r.json()) + .then(data => { + const checkbox = document.getElementById('emailNotifySwitch'); + const slider = document.getElementById('emailNotifySlider'); + const bg = checkbox.nextElementSibling; + checkbox.checked = data.enabled; + if (data.enabled) { + bg.style.backgroundColor = '#27ae60'; + slider.style.transform = 'translateX(24px)'; + } else { + bg.style.backgroundColor = '#ccc'; + slider.style.transform = 'translateX(0)'; + } + }) + .catch(() => {}); + } + + function toggleEmailNotify() { + const checkbox = document.getElementById('emailNotifySwitch'); + const slider = document.getElementById('emailNotifySlider'); + const bg = checkbox.nextElementSibling; + const enabled = checkbox.checked; + + // 立即更新UI + if (enabled) { + bg.style.backgroundColor = '#27ae60'; + slider.style.transform = 'translateX(24px)'; + } else { + bg.style.backgroundColor = '#ccc'; + slider.style.transform = 'translateX(0)'; + } + + fetch('/api/user/email-notify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled: enabled }) + }) + .then(r => r.json()) + .then(data => { + if (data.success) { + showToast(enabled ? '已开启邮件通知' : '已关闭邮件通知', 'success'); + } else { + // 恢复状态 + checkbox.checked = !enabled; + loadEmailNotify(); + showToast('设置失败', 'error'); + } + }) + .catch(() => { + checkbox.checked = !enabled; + loadEmailNotify(); + showToast('网络错误', 'error'); + }); + } + function bindEmail() { const email = document.getElementById('bindEmail').value.trim(); if (!email) {