This commit is contained in:
th committed 2026-04-10 11:46:15 +02:00
1 parent cc97754ae6
commit 86508ffcca
13 files changed
+945 -35

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
<?php
/**
* Minimal Gitea v1 REST client for the hyprcrd gitea plugin.
*
* Mirrors the Python `gitea_client.py` shape but only implements the
* subset needed for MVP: connection test, search/list/get issues, list
* comments. Write operations (create issue, comment, edit) are deferred
* to a later iteration — see TODO.md.
*
* Authentication is via the standard Gitea token header:
* Authorization: token <TOKEN>
*
* Errors throw GiteaApiException with status code + body.
*/
class GiteaApiException extends RuntimeException
{
public int $statusCode;
public string $body;
public function __construct(int $statusCode, string $body)
{
$this->statusCode = $statusCode;
$this->body = $body;
parent::__construct("Gitea API HTTP $statusCode: " . substr($body, 0, 200));
}
}
class GiteaClient
{
private string $baseUrl;
private string $token;
private int $timeout;
public function __construct(string $baseUrl, string $token, int $timeout = 20)
{
$this->baseUrl = rtrim($baseUrl, '/');
$this->token = $token;
$this->timeout = $timeout;
}
/**
* Verify the token works against the configured base URL by hitting
* /user. Returns the authenticated user dict on success.
* Throws GiteaApiException on failure.
*/
public function testConnection(): array
{
$resp = $this->request('GET', '/user');
return is_array($resp) ? $resp : [];
}
/**
* Cross-repo issue search. Mirrors Python search_issues().
* @param array $opts Any of: state, labels, milestones, q, type,
* since, before, assigned, created, mentioned,
* review_requested, reviewed, owner, team,
* page, limit
*/
public function searchIssues(array $opts = []): array
{
$resp = $this->request('GET', '/repos/issues/search', $opts);
return is_array($resp) ? $resp : [];
}
public function listRepoIssues(string $owner, string $repo, array $opts = []): array
{
$path = '/repos/' . rawurlencode($owner) . '/' . rawurlencode($repo) . '/issues';
$resp = $this->request('GET', $path, $opts);
return is_array($resp) ? $resp : [];
}
public function getIssue(string $owner, string $repo, int $index): array
{
$path = '/repos/' . rawurlencode($owner) . '/' . rawurlencode($repo) . '/issues/' . $index;
$resp = $this->request('GET', $path);
return is_array($resp) ? $resp : [];
}
public function listIssueComments(string $owner, string $repo, int $index, array $opts = []): array
{
$path = '/repos/' . rawurlencode($owner) . '/' . rawurlencode($repo) . '/issues/' . $index . '/comments';
$resp = $this->request('GET', $path, $opts);
return is_array($resp) ? $resp : [];
}
// ── Low-level request ─────────────────────────────────────────
private function request(string $method, string $path, array $query = [], ?array $payload = null)
{
$url = $this->baseUrl . '/api/v1' . $path;
$cleanQuery = [];
foreach ($query as $k => $v) {
if ($v === null || $v === '') continue;
if (is_bool($v)) $v = $v ? 'true' : 'false';
$cleanQuery[$k] = $v;
}
if ($cleanQuery) {
$url .= '?' . http_build_query($cleanQuery);
}
$ch = curl_init($url);
$headers = [
'Accept: application/json',
'Authorization: token ' . $this->token,
'User-Agent: hyprcrd-gitea-plugin/1.0',
];
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
];
if ($payload !== null) {
$headers[] = 'Content-Type: application/json';
$opts[CURLOPT_POSTFIELDS] = json_encode($payload);
}
$opts[CURLOPT_HTTPHEADER] = $headers;
curl_setopt_array($ch, $opts);
$body = curl_exec($ch);
if ($body === false) {
$err = curl_error($ch);
curl_close($ch);
throw new RuntimeException("Could not reach Gitea: $err");
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? '';
curl_close($ch);
if ($status >= 400) {
throw new GiteaApiException((int)$status, (string)$body);
}
if ($body === '' || $body === null) return null;
if (stripos($contentType, 'application/json') !== false || preg_match('/^\s*[\[\{]/', (string)$body)) {
$decoded = json_decode((string)$body, true);
return $decoded;
}
return $body;
}
}