Skip to content

Commit fdb30d2

Browse files
authored
Merge pull request #10 from AptS-1547/feat-polling
ADD: 添加多种⌚️支持
2 parents 7a21d13 + 0030d93 commit fdb30d2

File tree

15 files changed

+605
-67
lines changed

15 files changed

+605
-67
lines changed

NOTICE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This product includes software developed by AptS-1547 (https://github.com/AptS-1
66
This software uses the following third-party libraries that are distributed
77
under their own licenses:
88

9+
- APScheduler (https://github.com/agronholm/apscheduler) - MIT License
910
- FastAPI (https://fastapi.tiangolo.com/) - MIT License
1011
- Pydantic (https://pydantic-docs.helpmanual.io/) - MIT License
1112
- Pydantic-settings (https://github.com/pydantic/pydantic-settings) - MIT License

app/api/github_webhook.py

Lines changed: 249 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
from fastapi import APIRouter, Request, HTTPException
2525

2626
from 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
2829
from app.models.config import get_settings
2930

3031
router = APIRouter()
@@ -33,12 +34,11 @@
3334
config = 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

117189
def 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

Comments
 (0)