first commit
This commit is contained in:
46
old/app/models/__init__.py
Normal file
46
old/app/models/__init__.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 修改相对导入为绝对导入
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
from config import active_config
|
||||
|
||||
# 创建数据库引擎
|
||||
engine = create_engine(active_config.SQLALCHEMY_DATABASE_URI)
|
||||
|
||||
# 创建会话工厂
|
||||
session_factory = sessionmaker(bind=engine)
|
||||
Session = scoped_session(session_factory)
|
||||
|
||||
# 创建模型基类
|
||||
Base = declarative_base()
|
||||
|
||||
# 获取数据库会话
|
||||
def get_session():
|
||||
"""获取数据库会话"""
|
||||
return Session()
|
||||
|
||||
# 初始化数据库
|
||||
def init_db():
|
||||
"""初始化数据库,创建所有表"""
|
||||
# 导入所有模型以确保它们被注册
|
||||
from .domain import Domain
|
||||
from .mailbox import Mailbox
|
||||
from .email import Email
|
||||
from .attachment import Attachment
|
||||
|
||||
# 创建表
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
return get_session()
|
||||
|
||||
# 导出模型类
|
||||
from .domain import Domain
|
||||
from .mailbox import Mailbox
|
||||
from .email import Email
|
||||
from .attachment import Attachment
|
||||
|
||||
__all__ = ['Base', 'get_session', 'init_db', 'Domain', 'Mailbox', 'Email', 'Attachment']
|
||||
BIN
old/app/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
old/app/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/models/__pycache__/attachment.cpython-312.pyc
Normal file
BIN
old/app/models/__pycache__/attachment.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/models/__pycache__/domain.cpython-312.pyc
Normal file
BIN
old/app/models/__pycache__/domain.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/models/__pycache__/email.cpython-312.pyc
Normal file
BIN
old/app/models/__pycache__/email.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/models/__pycache__/mailbox.cpython-312.pyc
Normal file
BIN
old/app/models/__pycache__/mailbox.cpython-312.pyc
Normal file
Binary file not shown.
71
old/app/models/attachment.py
Normal file
71
old/app/models/attachment.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, LargeBinary
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from . import Base
|
||||
|
||||
class Attachment(Base):
|
||||
"""附件模型"""
|
||||
__tablename__ = 'attachments'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
email_id = Column(Integer, ForeignKey('emails.id'), nullable=False, index=True)
|
||||
filename = Column(String(255), nullable=False)
|
||||
content_type = Column(String(100), nullable=True)
|
||||
size = Column(Integer, nullable=False, default=0)
|
||||
storage_path = Column(String(500), nullable=True) # 用于文件系统存储
|
||||
content = Column(LargeBinary, nullable=True) # 用于小型附件的直接存储
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# 关联关系
|
||||
email = relationship("Email", back_populates="attachments")
|
||||
|
||||
@property
|
||||
def is_stored_in_fs(self):
|
||||
"""判断附件是否存储在文件系统中"""
|
||||
return bool(self.storage_path and not self.content)
|
||||
|
||||
def save_to_filesystem(self, content, base_path):
|
||||
"""将附件保存到文件系统"""
|
||||
# 确保目录存在
|
||||
os.makedirs(base_path, exist_ok=True)
|
||||
|
||||
# 创建文件路径
|
||||
file_path = os.path.join(
|
||||
base_path,
|
||||
f"{self.email_id}_{self.id}_{self.filename}"
|
||||
)
|
||||
|
||||
# 写入文件
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(content)
|
||||
|
||||
# 更新对象属性
|
||||
self.storage_path = file_path
|
||||
self.size = len(content)
|
||||
self.content = None # 清空内存中的内容
|
||||
|
||||
return file_path
|
||||
|
||||
def get_content(self, attachments_dir=None):
|
||||
"""获取附件内容,无论是从数据库还是文件系统"""
|
||||
if self.content:
|
||||
return self.content
|
||||
|
||||
if self.storage_path and os.path.exists(self.storage_path):
|
||||
with open(self.storage_path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
return None
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典,用于API响应"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"email_id": self.email_id,
|
||||
"filename": self.filename,
|
||||
"content_type": self.content_type,
|
||||
"size": self.size,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
35
old/app/models/domain.py
Normal file
35
old/app/models/domain.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
|
||||
from . import Base
|
||||
|
||||
|
||||
class Domain(Base):
|
||||
"""邮件域名模型"""
|
||||
__tablename__ = 'domains'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255), unique=True, nullable=False, index=True)
|
||||
description = Column(String(500), nullable=True)
|
||||
active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# 关系
|
||||
mailboxes = relationship("Mailbox", back_populates="domain", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Domain {self.name}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典,用于API响应"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"active": self.active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
"mailbox_count": len(self.mailboxes) if self.mailboxes else 0
|
||||
}
|
||||
148
old/app/models/email.py
Normal file
148
old/app/models/email.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import os
|
||||
import json
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from . import Base
|
||||
import config
|
||||
active_config = config.active_config
|
||||
|
||||
|
||||
class Email(Base):
|
||||
"""电子邮件模型"""
|
||||
__tablename__ = 'emails'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
mailbox_id = Column(Integer, ForeignKey('mailboxes.id'), nullable=False, index=True)
|
||||
sender = Column(String(255), nullable=False)
|
||||
recipients = Column(String(1000), nullable=False)
|
||||
subject = Column(String(500), nullable=True)
|
||||
body_text = Column(Text, nullable=True)
|
||||
body_html = Column(Text, nullable=True)
|
||||
received_at = Column(DateTime, default=datetime.utcnow)
|
||||
read = Column(Boolean, default=False)
|
||||
headers = Column(JSON, nullable=True)
|
||||
|
||||
# 提取的验证码和链接
|
||||
verification_code = Column(String(100), nullable=True)
|
||||
verification_link = Column(String(1000), nullable=True)
|
||||
|
||||
# 关联关系
|
||||
mailbox = relationship("Mailbox", back_populates="emails")
|
||||
attachments = relationship("Attachment", back_populates="email", cascade="all, delete-orphan")
|
||||
|
||||
def save_raw_email(self, raw_content):
|
||||
"""保存原始邮件内容到文件"""
|
||||
storage_path = active_config.MAIL_STORAGE_PATH
|
||||
mailbox_dir = os.path.join(storage_path, str(self.mailbox_id))
|
||||
os.makedirs(mailbox_dir, exist_ok=True)
|
||||
|
||||
# 保存原始邮件内容
|
||||
file_path = os.path.join(mailbox_dir, f"{self.id}.eml")
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(raw_content)
|
||||
|
||||
def extract_verification_data(self):
|
||||
"""
|
||||
尝试从邮件内容中提取验证码和验证链接
|
||||
这个方法会在邮件保存时自动调用
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 合并文本和HTML内容用于搜索
|
||||
content = f"{self.subject or ''} {self.body_text or ''} {self.body_html or ''}"
|
||||
logger.info(f"开始提取邮件ID={self.id}的验证信息,内容长度={len(content)}")
|
||||
|
||||
# 首先检查是否是Cursor验证邮件
|
||||
if "Verify your email" in self.subject and (
|
||||
"cursor.sh" in self.sender.lower() or
|
||||
"cursor" in self.sender.lower()
|
||||
):
|
||||
logger.info("检测到Cursor验证邮件")
|
||||
# 从HTML中提取6位数字验证码
|
||||
cursor_patterns = [
|
||||
r'(\d{6})</div>', # 匹配Cursor邮件中的6位数字验证码格式
|
||||
r'<div[^>]*>(\d{6})</div>', # 更宽松的匹配
|
||||
r'>(\d{6})<', # 最简单的形式
|
||||
r'(\d{6})' # 任何6位数字
|
||||
]
|
||||
|
||||
for pattern in cursor_patterns:
|
||||
matches = re.findall(pattern, content)
|
||||
if matches:
|
||||
self.verification_code = matches[0]
|
||||
logger.info(f"从Cursor邮件中提取到验证码: {self.verification_code}")
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
# 提取可能的验证码(4-8位数字或字母组合)
|
||||
code_patterns = [
|
||||
r'\b([A-Z0-9]{4,8})\b', # 大写字母和数字
|
||||
r'验证码[::]\s*([A-Z0-9]{4,8})', # 中文格式
|
||||
r'验证码是[::]\s*([A-Z0-9]{4,8})', # 中文格式2
|
||||
r'code[::]\s*([A-Z0-9]{4,8})', # 英文格式
|
||||
r'code is[::]\s*([A-Z0-9]{4,8})', # 英文格式2
|
||||
r'code[::]\s*<[^>]*>([A-Z0-9]{4,8})', # HTML格式
|
||||
r'<div[^>]*>([0-9]{4,8})</div>', # HTML分隔的数字
|
||||
r'<strong[^>]*>([A-Z0-9]{4,8})</strong>', # 粗体验证码
|
||||
]
|
||||
|
||||
for pattern in code_patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
if matches:
|
||||
# 过滤掉明显不是验证码的结果
|
||||
filtered_matches = [m for m in matches if len(m) >= 4 and not m.lower() in ['code', 'verify', 'http', 'https']]
|
||||
if filtered_matches:
|
||||
self.verification_code = filtered_matches[0]
|
||||
logger.info(f"提取到验证码: {self.verification_code}")
|
||||
break
|
||||
|
||||
# 提取验证链接
|
||||
link_patterns = [
|
||||
r'https?://\S+(?:verify|confirm|activate)\S+',
|
||||
r'https?://\S+(?:token|auth|account)\S+',
|
||||
]
|
||||
|
||||
for pattern in link_patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
if matches:
|
||||
self.verification_link = matches[0]
|
||||
logger.info(f"提取到验证链接: {self.verification_link}")
|
||||
break
|
||||
|
||||
# 如果没有找到验证码,但邮件主题暗示这是验证邮件
|
||||
verify_subjects = ['verify', 'confirmation', 'activate', 'validation', '验证', '确认']
|
||||
if not self.verification_code and any(subj in self.subject.lower() for subj in verify_subjects):
|
||||
logger.info("根据主题判断这可能是验证邮件,但未能提取到验证码")
|
||||
# 尝试从HTML中提取明显的数字序列
|
||||
if self.body_html:
|
||||
number_matches = re.findall(r'(\d{4,8})', self.body_html)
|
||||
filtered_numbers = [n for n in number_matches if len(n) >= 4 and len(n) <= 8]
|
||||
if filtered_numbers:
|
||||
self.verification_code = filtered_numbers[0]
|
||||
logger.info(f"从HTML中提取到可能的验证码: {self.verification_code}")
|
||||
|
||||
logger.info(f"验证信息提取完成: code={self.verification_code}, link={self.verification_link}")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Email {self.id}: {self.subject}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典,用于API响应"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"mailbox_id": self.mailbox_id,
|
||||
"sender": self.sender,
|
||||
"recipients": self.recipients,
|
||||
"subject": self.subject,
|
||||
"received_at": self.received_at.isoformat() if self.received_at else None,
|
||||
"read": self.read,
|
||||
"verification_code": self.verification_code,
|
||||
"verification_link": self.verification_link,
|
||||
"has_attachments": len(self.attachments) > 0 if self.attachments else False
|
||||
}
|
||||
50
old/app/models/mailbox.py
Normal file
50
old/app/models/mailbox.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import secrets
|
||||
|
||||
from . import Base
|
||||
|
||||
|
||||
class Mailbox(Base):
|
||||
"""邮箱模型"""
|
||||
__tablename__ = 'mailboxes'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
address = Column(String(255), unique=True, nullable=False, index=True)
|
||||
domain_id = Column(Integer, ForeignKey('domains.id'), nullable=False)
|
||||
password_hash = Column(String(255), nullable=True)
|
||||
description = Column(String(500), nullable=True)
|
||||
active = Column(Boolean, default=True)
|
||||
api_key = Column(String(64), unique=True, default=lambda: secrets.token_hex(16))
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
last_accessed = Column(DateTime, nullable=True)
|
||||
|
||||
# 关系
|
||||
domain = relationship("Domain", back_populates="mailboxes")
|
||||
emails = relationship("Email", back_populates="mailbox", cascade="all, delete-orphan")
|
||||
|
||||
@property
|
||||
def full_address(self):
|
||||
"""获取完整邮箱地址 (包含域名)"""
|
||||
return f"{self.address}@{self.domain.name}"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Mailbox {self.full_address}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典,用于API响应"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"address": self.address,
|
||||
"domain_id": self.domain_id,
|
||||
"domain_name": self.domain.name if self.domain else None,
|
||||
"full_address": self.full_address,
|
||||
"description": self.description,
|
||||
"active": self.active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
"last_accessed": self.last_accessed.isoformat() if self.last_accessed else None,
|
||||
"email_count": len(self.emails) if self.emails else 0
|
||||
}
|
||||
Reference in New Issue
Block a user