diff --git a/servers/mumu/src/github/comment.ts b/servers/mumu/src/github/comment.ts index 0c1f203..e095e58 100644 --- a/servers/mumu/src/github/comment.ts +++ b/servers/mumu/src/github/comment.ts @@ -1,7 +1,7 @@ import { redisStorage } from "../redis"; import type { SlackNotifier } from "../slack"; import type { SlackThread } from "../types"; -import type { PullRequestReviewComment } from "./schema"; +import type { Comment } from "./schema"; const MAX_CHARS = 200; @@ -14,18 +14,31 @@ const truncateBody = (body: string): string => { const escapeSlackLinkText = (text: string): string => text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); -export const handlePullRequestReviewComment = async ( - payload: PullRequestReviewComment, - slackNotifier: SlackNotifier, -) => { +function buildCommentMessage(payload: Comment): string { + const preview = truncateBody(payload.comment.body); + const safePreview = escapeSlackLinkText(preview); + + return [`> *${payload.comment.user.login}*`, `> <${payload.comment.html_url}|${safePreview}>`].join("\n"); +} + +export const handleComment = async (payload: Comment, slackNotifier: SlackNotifier): Promise => { if (payload.action !== "created") { - return JSON.stringify({ success: false, message: "Review comment action skipped." }); + return JSON.stringify({ success: false, message: "Comment action skipped." }); } const repoFullName = payload.repository.full_name; - const prNumber = payload.pull_request.number; - const cacheKey = `${repoFullName}#${prNumber}`; + let prNumber: number; + + if ("issue" in payload) { + if (!payload.issue.pull_request) { + return JSON.stringify({ success: false, message: "Issue comment (not PR); skipped." }); + } + prNumber = payload.issue.number; + } else { + prNumber = payload.pull_request.number; + } + const cacheKey = `${repoFullName}#${prNumber}`; const thread = await redisStorage.get(cacheKey); if (!thread?.threadTs) { @@ -35,15 +48,14 @@ export const handlePullRequestReviewComment = async ( }); } - const preview = truncateBody(payload.comment.body); - const safePreview = escapeSlackLinkText(preview); - const text = [`> *${payload.comment.user.login}*`, `> <${payload.comment.html_url}|${safePreview}>`].join("\n"); + const text = buildCommentMessage(payload); try { await slackNotifier.createThreadReply(thread.threadTs, text); } catch { - console.error(`${cacheKey}/${thread.channel}: review comment 슬랙 스레드 답변 전송 실패`); + console.error(`${cacheKey}/${thread.channel}: 슬랙 스레드 답변 전송 실패`); + return JSON.stringify({ success: false, message: "Slack thread reply failed" }); } - return JSON.stringify({ success: true, message: "Review comment processed successfully" }); + return JSON.stringify({ success: true, message: "Comment processed successfully" }); }; diff --git a/servers/mumu/src/github/schema.ts b/servers/mumu/src/github/schema.ts index 9d2e2c9..c9a458c 100644 --- a/servers/mumu/src/github/schema.ts +++ b/servers/mumu/src/github/schema.ts @@ -2,6 +2,9 @@ import { z } from "zod"; export type PullRequest = z.infer; export type PullRequestReviewComment = z.infer; +export type IssueComment = z.infer; + +export type Comment = z.infer; export const pullRequestSchema = z.object({ action: z.string(), @@ -44,3 +47,19 @@ export const pullRequestReviewCommentSchema = z.object({ }), repository: z.object({ full_name: z.string() }), }); + +export const issueCommentSchema = z.object({ + action: z.enum(["created", "edited", "deleted"]), + issue: z.object({ + number: z.number(), + pull_request: z.looseObject({}).optional(), + }), + comment: z.object({ + body: z.string(), + html_url: z.string(), + user: z.object({ login: z.string() }), + }), + repository: z.object({ full_name: z.string() }), +}); + +export const commentSchema = z.union([pullRequestReviewCommentSchema, issueCommentSchema]); diff --git a/servers/mumu/src/webhook.ts b/servers/mumu/src/webhook.ts index b4240b1..64f9f67 100644 --- a/servers/mumu/src/webhook.ts +++ b/servers/mumu/src/webhook.ts @@ -1,10 +1,10 @@ import { Router, type Request, type Response } from "express"; import { createSlackNotifier } from "./slack"; import { assertNonNullish } from "./utils/assert"; -import { pullRequestSchema, pullRequestReviewCommentSchema } from "./github/schema"; +import { pullRequestSchema, commentSchema } from "./github/schema"; import { FRONTEND_BOT_CHANNEL } from "./constant"; import { handlePullRequest } from "./github/pull_request"; -import { handlePullRequestReviewComment } from "./github/comment"; +import { handleComment } from "./github/comment"; import { isValidRepository } from "./config"; export function createWebhookRouter(): Router { @@ -37,10 +37,9 @@ export function createWebhookRouter(): Router { handlePullRequest(pullRequestSchema.parse(req.body), slackNotifier).then((res) => console.log(res)); break; } - case "pull_request_review_comment": { - handlePullRequestReviewComment(pullRequestReviewCommentSchema.parse(req.body), slackNotifier).then((res) => - console.log(res), - ); + case "pull_request_review_comment": + case "issue_comment": { + handleComment(commentSchema.parse(req.body), slackNotifier).then((res) => console.log(res)); break; } }