2424from fastapi import APIRouter , Request , HTTPException
2525
2626from app .core import GitHubWebhookHandler
27- from app .onebot import text , get_onebot_client
27+ from app .botclient import BotClient
28+ from app .models import MessageSegment
2829from app .models .config import get_settings
2930
3031router = APIRouter ()
3334config = get_settings ()
3435
3536@router .post ("" )
36- async def github_webhook (
37- request : Request ,
38- ): # pylint: disable=too-many-return-statements
37+ async def github_webhook (request : Request ):
3938 """处理 GitHub webhook 请求"""
39+ # TODO: 为了后期使用自定义模版,作为临时过渡,需要完全重构
4040
41- onebot_client = get_onebot_client ( )
41+ onebot_client = BotClient . get_client ( "onebot" )
4242
4343 if not onebot_client :
4444 logger .error ("OneBot 客户端未初始化,无法处理请求" )
@@ -54,6 +54,11 @@ async def github_webhook(
5454 logger .info ("缺少 X-GitHub-Event 头,忽略" )
5555 return {"status" : "ignored" , "message" : "缺少 X-GitHub-Event 头" }
5656
57+ payload = await request .json ()
58+ if not payload :
59+ logger .info ("请求体为空,忽略" )
60+ return {"status" : "ignored" , "message" : "请求体为空" }
61+
5762 try :
5863 await GitHubWebhookHandler .verify_signature (
5964 request ,
@@ -64,26 +69,27 @@ async def github_webhook(
6469 logger .info ("签名验证失败: %s" , e .detail )
6570 return {"status" : "ignored" , "message" : f"签名验证失败: { e .detail } " }
6671
67- payload = await request .json ()
68- if not payload :
69- logger .info ("请求体为空,忽略" )
70- return {"status" : "ignored" , "message" : "请求体为空" }
71-
7272 repo_name = payload .get ("repository" , {}).get ("full_name" )
73- branch = payload .get ("ref" , "" ).replace ("refs/heads/" , "" )
73+
74+ # 根据事件类型获取分支/PR/Issue信息用于匹配
75+ match_info = {}
76+ if event_type == "push" :
77+ match_info ["branch" ] = payload .get ("ref" , "" ).replace ("refs/heads/" , "" )
7478
7579 matched_webhook = GitHubWebhookHandler .find_matching_webhook (
7680 repo_name ,
77- branch ,
81+ match_info . get ( " branch" , "" ) ,
7882 event_type ,
7983 config .GITHUB_WEBHOOK
8084 )
8185
8286 if not matched_webhook :
83- logger .info ("找不到匹配的 webhook 配置: 仓库 %s, 分支 %s, 事件类型 %s" , repo_name , branch , event_type )
87+ logger .info ("找不到匹配的 webhook 配置: 仓库 %s, 事件类型 %s" , repo_name , event_type )
8488 return {"status" : "ignored" , "message" : "找不到匹配的 webhook 配置" }
8589
86- # 处理不同类型的事件,暂时只支持 push 事件
90+ message = None
91+
92+ # 处理不同类型的事件
8793 if event_type == "push" :
8894 push_data = GitHubWebhookHandler .extract_push_data (payload )
8995
@@ -93,37 +99,103 @@ async def github_webhook(
9399 push_data ["pusher" ],
94100 push_data ["commit_count" ])
95101
102+ message = format_github_push_message (
103+ repo_name = push_data ["repo_name" ],
104+ branch = push_data ["branch" ],
105+ pusher = push_data ["pusher" ],
106+ commit_count = push_data ["commit_count" ],
107+ commits = push_data ["commits" ]
108+ )
109+
110+ elif event_type == "pull_request" :
111+ pr_data = GitHubWebhookHandler .extract_pull_request_data (payload )
112+
113+ logger .info ("发现新的 pull_request 事件,来自 %s 仓库" , pr_data ["repo_name" ])
114+ logger .info ("PR #%s, 操作: %s, 用户: %s" ,
115+ pr_data ["pull_request_number" ],
116+ pr_data ["action" ],
117+ pr_data ["user" ])
118+
119+ message = format_github_pull_request_message (
120+ repo_name = pr_data ["repo_name" ],
121+ action = pr_data ["action" ],
122+ pull_request = pr_data ["pull_request" ],
123+ user = pr_data ["user" ]
124+ )
125+
126+ elif event_type == "issues" :
127+ issue_data = GitHubWebhookHandler .extract_issue_data (payload )
128+
129+ logger .info ("发现新的 issue 事件,来自 %s 仓库" , issue_data ["repo_name" ])
130+ logger .info ("Issue #%s, 操作: %s, 用户: %s" ,
131+ issue_data ["issue_number" ],
132+ issue_data ["action" ],
133+ issue_data ["user" ])
134+
135+ message = format_github_issue_message (
136+ repo_name = issue_data ["repo_name" ],
137+ action = issue_data ["action" ],
138+ issue = issue_data ["issue" ],
139+ user = issue_data ["user" ]
140+ )
141+
142+ elif event_type == "release" :
143+ release_data = GitHubWebhookHandler .extract_release_data (payload )
144+
145+ logger .info ("发现新的 release 事件,来自 %s 仓库" , release_data ["repo_name" ])
146+ logger .info ("版本: %s, 操作: %s, 用户: %s" ,
147+ release_data ["release_tag" ],
148+ release_data ["action" ],
149+ release_data ["user" ])
150+
151+ message = format_github_release_message (
152+ repo_name = release_data ["repo_name" ],
153+ action = release_data ["action" ],
154+ release = release_data ["release" ],
155+ user = release_data ["user" ]
156+ )
157+
158+ elif event_type == "issue_comment" :
159+ comment_data = GitHubWebhookHandler .extract_issue_comment_data (payload )
160+
161+ logger .info ("发现新的 issue_comment 事件,来自 %s 仓库" , comment_data ["repo_name" ])
162+ logger .info ("Issue #%s, 操作: %s, 用户: %s" ,
163+ comment_data ["issue_number" ],
164+ comment_data ["action" ],
165+ comment_data ["user" ])
166+
167+ message = format_github_issue_comment_message (
168+ repo_name = comment_data ["repo_name" ],
169+ action = comment_data ["action" ],
170+ comment = comment_data ["comment" ],
171+ issue_number = comment_data ["issue_number" ],
172+ user = comment_data ["user" ]
173+ )
174+ else :
175+ logger .info ("收到 %s 事件,但尚未实现处理逻辑" , event_type )
176+ return {"status" : "ignored" , "message" : f"暂不处理 { event_type } 类型的事件" }
177+
178+ if message :
96179 # 向配置的所有 OneBot 目标发送通知
97180 for target in matched_webhook .ONEBOT :
98181 logger .info ("正在发送消息到 QQ %s %s" , target .type , target .id )
99-
100- message = format_github_push_message (
101- repo_name = push_data ["repo_name" ],
102- branch = push_data ["branch" ],
103- pusher = push_data ["pusher" ],
104- commit_count = push_data ["commit_count" ],
105- commits = push_data ["commits" ]
106- )
107-
108- # 使用已有的客户端发送消息
109182 await onebot_client .send_message (target .type , target .id , message )
110183
111- return {"status" : "success" , "message" : "处理 push 事件成功" }
184+ return {"status" : "success" , "message" : f "处理 { event_type } 事件成功" }
112185
113- logger .info ("收到 %s 事件,但尚未实现处理逻辑" , event_type )
114- return {"status" : "ignored" , "message" : f"暂不处理 { event_type } 类型的事件" }
186+ return {"status" : "failed" , "message" : "处理事件时发生错误" }
115187
116188
117189def format_github_push_message (repo_name , branch , pusher , commit_count , commits ):
118190 """格式化 GitHub 推送消息"""
119191
120192 message = [
121- text ("📢 GitHub 推送通知\n " ),
122- text (f"仓库:{ repo_name } \n " ),
123- text (f"分支:{ branch } \n " ),
124- text (f"推送者:{ pusher } \n " ),
125- text (f"提交数量:{ commit_count } \n \n " ),
126- text ("最新提交:\n " )
193+ MessageSegment . text ("📢 GitHub 推送通知\n " ),
194+ MessageSegment . text (f"仓库:{ repo_name } \n " ),
195+ MessageSegment . text (f"分支:{ branch } \n " ),
196+ MessageSegment . text (f"推送者:{ pusher } \n " ),
197+ MessageSegment . text (f"提交数量:{ commit_count } \n \n " ),
198+ MessageSegment . text ("最新提交:\n " )
127199 ]
128200
129201 # 最多展示3条最新提交
@@ -132,6 +204,149 @@ def format_github_push_message(repo_name, branch, pusher, commit_count, commits)
132204 commit_message = commit ["message" ].split ("\n " )[0 ] # 只取第一行
133205 author = commit .get ("author" , {}).get ("name" , "未知" )
134206
135- message .append (text (f"[{ short_id } ] { commit_message } (by { author } )\n " ))
207+ message .append (MessageSegment .text (f"[{ short_id } ] { commit_message } (by { author } )\n " ))
208+
209+ return message
210+
211+ def format_github_pull_request_message (repo_name , action , pull_request , user ):
212+ """格式化 GitHub Pull Request 消息"""
213+
214+ # 针对不同动作定制消息内容
215+ action_text = {
216+ "opened" : "创建了" ,
217+ "closed" : "关闭了" if not pull_request .get ("merged" ) else "合并了" ,
218+ "reopened" : "重新打开了" ,
219+ "assigned" : "被分配了" ,
220+ "unassigned" : "被取消分配了" ,
221+ "review_requested" : "请求审核" ,
222+ "review_request_removed" : "取消审核请求" ,
223+ "labeled" : "被添加了标签" ,
224+ "unlabeled" : "被移除了标签" ,
225+ "synchronize" : "同步了" ,
226+ }.get (action , action )
227+
228+ message = [
229+ MessageSegment .text (f"📢 GitHub Pull Request { action_text } \n " ),
230+ MessageSegment .text (f"仓库:{ repo_name } \n " ),
231+ MessageSegment .text (f"PR #{ pull_request .get ('number' )} : { pull_request .get ('title' )} \n " ),
232+ MessageSegment .text (f"用户:{ user } \n " ),
233+ MessageSegment .text (f"状态:{ pull_request .get ('state' )} \n " )
234+ ]
235+
236+ # 添加分支信息
237+ base = pull_request .get ("base" , {}).get ("ref" , "" )
238+ head = pull_request .get ("head" , {}).get ("ref" , "" )
239+ if base and head :
240+ message .append (MessageSegment .text (f"目标分支:{ base } ← { head } \n " ))
241+
242+ # 添加链接
243+ if pull_request .get ("html_url" ):
244+ message .append (MessageSegment .text (f"链接:{ pull_request .get ('html_url' )} \n " ))
245+
246+ return message
247+
248+ def format_github_issue_message (repo_name , action , issue , user ):
249+ """格式化 GitHub Issue 消息"""
250+
251+ # 针对不同动作定制消息内容
252+ action_text = {
253+ "opened" : "创建了" ,
254+ "closed" : "关闭了" ,
255+ "reopened" : "重新打开了" ,
256+ "assigned" : "被分配了" ,
257+ "unassigned" : "被取消分配了" ,
258+ "labeled" : "被添加了标签" ,
259+ "unlabeled" : "被移除了标签" ,
260+ }.get (action , action )
261+
262+ message = [
263+ MessageSegment .text (f"📢 GitHub Issue { action_text } \n " ),
264+ MessageSegment .text (f"仓库:{ repo_name } \n " ),
265+ MessageSegment .text (f"Issue #{ issue .get ('number' )} : { issue .get ('title' )} \n " ),
266+ MessageSegment .text (f"用户:{ user } \n " ),
267+ MessageSegment .text (f"状态:{ issue .get ('state' )} \n " )
268+ ]
269+
270+ # 添加标签信息
271+ labels = issue .get ("labels" , [])
272+ if labels :
273+ label_names = [label .get ("name" , "" ) for label in labels ]
274+ message .append (MessageSegment .text (f"标签:{ ', ' .join (label_names )} \n " ))
275+
276+ # 添加链接
277+ if issue .get ("html_url" ):
278+ message .append (MessageSegment .text (f"链接:{ issue .get ('html_url' )} \n " ))
279+
280+ return message
281+
282+ def format_github_release_message (repo_name , action , release , user ):
283+ """格式化 GitHub Release 消息"""
284+
285+ # 针对不同动作定制消息内容
286+ action_text = {
287+ "published" : "发布了" ,
288+ "created" : "创建了" ,
289+ "edited" : "编辑了" ,
290+ "deleted" : "删除了" ,
291+ "prereleased" : "预发布了" ,
292+ "released" : "正式发布了" ,
293+ }.get (action , action )
294+
295+ tag_name = release .get ("tag_name" , "" )
296+ name = release .get ("name" , tag_name ) if release .get ("name" ) else tag_name
297+
298+ message = [
299+ MessageSegment .text (f"📢 GitHub Release { action_text } \n " ),
300+ MessageSegment .text (f"仓库:{ repo_name } \n " ),
301+ MessageSegment .text (f"版本:{ name } ({ tag_name } )\n " ),
302+ MessageSegment .text (f"发布者:{ user } \n " )
303+ ]
304+
305+ # 添加预发布信息
306+ if release .get ("prerelease" ):
307+ message .append (MessageSegment .text ("类型:预发布\n " ))
308+
309+ # 添加发布时间
310+ if release .get ("published_at" ):
311+ published_time = release .get ("published_at" )
312+ message .append (MessageSegment .text (f"发布时间:{ published_time } \n " ))
313+
314+ # 添加链接
315+ if release .get ("html_url" ):
316+ message .append (MessageSegment .text (f"链接:{ release .get ('html_url' )} \n " ))
317+
318+ return message
319+
320+ def format_github_issue_comment_message (repo_name , action , comment , issue_number , user ):
321+ """格式化 GitHub Issue/PR 评论消息"""
322+
323+ # 针对不同动作定制消息内容
324+ action_text = {
325+ "created" : "发表了" ,
326+ "edited" : "编辑了" ,
327+ "deleted" : "删除了" ,
328+ }.get (action , action )
329+
330+ # 判断是PR还是Issue
331+ issue_type = "PR" if comment .get ("pull_request" ) else "Issue"
332+
333+ message = [
334+ MessageSegment .text (f"📢 GitHub { issue_type } 评论 { action_text } \n " ),
335+ MessageSegment .text (f"仓库:{ repo_name } \n " ),
336+ MessageSegment .text (f"{ issue_type } #{ issue_number } \n " ),
337+ MessageSegment .text (f"用户:{ user } \n " )
338+ ]
339+
340+ # 添加评论内容预览
341+ body = comment .get ("body" , "" )
342+ if body and action != "deleted" :
343+ # 截取评论内容,最多显示100个字符
344+ preview = body [:100 ] + "..." if len (body ) > 100 else body
345+ preview = preview .replace ("\n " , " " )
346+ message .append (MessageSegment .text (f"内容:{ preview } \n " ))
347+
348+ # 添加链接
349+ if comment .get ("html_url" ):
350+ message .append (MessageSegment .text (f"链接:{ comment .get ('html_url' )} \n " ))
136351
137352 return message
0 commit comments