Skip to content

Conversation

@hu-qi
Copy link
Collaborator

@hu-qi hu-qi commented Dec 16, 2025

Summary by CodeRabbit

  • Refactor
    • Streamlined GitHub notification transformation pipeline for improved maintainability.
    • Updated Lark chatbot webhook integration configuration.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 16, 2025

Walkthrough

Consolidates GitHub-to-Feishu notification transformation by removing a dedicated card-generation script and merging its functionality into the main transform script. Updates the workflow to use the consolidated script with revised environment variables, centralizing event processing and card payload construction logic.

Changes

Cohort / File(s) Change Summary
Script Removal
​.github/scripts/transform-message-for-card.ts
Deleted entire TypeScript module that previously handled GitHub webhook event transformation into Feishu card payloads, including event-specific handlers, markdown sanitization, link formatting, and card assembly logic.
Script Consolidation & Refactoring
​.github/scripts/transform-message.ts
Significantly refactored to absorb removed script's responsibilities. Replaced content array structure with markdown elements array. Enhanced link generation from objects to markdown strings. Added createUserLink and sanitizeMarkdown utilities. Rewrote all event handlers to produce unified { title, elements } structure with markdown content blocks. Updated main flow to construct and output full Card schema 2.0 payload instead of simple post payload.
Workflow Configuration
​.github/workflows/Lark-notification.yml
Updated script invocation from transform-message-for-card.ts to transform-message.ts. Removed FEISHU_BOT_WEBHOOK_URL environment variable reference. Changed webhook URL source to use LARK_CHATBOT_HOOK_URL GitHub Secret.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Specific areas requiring extra attention:
    • Verify that sanitizeMarkdown correctly handles all GitHub markdown edge cases (code blocks, inline code, images, HTML comments, excessive newlines, truncation)
    • Ensure all event handler return shapes ({ title, elements }) are consistently applied across push, issues, pull_request, discussion, issue_comment, discussion_comment, release, and pull_request_review_comment handlers
    • Validate that the new Card schema 2.0 payload structure and markdown content blocks render correctly in Feishu
    • Confirm the fallback behavior for missing user data ("无" string) and missing commit URLs is appropriate

Possibly related PRs

Poem

🐰 A script retires to make room for one,
Markdown flows through Feishu, unified and spun,
Cards assembled cleanly from a single source,
The notification pipeline stays its course! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title 'Refactor GitHub event transformation pipeline for Feishu integration' accurately describes the main changes: consolidating event transformation logic from two files into one, restructuring the event handler return types, and updating the workflow to reference the new script.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
.github/scripts/transform-message.ts (2)

47-48: Consider using a more specific type instead of any.

The user parameter is typed as any, which bypasses type checking. Consider using GitHubUser | null | undefined for better type safety.

Apply this diff to improve type safety:

-const createUserLink = (user: any) =>
+const createUserLink = (user: GitHubUser | null | undefined) =>
   user ? createLink(user.html_url, user.login) : "无";

148-248: Consistent pattern across all event handlers with similar non-null assertion concerns.

All event handlers follow a uniform structure, which is good for maintainability. However, they all use non-null assertions on potentially undefined fields. Consider adding runtime checks or using optional chaining with fallbacks to prevent runtime errors.

For example, for the pull_request handler:

   pull_request: ({ event: { pull_request } }, actionText) => ({
-    title: `GitHub PR ${actionText}:${pull_request?.title}`,
+    title: `GitHub PR ${actionText}:${pull_request?.title || "未知 PR"}`,
     elements: [
       {
         tag: "markdown",
         content: [
-          createContentItem("链接:", createLink(pull_request!.html_url)),
+          createContentItem("链接:", pull_request?.html_url ? createLink(pull_request.html_url) : "无"),

Apply similar patterns to other handlers to improve resilience.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95e0cb7 and 56b46a2.

📒 Files selected for processing (3)
  • .github/scripts/transform-message-for-card.ts (0 hunks)
  • .github/scripts/transform-message.ts (3 hunks)
  • .github/workflows/Lark-notification.yml (2 hunks)
💤 Files with no reviewable changes (1)
  • .github/scripts/transform-message-for-card.ts
🔇 Additional comments (8)
.github/scripts/transform-message.ts (6)

24-43: LGTM! Action text mapping is clear and comprehensive.

The ACTION_TEXT_MAP provides a well-organized mapping of GitHub action types to their Chinese labels, and getActionText handles missing actions gracefully with a sensible fallback.


45-45: LGTM! Markdown link format is correct.

The simplified createLink function correctly generates Markdown-style links.


74-75: LGTM! Content item formatting is clean.

The function correctly formats content items with proper fallback for missing values.


87-123: LGTM! Push event handler includes proper fallbacks.

The handler correctly handles missing head_commit scenarios with appropriate fallback values for both URL and message.


252-267: LGTM! Error handling is robust.

The processEvent function properly validates handler existence and provides clear error messages with proper error chaining.


279-295: LGTM! Card structure follows Feishu schema 2.0 format.

The card construction correctly uses the Feishu/Lark card schema with proper header and body elements.

.github/workflows/Lark-notification.yml (2)

44-44: Good security improvement: Using GitHub Secrets instead of environment variables.

The change from environment variables to GitHub Secrets (LARK_CHATBOT_HOOK_URL) is a security best practice for handling sensitive webhook URLs. Ensure that this secret is properly configured in the repository settings.


30-31: Old script file has been successfully removed.

The transition from transform-message-for-card.ts to transform-message.ts is clean—the old script no longer exists in the repository and no references to it remain in the codebase.

Comment on lines +51 to +72
const sanitizeMarkdown = (text: string): string =>
text
// Remove code blocks
.replace(/```[\s\S]*?```/g, "[代码块]")
// Remove inline code
.replace(/`[^`]+`/g, (match) => match.slice(1, -1))
// Convert images to link text
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "🖼️ [$1]($2)")
// Convert ### headers to bold
.replace(/^###\s+(.+)$/gm, "**$1**")
// Convert ## headers to bold
.replace(/^##\s+(.+)$/gm, "**$1**")
// Convert # headers to bold
.replace(/^#\s+(.+)$/gm, "**$1**")
// Remove HTML comments
.replace(/<!--[\s\S]*?-->/g, "")
// Remove HTML tags (keep content)
.replace(/<[^>]+>/g, "")
// Remove excess empty lines
.replace(/\n{3,}/g, "\n\n")
// Truncate long content
.slice(0, 800) + (text.length > 800 ? "\n..." : "");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Truncation check uses original text length.

The function slices the sanitized text to 800 characters but checks text.length > 800 (the original text length) to decide whether to append the ellipsis. This could result in the ellipsis appearing when it shouldn't, or not appearing when it should.

Apply this diff to fix the truncation logic:

 const sanitizeMarkdown = (text: string): string =>
-  text
+  {
+  const sanitized = text
     // Remove code blocks
     .replace(/```[\s\S]*?```/g, "[代码块]")
     // Remove inline code
     .replace(/`[^`]+`/g, (match) => match.slice(1, -1))
     // Convert images to link text
     .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "🖼️ [$1]($2)")
     // Convert ### headers to bold
     .replace(/^###\s+(.+)$/gm, "**$1**")
     // Convert ## headers to bold
     .replace(/^##\s+(.+)$/gm, "**$1**")
     // Convert # headers to bold
     .replace(/^#\s+(.+)$/gm, "**$1**")
     // Remove HTML comments
     .replace(/<!--[\s\S]*?-->/g, "")
     // Remove HTML tags (keep content)
     .replace(/<[^>]+>/g, "")
     // Remove excess empty lines
-    .replace(/\n{3,}/g, "\n\n")
-    // Truncate long content
-    .slice(0, 800) + (text.length > 800 ? "\n..." : "");
+    .replace(/\n{3,}/g, "\n\n");
+  return sanitized.slice(0, 800) + (sanitized.length > 800 ? "\n..." : "");
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sanitizeMarkdown = (text: string): string =>
text
// Remove code blocks
.replace(/```[\s\S]*?```/g, "[代码块]")
// Remove inline code
.replace(/`[^`]+`/g, (match) => match.slice(1, -1))
// Convert images to link text
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "🖼️ [$1]($2)")
// Convert ### headers to bold
.replace(/^###\s+(.+)$/gm, "**$1**")
// Convert ## headers to bold
.replace(/^##\s+(.+)$/gm, "**$1**")
// Convert # headers to bold
.replace(/^#\s+(.+)$/gm, "**$1**")
// Remove HTML comments
.replace(/<!--[\s\S]*?-->/g, "")
// Remove HTML tags (keep content)
.replace(/<[^>]+>/g, "")
// Remove excess empty lines
.replace(/\n{3,}/g, "\n\n")
// Truncate long content
.slice(0, 800) + (text.length > 800 ? "\n..." : "");
const sanitizeMarkdown = (text: string): string => {
const sanitized = text
// Remove code blocks
.replace(/
🤖 Prompt for AI Agents
In .github/scripts/transform-message.ts around lines 51 to 72 the truncation
logic slices the sanitized string but checks text.length (original input) to
decide whether to append "…", which can misreport truncation; change the chain
to assign the result of all the .replace calls to a local variable (e.g.,
sanitized), then return sanitized.slice(0, 800) + (sanitized.length > 800 ?
"\n..." : ""); ensure the function closes correctly and no longer uses
text.length for the ellipsis check.

Comment on lines 125 to 146
issues: ({ event: { issue } }, actionText) => ({
title: `GitHub issue ${actionText}${issue?.title}`,
content: [
[createText("链接:"), createLink(issue!.html_url)],
[
createText("作者:"),
createLink(issue!.user!.html_url!, issue!.user!.login),
],
[
createText("指派:"),
issue?.assignee
? createLink(issue.assignee.html_url!, issue.assignee.login)
: createText("无"),
],
[
createText("标签:"),
createText(issue?.labels?.map(({ name }) => name).join(", ") || "无"),
],
[createText("里程碑:"), createText(issue?.milestone?.title || "无")],
[createText("描述:"), createText(issue?.body || "无")],
elements: [
{
tag: "markdown",
content: [
createContentItem("链接:", createLink(issue!.html_url)),
createContentItem("作者:", createUserLink(issue!.user!)),
createContentItem(
"指派:",
issue?.assignee ? createUserLink(issue.assignee) : "无",
),
createContentItem(
"标签:",
issue?.labels?.map(({ name }) => name).join(", ") || "无",
),
createContentItem("里程碑:", issue?.milestone?.title || "无"),
createContentItem("描述:", issue?.body || "无"),
].join("\n"),
},
],
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Explore the structure of the file and look at the type definitions
cd /tmp && git ls-files .github/scripts/ 2>/dev/null | head -20 || echo "Finding the file..."
find . -name "transform-message.ts" -o -name "*.ts" -path "*/.github/scripts/*" 2>/dev/null | head -5

Repository: Open-Source-Bazaar/feishu-action

Length of output: 58


🏁 Script executed:

#!/bin/bash
# Find the transform-message.ts file and examine its content
fd -t f "transform-message" . 2>/dev/null || find . -name "*transform*" -type f 2>/dev/null | grep -E "\.(ts|js)$" | head -10

Repository: Open-Source-Bazaar/feishu-action

Length of output: 58


🏁 Script executed:

#!/bin/bash
# List all tracked files in the repository
git ls-files | head -30
echo "---"
# Search for TypeScript files
find . -type f -name "*.ts" | head -20

Repository: Open-Source-Bazaar/feishu-action

Length of output: 504


🏁 Script executed:

#!/bin/bash
# Read the transform-message.ts file to understand the type definitions
cat -n .github/scripts/transform-message.ts

Repository: Open-Source-Bazaar/feishu-action

Length of output: 10393


🏁 Script executed:

#!/bin/bash
# Check how the event handlers are called and if there's any type narrowing
grep -n "event_name\|eventHandlers\[" .github/scripts/transform-message.ts
# Also check how the event types are imported and what guarantees they have
grep -n "octokit\|webhook" .github/scripts/transform-message.ts

Repository: Open-Source-Bazaar/feishu-action

Length of output: 608


🏁 Script executed:

#!/bin/bash
# Check the @octokit/openapi-types to understand the issue type
grep -r "webhook-issues-opened" . 2>/dev/null || echo "Type definition not in repo"
# Check package.json to see what version of octokit is used
cat package.json | grep -A5 -B5 "octokit"

Repository: Open-Source-Bazaar/feishu-action

Length of output: 174


Use type narrowing or fix the type definition for the issue field.

The issue field is typed as optional but accessed with non-null assertions. While GitHub's webhook API guarantees the issue field is always present in issues events, the type definition doesn't reflect this. Either narrow the type when handling the issues event or mark issue as required (non-optional) since the GitHub issues webhook event always includes the issue object. This removes the need for non-null assertions and improves type safety.

🤖 Prompt for AI Agents
In .github/scripts/transform-message.ts around lines 125 to 146, the code uses
non-null assertions on issue (issue!, issue.user!) while the event typing marks
issue optional; remove the assertions and either narrow the type at the callsite
(add a runtime guard that returns/throws if issue is missing before accessing
properties) or update the event type definition so issue is non-optional for
issue events; after fixing the type, access issue fields without "!" and adjust
any downstream typing accordingly to eliminate unsafe assertions.

Comment on lines 270 to +277
const event = JSON.parse((await stdin()) || "{}") as GitHubAction;
const zh_cn = processEvent(event);
const result = processEvent(event);

if (zh_cn) console.log(JSON.stringify({ post: { zh_cn } }));
else
if (!result) {
throw new Error(
`Unsupported ${event.event_name} event & ${event.action} action`
`Unsupported ${event.event_name} event & ${event.action} action`,
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dead code: The result validation is unnecessary.

The if (!result) check at line 273 is unreachable. The processEvent function either returns a valid result object or throws an error—it never returns a falsy value. This check can be safely removed.

Apply this diff to remove the dead code:

 const event = JSON.parse((await stdin()) || "{}") as GitHubAction;
 const result = processEvent(event);

-if (!result) {
-  throw new Error(
-    `Unsupported ${event.event_name} event & ${event.action} action`,
-  );
-}
-
 const card = {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const event = JSON.parse((await stdin()) || "{}") as GitHubAction;
const zh_cn = processEvent(event);
const result = processEvent(event);
if (zh_cn) console.log(JSON.stringify({ post: { zh_cn } }));
else
if (!result) {
throw new Error(
`Unsupported ${event.event_name} event & ${event.action} action`
`Unsupported ${event.event_name} event & ${event.action} action`,
);
}
const event = JSON.parse((await stdin()) || "{}") as GitHubAction;
const result = processEvent(event);
🤖 Prompt for AI Agents
In .github/scripts/transform-message.ts around lines 270 to 277, the if
(!result) check and its throw are dead code because processEvent either returns
a valid result or throws; remove the entire conditional block (the if statement
and its throw) so the code simply parses the event, calls processEvent, and uses
the returned result without the unreachable validation.

@hu-qi hu-qi changed the title Update to V3 Refactor GitHub event transformation pipeline for Feishu integration Dec 16, 2025
@hu-qi hu-qi merged commit 58f97d1 into Open-Source-Bazaar:master Dec 16, 2025
3 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant