Initial commit: 七牛云上传飞书机器人

功能:
- 飞书交互卡片支持
- 七牛云文件上传
- 自动 CDN 刷新
- 多存储桶配置
- 跨平台部署(Linux/macOS/Windows)
- Docker 支持
This commit is contained in:
饭团
2026-03-05 14:22:26 +08:00
commit b00567762f
15 changed files with 2286 additions and 0 deletions

151
src/feishu-api.js Normal file
View File

@@ -0,0 +1,151 @@
/**
* 飞书 API 封装
*/
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const https = require('https');
class FeishuAPI {
constructor() {
this.appId = process.env.FEISHU_APP_ID;
this.appSecret = process.env.FEISHU_APP_SECRET;
this.baseURL = 'https://open.feishu.cn/open-apis';
this.tokenCache = null;
this.tokenExpiry = 0;
}
// 获取访问令牌
async getAccessToken() {
if (this.tokenCache && Date.now() < this.tokenExpiry) {
return this.tokenCache;
}
try {
const response = await axios.post(
`${this.baseURL}/auth/v3/tenant_access_token/internal`,
{
app_id: this.appId,
app_secret: this.appSecret
},
{
headers: { 'Content-Type': 'application/json' }
}
);
const { tenant_access_token, expire } = response.data;
if (response.data.code !== 0) {
throw new Error(`获取 token 失败:${response.data.msg}`);
}
this.tokenCache = tenant_access_token;
this.tokenExpiry = Date.now() + (expire - 300) * 1000; // 提前 5 分钟过期
return tenant_access_token;
} catch (error) {
throw new Error(`飞书 API 错误:${error.message}`);
}
}
// 发送文本消息
async sendMessage(chatId, payload) {
const token = await this.getAccessToken();
try {
const response = await axios.post(
`${this.baseURL}/im/v1/messages`,
{
receive_id: chatId,
msg_type: payload.msg_type,
content: payload.content
},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
if (response.data.code !== 0) {
throw new Error(`发送消息失败:${response.data.msg}`);
}
return response.data;
} catch (error) {
throw new Error(`飞书消息发送失败:${error.message}`);
}
}
// 发送卡片消息
async sendCard(chatId, card) {
return this.sendMessage(chatId, {
msg_type: 'interactive',
content: JSON.stringify(card)
});
}
// 下载文件
async downloadFile(fileKey) {
const token = await this.getAccessToken();
const tempDir = path.join(process.cwd(), 'temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const tempFile = path.join(tempDir, `feishu_${Date.now()}_${fileKey}`);
return new Promise((resolve, reject) => {
const url = `${this.baseURL}/im/v1/files/${fileKey}/download`;
https.get(url, {
headers: {
'Authorization': `Bearer ${token}`
}
}, (res) => {
if (res.statusCode !== 200) {
reject(new Error(`下载失败:${res.statusCode}`));
return;
}
const file = fs.createWriteStream(tempFile);
res.pipe(file);
file.on('finish', () => {
file.close();
resolve(tempFile);
});
}).on('error', reject);
});
}
// 回复消息
async replyMessage(messageId, payload) {
const token = await this.getAccessToken();
try {
const response = await axios.post(
`${this.baseURL}/im/v1/messages`,
{
reply_id: messageId,
msg_type: payload.msg_type,
content: payload.content
},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
} catch (error) {
throw new Error(`飞书回复失败:${error.message}`);
}
}
}
module.exports = { FeishuAPI };