Skip to content

feat(notification): add Feishu webhook support#490

Merged
ding113 merged 3 commits intoding113:devfrom
BlackHole1:feature/feishu-webhook
Dec 31, 2025
Merged

feat(notification): add Feishu webhook support#490
ding113 merged 3 commits intoding113:devfrom
BlackHole1:feature/feishu-webhook

Conversation

@BlackHole1
Copy link
Contributor

@BlackHole1 BlackHole1 commented Dec 31, 2025

重构 webhook 通知系统,支持飞书和微信两种平台。

  • 引入 StructuredMessage 抽象层实现多平台渲染
  • 添加飞书卡片消息渲染器
  • 统一消息模板(熔断、成本、排行榜)


Resolves #485 - Implements custom Webhook notification support, enabling Feishu (飞书) in addition to WeChat Work

English Summary

This PR refactors the notification system from WeChat Work-only to a multi-platform webhook architecture, adding support for both WeChat Work (企业微信) and Feishu (飞书/Lark).

Problem

Previously, the system only supported WeChat Work webhooks (WeChatBot class), limiting users who use other enterprise collaboration platforms like Feishu or DingTalk.

Solution

Introduced a platform-agnostic notification architecture:

  • StructuredMessage abstraction - platform-independent message representation
  • Renderer pattern - separate renderers for WeChat (WeChatRenderer) and Feishu (FeishuCardRenderer)
  • Template system - unified templates for circuit breaker alerts, cost alerts, and daily leaderboards

Core Changes

New Architecture

src/lib/webhook/
├── notifier.ts           # WebhookNotifier (replaces WeChatBot)
├── renderers/
│   ├── wechat.ts         # WeChat markdown renderer
│   ├── feishu.ts         # Feishu interactive card renderer
│   └── index.ts          # Renderer factory
├── templates/
│   ├── circuit-breaker.ts
│   ├── cost-alert.ts
│   └── daily-leaderboard.ts
└── types.ts              # StructuredMessage type definitions

Breaking Changes

Removed Replacement Migration Path
WeChatBot class WebhookNotifier Use new WebhookNotifier(webhookUrl)
sendWeChatNotification() sendWebhookMessage() Direct replacement
buildCircuitBreakerAlert() buildCircuitBreakerMessage() Rename import
buildCostAlert() buildCostAlertMessage() Rename import
buildDailyLeaderboard() buildDailyLeaderboardMessage() Rename import
src/lib/wechat/bot.ts src/lib/webhook/notifier.ts Update import path
src/lib/wechat/message-templates.ts src/lib/webhook/templates/ Update import path

Note: Webhook URLs are auto-detected by hostname:

  • qyapi.weixin.qq.com → WeChat Work renderer
  • open.feishu.cn → Feishu renderer

Test Coverage

  • tests/unit/webhook/notifier.test.ts - Core notification logic
  • tests/unit/webhook/renderers/feishu.test.ts - Feishu card rendering
  • tests/unit/webhook/renderers/wechat.test.ts - WeChat markdown rendering
  • tests/unit/webhook/templates/templates.test.ts - All message templates
  • tests/unit/webhook/utils/retry.test.ts - Retry mechanism

UI Changes

  • Updated all i18n files: "WeChat Work" → "Webhook" (platform-agnostic)
  • Fixed test button type values: circuitBreakercircuit-breaker
  • Added type parameter to testWebhookAction() for template-specific testing

Database

  • Schema comment updated: 企业微信机器人通知配置Webhook 通知配置
  • No migration required (comment-only change)

Description enhanced by Claude AI

Greptile Summary

This PR refactors the notification system from WeChat-specific implementation to a multi-platform webhook system that supports both Feishu and WeChat.

Key Changes:

  • Introduced StructuredMessage abstraction layer that decouples business logic from platform-specific rendering
  • Added FeishuCardRenderer for Feishu interactive card messages
  • Added WeChatRenderer for WeChat markdown messages
  • Unified message templates for circuit-breaker alerts, cost alerts, and daily leaderboards
  • Migrated NotificationQueue from sendWeChatNotification to sendWebhookMessage
  • Enhanced webhook testing with platform-specific test messages
  • Deleted old WeChatBot class and WeChat-specific message templates
  • Added comprehensive test coverage for notifier, renderers, and templates

Breaking Changes:

  • WeChatBot API replaced with WebhookNotifier
  • Message template functions renamed (e.g., buildCircuitBreakerAlertbuildCircuitBreakerMessage)

Issues Found:

  • Test assertion mismatch: error message string doesn't match implementation

Confidence Score: 4/5

  • safe to merge with one test fix required
  • well-architected refactoring with good separation of concerns, comprehensive tests, and proper SSRF protection. One test has incorrect error message assertion that needs fixing.
  • tests/unit/webhook/notifier.test.ts needs test assertion fix for error message

Important Files Changed

Filename Overview
tests/unit/webhook/notifier.test.ts test suite with incorrect error message assertion for unsupported provider
src/lib/webhook/notifier.ts new webhook notifier with provider detection, retry logic, and unified message sending
src/lib/webhook/renderers/feishu.ts Feishu interactive card renderer with markdown elements and field layouts
src/lib/webhook/renderers/wechat.ts WeChat markdown renderer with localized timestamp formatting
src/lib/notification/notification-queue.ts migrated from WeChat-specific to webhook-based notification queue with dynamic data generation
src/actions/notifications.ts updated to use WebhookNotifier with SSRF protection and test message support

Sequence Diagram

sequenceDiagram
    participant User
    participant NotificationQueue
    participant WebhookNotifier
    participant Renderer
    participant Template
    participant WebhookAPI

    User->>NotificationQueue: addNotificationJob(type, webhookUrl, data)
    NotificationQueue->>NotificationQueue: queue.add(job)
    
    Note over NotificationQueue: Job Processing (with retry)
    NotificationQueue->>Template: buildMessage(data)
    Template-->>NotificationQueue: StructuredMessage
    
    NotificationQueue->>WebhookNotifier: send(message)
    WebhookNotifier->>WebhookNotifier: detectProvider()
    WebhookNotifier->>Renderer: createRenderer(providerType)
    Renderer-->>WebhookNotifier: renderer instance
    
    WebhookNotifier->>Renderer: render(message)
    alt Feishu
        Renderer->>Renderer: FeishuCardRenderer.render()
        Renderer-->>WebhookNotifier: Interactive Card JSON
    else WeChat
        Renderer->>Renderer: WeChatRenderer.render()
        Renderer-->>WebhookNotifier: Markdown JSON
    end
    
    WebhookNotifier->>WebhookNotifier: withRetry(doSend, maxRetries=3)
    loop Retry up to 3 times
        WebhookNotifier->>WebhookAPI: POST webhook with payload
        alt Success Response
            WebhookAPI-->>WebhookNotifier: {errcode: 0} or {code: 0}
            WebhookNotifier-->>NotificationQueue: {success: true}
        else Error Response
            WebhookAPI-->>WebhookNotifier: Error code
            WebhookNotifier->>WebhookNotifier: throw Error
            Note over WebhookNotifier: Exponential backoff delay
        end
    end
    
    NotificationQueue-->>User: Job completed or failed
Loading

重构 webhook 通知系统,支持飞书和微信两种平台。

- 引入 StructuredMessage 抽象层实现多平台渲染
- 添加飞书卡片消息渲染器
- 统一消息模板(熔断、成本、排行榜)
- 支持指数退避重试

BREAKING CHANGE: WeChatBot API 已替换为 WebhookNotifier

Signed-off-by: Kevin Cui <bh@bugs.cc>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @BlackHole1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在全面升级和优化系统的通知功能。通过引入一个抽象的 Webhook 通知框架,它使得系统能够更灵活地与各种第三方消息平台集成,而不仅仅局限于企业微信。这一改进不仅增加了对飞书等新平台的支持,还通过统一的消息结构和渲染逻辑,确保了通知内容在不同平台上的展示一致性和可维护性。

Highlights

  • 重构通知系统: 将原有的企业微信通知系统重构为通用的 Webhook 通知系统,支持多平台集成。
  • 新增飞书支持: 增加了对飞书 (Feishu) Webhook 的支持,允许将通知发送到飞书群聊。
  • 抽象消息结构: 引入 StructuredMessage 抽象层,实现消息内容的平台无关性,便于在不同平台渲染。
  • 统一消息模板: 统一了熔断、成本预警和排行榜等消息的模板,确保跨平台显示一致性。
  • 替换旧API: WeChatBot API 已被新的 WebhookNotifier 替换,提供更灵活的通知发送机制。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added size/XL Extra Large PR (> 1000 lines) enhancement New feature or request area:UI area:i18n area:core labels Dec 31, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 对通知系统进行了出色的重构,增加了对飞书 Webhook 的支持,并为未来扩展更多平台(如钉钉)打下了坚实的基础。通过引入 StructuredMessage 抽象层,成功地将消息内容的构建与具体平台的渲染逻辑解耦,这是一个非常优秀的设计。新的 webhook 模块结构清晰,职责分明,包含了通知器、渲染器和模板,使代码更易于维护和扩展。此外,为新功能编写了全面的单元测试,这极大地提升了代码的质量和可靠性。整体而言,这是一次高质量的重构。

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This is a well-executed refactoring that introduces a structured notification system supporting both WeChat and Feishu platforms. The code quality is high with proper abstraction, comprehensive unit tests, and adherence to project standards.

PR Size: XL

  • Lines changed: 1,805 (1,453 additions, 352 deletions)
  • Files changed: 34

Note: This XL PR could potentially be split into:

  1. Core abstraction layer (types, notifier, utils)
  2. WeChat migration (refactor existing code)
  3. Feishu support (new renderer)
  4. Templates refactoring
  5. Test coverage

However, the changes are tightly coupled and the current scope is manageable given the quality.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean (SSRF protection maintained)
  • Error handling - Clean (proper try-catch with logging and retry)
  • Type safety - Clean (no any usage, proper TypeScript types)
  • Documentation accuracy - Clean (comments are accurate and helpful)
  • Test coverage - Excellent (comprehensive unit tests for new modules)
  • Code clarity - Good (well-structured abstraction)

Highlights

Strengths:

  • Clean abstraction with StructuredMessage interface enabling multi-platform rendering
  • Registry pattern for renderers maintains extensibility
  • Comprehensive unit test coverage for all new modules
  • Proper error handling with retry logic and logging
  • Maintains SSRf protection from existing implementation
  • Consistent with existing codebase patterns (see src/lib/wechat/bot.ts)
  • i18n updates for all 5 locales
  • Breaking change clearly documented in PR description

Architecture:
The new notification system follows the existing patterns in the codebase:

  • Similar error handling pattern to WeChatBot.sendMarkdown() (lines 47-97 in bot.ts)
  • Retry logic abstraction mirrors the queue processor approach
  • Type safety maintained throughout

Automated review by Claude AI

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. tests/unit/webhook/notifier.test.ts, line 47-48 (link)

    syntax: test expects error message "Unsupported webhook URL" but the actual code throws "Unsupported webhook hostname: ${url.hostname}" (see src/lib/webhook/notifier.ts:46)

34 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request refactors the webhook notification system to support multiple platforms (Feishu and WeChat), replacing the previous WeChat-only implementation. The refactoring introduces a structured message abstraction layer with platform-specific renderers, enabling cleaner separation of concerns and easier extensibility.

Key Changes:

  • Introduced StructuredMessage abstraction layer for platform-independent message construction
  • Added Feishu card message renderer alongside the existing WeChat markdown renderer
  • Unified message templates for circuit breaker alerts, cost warnings, and leaderboard notifications

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/lib/webhook/notifier.ts New webhook notifier with provider auto-detection and retry logic
src/lib/webhook/renderers/wechat.ts WeChat markdown renderer implementation
src/lib/webhook/renderers/feishu.ts Feishu card renderer implementation
src/lib/webhook/templates/circuit-breaker.ts Structured circuit breaker alert message template
src/lib/webhook/templates/cost-alert.ts Structured cost alert message template
src/lib/webhook/templates/daily-leaderboard.ts Structured daily leaderboard message template
src/lib/webhook/templates/test-messages.ts Test message builder for webhook validation
src/lib/webhook/types.ts Type definitions for structured messages and webhook interfaces
src/lib/webhook/utils/retry.ts Retry utility with exponential backoff
src/lib/webhook/utils/date.ts Date formatting utility for China timezone
src/lib/notification/notification-queue.ts Updated to use new webhook system and structured messages
src/actions/notifications.ts Updated test webhook action to accept notification type parameter
src/app/[locale]/settings/notifications/page.tsx Updated UI to pass notification type to test webhook function
src/lib/constants/notification.constants.ts Centralized notification job type constants
src/lib/wechat/bot.ts Removed old WeChat-specific bot implementation
src/lib/wechat/message-templates.ts Removed old WeChat-specific message templates
tests/unit/webhook/ Comprehensive test coverage for new webhook system
messages/*/settings.json Updated UI strings to reflect generic webhook support
src/drizzle/schema.ts Updated comment to reflect generic webhook configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

],
totalRequests: 270,
totalCost: 22.7,
});
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement is missing a default case. While TypeScript's exhaustiveness checking ensures all NotificationJobType values are handled, adding a default case that throws an error would provide better runtime safety and clearer error messages if an unexpected value is passed.

Suggested change
});
});
default: {
const exhaustiveCheck: never = type;
throw new Error(`Unhandled NotificationJobType in buildTestMessage: ${exhaustiveCheck}`);
}

Copilot uses AI. Check for mistakes.
}

export function buildCostAlertMessage(data: CostAlertData): StructuredMessage {
const usagePercent = (data.currentCost / data.quotaLimit) * 100;
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Division by zero error: If quotaLimit is 0, line 10 will result in a division by zero, producing Infinity or NaN. This would cause incorrect percentage calculations and display. Add validation to handle the case where quotaLimit is zero or negative.

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +88
describe("buildCostAlertMessage", () => {
it("should create structured message for user cost alert", () => {
const data: CostAlertData = {
targetType: "user",
targetName: "张三",
targetId: 100,
currentCost: 8.5,
quotaLimit: 10,
threshold: 0.8,
period: "本周",
};

const message = buildCostAlertMessage(data);

expect(message.header.level).toBe("warning");
expect(message.header.icon).toBe("💰");
expect(message.header.title).toContain("成本预警");

const sectionsStr = JSON.stringify(message.sections);
expect(sectionsStr).toContain("张三");
expect(sectionsStr).toContain("8.5");
expect(sectionsStr).toContain("本周");
});

it("should create structured message for provider cost alert", () => {
const data: CostAlertData = {
targetType: "provider",
targetName: "GPT-4",
targetId: 1,
currentCost: 950,
quotaLimit: 1000,
threshold: 0.9,
period: "本月",
};

const message = buildCostAlertMessage(data);

expect(message.header.level).toBe("warning");
const sectionsStr = JSON.stringify(message.sections);
expect(sectionsStr).toContain("供应商");
});
});
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for the division by zero edge case. The tests should include a case where quotaLimit is 0 or negative to ensure the function handles these edge cases gracefully without producing Infinity or NaN values.

Copilot uses AI. Check for mistakes.

it("should use exponential backoff", async () => {
const delays: number[] = [];
const originalSetTimeout = globalThis.setTimeout;
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable originalSetTimeout.

Copilot uses AI. Check for mistakes.
Signed-off-by: Kevin Cui <bh@bugs.cc>
Signed-off-by: Kevin Cui <bh@bugs.cc>
@ding113 ding113 changed the base branch from main to dev December 31, 2025 07:01
@ding113 ding113 merged commit 68b1e6d into ding113:dev Dec 31, 2025
6 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Dec 31, 2025
@BlackHole1 BlackHole1 deleted the feature/feishu-webhook branch December 31, 2025 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:i18n area:UI enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

请求添加自定义WebHook通知

2 participants

Comments