1- import hmac
2- import hashlib
31import logging
4- from typing import Optional
5-
6- from fastapi import FastAPI , Request , HTTPException , Header
2+ from fastapi import FastAPI , Request , HTTPException
73
84from settings import Settings
95from send_message import send_github_notification
6+ from hooks .github_webhook import verify_signature , find_matching_webhook , extract_push_data
107
118logging .basicConfig (level = logging .INFO )
129logger = logging .getLogger (__name__ )
1512
1613settings = Settings ().from_yaml ()
1714
18- # 全局变量
19- WS_URL = settings .WS_URL
20- WS_ACCESS_TOKEN = settings .WS_ACCESS_TOKEN
21- GITHUB_WEBHOOK = settings .GITHUB_WEBHOOK
22-
23- def match_repository (repo_name : str , pattern : str ) -> bool :
24- """
25- 检查仓库名是否匹配配置中的模式
26- 支持大小写不敏感匹配和通配符(用户名/*)形式
27-
28- Args:
29- repo_name: 实际的仓库全名 (例如 'user/repo')
30- pattern: 配置中的仓库模式 (例如 'user/repo' 或 'user/*')
31-
32- Returns:
33- bool: 是否匹配
34- """
35- if not repo_name or not pattern :
36- return False
37-
38- repo_name = repo_name .lower ()
39- pattern = pattern .lower ()
40-
41- if pattern .endswith ('/*' ):
42- user = pattern [:- 2 ]
43- return repo_name .startswith (f"{ user } /" )
44-
45- return repo_name == pattern
46-
47- async def verify_signature (request : Request , x_hub_signature_256 : Optional [str ] = Header (None )):
48- """验证 GitHub webhook 签名"""
49-
50- try :
51- body = await request .body ()
52- payload = await request .json ()
53- repo_name = payload .get ("repository" , {}).get ("full_name" )
54- except Exception as e :
55- logger .error ("解析请求体失败: %s" , str (e ))
56- raise HTTPException (status_code = 400 , detail = "Invalid JSON payload" ) from e
57-
58- webhook_secret = None
59- for webhook in settings .GITHUB_WEBHOOK :
60- if any (match_repository (repo_name , repo_pattern ) for repo_pattern in webhook .REPO ):
61- webhook_secret = webhook .SECRET
62- break
63-
64- if not webhook_secret :
65- logger .warning ("仓库 %s 未配置 webhook 密钥,跳过签名验证" , repo_name )
66- return True
67-
68- if not x_hub_signature_256 :
69- raise HTTPException (status_code = 401 , detail = "Missing X-Hub-Signature-256 header" )
70-
71- signature = hmac .new (
72- key = webhook_secret .encode (),
73- msg = body ,
74- digestmod = hashlib .sha256
75- ).hexdigest ()
76-
77- expected_signature = f"sha256={ signature } "
78- if not hmac .compare_digest (expected_signature , x_hub_signature_256 ):
79- raise HTTPException (status_code = 401 , detail = "Invalid signature" )
80-
81- return True
82-
8315@app .post ("/github-webhook" )
8416async def github_webhook (request : Request ):
8517 """处理 GitHub webhook 请求"""
@@ -89,13 +21,20 @@ async def github_webhook(request: Request):
8921 logger .info ("收到非 JSON 格式的请求,忽略" )
9022 return {"status" : "ignored" , "message" : "只处理 application/json 格式的请求" }
9123
92- event_type = request .headers .get ("X-GitHub-Event" )
24+ event_type = request .headers .get ("X-GitHub-Event" , "" )
25+ if not event_type :
26+ logger .info ("缺少 X-GitHub-Event 头,忽略" )
27+ return {"status" : "ignored" , "message" : "缺少 X-GitHub-Event 头" }
9328
9429 try :
95- await verify_signature (request , request .headers .get ("X-Hub-Signature-256" ))
96- except HTTPException :
97- logger .info ("签名验证失败" )
98- return {"status" : "ignored" , "message" : "签名验证失败" }
30+ await verify_signature (
31+ request ,
32+ settings .GITHUB_WEBHOOK ,
33+ request .headers .get ("X-Hub-Signature-256" )
34+ )
35+ except HTTPException as e :
36+ logger .info (f"签名验证失败: { e .detail } " )
37+ return {"status" : "ignored" , "message" : f"签名验证失败: { e .detail } " }
9938
10039 payload = await request .json ()
10140 if not payload :
@@ -105,41 +44,38 @@ async def github_webhook(request: Request):
10544 repo_name = payload .get ("repository" , {}).get ("full_name" )
10645 branch = payload .get ("ref" , "" ).replace ("refs/heads/" , "" )
10746
108- matched_webhook = None
109- for webhook in settings .GITHUB_WEBHOOK :
110-
111- repo_matches = any (match_repository (repo_name , repo_pattern ) for repo_pattern in webhook .REPO )
112-
113- if (repo_matches and
114- branch in webhook .BRANCH and
115- event_type in webhook .EVENTS ):
116- matched_webhook = webhook
117- break
47+ matched_webhook = find_matching_webhook (
48+ repo_name ,
49+ branch ,
50+ event_type ,
51+ settings .GITHUB_WEBHOOK
52+ )
11853
11954 if not matched_webhook :
12055 logger .info ("找不到匹配的 webhook 配置: 仓库 %s, 分支 %s, 事件类型 %s" , repo_name , branch , event_type )
12156 return {"status" : "ignored" , "message" : "找不到匹配的 webhook 配置" }
12257
12358 # 处理不同类型的事件,暂时只支持 push 事件
12459 if event_type == "push" :
125- pusher = payload .get ("pusher" , {}).get ("name" )
126- commits = payload .get ("commits" , [])
127- commit_count = len (commits )
60+ push_data = extract_push_data (payload )
12861
129- logger .info ("发现新的 push 事件,来自 %s 仓库" , repo_name )
130- logger .info ("分支: %s,推送者: %s,提交数量: %s" , branch , pusher , commit_count )
62+ logger .info ("发现新的 push 事件,来自 %s 仓库" , push_data ["repo_name" ])
63+ logger .info ("分支: %s,推送者: %s,提交数量: %s" ,
64+ push_data ["branch" ],
65+ push_data ["pusher" ],
66+ push_data ["commit_count" ])
13167
13268 # 向配置的所有 OneBot 目标发送通知
13369 for target in matched_webhook .ONEBOT :
13470 logger .info ("正在发送消息到 QQ 群 %s" , target .id )
13571 await send_github_notification (
13672 ws_url = settings .WS_URL ,
13773 access_token = settings .WS_ACCESS_TOKEN ,
138- repo_name = repo_name ,
139- branch = branch ,
140- pusher = pusher ,
141- commit_count = commit_count ,
142- commits = commits ,
74+ repo_name = push_data [ " repo_name" ] ,
75+ branch = push_data [ " branch" ] ,
76+ pusher = push_data [ " pusher" ] ,
77+ commit_count = push_data [ " commit_count" ] ,
78+ commits = push_data [ " commits" ] ,
14379 onebot_type = target .type ,
14480 onebot_id = target .id
14581 )
0 commit comments