"""Gitea API客户端,用于与Gitea服务器通信""" import requests import json import logging from typing import Optional, Dict, Any, List logger = logging.getLogger(__name__) class GiteaClient: """Gitea API客户端类""" def __init__(self, server_url: str, access_token: str): """初始化Gitea客户端 Args: server_url: Gitea服务器URL access_token: Gitea访问令牌 """ self.server_url = server_url.rstrip('/') self.access_token = access_token self.api_url = f"{self.server_url}/api/v1" self.headers = { "Authorization": f"token {self.access_token}", "Content-Type": "application/json" } def _request(self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]: """发送API请求 Args: method: HTTP方法(GET, POST, PUT, DELETE) endpoint: API端点 data: 请求数据 Returns: API响应数据 """ url = f"{self.api_url}/{endpoint}" try: if method == "GET": response = requests.get(url, headers=self.headers, params=data) elif method == "POST": response = requests.post(url, headers=self.headers, json=data) elif method == "PUT": response = requests.put(url, headers=self.headers, json=data) elif method == "PATCH": response = requests.patch(url, headers=self.headers, json=data) elif method == "DELETE": response = requests.delete(url, headers=self.headers) else: raise ValueError(f"Unsupported HTTP method: {method}") response.raise_for_status() if response.content: return response.json() return None except requests.RequestException as e: logger.error(f"Gitea API request failed: {str(e)}") return None def create_repository(self, owner: str, name: str, description: str = "", private: bool = False) -> Optional[Dict[str, Any]]: """创建仓库 Args: owner: 所有者(用户或组织) name: 仓库名称 description: 仓库描述 private: 是否私有 Returns: 创建的仓库信息 """ data = { "name": name, "description": description, "private": private, "auto_init": True } # 优先尝试在指定的 owner(组织)下创建仓库 logger.info(f"Attempting to create repository for owner: {owner}") owner_response = self._request("POST", f"org/{owner}/repos", data) if owner_response: logger.info(f"Repository created successfully under owner: {owner}") return owner_response # 如果组织创建失败,尝试在用户下创建仓库 logger.info(f"Organization creation failed, trying user account") user_response = self._request("POST", f"user/repos", data) if user_response: logger.info(f"Repository created successfully in user account") return user_response logger.error(f"Failed to create repository for owner {owner}") return None def get_repository(self, owner: str, repo: str) -> Optional[Dict[str, Any]]: """获取仓库信息 Args: owner: 所有者(用户或组织) repo: 仓库名称 Returns: 仓库信息 """ return self._request("GET", f"repos/{owner}/{repo}") def list_repositories(self, owner: str) -> Optional[List[Dict[str, Any]]]: """列出用户或组织的仓库 Args: owner: 所有者(用户或组织) Returns: 仓库列表 """ return self._request("GET", f"users/{owner}/repos") def delete_repository(self, owner: str, repo: str) -> bool: """删除仓库 Args: owner: 所有者(用户或组织) repo: 仓库名称 Returns: 是否删除成功 """ response = self._request("DELETE", f"repos/{owner}/{repo}") return response is not None def create_file(self, owner: str, repo: str, path: str, content: str, message: str) -> Optional[Dict[str, Any]]: """创建或更新文件 Args: owner: 所有者(用户或组织) repo: 仓库名称 path: 文件路径 content: 文件内容(base64编码) message: 提交消息 Returns: 操作结果 """ data = { "content": content, "message": message } return self._request("POST", f"repos/{owner}/{repo}/contents/{path}", data) def get_file(self, owner: str, repo: str, path: str) -> Optional[Dict[str, Any]]: """获取文件内容 Args: owner: 所有者(用户或组织) repo: 仓库名称 path: 文件路径 Returns: 文件信息和内容 """ return self._request("GET", f"repos/{owner}/{repo}/contents/{path}") def get_repository_files(self, owner: str, repo: str, path: str = "") -> Optional[List[Dict[str, Any]]]: """获取仓库文件列表 Args: owner: 所有者(用户或组织) repo: 仓库名称 path: 目录路径(默认为根目录) Returns: 文件列表 """ result = self._request("GET", f"repos/{owner}/{repo}/contents/{path}") return result if isinstance(result, list) else None def check_connection(self) -> bool: """检查与Gitea服务器的连接 Returns: 是否连接成功 """ response = self._request("GET", "user") return response is not None def update_repository(self, owner: str, repo: str, name: Optional[str] = None, description: Optional[str] = None, private: Optional[bool] = None) -> Optional[Dict[str, Any]]: """更新仓库信息 Args: owner: 所有者(用户或组织) repo: 仓库名称 name: 新的仓库名称(可选) description: 新的仓库描述(可选) private: 是否私有(可选) Returns: 更新后的仓库信息 """ data = {} if name is not None: data["name"] = name if description is not None: data["description"] = description if private is not None: data["private"] = private return self._request("PATCH", f"repos/{owner}/{repo}", data)