ForcePilot/backend/package/yuxi/channel/extensions/github/monitor.py
Kris 702fcd4c33 feat(github): 新增GitHub渠道插件完整实现
新增了完整的GitHub集成插件,包含认证、会话管理、Webhook处理、消息收发、反应支持等核心功能,支持Issues、Discussions、Pull Request的交互与通知。
2026-05-21 10:48:42 +08:00

153 lines
5.9 KiB
Python

from __future__ import annotations
import logging
from datetime import datetime, UTC
from yuxi.channel.extensions.github.types import (
GitHubEventType,
GitHubSessionKind,
GitHubWebhookEvent,
)
from yuxi.channel.message.models import (
GroupContext,
MessageType,
PeerInfo,
UnifiedMessage,
)
from yuxi.channel.routing.models import PeerKind
logger = logging.getLogger(__name__)
class GitHubMonitor:
def event_to_unified_message(self, event: GitHubWebhookEvent) -> UnifiedMessage | None:
if not event.repository:
return None
repo_full_name = event.repository.full_name
sender_id = f"github:{event.sender.login}" if event.sender else ""
sender_name = event.sender.login if event.sender else ""
content = ""
session_kind = GitHubSessionKind.ISSUE
session_number = 0
session_title = ""
msg_id = f"gh:{event.event_id}"
timestamp = datetime.now(UTC)
reply_to_id = None
if event.event_type == GitHubEventType.ISSUE_COMMENT and event.issue and event.comment:
content = event.comment.body
session_kind = GitHubSessionKind.ISSUE
session_number = event.issue.number
session_title = event.issue.title
msg_id = f"gh:comment:{event.comment.id}"
reply_to_id = f"gh:issue:{event.issue.node_id}"
if event.comment.created_at:
try:
timestamp = datetime.fromisoformat(event.comment.created_at.replace("Z", "+00:00"))
except ValueError:
pass
elif event.event_type == GitHubEventType.ISSUES and event.issue:
session_kind = GitHubSessionKind.ISSUE
session_number = event.issue.number
session_title = event.issue.title
msg_id = f"gh:issue:{event.issue.node_id}"
if event.action == "opened":
content = event.issue.body
elif event.action == "closed":
content = f"Issue #{event.issue.number} 已关闭: {event.issue.title}"
elif event.action == "reopened":
content = f"Issue #{event.issue.number} 已重新打开: {event.issue.title}"
elif event.action == "labeled" and event.raw_payload.get("label"):
label_name = event.raw_payload["label"].get("name", "")
content = f"Issue #{event.issue.number} 已添加标签: {label_name}"
elif event.event_type == GitHubEventType.DISCUSSION_COMMENT and event.discussion and event.comment:
content = event.comment.body
session_kind = GitHubSessionKind.DISCUSSION
session_number = event.discussion.number
session_title = event.discussion.title
msg_id = f"gh:discussion_comment:{event.comment.id}"
elif event.event_type == GitHubEventType.PULL_REQUEST_REVIEW and event.pull_request:
content = (event.review.body or "") if event.review else ""
session_kind = GitHubSessionKind.PULL_REQUEST
session_number = event.pull_request.number
session_title = event.pull_request.title
msg_id = f"gh:pr_review:{event.review.id}" if event.review else f"gh:{event.event_id}"
if event.review and event.review.submitted_at:
try:
timestamp = datetime.fromisoformat(event.review.submitted_at.replace("Z", "+00:00"))
except ValueError:
pass
elif event.event_type == GitHubEventType.DISCUSSION and event.discussion:
content = event.discussion.body
session_kind = GitHubSessionKind.DISCUSSION
session_number = event.discussion.number
session_title = event.discussion.title
msg_id = f"gh:discussion:{event.discussion.node_id}"
elif event.event_type == GitHubEventType.PULL_REQUEST and event.pull_request:
content = event.pull_request.body
session_kind = GitHubSessionKind.PULL_REQUEST
session_number = event.pull_request.number
session_title = event.pull_request.title
msg_id = f"gh:pr:{event.pull_request.node_id}"
elif event.event_type == GitHubEventType.PULL_REQUEST_REVIEW_COMMENT and event.comment:
content = event.comment.body
session_kind = GitHubSessionKind.PULL_REQUEST
session_number = 0
session_title = ""
if event.pull_request:
session_number = event.pull_request.number
session_title = event.pull_request.title
msg_id = f"gh:pr_review_comment:{event.comment.id}"
if not content.strip():
return None
sender_info = PeerInfo(
kind=PeerKind.GROUP,
id=sender_id,
display_name=sender_name,
)
group = GroupContext(
id=f"github:{repo_full_name}:{session_kind.value}:{session_number}",
kind="group",
name=session_title,
)
html_url = (
event.issue.html_url if event.issue
else event.pull_request.html_url if event.pull_request
else ""
)
return UnifiedMessage(
msg_id=msg_id,
channel_type="github",
account_id="default",
content=content,
sender=sender_info,
message_type=MessageType.TEXT,
media_urls=[],
group=group,
timestamp=timestamp,
reply_to_id=reply_to_id,
raw_payload=event.raw_payload,
metadata={
"repository": repo_full_name,
"kind": session_kind.value,
"number": session_number,
"title": session_title,
"html_url": html_url,
"review_state": event.review.state if event.review else None,
},
)