增加临时邮箱功能
This commit is contained in:
1 parent
821b4bfd4f
commit
9bd31c3820
3 files changed
+1043
-37
No files matched your search
@@ -19,6 +19,8 @@
|
||||
- 📤 **导出功能** - 支持按分组导出邮箱账号信息
|
||||
- 🎨 **现代化 UI** - 简洁美观的四栏式界面布局
|
||||
- ⚡ **性能优化** - 邮箱列表缓存,快速切换分组
|
||||
- 🔥 **临时邮箱** - 集成 GPTMail API,一键生成临时邮箱
|
||||
- ⚙️ **系统设置** - 支持在线修改登录密码和 API Key
|
||||
|
||||
### 界面布局
|
||||
Web 应用采用四栏式布局设计:
|
||||
@@ -48,7 +50,7 @@ python web_outlook_app.py
|
||||
|
||||
启动后访问:http://127.0.0.1:5001
|
||||
|
||||
默认登录密码:`admin123`(可在 [`web_outlook_app.py`](web_outlook_app.py:27) 中修改 `LOGIN_PASSWORD` 变量)
|
||||
默认登录密码:`admin123`(可在 Web 界面的「设置」中修改)
|
||||
|
||||
### 3. 运行命令行测试工具
|
||||
|
||||
@@ -112,16 +114,50 @@ Web 应用提供以下 API 端点:
|
||||
| `/api/email/<email>/<message_id>` | GET | 获取邮件详情 |
|
||||
| `/api/groups/<id>/export` | GET | 导出分组邮箱 |
|
||||
| `/api/accounts/export` | GET | 导出所有邮箱 |
|
||||
| `/api/temp-emails` | GET | 获取所有临时邮箱 |
|
||||
| `/api/temp-emails/generate` | POST | 生成新的临时邮箱 |
|
||||
| `/api/temp-emails/<email>` | DELETE | 删除临时邮箱 |
|
||||
| `/api/temp-emails/<email>/messages` | GET | 获取临时邮箱邮件列表 |
|
||||
| `/api/temp-emails/<email>/messages/<id>` | GET | 获取临时邮件详情 |
|
||||
| `/api/temp-emails/<email>/clear` | DELETE | 清空临时邮箱所有邮件 |
|
||||
| `/api/settings` | GET | 获取系统设置 |
|
||||
| `/api/settings` | PUT | 更新系统设置 |
|
||||
|
||||
### 临时邮箱功能
|
||||
|
||||
系统集成了 GPTMail API 提供临时邮箱服务:
|
||||
|
||||
1. **生成临时邮箱** - 点击「临时邮箱」分组,然后点击「生成临时邮箱」按钮
|
||||
2. **查看邮件** - 选择临时邮箱后点击「获取邮件」按钮
|
||||
3. **清空邮件** - 点击临时邮箱旁的「清空」按钮,清空该邮箱的所有邮件
|
||||
4. **删除邮箱** - 点击临时邮箱旁的「删除」按钮,删除邮箱及其所有邮件
|
||||
|
||||
临时邮箱数据会存储在本地数据库中,方便后续查看。
|
||||
|
||||
### 系统设置
|
||||
|
||||
点击导航栏的「⚙️ 设置」按钮,可以修改以下配置:
|
||||
|
||||
1. **登录密码** - 修改 Web 界面的登录密码
|
||||
2. **GPTMail API Key** - 设置临时邮箱功能所需的 API Key
|
||||
|
||||
设置会保存在数据库中,重启应用后仍然有效。
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
### Web 应用配置
|
||||
|
||||
在 [`web_outlook_app.py`](web_outlook_app.py) 中可以修改以下配置:
|
||||
以下配置可以在 Web 界面的「设置」中修改:
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| 登录密码 | `admin123` | Web 界面登录密码 |
|
||||
| GPTMail API Key | 空 | 临时邮箱功能的 API Key |
|
||||
|
||||
以下配置需要在 [`web_outlook_app.py`](web_outlook_app.py) 中修改:
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `LOGIN_PASSWORD` | `admin123` | 登录密码 |
|
||||
| `DATABASE` | `outlook_accounts.db` | SQLite 数据库文件 |
|
||||
| `IMAP_SERVER_OLD` | `outlook.office365.com` | 旧版 IMAP 服务器 |
|
||||
| `IMAP_SERVER_NEW` | `outlook.live.com` | 新版 IMAP 服务器 |
|
||||
|
||||
+467
-30
@@ -989,6 +989,7 @@
|
||||
</div>
|
||||
<div class="navbar-actions">
|
||||
<button class="navbar-btn" onclick="showExportModal()">导出邮箱</button>
|
||||
<button class="navbar-btn" onclick="showSettingsModal()">⚙️ 设置</button>
|
||||
<button class="navbar-btn" onclick="logout()" style="color: #dc3545;">退出登录</button>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1219,6 +1220,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置模态框 -->
|
||||
<div class="modal" id="settingsModal">
|
||||
<div class="modal-content" style="width: 480px;">
|
||||
<div class="modal-header">
|
||||
<h3>⚙️ 系统设置</h3>
|
||||
<button class="modal-close" onclick="hideSettingsModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">登录密码</label>
|
||||
<input type="password" class="form-input" id="settingsPassword" placeholder="输入新密码(留空则不修改)">
|
||||
<div class="form-hint">用于登录系统的密码</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">GPTMail API Key</label>
|
||||
<input type="text" class="form-input" id="settingsApiKey" placeholder="输入 GPTMail API Key">
|
||||
<div class="form-hint">用于临时邮箱功能,可从 <a href="https://mail.chatgpt.org.uk/api" target="_blank" style="color: #0078d4;">gptmail.io</a> 获取</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="hideSettingsModal()">取消</button>
|
||||
<button class="btn btn-primary" onclick="saveSettings()">保存设置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息提示 -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
@@ -1233,6 +1260,8 @@
|
||||
let accountsCache = {}; // 缓存各分组的邮箱列表
|
||||
let editingGroupId = null;
|
||||
let selectedColor = '#1a1a1a';
|
||||
let isTempEmailGroup = false; // 是否是临时邮箱分组
|
||||
let tempEmailGroupId = null; // 临时邮箱分组 ID
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@@ -1299,6 +1328,13 @@
|
||||
|
||||
if (data.success) {
|
||||
groups = data.groups;
|
||||
|
||||
// 找到临时邮箱分组
|
||||
const tempGroup = groups.find(g => g.name === '临时邮箱');
|
||||
if (tempGroup) {
|
||||
tempEmailGroupId = tempGroup.id;
|
||||
}
|
||||
|
||||
renderGroupList(data.groups);
|
||||
updateGroupSelects();
|
||||
|
||||
@@ -1307,7 +1343,11 @@
|
||||
const group = groups.find(g => g.id === currentGroupId);
|
||||
if (group) {
|
||||
// 刷新当前分组的邮箱列表
|
||||
loadAccountsByGroup(currentGroupId, true);
|
||||
if (currentGroupId === tempEmailGroupId) {
|
||||
loadTempEmails(true);
|
||||
} else {
|
||||
loadAccountsByGroup(currentGroupId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1330,36 +1370,45 @@
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = groups.map(group => `
|
||||
<div class="group-item ${currentGroupId === group.id ? 'active' : ''}"
|
||||
data-group-id="${group.id}"
|
||||
onclick="selectGroup(${group.id})">
|
||||
<div class="group-row-1">
|
||||
<div class="group-color" style="background-color: ${group.color || '#666'}"></div>
|
||||
<span class="group-name">${escapeHtml(group.name)}</span>
|
||||
</div>
|
||||
<div class="group-row-2">
|
||||
<span class="group-count">${group.account_count || 0} 个邮箱</span>
|
||||
<div class="group-actions">
|
||||
<button class="group-action-btn" onclick="event.stopPropagation(); editGroup(${group.id})" title="编辑">✏️</button>
|
||||
${group.id !== 1 ? `<button class="group-action-btn" onclick="event.stopPropagation(); deleteGroup(${group.id})" title="删除">🗑️</button>` : ''}
|
||||
container.innerHTML = groups.map(group => {
|
||||
const isSystem = group.is_system === 1 || group.name === '临时邮箱';
|
||||
const isTempGroup = group.name === '临时邮箱';
|
||||
const isDefault = group.id === 1;
|
||||
|
||||
return `
|
||||
<div class="group-item ${currentGroupId === group.id ? 'active' : ''} ${isTempGroup ? 'temp-email-group' : ''}"
|
||||
data-group-id="${group.id}"
|
||||
onclick="selectGroup(${group.id})">
|
||||
<div class="group-row-1">
|
||||
<div class="group-color" style="background-color: ${group.color || '#666'}"></div>
|
||||
<span class="group-name">${escapeHtml(group.name)}${isTempGroup ? ' ⚡' : ''}</span>
|
||||
</div>
|
||||
<div class="group-row-2">
|
||||
<span class="group-count">${group.account_count || 0} 个邮箱</span>
|
||||
<div class="group-actions">
|
||||
${!isSystem ? `<button class="group-action-btn" onclick="event.stopPropagation(); editGroup(${group.id})" title="编辑">✏️</button>` : ''}
|
||||
${!isDefault && !isSystem ? `<button class="group-action-btn" onclick="event.stopPropagation(); deleteGroup(${group.id})" title="删除">🗑️</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 选择分组
|
||||
async function selectGroup(groupId) {
|
||||
currentGroupId = groupId;
|
||||
|
||||
// 检查是否是临时邮箱分组
|
||||
const group = groups.find(g => g.id === groupId);
|
||||
isTempEmailGroup = group && group.name === '临时邮箱';
|
||||
|
||||
// 更新分组列表 UI
|
||||
document.querySelectorAll('.group-item').forEach(item => {
|
||||
item.classList.toggle('active', parseInt(item.dataset.groupId) === groupId);
|
||||
});
|
||||
|
||||
// 更新邮箱面板标题
|
||||
const group = groups.find(g => g.id === groupId);
|
||||
if (group) {
|
||||
document.getElementById('currentGroupName').textContent = group.name;
|
||||
document.getElementById('currentGroupColor').style.backgroundColor = group.color || '#666';
|
||||
@@ -1371,8 +1420,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 更新底部按钮
|
||||
updateAccountPanelFooter();
|
||||
|
||||
// 加载该分组的邮箱
|
||||
await loadAccountsByGroup(groupId);
|
||||
if (isTempEmailGroup) {
|
||||
await loadTempEmails();
|
||||
} else {
|
||||
await loadAccountsByGroup(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新账号面板底部按钮
|
||||
function updateAccountPanelFooter() {
|
||||
const footer = document.querySelector('.account-panel-footer');
|
||||
if (isTempEmailGroup) {
|
||||
footer.innerHTML = `
|
||||
<button class="add-account-btn" onclick="generateTempEmail()">+ 生成临时邮箱</button>
|
||||
`;
|
||||
} else {
|
||||
footer.innerHTML = `
|
||||
<button class="add-account-btn" onclick="showAddAccountModal()">+ 导入邮箱</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分组下的账号
|
||||
@@ -1568,25 +1638,318 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 账号相关 ====================
|
||||
// ==================== 临时邮箱相关 ====================
|
||||
|
||||
// 选择账号
|
||||
function selectAccount(email) {
|
||||
// 加载临时邮箱列表
|
||||
async function loadTempEmails(forceRefresh = false) {
|
||||
const container = document.getElementById('accountList');
|
||||
|
||||
if (!forceRefresh && accountsCache['temp']) {
|
||||
renderTempEmailList(accountsCache['temp']);
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '<div class="loading loading-small"><div class="loading-spinner"></div></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/temp-emails');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
accountsCache['temp'] = data.emails;
|
||||
renderTempEmailList(data.emails);
|
||||
|
||||
const group = groups.find(g => g.name === '临时邮箱');
|
||||
if (group) {
|
||||
group.account_count = data.emails.length;
|
||||
renderGroupList(groups);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = '<div class="empty-state"><div class="empty-state-text">加载失败</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染临时邮箱列表
|
||||
function renderTempEmailList(emails) {
|
||||
const container = document.getElementById('accountList');
|
||||
|
||||
if (emails.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚡</div>
|
||||
<div class="empty-state-text">暂无临时邮箱<br>点击下方按钮生成</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = emails.map(email => `
|
||||
<div class="account-item ${currentAccount === email.email ? 'active' : ''}"
|
||||
onclick="selectTempEmail('${email.email}')">
|
||||
<div class="account-email" title="${email.email}">
|
||||
${email.email}
|
||||
</div>
|
||||
<div class="account-actions">
|
||||
<button class="account-action-btn" onclick="event.stopPropagation(); copyEmail('${email.email}')" title="复制邮箱">复制</button>
|
||||
<button class="account-action-btn" onclick="event.stopPropagation(); clearTempEmailMessages('${email.email}')" title="清空邮件">清空</button>
|
||||
<button class="account-action-btn delete" onclick="event.stopPropagation(); deleteTempEmail('${email.email}')" title="删除">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 生成临时邮箱
|
||||
async function generateTempEmail() {
|
||||
// 获取按钮并显示加载状态
|
||||
const btn = document.querySelector('.account-panel-footer .add-account-btn');
|
||||
const originalText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⏳ 生成中...';
|
||||
btn.style.opacity = '0.7';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/temp-emails/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast(`临时邮箱已生成: ${data.email}`, 'success');
|
||||
delete accountsCache['temp'];
|
||||
loadTempEmails(true);
|
||||
loadGroups();
|
||||
} else {
|
||||
showToast(data.error || '生成失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('生成临时邮箱失败', 'error');
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
btn.disabled = false;
|
||||
btn.textContent = originalText;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
|
||||
// 选择临时邮箱
|
||||
function selectTempEmail(email) {
|
||||
currentAccount = email;
|
||||
isTempEmailGroup = true;
|
||||
|
||||
// 更新顶部当前账号显示
|
||||
document.getElementById('currentAccount').classList.add('show');
|
||||
document.getElementById('currentAccountEmail').textContent = email;
|
||||
document.getElementById('currentAccountEmail').textContent = email + ' (临时)';
|
||||
|
||||
// 更新邮箱列表 UI
|
||||
document.querySelectorAll('.account-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
if (item.querySelector('.account-email').textContent === email) {
|
||||
const emailEl = item.querySelector('.account-email');
|
||||
if (emailEl && emailEl.textContent.includes(email)) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('emailList').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📬</div>
|
||||
<div class="empty-state-text">点击"获取邮件"按钮获取邮件</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('emailDetail').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📄</div>
|
||||
<div class="empty-state-text">选择一封邮件查看详情</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('emailDetailToolbar').style.display = 'none';
|
||||
document.getElementById('emailCount').textContent = '';
|
||||
document.getElementById('methodTag').style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空临时邮箱的所有邮件
|
||||
async function clearTempEmailMessages(email) {
|
||||
if (!confirm(`确定要清空临时邮箱 ${email} 的所有邮件吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/temp-emails/${encodeURIComponent(email)}/clear`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('邮件已清空', 'success');
|
||||
|
||||
// 如果当前选中的就是这个邮箱,清空邮件列表
|
||||
if (currentAccount === email) {
|
||||
currentEmails = [];
|
||||
document.getElementById('emailCount').textContent = '(0)';
|
||||
document.getElementById('emailList').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📭</div>
|
||||
<div class="empty-state-text">收件箱为空</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('emailDetail').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📄</div>
|
||||
<div class="empty-state-text">选择一封邮件查看详情</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('emailDetailToolbar').style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
showToast(data.error || '清空失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('清空失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 删除临时邮箱
|
||||
async function deleteTempEmail(email) {
|
||||
if (!confirm(`确定要删除临时邮箱 ${email} 吗?\n该邮箱的所有邮件也将被删除。`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/temp-emails/${encodeURIComponent(email)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('临时邮箱已删除', 'success');
|
||||
delete accountsCache['temp'];
|
||||
|
||||
if (currentAccount === email) {
|
||||
currentAccount = null;
|
||||
document.getElementById('currentAccount').classList.remove('show');
|
||||
document.getElementById('emailList').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📬</div>
|
||||
<div class="empty-state-text">请从左侧选择一个邮箱账号</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('emailDetail').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📄</div>
|
||||
<div class="empty-state-text">选择一封邮件查看详情</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
loadTempEmails(true);
|
||||
loadGroups();
|
||||
} else {
|
||||
showToast(data.error || '删除失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('删除失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载临时邮箱的邮件
|
||||
async function loadTempEmailMessages(email) {
|
||||
const container = document.getElementById('emailList');
|
||||
container.innerHTML = '<div class="loading"><div class="loading-spinner"></div></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/temp-emails/${encodeURIComponent(email)}/messages`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
currentEmails = data.emails;
|
||||
currentMethod = 'gptmail';
|
||||
|
||||
const methodTag = document.getElementById('methodTag');
|
||||
methodTag.textContent = 'GPTMail';
|
||||
methodTag.style.display = 'inline';
|
||||
methodTag.style.backgroundColor = '#00bcf2';
|
||||
methodTag.style.color = 'white';
|
||||
|
||||
document.getElementById('emailCount').textContent = `(${data.count})`;
|
||||
|
||||
renderEmailList(data.emails);
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">${data.error || '加载失败'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">网络错误,请重试</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取临时邮件详情
|
||||
async function getTempEmailDetail(messageId, index) {
|
||||
document.querySelectorAll('.email-item').forEach((item, i) => {
|
||||
item.classList.toggle('active', i === index);
|
||||
});
|
||||
|
||||
document.getElementById('emailDetailToolbar').style.display = 'flex';
|
||||
|
||||
const container = document.getElementById('emailDetail');
|
||||
container.innerHTML = '<div class="loading"><div class="loading-spinner"></div></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/temp-emails/${encodeURIComponent(currentAccount)}/messages/${encodeURIComponent(messageId)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
renderEmailDetail(data.email);
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">${data.error || '加载失败'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">网络错误,请重试</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 账号相关 ====================
|
||||
|
||||
// 选择账号
|
||||
function selectAccount(email) {
|
||||
currentAccount = email;
|
||||
isTempEmailGroup = false;
|
||||
|
||||
document.getElementById('currentAccount').classList.add('show');
|
||||
document.getElementById('currentAccountEmail').textContent = email;
|
||||
|
||||
document.querySelectorAll('.account-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
const emailEl = item.querySelector('.account-email');
|
||||
if (emailEl && emailEl.textContent.includes(email)) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// 清空邮件列表,提示用户点击获取邮件
|
||||
document.getElementById('emailList').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📬</div>
|
||||
@@ -1594,7 +1957,6 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 清空邮件详情
|
||||
document.getElementById('emailDetail').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📄</div>
|
||||
@@ -2031,9 +2393,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据是否是临时邮箱选择不同的点击处理函数
|
||||
const clickHandler = isTempEmailGroup ? 'getTempEmailDetail' : 'selectEmail';
|
||||
|
||||
container.innerHTML = emails.map((email, index) => `
|
||||
<div class="email-item ${email.is_read === false ? 'unread' : ''}"
|
||||
onclick="selectEmail('${email.id}', ${index})">
|
||||
<div class="email-item ${email.is_read === false ? 'unread' : ''}"
|
||||
onclick="${clickHandler}('${email.id}', ${index})">
|
||||
<div class="email-from">${escapeHtml(email.from)}</div>
|
||||
<div class="email-subject">${escapeHtml(email.subject || '无主题')}</div>
|
||||
<div class="email-preview">${escapeHtml(email.body_preview || '')}</div>
|
||||
@@ -2203,7 +2568,11 @@
|
||||
// 刷新邮件
|
||||
function refreshEmails() {
|
||||
if (currentAccount) {
|
||||
loadEmails(currentAccount);
|
||||
if (isTempEmailGroup) {
|
||||
loadTempEmailMessages(currentAccount);
|
||||
} else {
|
||||
loadEmails(currentAccount);
|
||||
}
|
||||
} else {
|
||||
showToast('请先选择一个邮箱账号', 'error');
|
||||
}
|
||||
@@ -2263,6 +2632,73 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 设置相关 ====================
|
||||
|
||||
// 显示设置模态框
|
||||
async function showSettingsModal() {
|
||||
document.getElementById('settingsModal').classList.add('show');
|
||||
await loadSettings();
|
||||
}
|
||||
|
||||
// 隐藏设置模态框
|
||||
function hideSettingsModal() {
|
||||
document.getElementById('settingsModal').classList.remove('show');
|
||||
// 清空密码输入框
|
||||
document.getElementById('settingsPassword').value = '';
|
||||
}
|
||||
|
||||
// 加载设置
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const response = await fetch('/api/settings');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// 密码不显示,只显示 API Key
|
||||
document.getElementById('settingsApiKey').value = data.settings.gptmail_api_key || '';
|
||||
// 密码框留空
|
||||
document.getElementById('settingsPassword').value = '';
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('加载设置失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
async function saveSettings() {
|
||||
const password = document.getElementById('settingsPassword').value;
|
||||
const apiKey = document.getElementById('settingsApiKey').value.trim();
|
||||
|
||||
const settings = {};
|
||||
|
||||
// 只有输入了密码才更新密码
|
||||
if (password) {
|
||||
settings.login_password = password;
|
||||
}
|
||||
|
||||
// API Key 可以为空(清除)
|
||||
settings.gptmail_api_key = apiKey;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('设置已保存', 'success');
|
||||
hideSettingsModal();
|
||||
} else {
|
||||
showToast(data.error || '保存失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('保存设置失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘快捷键
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
@@ -2270,6 +2706,7 @@
|
||||
hideAddAccountModal();
|
||||
hideEditAccountModal();
|
||||
hideExportModal();
|
||||
hideSettingsModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
+537
-4
@@ -4,6 +4,7 @@
|
||||
Outlook 邮件 Web 应用
|
||||
基于 Flask 的 Web 界面,支持多邮箱管理和邮件查看
|
||||
使用 SQLite 数据库存储邮箱信息,支持分组管理
|
||||
支持 GPTMail 临时邮箱服务
|
||||
"""
|
||||
|
||||
import email
|
||||
@@ -40,6 +41,13 @@ IMAP_PORT = 993
|
||||
# 数据库文件
|
||||
DATABASE = "outlook_accounts.db"
|
||||
|
||||
# GPTMail API 配置
|
||||
GPTMAIL_BASE_URL = "https://mail.chatgpt.org.uk"
|
||||
GPTMAIL_API_KEY = "gpt-test" # 测试 API Key,可以修改为正式 Key
|
||||
|
||||
# 临时邮箱分组 ID(系统保留)
|
||||
TEMP_EMAIL_GROUP_ID = -1
|
||||
|
||||
|
||||
# ==================== 数据库操作 ====================
|
||||
|
||||
@@ -65,6 +73,15 @@ def init_db():
|
||||
conn = sqlite3.connect(DATABASE)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 创建设置表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建分组表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
@@ -72,6 +89,7 @@ def init_db():
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
color TEXT DEFAULT '#1a1a1a',
|
||||
is_system INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
@@ -93,6 +111,35 @@ def init_db():
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建临时邮箱表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS temp_emails (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建临时邮件表(存储从 GPTMail 获取的邮件)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS temp_email_messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
message_id TEXT UNIQUE NOT NULL,
|
||||
email_address TEXT NOT NULL,
|
||||
from_address TEXT,
|
||||
subject TEXT,
|
||||
content TEXT,
|
||||
html_content TEXT,
|
||||
has_html INTEGER DEFAULT 0,
|
||||
timestamp INTEGER,
|
||||
raw_content TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (email_address) REFERENCES temp_emails (email)
|
||||
)
|
||||
''')
|
||||
|
||||
# 检查并添加缺失的列(数据库迁移)
|
||||
cursor.execute("PRAGMA table_info(accounts)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
@@ -106,22 +153,95 @@ def init_db():
|
||||
if 'updated_at' not in columns:
|
||||
cursor.execute('ALTER TABLE accounts ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP')
|
||||
|
||||
# 检查 groups 表是否有 is_system 列
|
||||
cursor.execute("PRAGMA table_info(groups)")
|
||||
group_columns = [col[1] for col in cursor.fetchall()]
|
||||
if 'is_system' not in group_columns:
|
||||
cursor.execute('ALTER TABLE groups ADD COLUMN is_system INTEGER DEFAULT 0')
|
||||
|
||||
# 创建默认分组
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO groups (name, description, color)
|
||||
VALUES ('默认分组', '未分组的邮箱', '#666666')
|
||||
''')
|
||||
|
||||
# 创建临时邮箱分组(系统分组)
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO groups (name, description, color, is_system)
|
||||
VALUES ('临时邮箱', 'GPTMail 临时邮箱服务', '#00bcf2', 1)
|
||||
''')
|
||||
|
||||
# 初始化默认设置
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO settings (key, value)
|
||||
VALUES ('login_password', ?)
|
||||
''', (LOGIN_PASSWORD,))
|
||||
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO settings (key, value)
|
||||
VALUES ('gptmail_api_key', ?)
|
||||
''', (GPTMAIL_API_KEY,))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# ==================== 设置操作 ====================
|
||||
|
||||
def get_setting(key: str, default: str = '') -> str:
|
||||
"""获取设置值"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT value FROM settings WHERE key = ?', (key,))
|
||||
row = cursor.fetchone()
|
||||
return row['value'] if row else default
|
||||
|
||||
|
||||
def set_setting(key: str, value: str) -> bool:
|
||||
"""设置值"""
|
||||
db = get_db()
|
||||
try:
|
||||
db.execute('''
|
||||
INSERT OR REPLACE INTO settings (key, value, updated_at)
|
||||
VALUES (?, ?, CURRENT_TIMESTAMP)
|
||||
''', (key, value))
|
||||
db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_all_settings() -> Dict[str, str]:
|
||||
"""获取所有设置"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT key, value FROM settings')
|
||||
rows = cursor.fetchall()
|
||||
return {row['key']: row['value'] for row in rows}
|
||||
|
||||
|
||||
def get_login_password() -> str:
|
||||
"""获取登录密码(优先从数据库读取)"""
|
||||
password = get_setting('login_password')
|
||||
return password if password else LOGIN_PASSWORD
|
||||
|
||||
|
||||
def get_gptmail_api_key() -> str:
|
||||
"""获取 GPTMail API Key(优先从数据库读取)"""
|
||||
api_key = get_setting('gptmail_api_key')
|
||||
return api_key if api_key else GPTMAIL_API_KEY
|
||||
|
||||
|
||||
# ==================== 分组操作 ====================
|
||||
|
||||
def load_groups() -> List[Dict]:
|
||||
"""加载所有分组"""
|
||||
"""加载所有分组(临时邮箱分组排在最前面)"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT * FROM groups ORDER BY id')
|
||||
# 使用 CASE 语句让临时邮箱分组排在最前面
|
||||
cursor = db.execute('''
|
||||
SELECT * FROM groups
|
||||
ORDER BY
|
||||
CASE WHEN name = '临时邮箱' THEN 0 ELSE 1 END,
|
||||
id
|
||||
''')
|
||||
rows = cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
@@ -569,7 +689,10 @@ def login():
|
||||
data = request.json if request.is_json else request.form
|
||||
password = data.get('password', '')
|
||||
|
||||
if password == LOGIN_PASSWORD:
|
||||
# 从数据库获取密码,如果没有则使用默认密码
|
||||
correct_password = get_login_password()
|
||||
|
||||
if password == correct_password:
|
||||
session['logged_in'] = True
|
||||
session.permanent = True
|
||||
return jsonify({'success': True, 'message': '登录成功'})
|
||||
@@ -603,7 +726,11 @@ def api_get_groups():
|
||||
groups = load_groups()
|
||||
# 添加每个分组的邮箱数量
|
||||
for group in groups:
|
||||
group['account_count'] = get_group_account_count(group['id'])
|
||||
if group['name'] == '临时邮箱':
|
||||
# 临时邮箱分组从 temp_emails 表获取数量
|
||||
group['account_count'] = get_temp_email_count()
|
||||
else:
|
||||
group['account_count'] = get_group_account_count(group['id'])
|
||||
return jsonify({'success': True, 'groups': groups})
|
||||
|
||||
|
||||
@@ -1018,6 +1145,411 @@ def api_get_email_detail(email_addr, message_id):
|
||||
return jsonify({'success': False, 'error': '获取邮件详情失败'})
|
||||
|
||||
|
||||
# ==================== GPTMail 临时邮箱 API ====================
|
||||
|
||||
def gptmail_request(method: str, endpoint: str, params: dict = None, json_data: dict = None) -> Optional[Dict]:
|
||||
"""发送 GPTMail API 请求"""
|
||||
try:
|
||||
url = f"{GPTMAIL_BASE_URL}{endpoint}"
|
||||
# 从数据库获取 API Key
|
||||
api_key = get_gptmail_api_key()
|
||||
headers = {
|
||||
"X-API-Key": api_key,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
if method.upper() == 'GET':
|
||||
response = requests.get(url, headers=headers, params=params, timeout=30)
|
||||
elif method.upper() == 'POST':
|
||||
response = requests.post(url, headers=headers, json=json_data, timeout=30)
|
||||
elif method.upper() == 'DELETE':
|
||||
response = requests.delete(url, headers=headers, params=params, timeout=30)
|
||||
else:
|
||||
return None
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {'success': False, 'error': f'API 请求失败: {response.status_code}'}
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': f'请求异常: {str(e)}'}
|
||||
|
||||
|
||||
def generate_temp_email(prefix: str = None, domain: str = None) -> Optional[str]:
|
||||
"""生成临时邮箱地址"""
|
||||
json_data = {}
|
||||
if prefix:
|
||||
json_data['prefix'] = prefix
|
||||
if domain:
|
||||
json_data['domain'] = domain
|
||||
|
||||
if json_data:
|
||||
result = gptmail_request('POST', '/api/generate-email', json_data=json_data)
|
||||
else:
|
||||
result = gptmail_request('GET', '/api/generate-email')
|
||||
|
||||
if result and result.get('success'):
|
||||
return result.get('data', {}).get('email')
|
||||
return None
|
||||
|
||||
|
||||
def get_temp_emails_from_api(email_addr: str) -> Optional[List[Dict]]:
|
||||
"""从 GPTMail API 获取邮件列表"""
|
||||
result = gptmail_request('GET', '/api/emails', params={'email': email_addr})
|
||||
|
||||
if result and result.get('success'):
|
||||
return result.get('data', {}).get('emails', [])
|
||||
return None
|
||||
|
||||
|
||||
def get_temp_email_detail_from_api(message_id: str) -> Optional[Dict]:
|
||||
"""从 GPTMail API 获取邮件详情"""
|
||||
result = gptmail_request('GET', f'/api/email/{message_id}')
|
||||
|
||||
if result and result.get('success'):
|
||||
return result.get('data')
|
||||
return None
|
||||
|
||||
|
||||
def delete_temp_email_from_api(message_id: str) -> bool:
|
||||
"""从 GPTMail API 删除邮件"""
|
||||
result = gptmail_request('DELETE', f'/api/email/{message_id}')
|
||||
return result and result.get('success', False)
|
||||
|
||||
|
||||
def clear_temp_emails_from_api(email_addr: str) -> bool:
|
||||
"""清空 GPTMail 邮箱的所有邮件"""
|
||||
result = gptmail_request('DELETE', '/api/emails/clear', params={'email': email_addr})
|
||||
return result and result.get('success', False)
|
||||
|
||||
|
||||
# ==================== 临时邮箱数据库操作 ====================
|
||||
|
||||
def get_temp_email_group_id() -> int:
|
||||
"""获取临时邮箱分组的 ID"""
|
||||
db = get_db()
|
||||
cursor = db.execute("SELECT id FROM groups WHERE name = '临时邮箱'")
|
||||
row = cursor.fetchone()
|
||||
return row['id'] if row else 2
|
||||
|
||||
|
||||
def load_temp_emails() -> List[Dict]:
|
||||
"""加载所有临时邮箱"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT * FROM temp_emails ORDER BY created_at DESC')
|
||||
rows = cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def get_temp_email_by_address(email_addr: str) -> Optional[Dict]:
|
||||
"""根据邮箱地址获取临时邮箱"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT * FROM temp_emails WHERE email = ?', (email_addr,))
|
||||
row = cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def add_temp_email(email_addr: str) -> bool:
|
||||
"""添加临时邮箱"""
|
||||
db = get_db()
|
||||
try:
|
||||
db.execute('INSERT INTO temp_emails (email) VALUES (?)', (email_addr,))
|
||||
db.commit()
|
||||
return True
|
||||
except sqlite3.IntegrityError:
|
||||
return False
|
||||
|
||||
|
||||
def delete_temp_email(email_addr: str) -> bool:
|
||||
"""删除临时邮箱及其所有邮件"""
|
||||
db = get_db()
|
||||
try:
|
||||
db.execute('DELETE FROM temp_email_messages WHERE email_address = ?', (email_addr,))
|
||||
db.execute('DELETE FROM temp_emails WHERE email = ?', (email_addr,))
|
||||
db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def save_temp_email_messages(email_addr: str, messages: List[Dict]) -> int:
|
||||
"""保存临时邮件到数据库"""
|
||||
db = get_db()
|
||||
saved = 0
|
||||
for msg in messages:
|
||||
try:
|
||||
db.execute('''
|
||||
INSERT OR REPLACE INTO temp_email_messages
|
||||
(message_id, email_address, from_address, subject, content, html_content, has_html, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
msg.get('id'),
|
||||
email_addr,
|
||||
msg.get('from_address', ''),
|
||||
msg.get('subject', ''),
|
||||
msg.get('content', ''),
|
||||
msg.get('html_content', ''),
|
||||
1 if msg.get('has_html') else 0,
|
||||
msg.get('timestamp', 0)
|
||||
))
|
||||
saved += 1
|
||||
except Exception:
|
||||
continue
|
||||
db.commit()
|
||||
return saved
|
||||
|
||||
|
||||
def get_temp_email_messages(email_addr: str) -> List[Dict]:
|
||||
"""获取临时邮箱的所有邮件(从数据库)"""
|
||||
db = get_db()
|
||||
cursor = db.execute('''
|
||||
SELECT * FROM temp_email_messages
|
||||
WHERE email_address = ?
|
||||
ORDER BY timestamp DESC
|
||||
''', (email_addr,))
|
||||
rows = cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def get_temp_email_message_by_id(message_id: str) -> Optional[Dict]:
|
||||
"""根据 ID 获取临时邮件"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT * FROM temp_email_messages WHERE message_id = ?', (message_id,))
|
||||
row = cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def delete_temp_email_message(message_id: str) -> bool:
|
||||
"""删除临时邮件"""
|
||||
db = get_db()
|
||||
try:
|
||||
db.execute('DELETE FROM temp_email_messages WHERE message_id = ?', (message_id,))
|
||||
db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_temp_email_count() -> int:
|
||||
"""获取临时邮箱数量"""
|
||||
db = get_db()
|
||||
cursor = db.execute('SELECT COUNT(*) as count FROM temp_emails')
|
||||
row = cursor.fetchone()
|
||||
return row['count'] if row else 0
|
||||
|
||||
|
||||
# ==================== 临时邮箱 API 路由 ====================
|
||||
|
||||
@app.route('/api/temp-emails', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_temp_emails():
|
||||
"""获取所有临时邮箱"""
|
||||
emails = load_temp_emails()
|
||||
return jsonify({'success': True, 'emails': emails})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/generate', methods=['POST'])
|
||||
@login_required
|
||||
def api_generate_temp_email():
|
||||
"""生成新的临时邮箱"""
|
||||
data = request.json or {}
|
||||
prefix = data.get('prefix')
|
||||
domain = data.get('domain')
|
||||
|
||||
email_addr = generate_temp_email(prefix, domain)
|
||||
|
||||
if email_addr:
|
||||
if add_temp_email(email_addr):
|
||||
return jsonify({'success': True, 'email': email_addr, 'message': '临时邮箱创建成功'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '邮箱已存在'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '生成临时邮箱失败,请稍后重试'})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>', methods=['DELETE'])
|
||||
@login_required
|
||||
def api_delete_temp_email(email_addr):
|
||||
"""删除临时邮箱"""
|
||||
if delete_temp_email(email_addr):
|
||||
return jsonify({'success': True, 'message': '临时邮箱已删除'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '删除失败'})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>/messages', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_temp_email_messages(email_addr):
|
||||
"""获取临时邮箱的邮件列表"""
|
||||
api_messages = get_temp_emails_from_api(email_addr)
|
||||
|
||||
if api_messages:
|
||||
save_temp_email_messages(email_addr, api_messages)
|
||||
|
||||
messages = get_temp_email_messages(email_addr)
|
||||
|
||||
formatted = []
|
||||
for msg in messages:
|
||||
formatted.append({
|
||||
'id': msg.get('message_id'),
|
||||
'from': msg.get('from_address', '未知'),
|
||||
'subject': msg.get('subject', '无主题'),
|
||||
'body_preview': (msg.get('content', '') or '')[:200],
|
||||
'date': msg.get('created_at', ''),
|
||||
'timestamp': msg.get('timestamp', 0),
|
||||
'has_html': msg.get('has_html', 0)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'emails': formatted,
|
||||
'count': len(formatted),
|
||||
'method': 'GPTMail'
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>/messages/<path:message_id>', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_temp_email_message_detail(email_addr, message_id):
|
||||
"""获取临时邮件详情"""
|
||||
msg = get_temp_email_message_by_id(message_id)
|
||||
|
||||
if not msg:
|
||||
api_msg = get_temp_email_detail_from_api(message_id)
|
||||
if api_msg:
|
||||
save_temp_email_messages(email_addr, [api_msg])
|
||||
msg = get_temp_email_message_by_id(message_id)
|
||||
|
||||
if msg:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'email': {
|
||||
'id': msg.get('message_id'),
|
||||
'from': msg.get('from_address', '未知'),
|
||||
'to': email_addr,
|
||||
'subject': msg.get('subject', '无主题'),
|
||||
'body': msg.get('html_content') if msg.get('has_html') else msg.get('content', ''),
|
||||
'body_type': 'html' if msg.get('has_html') else 'text',
|
||||
'date': msg.get('created_at', ''),
|
||||
'timestamp': msg.get('timestamp', 0)
|
||||
}
|
||||
})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '邮件不存在'})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>/messages/<path:message_id>', methods=['DELETE'])
|
||||
@login_required
|
||||
def api_delete_temp_email_message(email_addr, message_id):
|
||||
"""删除临时邮件"""
|
||||
delete_temp_email_from_api(message_id)
|
||||
if delete_temp_email_message(message_id):
|
||||
return jsonify({'success': True, 'message': '邮件已删除'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '删除失败'})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>/clear', methods=['DELETE'])
|
||||
@login_required
|
||||
def api_clear_temp_email_messages(email_addr):
|
||||
"""清空临时邮箱的所有邮件"""
|
||||
clear_temp_emails_from_api(email_addr)
|
||||
db = get_db()
|
||||
try:
|
||||
db.execute('DELETE FROM temp_email_messages WHERE email_address = ?', (email_addr,))
|
||||
db.commit()
|
||||
return jsonify({'success': True, 'message': '邮件已清空'})
|
||||
except Exception:
|
||||
return jsonify({'success': False, 'error': '清空失败'})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>/refresh', methods=['POST'])
|
||||
@login_required
|
||||
def api_refresh_temp_email_messages(email_addr):
|
||||
"""刷新临时邮箱的邮件"""
|
||||
api_messages = get_temp_emails_from_api(email_addr)
|
||||
|
||||
if api_messages is not None:
|
||||
saved = save_temp_email_messages(email_addr, api_messages)
|
||||
messages = get_temp_email_messages(email_addr)
|
||||
|
||||
formatted = []
|
||||
for msg in messages:
|
||||
formatted.append({
|
||||
'id': msg.get('message_id'),
|
||||
'from': msg.get('from_address', '未知'),
|
||||
'subject': msg.get('subject', '无主题'),
|
||||
'body_preview': (msg.get('content', '') or '')[:200],
|
||||
'date': msg.get('created_at', ''),
|
||||
'timestamp': msg.get('timestamp', 0),
|
||||
'has_html': msg.get('has_html', 0)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'emails': formatted,
|
||||
'count': len(formatted),
|
||||
'new_count': saved,
|
||||
'method': 'GPTMail'
|
||||
})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '获取邮件失败'})
|
||||
|
||||
|
||||
# ==================== 设置 API ====================
|
||||
|
||||
@app.route('/api/settings', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_settings():
|
||||
"""获取所有设置"""
|
||||
settings = get_all_settings()
|
||||
# 隐藏密码的部分字符
|
||||
if 'login_password' in settings:
|
||||
pwd = settings['login_password']
|
||||
if len(pwd) > 2:
|
||||
settings['login_password_masked'] = pwd[0] + '*' * (len(pwd) - 2) + pwd[-1]
|
||||
else:
|
||||
settings['login_password_masked'] = '*' * len(pwd)
|
||||
return jsonify({'success': True, 'settings': settings})
|
||||
|
||||
|
||||
@app.route('/api/settings', methods=['PUT'])
|
||||
@login_required
|
||||
def api_update_settings():
|
||||
"""更新设置"""
|
||||
data = request.json
|
||||
updated = []
|
||||
errors = []
|
||||
|
||||
# 更新登录密码
|
||||
if 'login_password' in data:
|
||||
new_password = data['login_password'].strip()
|
||||
if new_password:
|
||||
if len(new_password) < 4:
|
||||
errors.append('密码长度至少为 4 位')
|
||||
elif set_setting('login_password', new_password):
|
||||
updated.append('登录密码')
|
||||
else:
|
||||
errors.append('更新登录密码失败')
|
||||
|
||||
# 更新 GPTMail API Key
|
||||
if 'gptmail_api_key' in data:
|
||||
new_api_key = data['gptmail_api_key'].strip()
|
||||
if new_api_key:
|
||||
if set_setting('gptmail_api_key', new_api_key):
|
||||
updated.append('GPTMail API Key')
|
||||
else:
|
||||
errors.append('更新 GPTMail API Key 失败')
|
||||
|
||||
if errors:
|
||||
return jsonify({'success': False, 'error': ';'.join(errors)})
|
||||
|
||||
if updated:
|
||||
return jsonify({'success': True, 'message': f'已更新:{", ".join(updated)}'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '没有需要更新的设置'})
|
||||
|
||||
|
||||
# ==================== 主程序 ====================
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -1033,6 +1565,7 @@ if __name__ == '__main__':
|
||||
print("=" * 60)
|
||||
print(f"访问地址: http://127.0.0.1:{port}")
|
||||
print(f"数据库文件: {DATABASE}")
|
||||
print(f"GPTMail API: {GPTMAIL_BASE_URL}")
|
||||
print("=" * 60)
|
||||
|
||||
app.run(debug=True, host='127.0.0.1', port=port)
|
||||
Reference in new issue
Block a user