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 ); } } }