2026-03-16-feishu-bot-integration.md 28 KB

Feishu Bot Integration Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Enable Feishu (飞书) bot integration as a standalone Plugin that users can manage via the system's "Plugins" menu. This allows users to bind bots, interact with RAG, and access assessment features in a modular, plug-and-play fashion.

Architecture: Implement the integration as a pluggable FeishuModule. It acts as a bridge between Feishu Open Platform and AuraK's core services. The plugin is managed through the new /plugins workspace, isolating its UI and backend logic from the core system.

Tech Stack:

  • NestJS (existing)
  • Feishu Open Platform APIs (im/v1/messages, auth/v3/tenant_access_token)
  • Event subscription (Webhooks)
  • Existing: ChatService, AssessmentService, JWT Auth

Architecture Overview

┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│  Feishu User│─────▶│ Feishu Server│─────▶│  AuraK API  │
│  (User A)   │◀─────│   Webhook    │◀─────│  /feishu/*  │
└─────────────┘      └──────────────┘      └─────────────┘
                                                      │
                                                      ▼
                                              ┌─────────────┐
                                              │ FeishuModule│
                                              │ - Bot Entity│
                                              │ - Message   │
                                              │ - Events    │
                                              └─────────────┘
                                                      │
                              ┌───────────────────────┼───────────────────────┐
                              ▼                       ▼                       ▼
                      ┌─────────────┐        ┌─────────────┐        ┌─────────────┐
                      │ ChatService │        │AssessmentSvc│        │ PluginsSvc  │
                      │  (RAG Q&A)  │        │ (评测对话)   │        │ (插件状态管理)│
                      └─────────────┘        └─────────────┘        └─────────────┘

Plugin Isolation Strategy:

  • Backend: FeishuModule is a standalone NestJS module. It handles its own database entities and webhook logic.
  • Frontend: Integrated as a sub-view within /plugins. When the Feishu plugin is "enabled", its configuration UI is rendered.
  • Decoupling: Communicates with core services via standard service calls or an event-subscriber pattern.

File Structure

New Files to Create

server/src/
├── feishu/
│   ├── feishu.module.ts              # Module definition
│   ├── feishu.controller.ts          # Webhook endpoints
│   ├── feishu.service.ts             # Business logic
│   ├── feishu.gateway.ts             # Event handling (optional)
│   ├── entities/
│   │   └── feishu-bot.entity.ts      # Bot configuration entity
│   ├── dto/
│   │   ├── create-bot.dto.ts         # Create bot DTO
│   │   ├── bind-bot.dto.ts           # Bind bot DTO
│   │   └── feishu-webhook.dto.ts    # Webhook event DTO
│   └── interfaces/
│       └── feishu.interface.ts       # TypeScript interfaces

Existing Files to Modify

server/src/
├── app.module.ts                      # Import FeishuModule
├── user/
│   └── user.entity.ts                # Add one-to-many relation to FeishuBot
├── user/user.service.ts               # Add methods to get/set Feishu binding

Chunk 1: Entity and DTO Definitions

Task 1.1: Create FeishuBot Entity

Files:

  • Create: server/src/feishu/entities/feishu-bot.entity.ts

  • [ ] Step 1: Create the FeishuBot entity file

    import {
    Entity,
    PrimaryGeneratedColumn,
    Column,
    CreateDateColumn,
    UpdateDateColumn,
    ManyToOne,
    JoinColumn,
    } from 'typeorm';
    import { User } from '../../user/user.entity';
    
    @Entity('feishu_bots')
    export class FeishuBot {
    @PrimaryGeneratedColumn('uuid')
    id: string;
    
    @Column({ name: 'user_id' })
    userId: string; // Plugin manages its own relationship to the core User
    
    @ManyToOne(() => User)
    @JoinColumn({ name: 'user_id' })
    user: User;
    
    @Column({ name: 'app_id', length: 64 })
    appId: string;
    // ... (rest of the fields as defined previously)
    }
    
  • [ ] Step 2: Decoupled Relation Instead of modifying the core User entity directly, the FeishuBot entity maintains its own reference to User. This keeps the core system clean and allows the plugin to be purely optional.

  • [ ] Step 3: Create DTOs

Create: server/src/feishu/dto/create-bot.dto.ts

import { IsString, IsNotEmpty, IsOptional, IsBoolean } from 'class-validator';

export class CreateFeishuBotDto {
  @IsString()
  @IsNotEmpty()
  appId: string;

  @IsString()
  @IsNotEmpty()
  appSecret: string;

  @IsString()
  @IsOptional()
  verificationToken?: string;

  @IsString()
  @IsOptional()
  encryptKey?: string;

  @IsString()
  @IsOptional()
  botName?: string;

  @IsBoolean()
  @IsOptional()
  enabled?: boolean;
}

Create: server/src/feishu/dto/bind-bot.dto.ts

import { IsString, IsNotEmpty, IsUUID } from 'class-validator';

export class BindFeishuBotDto {
  @IsUUID()
  @IsNotEmpty()
  botId: string;

  @IsString()
  @IsNotEmpty()
  verificationCode?: string;  // Optional: 用于验证绑定关系
}

Chunk 2: Core Service Implementation

Task 2.1: Create Feishu Service

Files:

  • Create: server/src/feishu/feishu.service.ts

  • [ ] Step 1: Implement FeishuService with token management and message sending

    import { Injectable, Logger } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository } from 'typeorm';
    import { ConfigService } from '@nestjs/config';
    import { HttpService } from '@nestjs/axios';
    import { FeishuBot } from './entities/feishu-bot.entity';
    import { CreateFeishuBotDto } from './dto/create-bot.dto';
    
    @Injectable()
    export class FeishuService {
    private readonly logger = new Logger(FeishuService.name);
    private readonly feishuApiBase = 'https://open.feishu.cn/open-apis';
    
    constructor(
    @InjectRepository(FeishuBot)
    private botRepository: Repository<FeishuBot>,
    private httpService: HttpService,
    private configService: ConfigService,
    ) {}
    
    /**
    * Create or update a Feishu bot for a user
    */
    async createBot(userId: string, dto: CreateFeishuBotDto): Promise<FeishuBot> {
    // Check if bot already exists for this user with same appId
    const existing = await this.botRepository.findOne({
      where: { userId, appId: dto.appId },
    });
    
    if (existing) {
      // Update existing bot
      Object.assign(existing, dto);
      return this.botRepository.save(existing);
    }
    
    // Create new bot
    const bot = this.botRepository.create({
      userId,
      ...dto,
    });
    return this.botRepository.save(bot);
    }
    
    /**
    * Get all bots for a user
    */
    async getUserBots(userId: string): Promise<FeishuBot[]> {
    return this.botRepository.find({ where: { userId } });
    }
    
    /**
    * Get bot by ID
    */
    async getBotById(botId: string): Promise<FeishuBot | null> {
    return this.botRepository.findOne({ where: { id: botId } });
    }
    
    /**
    * Get bot by appId
    */
    async getBotByAppId(appId: string): Promise<FeishuBot | null> {
    return this.botRepository.findOne({ where: { appId } });
    }
    
    /**
    * Get or refresh tenant_access_token
    */
    async getValidToken(bot: FeishuBot): Promise<string> {
    // Check if token is still valid (expire in 2 hours, refresh at 1.5 hours)
    if (
      bot.tokenExpiresAt &&
      bot.tenantAccessToken &&
      new Date(bot.tokenExpiresAt) > new Date(Date.now() + 30 * 60 * 1000)
    ) {
      return bot.tenantAccessToken;
    }
    
    // Refresh token
    const response = await this.httpService
      .post(`${this.feishuApiBase}/auth/v3/tenant_access_token/internal`, {
        app_id: bot.appId,
        app_secret: bot.appSecret,
      })
      .toPromise();
    
    if (response.data.code !== 0) {
      throw new Error(`Failed to get token: ${response.data.msg}`);
    }
    
    const { tenant_access_token, expire } = response.data;
    
    // Update bot with new token
    bot.tenantAccessToken = tenant_access_token;
    bot.tokenExpiresAt = new Date(Date.now() + expire * 1000);
    await this.botRepository.save(bot);
    
    return tenant_access_token;
    }
    
    /**
    * Send message to Feishu user
    */
    async sendMessage(
    bot: FeishuBot,
    receiveIdType: 'open_id' | 'user_id' | 'union_id' | 'chat_id',
    receiveId: string,
    messageType: 'text' | 'interactive' | 'post',
    content: any,
    ): Promise<string> {
    const token = await this.getValidToken(bot);
    
    const response = await this.httpService
      .post(
        `${this.feishuApiBase}/im/v1/messages?receive_id_type=${receiveIdType}`,
        {
          receive_id: receiveId,
          msg_type: messageType,
          content: JSON.stringify(content),
        },
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      )
      .toPromise();
    
    if (response.data.code !== 0) {
      throw new Error(`Failed to send message: ${response.data.msg}`);
    }
    
    return response.data.data.message_id;
    }
    
    /**
    * Send text message (convenience method)
    */
    async sendTextMessage(
    bot: FeishuBot,
    receiveIdType: 'open_id' | 'user_id' | 'chat_id',
    receiveId: string,
    text: string,
    ): Promise<string> {
    return this.sendMessage(bot, receiveIdType, receiveId, 'text', { text });
    }
    
    /**
    * Reply to a message
    */
    async replyMessage(
    bot: FeishuBot,
    messageId: string,
    messageType: 'text' | 'interactive' | 'post',
    content: any,
    ): Promise<string> {
    const token = await this.getValidToken(bot);
    
    const response = await this.httpService
      .post(
        `${this.feishuApiBase}/im/v1/messages/${messageId}/reply`,
        {
          msg_type: messageType,
          content: JSON.stringify(content),
        },
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      )
      .toPromise();
    
    if (response.data.code !== 0) {
      throw new Error(`Failed to reply message: ${response.data.msg}`);
    }
    
    return response.data.data.message_id;
    }
    
    /**
    * Upload image for sending
    */
    async uploadImage(
    bot: FeishuBot,
    imageType: 'message' | 'avatar',
    image: Buffer,
    imageName: string,
    ): Promise<string> {
    const token = await this.getValidToken(bot);
    
    const FormData = require('form-data');
    const form = new FormData();
    form.append('image_type', imageType);
    form.append('image', image, { filename: imageName });
    
    const response = await this.httpService
      .post(`${this.feishuApiBase}/im/v1/images`, form, {
        headers: {
          Authorization: `Bearer ${token}`,
          ...form.getHeaders(),
        },
      })
      .toPromise();
    
    if (response.data.code !== 0) {
      throw new Error(`Failed to upload image: ${response.data.msg}`);
    }
    
    return response.data.data.image_key;
    }
    
    /**
    * Delete bot
    */
    async deleteBot(botId: string): Promise<void> {
    await this.botRepository.delete(botId);
    }
    
    /**
    * Enable/disable bot
    */
    async setBotEnabled(botId: string, enabled: boolean): Promise<FeishuBot> {
    const bot = await this.botRepository.findOne({ where: { id: botId } });
    if (!bot) {
      throw new Error('Bot not found');
    }
    bot.enabled = enabled;
    return this.botRepository.save(bot);
    }
    }
    

Chunk 3: Controller and Webhook Endpoints

Task 3.1: Create Feishu Controller

Files:

  • Create: server/src/feishu/feishu.controller.ts

  • [ ] Step 1: Implement webhook endpoints

    import {
    Controller,
    Post,
    Get,
    Delete,
    Body,
    Param,
    Query,
    Headers,
    UseGuards,
    Request,
    RawBodyRequest,
    Req,
    } from '@nestjs/common';
    import { Request as ExpressRequest } from 'express';
    import { FeishuService } from './feishu.service';
    import { CreateFeishuBotDto } from './dto/create-bot.dto';
    import { JwtAuthGuard } from '../auth/jwt-auth.guard';
    import { CreateSignatureDto, VerifyWebhookDto } from './dto/webhook.dto';
    
    @Controller('feishu')
    export class FeishuController {
    constructor(private readonly feishuService: FeishuService) {}
    
    /**
    * GET /feishu/bots - List user's bots
    * Requires JWT auth
    */
    @Get('bots')
    @UseGuards(JwtAuthGuard)
    async listBots(@Request() req) {
    const bots = await this.feishuService.getUserBots(req.user.id);
    // Mask sensitive data
    return bots.map((bot) => ({
      id: bot.id,
      appId: bot.appId,
      botName: bot.botName,
      enabled: bot.enabled,
      isDefault: bot.isDefault,
      createdAt: bot.createdAt,
    }));
    }
    
    /**
    * POST /feishu/bots - Create a new bot
    * Requires JWT auth
    */
    @Post('bots')
    @UseGuards(JwtAuthGuard)
    async createBot(@Request() req, @Body() dto: CreateFeishuBotDto) {
    const bot = await this.feishuService.createBot(req.user.id, dto);
    return {
      id: bot.id,
      appId: bot.appId,
      botName: bot.botName,
      enabled: bot.enabled,
    };
    }
    
    /**
    * DELETE /feishu/bots/:id - Delete a bot
    * Requires JWT auth
    */
    @Delete('bots/:id')
    @UseGuards(JwtAuthGuard)
    async deleteBot(@Request() req, @Param('id') botId: string) {
    await this.feishuService.deleteBot(botId);
    return { success: true };
    }
    
    /**
    * POST /feishu/webhook - Feishu webhook endpoint
    * Public endpoint - no auth required (uses verification token)
    */
    @Post('webhook')
    async handleWebhook(
    @Body() body: any,
    @Headers('x-xsign') xSign?: string,
    @Headers('x-timESTAMP') xTimestamp?: string,
    ) {
    this.logger.log(`Received webhook: ${JSON.stringify(body)}`);
    
    const { type, schema, event } = body;
    
    // Handle URL verification (飞书首次配置时验证)
    if (type === 'url_verification') {
      return {
        challenge: body.challenge,
      };
    }
    
    // Handle event callback
    if (type === 'event_callback') {
      const { event_type, token } = body;
    
      // Verify token
      if (token !== body?.verify_token) {
        this.logger.warn('Webhook token verification failed');
        return { success: false };
      }
    
      // Handle different event types
      switch (event_type) {
        case 'im.message.p2p_msg_received':
          // Handle private message
          await this.handleP2PMsg(event);
          break;
    
        case 'im.message.group_at_msg_received':
          // Handle group @message
          await this.handleGroupMsg(event);
          break;
    
        default:
          this.logger.log(`Unhandled event type: ${event_type}`);
      }
    }
    
    return { success: true };
    }
    
    /**
    * Handle private message
    */
    private async handleP2PMsg(event: any) {
    const { message } = event;
    const { message_id, chat_id, sender, message_type, body } = message;
    
    // Get message content
    const content = JSON.parse(message.content || '{}');
    const text = content.text || '';
    
    // Find bot by app_id (from chat or event)
    const openId = sender?.id?.open_id;
    if (!openId) {
      this.logger.warn('No sender open_id found');
      return;
    }
    
    // Find user's bot
    const bot = await this.feishuService.getBotByAppId(sender?.sender_id?.app_id);
    if (!bot || !bot.enabled) {
      this.logger.warn('Bot not found or disabled');
      return;
    }
    
    // Process message through RAG/Chat service
    await this.processMessage(bot, openId, message_id, text);
    }
    
    /**
    * Handle group message (@bot)
    */
    private async handleGroupMsg(event: any) {
    const { message } = event;
    const { message_id, chat_id, sender, message_type, content } = message;
    
    // Check if bot was mentioned
    const msgContent = JSON.parse(content || '{}');
    // Group messages often require specific handling
    
    // Similar to P2P but with chat_id context
    await this.handleP2PMsg(event);
    }
    
    /**
    * Process message through ChatService
    */
    private async processMessage(
    bot: any,
    openId: string,
    messageId: string,
    text: string,
    ) {
    // TODO: Integrate with ChatService
    // This will be implemented in Chunk 5
    
    // For now, echo back (placeholder)
    try {
      await this.feishuService.sendTextMessage(
        bot,
        'open_id',
        openId,
        `Received: ${text}`,
      );
    } catch (error) {
      this.logger.error('Failed to send response', error);
    }
    }
    }
    
  • [ ] Step 2: Create webhook DTOs

Create: server/src/feishu/dto/webhook.dto.ts

import { IsString, IsOptional } from 'class-validator';

export class CreateSignatureDto {
  @IsString()
  @IsOptional()
  timestamp?: string;

  @IsString()
  @IsOptional()
  nonce?: string;
}

export class VerifyWebhookDto {
  @IsString()
  token: string;

  @IsString()
  @IsOptional()
  challenge?: string;
}

Chunk 4: Plugin Registration

Task 4.1: Register Feishu Plugin

Note: This module acts as an optional extension to the AuraK ecosystem.

Files:

  • Create: server/src/feishu/feishu.module.ts
  • Modify: server/src/app.module.ts

  • [ ] Step 1: Create FeishuModule with isolated exports

    import { Module, forwardRef } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { HttpModule } from '@nestjs/axios';
    import { FeishuController } from './feishu.controller';
    import { FeishuService } from './feishu.service';
    import { FeishuBot } from './entities/feishu-bot.entity';
    import { ChatModule } from '../chat/chat.module';
    import { UserModule } from '../user/user.module';
    
    @Module({
    imports: [
    TypeOrmModule.forFeature([FeishuBot]),
    HttpModule,
    forwardRef(() => ChatModule),
    forwardRef(() => UserModule),
    ],
    controllers: [FeishuController],
    providers: [FeishuService],
    exports: [FeishuService, TypeOrmModule],
    })
    export class FeishuModule {}
    
  • [ ] Step 2: Add FeishuModule to AppModule

Modify: server/src/app.module.ts

Add import:

import { FeishuModule } from './feishu/feishu.module';

Add to imports array:

FeishuModule,

Chunk 5: Integration with ChatService

Task 5.1: Connect Feishu messages to ChatService

Files:

  • Modify: server/src/feishu/feishu.controller.ts
  • Modify: server/src/feishu/feishu.service.ts

  • [ ] Step 1: Extend FeishuService to handle chat integration

Add to FeishuService:

  // Import these
  import { ChatService } from '../chat/chat.service';
  import { ModelConfigService } from '../model-config/model-config.service';
  import { TenantService } from '../tenant/tenant.service';
  import { UserService } from '../user/user.service';
  import { ModelType } from '../types';

  // Add to constructor
  constructor(
    // ... existing
    private chatService: ChatService,
    private modelConfigService: ModelConfigService,
    private tenantService: TenantService,
    private userService: UserService,
  ) {}

  /**
   * Process chat message through RAG
   */
  async processChatMessage(
    bot: FeishuBot,
    openId: string,
    messageId: string,
    userMessage: string,
  ): Promise<void> {
    // Get user by Feishu open_id mapping (future: map table)
    // For now, use userId from bot
    const userId = bot.userId;
    
    // Get user's tenant
    const user = await this.userService.findById(userId);
    const tenantId = user?.tenantId || 'default';

    // Get user's LLM config
    const llmModel = await this.modelConfigService.findDefaultByType(
      tenantId,
      ModelType.LLM,
    );

    if (!llmModel) {
      await this.sendTextMessage(bot, 'open_id', openId, '请先配置 LLM 模型');
      return;
    }

    // Send "thinking" message
    await this.sendTextMessage(bot, 'open_id', openId, '正在思考...');

    // Stream chat response
    const stream = this.chatService.streamChat(
      userMessage,
      [],  // No history for now (future: persist per openId)
      userId,
      llmModel as any,
      user?.userSetting?.language || 'zh',
      undefined, // selectedEmbeddingId
      undefined, // selectedGroups
      undefined, // selectedFiles
      undefined, // historyId
      false, // enableRerank
      undefined, // selectedRerankId
      undefined, // temperature
      undefined, // maxTokens
      10, // topK
      0.7, // similarityThreshold
      undefined, // rerankSimilarityThreshold
      undefined, // enableQueryExpansion
      undefined, // enableHyDE
      tenantId,
    );

    let fullResponse = '';
    for await (const chunk of stream) {
      if (chunk.type === 'content') {
        fullResponse += chunk.data;
        // Could send incrementally, but Feishu prefers complete messages
      }
    }

    // Send final response
    // Truncate if too long (Feishu has limits)
    const maxLength = 5000;
    const finalMessage = fullResponse.length > maxLength
      ? fullResponse.substring(0, maxLength) + '...(内容过长)'
      : fullResponse;

    await this.sendTextMessage(bot, 'open_id', openId, finalMessage);
  }
  • Step 2: Update controller to use chat integration

Modify the processMessage method in FeishuController to call feishuService.processChatMessage():

  private async processMessage(
    bot: any,
    openId: string,
    messageId: string,
    text: string,
  ) {
    try {
      await this.feishuService.processChatMessage(bot, openId, messageId, text);
    } catch (error) {
      this.logger.error('Failed to process message', error);
      try {
        await this.feishuService.sendTextMessage(
          bot,
          'open_id',
          openId,
          '抱歉,处理消息时发生错误,请稍后重试。',
        );
      } catch (sendError) {
        this.logger.error('Failed to send error message', sendError);
      }
    }
  }

Chunk 6: Frontend Integration (Optional)

Task 6.1: Add Feishu sub-view to Plugins

Files:

  • Create: web/src/pages/Plugins/FeishuPlugin.tsx
  • Modify: web/src/components/views/PluginsView.tsx
  • Modify: web/src/services/api.ts

  • [ ] Step 1: Create the Plugin Configuration UI This view should match the design of other plugin cards in the /plugins page but provide a detailed setup guide and form for:

  • App ID / App Secret

  • Webhook URL (Read-only generated URL)

  • Verification Token & Encrypt Key

  • [ ] Step 2: Register the Plugin in PluginsView Modify the main plugins listing to include "Feishu Bot" as an available (or installed) plugin.

Modify: web/src/services/api.ts

// Add Feishu API calls
export const feishuApi = {
  listBots: () => api.get('/feishu/bots'),
  createBot: (data: CreateBotDto) => api.post('/feishu/bots', data),
  deleteBot: (botId: string) => api.delete(`/feishu/bots/${botId}`),
};
  • Step 3: Implementation of PluginsView.tsx If web/src/components/views/PluginsView.tsx does not exist, create a generic plugin management layout that can host the Feishu configuration.

Layout Requirements:

  • Grid of available plugins.
  • Status toggle (Enabled/Disabled).
  • Detail/Configuration view for active plugins.

Testing Strategy

Unit Tests

  • feishu.service.spec.ts - Test token refresh, message sending
  • feishu.controller.spec.ts - Test webhook endpoints

Integration Tests

  • Test full flow: Feishu message → Webhook → ChatService → Response

Manual Testing

  1. Create Feishu app in 开放平台
  2. Configure webhook URL (use ngrok for local)
  3. Subscribe to message events
  4. Send message to bot
  5. Verify RAG response

Security Considerations

  1. Token Storage: Encrypt app_secret and encrypt_key before storing in DB.
  2. Webhook Verification:

    • Verify verify_token for simplicity.
    • Recommended: Validate X-Lark-Signature using encrypt_key to ensure authenticity.
  3. Rate Limiting: Implement a message queue (e.g., BullMQ) for outbound messages to respect Feishu's global and per-bot rate limits.

  4. User Privacy: Implement an opt-in flow for group chats to ensure the bot only processes messages when explicitly allowed or mentioned.


Advanced Optimizations (Recommended)

1. Webhook Performance & Reliability

  • Immediate Response: Feishu requires a response within 3 seconds. The RAG process can take 10s+.
  • Optimization: The handleWebhook should only validate the request and push the event to an internal queue, returning 200 OK immediately. A background worker then processes the RAG logic and sends the response.
  • Deduplication: Use the event_id in the webhook payload to ignore duplicate retries from Feishu.

2. UX: Managed "Thinking" State

  • Simulated Streaming: Since Feishu doesn't support SSE, send an initial "Thinking..." message and use the PATCH /im/v1/messages/:message_id API to update the message content every few seconds as chunks arrive.
  • Interactive Cards: Use Message Cards instead of plain text for:
    • Showing search citations with clickable links.
    • Providing "Regenerate" or "Clear Context" buttons.
    • Displaying assessment results with formatting (tables/charts).

3. Context & History Management

  • OpenID Mapping: Maintain a mapping between Feishu open_id and AuraK userId to persist chat history across different devices/platforms.
  • Thread Support: Use Feishu's root_id and parent_id to allow users to ask follow-up questions within a thread, keeping the UI clean.

4. Multi-modal Support

  • File Ingestion: Support im.message.file_received events. If a user sends a PDF/Docx to the bot, automatically import it into a "Feishu Uploads" group for immediate RAG context.
  • Image Analysis: Use the VisionService to handle images sent via Feishu.

Future Enhancements

  1. Assessment Integration: Bind assessment sessions to Feishu conversations using interactive card forms.
  2. Rich Responses: Use Feishu interactive cards for better visual presentation.
  3. Multi-bot Support: Users can have multiple bots for different specialized tasks.
  4. Group Chats: Support bot in group chats with specific @mention logic and moderation.
  5. Voice Messages: Handle voice message transcription via Feishu's audio-to-text API for accessibility.

Dependencies

  • @nestjs/axios - For HTTP requests to Feishu API
  • form-data - For file/image uploads
  • Optional: crypto (built-in) - For signature verification

Reference Links


Plan created: 2026-03-16 Based on: Feishu integration analysis