|
|
const express = require('express') |
|
|
const router = express.Router() |
|
|
const logger = require('../utils/logger') |
|
|
const webhookService = require('../services/webhookService') |
|
|
const webhookConfigService = require('../services/webhookConfigService') |
|
|
const { authenticateAdmin } = require('../middleware/auth') |
|
|
const { getISOStringWithTimezone } = require('../utils/dateHelper') |
|
|
|
|
|
|
|
|
router.get('/config', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const config = await webhookConfigService.getConfig() |
|
|
res.json({ |
|
|
success: true, |
|
|
config |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('获取webhook配置失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: '获取webhook配置失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.post('/config', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const config = await webhookConfigService.saveConfig(req.body) |
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Webhook配置已保存', |
|
|
config |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('保存webhook配置失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: error.message || '保存webhook配置失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.post('/platforms', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const platform = await webhookConfigService.addPlatform(req.body) |
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Webhook平台已添加', |
|
|
platform |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('添加webhook平台失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: error.message || '添加webhook平台失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.put('/platforms/:id', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const platform = await webhookConfigService.updatePlatform(req.params.id, req.body) |
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Webhook平台已更新', |
|
|
platform |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('更新webhook平台失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: error.message || '更新webhook平台失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.delete('/platforms/:id', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
await webhookConfigService.deletePlatform(req.params.id) |
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Webhook平台已删除' |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('删除webhook平台失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: error.message || '删除webhook平台失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.post('/platforms/:id/toggle', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const platform = await webhookConfigService.togglePlatform(req.params.id) |
|
|
res.json({ |
|
|
success: true, |
|
|
message: `Webhook平台已${platform.enabled ? '启用' : '禁用'}`, |
|
|
platform |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('切换webhook平台状态失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: error.message || '切换webhook平台状态失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.post('/test', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const { |
|
|
url, |
|
|
type = 'custom', |
|
|
secret, |
|
|
enableSign, |
|
|
deviceKey, |
|
|
serverUrl, |
|
|
level, |
|
|
sound, |
|
|
group, |
|
|
|
|
|
host, |
|
|
port, |
|
|
secure, |
|
|
user, |
|
|
pass, |
|
|
from, |
|
|
to, |
|
|
ignoreTLS, |
|
|
botToken, |
|
|
chatId, |
|
|
apiBaseUrl, |
|
|
proxyUrl |
|
|
} = req.body |
|
|
|
|
|
|
|
|
if (type === 'bark') { |
|
|
if (!deviceKey) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing device key', |
|
|
message: '请提供Bark设备密钥' |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
if (serverUrl) { |
|
|
try { |
|
|
new URL(serverUrl) |
|
|
} catch (urlError) { |
|
|
return res.status(400).json({ |
|
|
error: 'Invalid server URL format', |
|
|
message: '请提供有效的Bark服务器URL' |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
logger.info(`🧪 测试webhook: ${type} - Device Key: ${deviceKey.substring(0, 8)}...`) |
|
|
} else if (type === 'smtp') { |
|
|
|
|
|
if (!host) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing SMTP host', |
|
|
message: '请提供SMTP服务器地址' |
|
|
}) |
|
|
} |
|
|
if (!user) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing SMTP user', |
|
|
message: '请提供SMTP用户名' |
|
|
}) |
|
|
} |
|
|
if (!pass) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing SMTP password', |
|
|
message: '请提供SMTP密码' |
|
|
}) |
|
|
} |
|
|
if (!to) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing recipient email', |
|
|
message: '请提供收件人邮箱' |
|
|
}) |
|
|
} |
|
|
|
|
|
logger.info(`🧪 测试webhook: ${type} - ${host}:${port || 587} -> ${to}`) |
|
|
} else if (type === 'telegram') { |
|
|
if (!botToken) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing Telegram bot token', |
|
|
message: '请提供 Telegram 机器人 Token' |
|
|
}) |
|
|
} |
|
|
if (!chatId) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing Telegram chat id', |
|
|
message: '请提供 Telegram Chat ID' |
|
|
}) |
|
|
} |
|
|
|
|
|
if (apiBaseUrl) { |
|
|
try { |
|
|
const parsed = new URL(apiBaseUrl) |
|
|
if (!['http:', 'https:'].includes(parsed.protocol)) { |
|
|
return res.status(400).json({ |
|
|
error: 'Invalid Telegram API base url protocol', |
|
|
message: 'Telegram API 基础地址仅支持 http 或 https' |
|
|
}) |
|
|
} |
|
|
} catch (urlError) { |
|
|
return res.status(400).json({ |
|
|
error: 'Invalid Telegram API base url', |
|
|
message: '请提供有效的 Telegram API 基础地址' |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
if (proxyUrl) { |
|
|
try { |
|
|
const parsed = new URL(proxyUrl) |
|
|
const supportedProtocols = ['http:', 'https:', 'socks4:', 'socks4a:', 'socks5:'] |
|
|
if (!supportedProtocols.includes(parsed.protocol)) { |
|
|
return res.status(400).json({ |
|
|
error: 'Unsupported proxy protocol', |
|
|
message: 'Telegram 代理仅支持 http/https/socks 协议' |
|
|
}) |
|
|
} |
|
|
} catch (urlError) { |
|
|
return res.status(400).json({ |
|
|
error: 'Invalid proxy url', |
|
|
message: '请提供有效的代理地址' |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
logger.info(`🧪 测试webhook: ${type} - Chat ID: ${chatId}`) |
|
|
} else { |
|
|
|
|
|
if (!url) { |
|
|
return res.status(400).json({ |
|
|
error: 'Missing webhook URL', |
|
|
message: '请提供webhook URL' |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
new URL(url) |
|
|
} catch (urlError) { |
|
|
return res.status(400).json({ |
|
|
error: 'Invalid URL format', |
|
|
message: '请提供有效的webhook URL' |
|
|
}) |
|
|
} |
|
|
|
|
|
logger.info(`🧪 测试webhook: ${type} - ${url}`) |
|
|
} |
|
|
|
|
|
|
|
|
const platform = { |
|
|
type, |
|
|
url, |
|
|
secret, |
|
|
enableSign, |
|
|
enabled: true, |
|
|
timeout: 10000 |
|
|
} |
|
|
|
|
|
|
|
|
if (type === 'bark') { |
|
|
platform.deviceKey = deviceKey |
|
|
platform.serverUrl = serverUrl |
|
|
platform.level = level |
|
|
platform.sound = sound |
|
|
platform.group = group |
|
|
} else if (type === 'smtp') { |
|
|
|
|
|
platform.host = host |
|
|
platform.port = port || 587 |
|
|
platform.secure = secure || false |
|
|
platform.user = user |
|
|
platform.pass = pass |
|
|
platform.from = from |
|
|
platform.to = to |
|
|
platform.ignoreTLS = ignoreTLS || false |
|
|
} else if (type === 'telegram') { |
|
|
platform.botToken = botToken |
|
|
platform.chatId = chatId |
|
|
platform.apiBaseUrl = apiBaseUrl |
|
|
platform.proxyUrl = proxyUrl |
|
|
} |
|
|
|
|
|
const result = await webhookService.testWebhook(platform) |
|
|
|
|
|
const identifier = (() => { |
|
|
if (type === 'bark') { |
|
|
return `Device: ${deviceKey.substring(0, 8)}...` |
|
|
} |
|
|
if (type === 'smtp') { |
|
|
const recipients = Array.isArray(to) ? to.join(', ') : to |
|
|
return `${host}:${port || 587} -> ${recipients}` |
|
|
} |
|
|
if (type === 'telegram') { |
|
|
return `Chat ID: ${chatId}` |
|
|
} |
|
|
return url |
|
|
})() |
|
|
|
|
|
if (result.success) { |
|
|
logger.info(`✅ Webhook测试成功: ${identifier}`) |
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Webhook测试成功', |
|
|
url: type === 'bark' ? undefined : url, |
|
|
deviceKey: type === 'bark' ? `${deviceKey.substring(0, 8)}...` : undefined |
|
|
}) |
|
|
} else { |
|
|
logger.warn(`❌ Webhook测试失败: ${identifier} - ${result.error}`) |
|
|
res.status(400).json({ |
|
|
success: false, |
|
|
message: 'Webhook测试失败', |
|
|
url: type === 'bark' ? undefined : url, |
|
|
deviceKey: type === 'bark' ? `${deviceKey.substring(0, 8)}...` : undefined, |
|
|
error: result.error |
|
|
}) |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error('❌ Webhook测试错误:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: '测试webhook失败' |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
router.post('/test-notification', authenticateAdmin, async (req, res) => { |
|
|
try { |
|
|
const { |
|
|
type = 'test', |
|
|
accountId = 'test-account-id', |
|
|
accountName = '测试账号', |
|
|
platform = 'claude-oauth', |
|
|
status = 'test', |
|
|
errorCode = 'TEST_NOTIFICATION', |
|
|
reason = '手动测试通知', |
|
|
message = '这是一条测试通知消息,用于验证 Webhook 通知功能是否正常工作' |
|
|
} = req.body |
|
|
|
|
|
logger.info(`🧪 发送测试通知: ${type}`) |
|
|
|
|
|
|
|
|
const config = await webhookConfigService.getConfig() |
|
|
logger.debug( |
|
|
`Webhook配置: enabled=${config.enabled}, platforms=${config.platforms?.length || 0}` |
|
|
) |
|
|
if (!config.enabled) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
message: 'Webhook通知未启用,请先在设置中启用通知功能' |
|
|
}) |
|
|
} |
|
|
|
|
|
const enabledPlatforms = await webhookConfigService.getEnabledPlatforms() |
|
|
logger.info(`找到 ${enabledPlatforms.length} 个启用的通知平台`) |
|
|
|
|
|
if (enabledPlatforms.length === 0) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
message: '没有启用的通知平台,请先添加并启用至少一个通知平台' |
|
|
}) |
|
|
} |
|
|
|
|
|
const testData = { |
|
|
accountId, |
|
|
accountName, |
|
|
platform, |
|
|
status, |
|
|
errorCode, |
|
|
reason, |
|
|
message, |
|
|
timestamp: getISOStringWithTimezone(new Date()) |
|
|
} |
|
|
|
|
|
const result = await webhookService.sendNotification(type, testData) |
|
|
|
|
|
|
|
|
if (!result) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
message: 'Webhook服务未返回结果,请检查配置和日志', |
|
|
enabledPlatforms: enabledPlatforms.length |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
if (result.succeeded === 0 && result.failed === 0) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
message: '没有发送任何通知,请检查通知类型配置', |
|
|
result, |
|
|
enabledPlatforms: enabledPlatforms.length |
|
|
}) |
|
|
} |
|
|
|
|
|
if (result.failed > 0) { |
|
|
logger.warn(`⚠️ 测试通知部分失败: ${result.succeeded}成功, ${result.failed}失败`) |
|
|
return res.json({ |
|
|
success: true, |
|
|
message: `测试通知部分成功: ${result.succeeded}个平台成功, ${result.failed}个平台失败`, |
|
|
data: testData, |
|
|
result |
|
|
}) |
|
|
} |
|
|
|
|
|
logger.info(`✅ 测试通知发送成功到 ${result.succeeded} 个平台`) |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: `测试通知已成功发送到 ${result.succeeded} 个平台`, |
|
|
data: testData, |
|
|
result |
|
|
}) |
|
|
} catch (error) { |
|
|
logger.error('❌ 发送测试通知失败:', error) |
|
|
res.status(500).json({ |
|
|
error: 'Internal server error', |
|
|
message: `发送测试通知失败: ${error.message}` |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
module.exports = router |
|
|
|