-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: refactor to use Guzzle #2
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RetroAchievements\Api\Exceptions; | ||
|
||
use Exception; | ||
|
||
class GenericException extends Exception implements RetroAchievementsException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RetroAchievements\Api\Exceptions; | ||
|
||
use Exception; | ||
|
||
class NotFoundException extends Exception implements RetroAchievementsException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RetroAchievements\Api\Exceptions; | ||
|
||
interface RetroAchievementsException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RetroAchievements\Api\Exceptions; | ||
|
||
use Exception; | ||
|
||
class UnauthorizedException extends Exception implements RetroAchievementsException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RetroAchievements\Api; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use RetroAchievements\Api\Exceptions\GenericException; | ||
use RetroAchievements\Api\Exceptions\NotFoundException; | ||
use RetroAchievements\Api\Exceptions\UnauthorizedException; | ||
|
||
trait MakesHttpRequests | ||
{ | ||
protected function get(string $uri) | ||
{ | ||
return $this->request('GET', $uri); | ||
} | ||
|
||
protected function post(string $uri, array $payload = []) | ||
{ | ||
return $this->request('POST', $uri, $payload); | ||
} | ||
|
||
protected function put(string $uri, array $payload = []) | ||
{ | ||
return $this->request('PUT', $uri, $payload); | ||
} | ||
|
||
protected function delete(string $uri, array $payload = []) | ||
{ | ||
return $this->request('DELETE', $uri, $payload); | ||
} | ||
|
||
protected function request(string $verb, string $uri, array $payload = []) | ||
{ | ||
$response = $this->client->request( | ||
$verb, | ||
$uri, | ||
empty($payload) ? [] : ['form_params' => $payload] | ||
); | ||
|
||
if (! $this->isSuccessful($response)) { | ||
$this->handleRequestError($response); | ||
} | ||
|
||
$responseBody = (string) $response->getBody(); | ||
|
||
return json_decode($responseBody, true) ?: $responseBody; | ||
} | ||
|
||
protected function isSuccessful(ResponseInterface $response): bool | ||
{ | ||
return (int) substr((string) $response->getStatusCode(), 0, 1) === 2; | ||
} | ||
|
||
protected function handleRequestError(ResponseInterface $response): void | ||
{ | ||
if ($response->getStatusCode() === 404) { | ||
throw new NotFoundException((string) $response->getBody()); | ||
} | ||
|
||
if ($response->getStatusCode() === 401) { | ||
throw new UnauthorizedException((string) $response->getBody()); | ||
} | ||
|
||
throw new GenericException((string) $response->getBody()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RetroAchievements\Api; | ||
|
||
use GuzzleHttp\Client; | ||
|
||
class RetroAchievements | ||
{ | ||
use MakesHttpRequests; | ||
|
||
public const BASE_URI = 'https://retroachievements.org'; | ||
|
||
public const API_VERSION = 1; | ||
|
||
protected Client $client; | ||
|
||
public function __construct(public string $username, public string $apiKey, string $baseUri = self::BASE_URI) | ||
{ | ||
$this->client = new Client([ | ||
'base_uri' => $baseUri, | ||
'http_errors' => false, | ||
'headers' => [ | ||
'Accept' => 'application/json', | ||
'Content-Type' => 'application/json', | ||
], | ||
]); | ||
} | ||
|
||
public function setClient(Client $client): self | ||
{ | ||
$this->client = $client; | ||
|
||
return $this; | ||
} | ||
|
||
public function getTopTenUsers(): mixed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if these should be split into action traits based on the subtypes in the API docs. It would increase readability and discovery. For example, |
||
{ | ||
return $this->getApiUrl('API_GetTopTenUsers.php'); | ||
} | ||
|
||
public function getGameInfo(int $gameID): mixed | ||
{ | ||
return $this->getApiUrl('API_GetGame.php', [ | ||
'i' => $gameID, | ||
]); | ||
} | ||
|
||
public function getGameInfoExtended(int $gameID): mixed | ||
{ | ||
return $this->getApiUrl('API_GetGameExtended.php', [ | ||
'i' => $gameID, | ||
]); | ||
} | ||
|
||
public function getConsoleIDs(): mixed | ||
{ | ||
return $this->getApiUrl('API_GetConsoleIDs.php'); | ||
} | ||
|
||
public function getGameList(int $consoleID): mixed | ||
{ | ||
return $this->getApiUrl('API_GetGameList.php', [ | ||
'i' => $consoleID, | ||
]); | ||
} | ||
|
||
public function getFeedFor(string $user, int $count, int $offset = 0): mixed | ||
{ | ||
return $this->getApiUrl('API_GetFeed.php', [ | ||
'u' => $user, | ||
'c' => $count, | ||
'o' => $offset, | ||
]); | ||
} | ||
|
||
public function getUserRankAndScore(string $user): mixed | ||
{ | ||
return $this->getApiUrl('API_GetUserRankAndScore.php', [ | ||
'u' => $user, | ||
]); | ||
} | ||
|
||
public function getUserProgress(string $user, string $gameIDCSV): mixed | ||
{ | ||
$gameIDCSV = preg_replace('/\s+/', '', $gameIDCSV); // Remove all whitespace | ||
|
||
return $this->getApiUrl('API_GetUserProgress.php', [ | ||
'u' => $user, | ||
'i' => $gameIDCSV, | ||
]); | ||
} | ||
|
||
public function getUserRecentlyPlayedGames(string $user, int $count, int $offset = 0): mixed | ||
{ | ||
return $this->getApiUrl('API_GetUserRecentlyPlayedGames.php', [ | ||
'u' => $user, | ||
'c' => $count, | ||
'o' => $offset, | ||
]); | ||
} | ||
|
||
public function getUserSummary(string $user, int $numRecentGames): mixed | ||
{ | ||
return $this->getApiUrl('API_GetUserSummary.php', [ | ||
'u' => $user, | ||
'g' => $numRecentGames, | ||
'a' => 5, | ||
]); | ||
} | ||
|
||
public function getGameInfoAndUserProgress(string $user, int $gameID): mixed | ||
{ | ||
return $this->getApiUrl('API_GetGameInfoAndUserProgress.php', [ | ||
'u' => $user, | ||
'g' => $gameID, | ||
]); | ||
} | ||
|
||
public function getAchievementsEarnedOnDay(string $user, string $date): mixed | ||
{ | ||
return $this->getApiUrl('API_GetAchievementsEarnedOnDay.php', [ | ||
'u' => $user, | ||
'd' => $date, | ||
]); | ||
} | ||
|
||
public function getAchievementsEarnedBetween(string $user, string $startDate, string $endDate): mixed | ||
{ | ||
return $this->getApiUrl('API_GetAchievementsEarnedBetween.php', [ | ||
'u' => $user, | ||
'f' => strtotime($startDate), | ||
't' => strtotime($endDate), | ||
]); | ||
} | ||
|
||
protected function getApiUrl(string $endpoint, array $parameters = []): mixed | ||
{ | ||
return $this->get( | ||
sprintf('/API/%s?%s', $endpoint, http_build_query([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've changed this to It's kind of a shame, but I assume this will be resolved when migrating to proper Laravel routes and controllers for the API. 👍🏻 Speaking of, are there any plans / a roadmap for this (I'd be interested in helping)? |
||
'z' => $this->username, | ||
'y' => $this->apiKey, | ||
] + $parameters)) | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All exceptions implement this, which allows for catching any RA client exception. 👍🏻