通过webhook实现ai自动回复需要输入邮箱和目标

This commit is contained in:
wanjia 2025-05-20 16:56:35 +08:00
parent 0bcd8822dc
commit 22e8a672ed
2 changed files with 470 additions and 103 deletions

View File

@ -17,6 +17,9 @@ from rest_framework.permissions import IsAuthenticated
from apps.gmail.models import GmailCredential, GmailConversation, AutoReplyConfig
from apps.gmail.services.gmail_service import GmailService
from apps.gmail.serializers import AutoReplyConfigSerializer
from apps.gmail.services.goal_service import get_or_create_goal, get_conversation_summary
from apps.chat.models import ChatHistory
from apps.knowledge_base.models import KnowledgeBase
logger = logging.getLogger(__name__)
@ -295,7 +298,7 @@ class AutoGmailConversationView(APIView):
def post(self, request, *args, **kwargs):
"""
创建自动对话发送第一条打招呼消息
创建自动对话根据需要发送第一条打招呼消息
请求参数:
- user_email: 用户的Gmail邮箱已授权
@ -317,42 +320,140 @@ class AutoGmailConversationView(APIView):
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 获取或创建活跃对话
# 先查找现有对话
# 验证Gmail凭证
credential = GmailCredential.objects.filter(user=request.user, email=user_email).first()
if not credential:
return Response({
'code': 400,
'message': f"未找到{user_email}的Gmail授权",
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
logger.info(f"开始创建/更新Gmail自动对话: 用户={request.user.username}, 邮箱={user_email}, 达人={influencer_email}")
# 查找现有对话
existing_conversation = GmailConversation.objects.filter(
user=request.user,
user_email=user_email,
influencer_email=influencer_email
).first()
if existing_conversation:
# 激活现有对话
existing_conversation.is_active = True
existing_conversation.save()
logger.info(f"找到并激活现有对话: {existing_conversation.conversation_id}")
conversation_id = None
is_new_conversation = False
# 调用服务创建自动对话
success, result, goal = AutoGmailConversationService.create_auto_conversation(
user=request.user,
user_email=user_email,
influencer_email=influencer_email,
greeting_message="", # 使用空字符串,实际消息在服务中已固定
if existing_conversation:
# 使用现有对话
conversation = existing_conversation
conversation_id = conversation.conversation_id
# 激活对话
conversation.is_active = True
conversation.save()
logger.info(f"找到并激活现有对话: {conversation_id}, has_sent_greeting={conversation.has_sent_greeting}")
else:
# 创建新对话
conversation_id = f"gmail_{request.user.id}_{str(uuid.uuid4())[:8]}"
conversation = GmailConversation.objects.create(
user=request.user,
user_email=user_email,
influencer_email=influencer_email,
conversation_id=conversation_id,
title=f"{influencer_email} 的Gmail对话",
is_active=True,
has_sent_greeting=False, # 新创建的对话尚未发送打招呼消息
metadata={
'auto_conversation': True,
'created_at': timezone.now().isoformat()
}
)
is_new_conversation = True
logger.info(f"创建新的自动对话: {conversation_id}")
# 使用goal_service创建或更新目标
goal, is_new_goal = get_or_create_goal(
user=request.user,
conversation_id=conversation_id,
goal_description=goal_description
)
if not success:
return Response({
'code': 400,
'message': result,
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
logger.info(f"目标{'创建' if is_new_goal else '更新'}成功: {goal.id if goal else 'None'}")
# 确保对话是活跃的
conversation = GmailConversation.objects.filter(conversation_id=result).first()
if conversation and not conversation.is_active:
conversation.is_active = True
conversation.save()
logger.info(f"已将对话 {conversation.conversation_id} 设置为活跃")
# 检查是否需要发送打招呼消息
if not conversation.has_sent_greeting:
logger.info(f"准备发送打招呼消息: {conversation_id}")
# 固定的打招呼消息
greeting_message = """Paid Collaboration Opportunity with TikTok's #1 Fragrance Brand 🌸
Hi,
I'm Vira from OOIN Media, and I'm reaching out on behalf of a top-performing fragrance brand Sttes on TikTok Shopcurrently ranked #1 in the perfume category.
This brand has already launched several viral products and is now looking to partner with select creators like you through paid collaborations to continue driving awareness and sales.
We'd love to explore a partnership and would appreciate it if you could share:
Your rate for a single TikTok video
Whether you offer bundle pricing for multiple videos
Any additional details or formats you offer (e.g. story integration, livestream add-ons, etc.)
The product has strong market traction, proven conversions, and a competitive commission structure if you're also open to affiliate partnerships.
Looking forward to the opportunity to work together and hearing your rates!
Warm regards,
Vira
OOIN Media"""
# 发送打招呼消息
subject = "Paid Collaboration Opportunity with TikTok's #1 Fragrance Brand"
logger.info(f"开始向 {influencer_email} 发送打招呼消息")
# 使用GmailService发送邮件
success, message_id = GmailService.send_email(
user=request.user,
user_email=user_email,
to_email=influencer_email,
subject=subject,
body=greeting_message
)
if success:
# 将打招呼消息保存到聊天历史 - 使用更完整的方式
try:
# 查找或创建默认知识库
knowledge_base = KnowledgeBase.objects.filter(user_id=request.user.id, type='private').first()
if knowledge_base:
# 创建聊天消息
ChatHistory.objects.create(
user=request.user,
knowledge_base=knowledge_base,
conversation_id=conversation_id,
message_id=f"greeting_{message_id}",
title=conversation.title,
role="user", # 用户发出的消息
content=greeting_message,
metadata={
'gmail_message_id': message_id,
'from': user_email,
'to': influencer_email,
'date': timezone.now().isoformat(),
'subject': subject,
'greeting': True,
'source': 'gmail'
}
)
logger.info(f"打招呼消息已保存到聊天历史: {message_id}")
else:
logger.warning("未找到默认知识库,打招呼消息未保存到聊天历史")
except Exception as chat_error:
logger.error(f"保存打招呼消息到聊天历史失败: {str(chat_error)}")
# 这里我们继续执行,不影响主流程
# 更新对话的has_sent_greeting字段
conversation.has_sent_greeting = True
conversation.save(update_fields=['has_sent_greeting', 'updated_at'])
logger.info(f"对话 {conversation_id} 已发送打招呼消息并更新了has_sent_greeting=True")
else:
logger.error(f"发送打招呼消息失败: {message_id}")
return Response({
'code': 500,
'message': f"发送打招呼消息失败: {message_id}",
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
logger.info(f"对话 {conversation_id} 已经发送过打招呼消息,不再重复发送")
# 设置Gmail推送通知
notification_result, notification_error = GmailService.setup_gmail_push_notification(
@ -361,29 +462,34 @@ class AutoGmailConversationView(APIView):
)
if not notification_result and notification_error:
logger.warning(f"设置Gmail推送通知失败: {notification_error},但对话创建成功")
logger.warning(f"设置Gmail推送通知失败: {notification_error},但对话创建/更新成功")
# 生成对话摘要(如果有足够的消息)
summary = get_conversation_summary(conversation_id)
# 返回结果
return Response({
'code': 201,
'message': '自动对话创建成功,已发送打招呼消息',
'code': 201 if is_new_conversation else 200,
'message': '自动对话创建成功' if is_new_conversation else '自动对话更新成功',
'data': {
'conversation_id': result,
'conversation_id': conversation_id,
'goal_id': str(goal.id) if goal else None,
'user_email': user_email,
'influencer_email': influencer_email,
'is_active': True,
'has_sent_greeting': conversation.has_sent_greeting,
'goal_description': goal_description,
'push_notification': notification_result
'push_notification': notification_result,
'summary': summary
}
}, status=status.HTTP_201_CREATED)
}, status=status.HTTP_201_CREATED if is_new_conversation else status.HTTP_200_OK)
except Exception as e:
logger.error(f"创建自动对话失败: {str(e)}")
logger.error(f"创建/更新自动对话失败: {str(e)}")
error_details = traceback.format_exc()
return Response({
'code': 500,
'message': f'创建自动对话失败: {str(e)}',
'message': f'创建/更新自动对话失败: {str(e)}',
'data': {
'details': error_details[:500]
}

View File

@ -40,8 +40,12 @@ class GmailAuthInitiateView(APIView):
"""
处理 POST 请求启动 Gmail OAuth2 认证并返回授权 URL
支持两种方式提供客户端密钥:
1. 在请求体中提供client_secret_json字段
2. 上传名为client_secret_file的JSON文件
Args:
request: Django REST Framework 请求对象包含客户端密钥 JSON 数据
request: Django REST Framework 请求对象包含客户端密钥 JSON 数据或文件
Returns:
Response: 包含授权 URL JSON 响应成功时或错误信息失败时
@ -52,34 +56,78 @@ class GmailAuthInitiateView(APIView):
500: 服务器内部错误如认证服务失败
"""
logger.debug(f"Received auth initiate request: {request.data}")
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
# 检查是否是文件上传方式
client_secret_json = None
if 'client_secret_file' in request.FILES:
try:
# 从请求数据中提取客户端密钥 JSON
client_secret_json = serializer.validated_data['client_secret_json']
# 调用 GmailService 生成授权 URL
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
logger.info(f"Generated auth URL for user {request.user.id}")
# 读取上传的JSON文件内容
client_secret_file = request.FILES['client_secret_file']
client_secret_json = json.loads(client_secret_file.read().decode('utf-8'))
logger.info(f"从上传文件读取到客户端密钥JSON")
except json.JSONDecodeError as e:
logger.error(f"解析客户端密钥JSON文件失败: {str(e)}")
return Response({
'code': 200,
'message': '成功生成授权URL',
'data': {'auth_url': auth_url}
}, status=status.HTTP_200_OK)
'code': 400,
'message': f'无效的JSON文件格式: {str(e)}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
# 记录错误并返回服务器错误响应
logger.error(f"Error initiating authentication for user {request.user.id}: {str(e)}")
logger.error(f"处理上传文件失败: {str(e)}")
return Response({
'code': 500,
'message': f'认证初始化错误:{str(e)}',
'message': f'处理上传文件失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response({
'code': 400,
'message': '请求数据无效',
'data': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
# 如果不是文件上传则尝试从请求数据中提取JSON
if not client_secret_json:
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
try:
# 从请求数据中提取客户端密钥 JSON
client_secret_json = serializer.validated_data['client_secret_json']
except Exception as e:
logger.error(f"未提供客户端密钥JSON: {str(e)}")
return Response({
'code': 400,
'message': '请提供client_secret_json字段或上传client_secret_file文件',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
else:
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response({
'code': 400,
'message': '请求数据无效请提供client_secret_json字段或上传client_secret_file文件',
'data': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
# 如果此时仍然没有client_secret_json返回错误
if not client_secret_json:
return Response({
'code': 400,
'message': '请提供client_secret_json字段或上传client_secret_file文件',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
try:
# 调用 GmailService 生成授权 URL
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
logger.info(f"Generated auth URL for user {request.user.id}")
return Response({
'code': 200,
'message': '成功生成授权URL',
'data': {'auth_url': auth_url}
}, status=status.HTTP_200_OK)
except Exception as e:
# 记录错误并返回服务器错误响应
logger.error(f"Error initiating authentication for user {request.user.id}: {str(e)}")
return Response({
'code': 500,
'message': f'认证初始化错误:{str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailAuthCompleteView(APIView):
@ -92,8 +140,12 @@ class GmailAuthCompleteView(APIView):
"""
处理 POST 请求使用授权代码完成 Gmail OAuth2 认证并保存凭证
支持两种方式提供客户端密钥:
1. 在请求体中提供client_secret_json字段
2. 上传名为client_secret_file的JSON文件
Args:
request: Django REST Framework 请求对象包含授权代码和客户端密钥 JSON
request: Django REST Framework 请求对象包含授权代码和客户端密钥 JSON 或文件
Returns:
Response: 包含已保存凭证数据的 JSON 响应成功时或错误信息失败时
@ -104,37 +156,89 @@ class GmailAuthCompleteView(APIView):
500: 服务器内部错误如认证失败
"""
logger.debug(f"Received auth complete request: {request.data}")
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
# 检查是否是文件上传方式
client_secret_json = None
if 'client_secret_file' in request.FILES:
try:
# 提取授权代码和客户端密钥 JSON
auth_code = serializer.validated_data['auth_code']
client_secret_json = serializer.validated_data['client_secret_json']
# 完成认证并保存凭证
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
# 序列化凭证数据以返回
serializer = GmailCredentialSerializer(credential, context={'request': request})
logger.info(f"Authentication completed for user {request.user.id}, email: {credential.email}")
# 读取上传的JSON文件内容
client_secret_file = request.FILES['client_secret_file']
client_secret_json = json.loads(client_secret_file.read().decode('utf-8'))
logger.info(f"从上传文件读取到客户端密钥JSON")
except json.JSONDecodeError as e:
logger.error(f"解析客户端密钥JSON文件失败: {str(e)}")
return Response({
'code': 201,
'message': '认证完成并成功保存凭证',
'data': serializer.data
}, status=status.HTTP_201_CREATED)
'code': 400,
'message': f'无效的JSON文件格式: {str(e)}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
# 记录错误并返回服务器错误响应
logger.error(f"Error completing authentication for user {request.user.id}: {str(e)}")
logger.error(f"处理上传文件失败: {str(e)}")
return Response({
'code': 500,
'message': f'完成认证时发生错误:{str(e)}',
'message': f'处理上传文件失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response({
'code': 400,
'message': '请求数据无效',
'data': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
# 获取授权码,无论是哪种方式都需要
auth_code = request.data.get('auth_code')
if not auth_code:
return Response({
'code': 400,
'message': '必须提供授权码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 如果不是文件上传则尝试从请求数据中提取JSON
if not client_secret_json:
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
try:
# 从请求数据中提取客户端密钥 JSON
client_secret_json = serializer.validated_data['client_secret_json']
except Exception as e:
logger.error(f"未提供客户端密钥JSON: {str(e)}")
return Response({
'code': 400,
'message': '请提供client_secret_json字段或上传client_secret_file文件',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
else:
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response({
'code': 400,
'message': '请求数据无效请提供client_secret_json字段或上传client_secret_file文件',
'data': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
# 如果此时仍然没有client_secret_json返回错误
if not client_secret_json:
return Response({
'code': 400,
'message': '请提供client_secret_json字段或上传client_secret_file文件',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
try:
# 完成认证并保存凭证
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
# 序列化凭证数据以返回
return_serializer = GmailCredentialSerializer(credential, context={'request': request})
logger.info(f"Authentication completed for user {request.user.id}, email: {credential.email}")
return Response({
'code': 201,
'message': '认证完成并成功保存凭证',
'data': return_serializer.data
}, status=status.HTTP_201_CREATED)
except Exception as e:
# 记录错误并返回服务器错误响应
logger.error(f"Error completing authentication for user {request.user.id}: {str(e)}")
return Response({
'code': 500,
'message': f'完成认证时发生错误:{str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailCredentialViewSet(viewsets.ModelViewSet):
@ -212,36 +316,21 @@ class GmailCredentialViewSet(viewsets.ModelViewSet):
client_secret_json = serializer.validated_data.get('client_secret_json')
if not auth_code: # 初始化OAuth
auth_url = GmailService.start_oauth(client_secret_json)
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
return Response({
'code': 200,
'message': '授权URL生成成功',
'data': {'auth_url': auth_url}
})
else: # 完成OAuth
email, credentials = GmailService.complete_oauth(client_secret_json, auth_code)
if not email:
return Response({
'code': 400,
'message': f'授权失败: {credentials}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 保存凭证
serializer.save(
user=request.user,
email=email,
credentials=credentials,
is_valid=True
)
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
return Response({
'code': 201,
'message': '授权成功',
'data': {
'id': serializer.instance.id,
'email': email,
'is_valid': True
'id': credential.id,
'email': credential.email,
'is_valid': credential.is_valid
}
}, status=status.HTTP_201_CREATED)
@ -365,6 +454,68 @@ class GmailCredentialViewSet(viewsets.ModelViewSet):
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=False, methods=['post'])
def upload_client_secret(self, request):
"""
通过上传客户端密钥JSON文件来创建Gmail凭证
请求参数:
- client_secret_file: Google Cloud项目的客户端密钥JSON文件
- auth_code: [可选] 授权码用于完成OAuth流程
"""
try:
# 检查是否上传了文件
if 'client_secret_file' not in request.FILES:
return Response({
'code': 400,
'message': '未找到客户端密钥文件请使用client_secret_file字段上传',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 读取上传的JSON文件内容
client_secret_file = request.FILES['client_secret_file']
client_secret_json = json.loads(client_secret_file.read().decode('utf-8'))
# 获取可选的授权码
auth_code = request.data.get('auth_code', '')
if not auth_code: # 初始化OAuth
# 调用GmailService生成授权URL
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
return Response({
'code': 200,
'message': '授权URL生成成功',
'data': {'auth_url': auth_url}
})
else: # 完成OAuth
# 完成认证并保存凭证
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
return Response({
'code': 201,
'message': '授权成功',
'data': {
'id': credential.id,
'email': credential.email,
'is_valid': credential.is_valid
}
}, status=status.HTTP_201_CREATED)
except json.JSONDecodeError as e:
logger.error(f"解析客户端密钥JSON文件失败: {str(e)}")
return Response({
'code': 400,
'message': f'无效的JSON文件格式: {str(e)}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f"上传客户端密钥文件失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
'code': 500,
'message': f'处理客户端密钥文件失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailConversationView(APIView):
"""
@ -846,12 +997,120 @@ class GmailWebhookView(APIView):
# 获取并保存最新邮件
print("[Gmail Webhook] 开始获取最新邮件...")
# 处理新邮件,使用静态方法而不是实例化类
try:
# 使用GmailService的静态方法处理新邮件
processed_emails = GmailService.process_new_emails(user, credential, history_id)
print(f"[Gmail Webhook] 新邮件处理完成,共 {len(processed_emails)}")
# 处理活跃对话的自动回复
if processed_emails:
print(f"[Gmail Webhook] 开始处理自动回复...")
# 获取刚处理的邮件的对应的对话信息
from apps.gmail.models import GmailConversation, UserGoal
from apps.chat.models import ChatHistory
# 遍历每个处理过的邮件ID
for email_id in processed_emails:
try:
# 通过邮件ID查找对应的聊天记录
chat_msg = ChatHistory.objects.filter(
metadata__gmail_message_id=email_id
).order_by('-created_at').first()
if not chat_msg:
print(f"[Gmail Webhook] 邮件 {email_id} 未找到对应的聊天记录,跳过自动回复")
continue
conversation_id = chat_msg.conversation_id
# 检查对话是否是活跃的
conversation = GmailConversation.objects.filter(
conversation_id=conversation_id,
is_active=True
).first()
if not conversation:
print(f"[Gmail Webhook] 对话 {conversation_id} 不是活跃的,跳过自动回复")
continue
print(f"[Gmail Webhook] 发现活跃对话 {conversation_id},准备自动回复")
# 检查邮件角色,只有达人发送的邮件才自动回复
if chat_msg.role != 'assistant':
print(f"[Gmail Webhook] 邮件 {email_id} 不是达人发送的,跳过自动回复")
continue
# 查找对话的目标
goal = UserGoal.objects.filter(
conversation=conversation,
is_active=True
).first()
if not goal:
print(f"[Gmail Webhook] 对话 {conversation_id} 没有活跃目标,跳过自动回复")
continue
# 获取对话摘要
from apps.gmail.services.goal_service import get_conversation_summary, get_last_message, generate_recommended_reply
conversation_summary = get_conversation_summary(conversation_id)
if not conversation_summary:
conversation_summary = "无对话摘要"
# 获取最后一条达人消息
last_message = get_last_message(conversation_id)
if not last_message:
print(f"[Gmail Webhook] 对话 {conversation_id} 没有达人消息,跳过自动回复")
continue
# 生成推荐回复
reply_content, error = generate_recommended_reply(
user=user,
goal_description=goal.description,
conversation_summary=conversation_summary,
last_message=last_message
)
if error:
print(f"[Gmail Webhook] 生成推荐回复失败: {error}")
continue
if not reply_content:
print(f"[Gmail Webhook] 生成的推荐回复为空")
continue
# 构建回复的主题
subject = chat_msg.metadata.get('subject', '')
reply_subject = f"回复: {subject}" if not subject.startswith('回复:') else subject
# 准备发送自动回复
print(f"[Gmail Webhook] 准备发送自动回复: 从{conversation.user_email}{conversation.influencer_email}")
success, reply_message_id = GmailService.send_email(
user=user,
user_email=conversation.user_email,
to_email=conversation.influencer_email,
subject=reply_subject,
body=reply_content
)
if success:
print(f"[Gmail Webhook] 已成功发送自动回复: {reply_message_id}")
# 更新目标状态
goal.last_activity_time = timezone.now()
if goal.status == 'pending':
goal.status = 'in_progress'
goal.save(update_fields=['last_activity_time', 'status', 'updated_at'])
else:
print(f"[Gmail Webhook] 发送自动回复失败: {reply_message_id}")
except Exception as reply_error:
print(f"[Gmail Webhook] 处理自动回复过程中出错: {str(reply_error)}")
import traceback
print(traceback.format_exc())
# 更新凭证的历史ID移到处理完成后再更新
if history_id:
credential.last_history_id = history_id
@ -861,10 +1120,12 @@ class GmailWebhookView(APIView):
except Exception as e:
print(f"[Gmail Webhook] 获取最新邮件失败: {str(e)}")
logger.error(f"获取最新邮件失败: {str(e)}")
import traceback
print(traceback.format_exc())
else:
print(f"[Gmail Webhook] 警告: 未找到对应的Gmail凭证: {email_address}")
logger.warning(f"收到推送通知但未找到对应的Gmail凭证: {email_address}")
except Exception as e:
print(f"[Gmail Webhook] 解析推送数据失败: {str(e)}")
logger.error(f"解析推送数据失败: {str(e)}")