164 lines
5.5 KiB
PHP
164 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Domain\Manga\Infrastructure\Client;
|
|
|
|
use App\Domain\Manga\Domain\Contract\Client\MangadexClientInterface;
|
|
use App\Domain\Manga\Domain\Exception\MangadexApiException;
|
|
use App\Domain\Manga\Domain\Exception\MangadexAuthenticationException;
|
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
|
|
class MangadexClient implements MangadexClientInterface
|
|
{
|
|
private const API_URL = 'https://api.mangadex.org';
|
|
private const AUTH_URL = 'https://auth.mangadex.org/realms/mangadex/protocol/openid-connect/token';
|
|
private const EXCLUDED_TAGS = ['0234a31e-a729-4e28-9d6a-3f87c4966b9e' , 'b13b2a48-c720-44a9-9c77-39c9979373fb', '891cf039-b895-47f0-9229-bef4c96eccd4'];
|
|
|
|
private ?string $accessToken = null;
|
|
private ?string $refreshToken = null;
|
|
|
|
public function __construct(
|
|
private HttpClientInterface $client,
|
|
private string $clientId,
|
|
private string $clientSecret,
|
|
private string $username,
|
|
private string $password
|
|
) {}
|
|
|
|
public function authenticate(): void
|
|
{
|
|
try {
|
|
$response = $this->client->request('POST', self::AUTH_URL, [
|
|
'body' => [
|
|
'grant_type' => 'password',
|
|
'username' => $this->username,
|
|
'password' => $this->password,
|
|
'client_id' => $this->clientId,
|
|
'client_secret' => $this->clientSecret,
|
|
]
|
|
]);
|
|
|
|
$data = $response->toArray();
|
|
|
|
if (!isset($data['access_token'], $data['refresh_token'])) {
|
|
throw new MangadexAuthenticationException('Invalid authentication response from Mangadex');
|
|
}
|
|
|
|
$this->accessToken = $data['access_token'];
|
|
$this->refreshToken = $data['refresh_token'];
|
|
} catch (\Exception $e) {
|
|
throw new MangadexAuthenticationException(
|
|
'Failed to authenticate with Mangadex: ' . $e->getMessage(),
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
|
|
public function refreshToken(): void
|
|
{
|
|
if (!$this->refreshToken) {
|
|
throw new MangadexAuthenticationException('No refresh token available');
|
|
}
|
|
|
|
try {
|
|
$response = $this->client->request('POST', self::AUTH_URL, [
|
|
'body' => [
|
|
'grant_type' => 'refresh_token',
|
|
'refresh_token' => $this->refreshToken,
|
|
'client_id' => $this->clientId,
|
|
'client_secret' => $this->clientSecret,
|
|
]
|
|
]);
|
|
|
|
$data = $response->toArray();
|
|
|
|
if (!isset($data['access_token'])) {
|
|
throw new MangadexAuthenticationException('Invalid refresh token response from Mangadex');
|
|
}
|
|
|
|
$this->accessToken = $data['access_token'];
|
|
$this->refreshToken = $data['refresh_token'] ?? $this->refreshToken;
|
|
} catch (\Exception $e) {
|
|
throw new MangadexAuthenticationException(
|
|
'Failed to refresh token: ' . $e->getMessage(),
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
|
|
public function searchManga(string $title): array
|
|
{
|
|
return $this->get('/manga', [
|
|
'title' => $title,
|
|
'contentRating' => ['safe', 'suggestive', 'erotica'],
|
|
'excludedTags' => self::EXCLUDED_TAGS,
|
|
'includes' => ['cover_art', 'author'],
|
|
'limit' => 50,
|
|
]);
|
|
}
|
|
|
|
public function getMangaRatings(array $mangaIds): array
|
|
{
|
|
return $this->get('/statistics/manga', [
|
|
'manga' => $mangaIds,
|
|
]);
|
|
}
|
|
|
|
public function getMangaFeed(string $mangaId, int $offset = 0, int $limit = 500, string $order = 'asc'): array
|
|
{
|
|
return $this->get('/manga/' . $mangaId . '/feed', [
|
|
'limit' => $limit,
|
|
// 'translatedLanguage' => ['en'],
|
|
'includeUnavailable' => 1,
|
|
'order' => ['chapter' => $order],
|
|
'offset' => $offset,
|
|
]);
|
|
}
|
|
|
|
public function getMangaAggregate(string $mangaId): array
|
|
{
|
|
return $this->get('/manga/' . $mangaId . '/aggregate');
|
|
}
|
|
|
|
public function getManga(string $mangaId): array
|
|
{
|
|
return $this->get('/manga/' . $mangaId, [
|
|
'includes' => ['cover_art', 'author']
|
|
]);
|
|
}
|
|
|
|
private function get(string $endpoint, array $params = []): array
|
|
{
|
|
try {
|
|
if (!$this->accessToken) {
|
|
$this->authenticate();
|
|
}
|
|
|
|
$response = $this->client->request('GET', self::API_URL . $endpoint, [
|
|
'query' => $params,
|
|
'headers' => [
|
|
'Authorization' => 'Bearer ' . $this->accessToken
|
|
]
|
|
]);
|
|
|
|
// Handle 401 (Unauthorized) by refreshing the token and retrying
|
|
if ($response->getStatusCode() === 401) {
|
|
$this->refreshToken();
|
|
$response = $this->client->request('GET', self::API_URL . $endpoint, [
|
|
'query' => $params,
|
|
'headers' => [
|
|
'Authorization' => 'Bearer ' . $this->accessToken
|
|
]
|
|
]);
|
|
}
|
|
|
|
return $response->toArray();
|
|
} catch (MangadexAuthenticationException $e) {
|
|
throw $e;
|
|
} catch (\Exception $e) {
|
|
throw new MangadexApiException(
|
|
sprintf('Failed to fetch data from Mangadex: %s', $e->getMessage()),
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
} |