// 1. 【引入依赖】 const fastify = require('fastify')({ logger: true }); // undici 是 Node.js 内置的高性能 HTTP 客户端,我们用它来发请求 const { request: undiciRequest } = require('undici'); // 2. 【配置】- 从环境变量读取 Gotify 的信息 // 这是最佳实践,避免将敏感信息硬编码在代码里 // const GOTIFY_URL = process.env.GOTIFY_URL; // 例如: 'http://your-gotify-server.com/message' // const GOTIFY_TOKEN = process.env.GOTIFY_TOKEN; // 你的 Gotify 应用 Token const GOTIFY_URL = 'https://gotify.zotv.ru/message'; // 例如: 'http://your-gotify-server.com/message' const GOTIFY_TOKEN = 'A1wFaeaj-VskqyF'; // 你的 Gotify 应用 Token //测试一些gitea的webhook功能 //财产测试git的密钥功能 //测试git推送 //再来 //测试通过了吗? // 启动前检查配置是否齐全 if (!GOTIFY_URL || !GOTIFY_TOKEN) { console.error('错误:请设置 GOTIFY_URL 和 GOTIFY_TOKEN 环境变量!'); process.exit(1); } // 3. 【核心路由】 - 接收和转发 Webhook fastify.post('/webhook/feishu', async (request, reply) => { fastify.log.info('收到来自飞书的 Webhook 请求...'); const feishuPayload = request.body; let title = '来自飞书的新消息'; // 默认标题 let message = ''; // --- 解析飞书消息 --- // A. 处理简单的 "text" 类型消息 if (feishuPayload.msg_type === 'text') { message = feishuPayload.content?.text || '无法解析的文本消息'; } // B. 处理 "interactive" (卡片消息) 类型 else if (feishuPayload.msg_type === 'interactive' && feishuPayload.card) { // 尝试从卡片标题中获取标题 title = feishuPayload.card.header?.title?.content || title; // 尝试从卡片的第一个元素中获取内容 // (这是一个简化的逻辑,真实卡片可能很复杂) const firstElement = feishuPayload.card.elements?.[0]; if (firstElement?.tag === 'div' && firstElement.text) { message = firstElement.text.content || '无法解析的卡片内容'; } else { message = JSON.stringify(feishuPayload.card.elements); // 如果结构不认识,就发原始数据 } } // C. 其他未知类型 else { fastify.log.warn('收到了未知的飞书消息类型,将内容作为原始JSON转发'); message = JSON.stringify(feishuPayload, null, 2); } // --- 转发到 Gotify --- const gotifyPayload = { title: title, message: message, priority: 5, // Gotify 的消息优先级,可以按需调整 }; try { fastify.log.info(`准备转发到 Gotify: ${GOTIFY_URL}`); const { statusCode } = await undiciRequest(`${GOTIFY_URL}?token=${GOTIFY_TOKEN}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gotifyPayload), }); if (statusCode >= 200 && statusCode < 300) { fastify.log.info('成功转发消息到 Gotify!'); } else { fastify.log.error(`转发到 Gotify 失败,状态码: ${statusCode}`); } } catch (error) { fastify.log.error(`转发到 Gotify 时发生网络错误: ${error.message}`); } // --- 响应飞书 --- // **非常重要**:无论转发是否成功,都应该立即回复飞书一个成功的响应。 // 否则飞书会认为你的 Webhook 地址有问题,并可能不停重试。 return { success: true, message: '消息已接收' }; }); // 4. 【启动服务器】 const start = async () => { try { // 监听 0.0.0.0 以便从局域网或 Docker 容器外访问 await fastify.listen({ port: 3000, host: '0.0.0.0' }); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();