新增了完整的GitHub集成插件,包含认证、会话管理、Webhook处理、消息收发、反应支持等核心功能,支持Issues、Discussions、Pull Request的交互与通知。
158 lines
5.3 KiB
Python
158 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
import httpx
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
GRAPHQL_ENDPOINT = "https://api.github.com/graphql"
|
|
|
|
|
|
class GitHubGraphQLClient:
|
|
def __init__(self, token_getter):
|
|
self._get_token = token_getter
|
|
|
|
async def _execute(self, query: str, variables: dict | None = None) -> dict:
|
|
token = await self._get_token()
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Accept": "application/vnd.github+json",
|
|
}
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as client:
|
|
resp = await client.post(
|
|
GRAPHQL_ENDPOINT,
|
|
json={"query": query, "variables": variables or {}},
|
|
headers=headers,
|
|
)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
|
|
async def get_discussion(self, owner: str, repo: str, number: int) -> dict | None:
|
|
query = """
|
|
query($owner: String!, $repo: String!, $number: Int!) {
|
|
repository(owner: $owner, name: $repo) {
|
|
discussion(number: $number) {
|
|
id
|
|
number
|
|
title
|
|
body
|
|
url
|
|
createdAt
|
|
category { name }
|
|
author { login }
|
|
comments(first: 50) {
|
|
nodes {
|
|
id
|
|
body
|
|
author { login }
|
|
createdAt
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
result = await self._execute(query, {"owner": owner, "repo": repo, "number": number})
|
|
data = result.get("data", {})
|
|
repo_data = data.get("repository", {})
|
|
discussion = repo_data.get("discussion")
|
|
return discussion
|
|
|
|
async def get_discussion_categories(self, owner: str, repo: str) -> list[dict]:
|
|
query = """
|
|
query($owner: String!, $repo: String!) {
|
|
repository(owner: $owner, name: $repo) {
|
|
discussionCategories(first: 50) {
|
|
nodes {
|
|
id
|
|
name
|
|
emoji
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
result = await self._execute(query, {"owner": owner, "repo": repo})
|
|
data = result.get("data", {})
|
|
repo_data = data.get("repository", {})
|
|
cats = repo_data.get("discussionCategories", {})
|
|
return cats.get("nodes", [])
|
|
|
|
async def create_discussion(
|
|
self, repository_id: str, category_id: str, title: str, body: str,
|
|
) -> dict | None:
|
|
mutation = """
|
|
mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
|
|
createDiscussion(input: {
|
|
repositoryId: $repoId,
|
|
categoryId: $categoryId,
|
|
title: $title,
|
|
body: $body
|
|
}) {
|
|
discussion { id number url title }
|
|
}
|
|
}
|
|
"""
|
|
result = await self._execute(mutation, {
|
|
"repoId": repository_id,
|
|
"categoryId": category_id,
|
|
"title": title,
|
|
"body": body,
|
|
})
|
|
if "errors" in result:
|
|
logger.error("GraphQL createDiscussion error: %s", result["errors"])
|
|
return None
|
|
return result.get("data", {}).get("createDiscussion", {}).get("discussion")
|
|
|
|
async def close_discussion(self, discussion_node_id: str, reason: str = "RESOLVED") -> dict | None:
|
|
mutation = """
|
|
mutation($discussionId: ID!, $reason: DiscussionCloseReason!) {
|
|
closeDiscussion(input: { discussionId: $discussionId, reason: $reason }) {
|
|
discussion { id number closed }
|
|
}
|
|
}
|
|
"""
|
|
result = await self._execute(mutation, {
|
|
"discussionId": discussion_node_id,
|
|
"reason": reason,
|
|
})
|
|
if "errors" in result:
|
|
logger.error("GraphQL closeDiscussion error: %s", result["errors"])
|
|
return None
|
|
return result.get("data", {}).get("closeDiscussion", {}).get("discussion")
|
|
|
|
async def update_discussion(
|
|
self, discussion_node_id: str, title: str | None = None, body: str | None = None,
|
|
) -> dict | None:
|
|
mutation = """
|
|
mutation($discussionId: ID!, $title: String, $body: String) {
|
|
updateDiscussion(input: { discussionId: $discussionId, title: $title, body: $body }) {
|
|
discussion { id number title body url }
|
|
}
|
|
}
|
|
"""
|
|
result = await self._execute(mutation, {
|
|
"discussionId": discussion_node_id,
|
|
"title": title,
|
|
"body": body,
|
|
})
|
|
if "errors" in result:
|
|
logger.error("GraphQL updateDiscussion error: %s", result["errors"])
|
|
return None
|
|
return result.get("data", {}).get("updateDiscussion", {}).get("discussion")
|
|
|
|
async def delete_discussion(self, discussion_node_id: str) -> dict:
|
|
mutation = """
|
|
mutation($discussionId: ID!) {
|
|
deleteDiscussion(input: { discussionId: $discussionId }) {
|
|
discussion { id }
|
|
}
|
|
}
|
|
"""
|
|
result = await self._execute(mutation, {"discussionId": discussion_node_id})
|
|
if "errors" in result:
|
|
logger.error("GraphQL deleteDiscussion error: %s", result["errors"])
|
|
return {"success": False, "error": result["errors"][0].get("message", "GraphQL error")}
|
|
return {"success": True}
|