File size: 12,465 Bytes
69b897d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
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')

// 获取webhook配置
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配置失败'
    })
  }
})

// 保存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配置失败'
    })
  }
})

// 添加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平台失败'
    })
  }
})

// 更新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平台失败'
    })
  }
})

// 删除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平台失败'
    })
  }
})

// 切换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平台状态失败'
    })
  }
})

// 测试Webhook连通性
router.post('/test', authenticateAdmin, async (req, res) => {
  try {
    const {
      url,
      type = 'custom',
      secret,
      enableSign,
      deviceKey,
      serverUrl,
      level,
      sound,
      group,
      // SMTP 相关字段
      host,
      port,
      secure,
      user,
      pass,
      from,
      to,
      ignoreTLS,
      botToken,
      chatId,
      apiBaseUrl,
      proxyUrl
    } = req.body

    // Bark平台特殊处理
    if (type === 'bark') {
      if (!deviceKey) {
        return res.status(400).json({
          error: 'Missing device key',
          message: '请提供Bark设备密钥'
        })
      }

      // 验证服务器URL(如果提供)
      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') {
      // 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 {
      // 其他平台验证URL
      if (!url) {
        return res.status(400).json({
          error: 'Missing webhook URL',
          message: '请提供webhook URL'
        })
      }

      // 验证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
    }

    // 添加Bark特有字段
    if (type === 'bark') {
      platform.deviceKey = deviceKey
      platform.serverUrl = serverUrl
      platform.level = level
      platform.sound = sound
      platform.group = group
    } else if (type === 'smtp') {
      // 添加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}`)

    // 先检查webhook配置
    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