feat: allow send message to group chat

Signed-off-by: d0zingcat <iamtangli42@gmail.com>
This commit is contained in:
2026-01-13 22:40:38 +08:00
parent 3df151c5eb
commit a1c6141b31
17 changed files with 664 additions and 105 deletions

View File

@@ -1,94 +1,69 @@
import * as lark from '@larksuiteoapi/node-sdk';
export class FeishuClient {
public client: lark.Client;
private appId: string;
private appSecret: string;
private token: string | null = null;
private tokenExpireAt: number = 0;
constructor(appId: string, appSecret: string) {
this.appId = appId;
this.appSecret = appSecret;
}
private async getTenantAccessToken(): Promise<string> {
if (this.token && Date.now() < this.tokenExpireAt) {
return this.token;
}
const res = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
app_id: this.appId,
app_secret: this.appSecret,
}),
this.client = new lark.Client({
appId: appId,
appSecret: appSecret,
disableTokenCache: false,
});
const data = await res.json();
if (data.code !== 0) {
throw new Error(`Failed to get tenant access token: ${data.msg}`);
}
this.token = data.tenant_access_token;
// Expire 5 minutes early to be safe
this.tokenExpireAt = Date.now() + (data.expire * 1000) - 300000;
return this.token!;
}
async sendMessage(receiveId: string, receiveIdType: 'open_id' | 'user_id' | 'email', msgType: string, content: any) {
const token = await this.getTenantAccessToken();
// Content needs to be stringified for 'text' type, but might be object for 'interactive'
// Feishu API expects 'content' field to be a JSON string for most types
async sendMessage(receiveId: string, receiveIdType: 'open_id' | 'user_id' | 'email' | 'chat_id', msgType: string, content: any) {
// Content needs to be stringified for 'text' type in API, but SDK might handle it differently?
// Actually SDK expects 'content' as string JSON for 'im.v1.messages.create'
const contentStr = typeof content === 'string' ? content : JSON.stringify(content);
const res = await fetch(`https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=${receiveIdType}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
receive_id: receiveId,
msg_type: msgType,
content: contentStr,
}),
});
try {
const response = await this.client.im.message.create({
params: {
receive_id_type: receiveIdType,
},
data: {
receive_id: receiveId,
msg_type: msgType,
content: contentStr,
},
});
const data = await res.json();
if (data.code !== 0) {
console.error('Feishu send message error:', data);
throw new Error(`Failed to send message: ${data.msg}`);
if (response.code !== 0) {
console.error('Feishu send message error:', response);
throw new Error(`Failed to send message: ${response.msg}`);
}
return response.data;
} catch (e) {
console.error('Feishu SDK error:', e);
throw e;
}
return data;
}
async getUserAccessToken(code: string): Promise<any> {
const token = await this.getTenantAccessToken();
const res = await fetch('https://open.feishu.cn/open-apis/authen/v1/access_token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
grant_type: 'authorization_code',
code,
}),
});
try {
const response = await this.client.authen.accessToken.create({
data: {
grant_type: 'authorization_code',
code,
},
});
const data = await res.json();
if (data.code !== 0) {
console.error('Feishu get user access token error:', data);
throw new Error(`Failed to get user access token: ${data.msg}`);
if (response.code !== 0) {
console.error('Feishu get user access token error:', response);
throw new Error(`Failed to get user access token: ${response.msg}`);
}
return response.data;
} catch (e) {
console.error('Feishu SDK error:', e);
throw e;
}
return data.data;
}
}
// Singleton instance - replace with env vars in production
// Singleton instance
export const feishuClient = new FeishuClient(
process.env.FEISHU_APP_ID || 'cli_xxx',
process.env.FEISHU_APP_SECRET || 'xxx'
process.env.FEISHU_APP_ID || '',
process.env.FEISHU_APP_SECRET || ''
);