MCP-FEISHU
by NINGyv179
Verified
- mcp-feishu
- src
- services
/**
* Service for interacting with Eolink OpenAPI
*/
import axios from "axios";
// import { Api, Project, ApiTestRequest, ApiTestResponse } from '../models/api.js';
class FeishuService {
private APP_ID: string;
private APP_SECRET: string;
private TABLE_ID: string;
private APP_TOKEN: string;
constructor() {
// # 缺陷清单所绑定的应用 ID
this.APP_ID = process.env.APP_ID || "";
// # 缺陷清单所绑定的应用 Secret
this.APP_SECRET = process.env.APP_SECRET || "";
// # 缺陷清单所绑定的表格 ID,可以在表格页面 URL 中找到,(目前绑定的是缺陷清单这个表,如需绑定其他表格,需要道对应表中绑定应用,然后使用对应的应用信息和表格id去操作)
this.TABLE_ID = process.env.TABLE_ID || "";
// # 这个token是固定的,每个人登录都有一个,表格页面F12搜索请求,meta/?token=,即可看到,可替换成个人的
this.APP_TOKEN = process.env.APP_TOKEN || "";
}
/**
* 获取租户访问令牌(鉴权)
* @returns
*/
async getTenantAccessToken() {
const { data } = await axios.post(
"https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal",
{
app_id: this.APP_ID,
app_secret: this.APP_SECRET,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
const tokenMap = data;
const TENANT_ACCESS_TOKEN = tokenMap?.tenant_access_token || "";
return TENANT_ACCESS_TOKEN;
}
/**
* 根据字段值获取记录链接
* @param fieldName 字段名
* @param fieldValue 字段值
* @returns 记录链接
*/
async getRecord(fieldName: string, fieldValue: string): Promise<string | null> {
const tenantAccessToken = await this.getTenantAccessToken();
// 调用列出记录 API
const { data } = await axios.get(
`https://open.feishu.cn/open-apis/bitable/v1/apps/${this.APP_TOKEN}/tables/${this.TABLE_ID}/records`,
{
headers: {
Authorization: `Bearer ${tenantAccessToken}`,
},
params: {
filter: `CurrentValue.[${fieldName}] = "${fieldValue}"`, // 修改过滤条件的格式
},
}
);
if (data.code !== 0) {
throw new Error(`Failed to get records: ${data.msg}`);
}
// 获取记录 ID
const records = data.data.items;
if (records.length === 0) {
return null; // 没有找到匹配的记录
}
const recordId = records[0].record_id;
// 构造记录链接
let sharedLink = '查找不到此记录'
if (recordId) {
sharedLink = (await this.getSharedLink(recordId)) || sharedLink;
}
return sharedLink;
}
/**
* 获取分享链接
* @param recordId 记录ID
* @returns
*/
async getSharedLink(recordId: string): Promise<string | null> {
const tenantAccessToken = await this.getTenantAccessToken();
// 调用批量获取记录 API
const { data } = await axios.post(
`https://open.feishu.cn/open-apis/bitable/v1/apps/${this.APP_TOKEN}/tables/${this.TABLE_ID}/records/batch_get`,
{
record_ids: [recordId],
with_shared_url: true, // 请求返回分享链接
},
{
headers: {
Authorization: `Bearer ${tenantAccessToken}`,
"Content-Type": "application/json",
},
}
);
if (data.code !== 0) {
throw new Error(`Failed to get shared link: ${data.msg}`);
}
const records = data.data.records;
if (records.length === 0 || !records[0].shared_url) {
return null; // 没有找到分享链接
}
return records[0].shared_url; // 返回分享链接
}
/**
* 查找特定时间段内修改了状态的记录
* @param startDate 开始日期(时间戳,毫秒)
* @param endDate 结束日期(时间戳,毫秒)
* @returns {Promise<any[]>} 返回记录列表
*/
async getRecordsByStatus(status: string, startDate: number, endDate: number): Promise<any[]> {
const tenantAccessToken = await this.getTenantAccessToken();
// 构造请求体
const requestBody = {
filter: {
conjunction: "and",
conditions: [
{
field_name: "状态",
operator: "is",
value: [status], // 筛选状态字段为指定值(如“已修复”)
},
{
field_name: "最近更新时间",
operator: "isGreaterEqual",
value: [startDate], // 开始日期
},
{
field_name: "最近更新时间",
operator: "isLessEqual",
value: [endDate], // 结束日期
},
],
},
sort: [
{
field_name: "最近更新时间",
desc: true, // 按最近更新时间倒序排序
},
],
field_names: ["状态", "最近更新时间"], // 返回字段
};
// 调用查询记录 API
const { data } = await axios.post(
`https://open.feishu.cn/open-apis/bitable/v1/apps/${this.APP_TOKEN}/tables/${this.TABLE_ID}/records/search`,
requestBody,
{
headers: {
Authorization: `Bearer ${tenantAccessToken}`,
"Content-Type": "application/json",
},
}
);
if (data.code !== 0) {
throw new Error(`Failed to get records: ${data.msg}`);
}
return data.data.items; // 返回记录列表
}
}
export default new FeishuService();