Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
GITHUB_API = "https://api.github.com/graphql"
REST_API = "https://api.github.com"

# 时间戳验证阈值(秒)
EVENT_TIME_THRESHOLD_SECONDS = 60 # 触发节点创建时间比通知更新时间早超过此阈值时,视为旧事件

# 双 Token 架构
BOT_TOKEN = os.getenv("BOT_TOKEN") # 机器人Token:仅用于读取通知和标记已读
GQL_TOKEN = os.getenv("GQL_TOKEN") # 个人PAT:用于GraphQL查询和触发Workflow
Expand Down Expand Up @@ -745,6 +748,23 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict):
pass
return

# 检查通知类型 - 只处理 "Issue", "PullRequest", "Commit", "Discussion"
# GitHub 通知的 subject.type 可以是多种类型,我们只处理这几种核心类型。
# 其它事件(如 issue 关闭)也可能触发 mention 通知,需要后续通过时间戳进一步过滤。
subject_type = note.get("subject", {}).get("type")
logger.info(f"Notification subject type: {subject_type}")

if subject_type and subject_type not in ["Issue", "PullRequest", "Commit", "Discussion"]:
logger.info(f"Ignoring notification with unsupported subject type: {subject_type}")
try:
await client.patch(
f"{REST_API}/notifications/threads/{thread_id}",
headers={"Authorization": f"token {BOT_TOKEN}"}
)
except Exception as e:
logger.warning(f"Failed to mark notification {thread_id} as read: {e}")
return

# 1. 获取资源详情
resource_data = await fetch_resource_details(client, raw_url)
if not resource_data:
Expand Down Expand Up @@ -882,6 +902,37 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict):
pass
return

# 5. 验证触发节点的时间戳 - 防止处理旧事件
# 检查触发节点的创建时间是否在通知的更新时间之前(说明是旧的 @mention)
# 这可以防止 issue 关闭等事件触发 workflow(这些事件会发送通知,但没有新的 @mention)
notification_updated_at = note.get("updated_at")
if notification_updated_at and trigger_node.created_at:
try:
# 解析时间戳(ISO 8601 格式)
note_time = datetime.fromisoformat(notification_updated_at.replace("Z", "+00:00"))
trigger_time = datetime.fromisoformat(trigger_node.created_at.replace("Z", "+00:00"))

# 如果触发节点的创建时间比通知更新时间早很多(超过阈值)
# 说明这不是新的 @mention,而是旧事件的后续通知(如 issue 关闭)
time_diff = (note_time - trigger_time).total_seconds()
if time_diff > EVENT_TIME_THRESHOLD_SECONDS:
logger.warning(f"Trigger node is too old (created {time_diff:.0f}s before notification update). This might be an event notification (e.g., issue closed) rather than a new @mention.")
logger.warning(f"Notification updated_at: {notification_updated_at}, Trigger created_at: {trigger_node.created_at}")
# 标记为已读但不触发 workflow
try:
await client.patch(
f"{REST_API}/notifications/threads/{thread_id}",
headers={"Authorization": f"token {BOT_TOKEN}"}
)
except Exception as e:
logger.warning(f"Failed to mark notification {thread_id} as read: {e}")
return
else:
logger.info(f"Trigger node time check passed (time diff: {time_diff:.0f}s)")
except Exception as e:
logger.warning(f"Could not parse timestamps for validation: {e}")
# 继续处理,但记录警告

# 验证触发消息是否存在
if not trigger_node.body or not trigger_node.body.strip():
logger.error(f"Trigger node {trigger_node.id} has empty body. Cannot proceed with workflow.")
Expand All @@ -899,10 +950,10 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict):
pass
return

# 5. 构建完整上下文(使用修复后的build_rich_context)
# 6. 构建完整上下文(使用修复后的build_rich_context)
context = build_rich_context(resource_data, timeline_items, trigger_node, raw_url, thread_id)

# 6. 获取diff内容(根据触发类型决定)
# 7. 获取diff内容(根据触发类型决定)
if resource_data["__typename"] in ["PullRequest", "Commit"]:
# 如果是review或review_comment触发,不获取完整PR diff(使用review comments的diff_hunk)
if trigger_node and trigger_node.type in ["review", "review_comment"]:
Expand Down