2017-02-07 13:11:42 -05:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
/**
|
2017-02-15 16:13:32 -05:00
|
|
|
* Hummingbird Anime List Client
|
2017-02-07 13:11:42 -05:00
|
|
|
*
|
2018-08-22 13:48:27 -04:00
|
|
|
* An API client for Kitsu to manage anime and manga watch lists
|
2017-02-07 13:11:42 -05:00
|
|
|
*
|
2023-07-13 11:08:05 -04:00
|
|
|
* PHP version 8.1
|
2017-02-07 13:11:42 -05:00
|
|
|
*
|
2023-07-13 11:08:05 -04:00
|
|
|
* @copyright 2015 - 2023 Timothy J. Warren <tim@timshome.page>
|
2017-02-07 13:11:42 -05:00
|
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
2020-12-10 17:06:50 -05:00
|
|
|
* @version 5.2
|
2023-07-13 11:08:05 -04:00
|
|
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
2017-02-07 13:11:42 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Aviat\AnimeClient\API;
|
|
|
|
|
2023-10-26 11:10:21 -04:00
|
|
|
use Amp\Future;
|
|
|
|
use Amp\Http\Client\Form;
|
2023-07-13 11:06:52 -04:00
|
|
|
use Amp\Http\Client\{HttpClientBuilder, HttpException, Request};
|
2017-02-08 15:48:20 -05:00
|
|
|
use Aviat\Ion\Json;
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
use InvalidArgumentException;
|
|
|
|
use Psr\Log\LoggerAwareTrait;
|
2022-03-03 17:26:09 -05:00
|
|
|
use Throwable;
|
2023-10-26 11:10:21 -04:00
|
|
|
|
|
|
|
use function Amp\async;
|
2022-03-03 17:26:09 -05:00
|
|
|
use function Aviat\AnimeClient\getResponse;
|
|
|
|
use const Aviat\AnimeClient\USER_AGENT;
|
2017-02-07 13:11:42 -05:00
|
|
|
|
|
|
|
/**
|
2020-03-11 16:26:17 -04:00
|
|
|
* Wrapper around Http\Client to make it easier to build API requests
|
2017-02-07 13:11:42 -05:00
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
abstract class APIRequestBuilder
|
|
|
|
{
|
2017-02-07 13:11:42 -05:00
|
|
|
use LoggerAwareTrait;
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2020-10-21 17:59:43 -04:00
|
|
|
/**
|
|
|
|
* Where to look for GraphQL request files
|
|
|
|
*/
|
2021-02-03 09:45:18 -05:00
|
|
|
protected string $filePath = '';
|
2020-10-21 17:59:43 -04:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Url prefix for making url requests
|
|
|
|
*/
|
2020-04-10 20:01:46 -04:00
|
|
|
protected string $baseUrl = '';
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Url path of the request
|
|
|
|
*/
|
2020-04-10 20:01:46 -04:00
|
|
|
protected string $path = '';
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Query string for the request
|
|
|
|
*/
|
2020-04-10 20:01:46 -04:00
|
|
|
protected string $query = '';
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Default request headers
|
|
|
|
*/
|
2020-04-10 20:01:46 -04:00
|
|
|
protected array $defaultHeaders = [];
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
2018-01-18 16:21:45 -05:00
|
|
|
* Valid HTTP request methods
|
2017-02-07 13:11:42 -05:00
|
|
|
*/
|
2020-04-10 20:01:46 -04:00
|
|
|
protected array $validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* The current request
|
|
|
|
*/
|
2020-04-10 20:01:46 -04:00
|
|
|
protected Request $request;
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2018-10-01 10:50:22 -04:00
|
|
|
/**
|
|
|
|
* Do a basic minimal GET request
|
|
|
|
*/
|
|
|
|
public static function simpleRequest(string $uri): Request
|
|
|
|
{
|
2020-04-10 20:01:46 -04:00
|
|
|
$request = (new Request($uri));
|
|
|
|
$request->setHeader('User-Agent', USER_AGENT);
|
2020-05-04 17:13:03 -04:00
|
|
|
$request->setTcpConnectTimeout(300000);
|
|
|
|
$request->setTransferTimeout(300000);
|
2020-04-10 20:01:46 -04:00
|
|
|
|
|
|
|
return $request;
|
2018-10-01 10:50:22 -04:00
|
|
|
}
|
|
|
|
|
2017-02-08 15:48:20 -05:00
|
|
|
/**
|
|
|
|
* Set an authorization header
|
|
|
|
*
|
|
|
|
* @param string $type The type of authorization, eg, basic, bearer, etc.
|
|
|
|
* @param string $value The authorization value
|
|
|
|
*/
|
|
|
|
public function setAuth(string $type, string $value): self
|
|
|
|
{
|
|
|
|
$authString = ucfirst($type) . ' ' . $value;
|
|
|
|
$this->setHeader('Authorization', $authString);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
2017-02-08 00:44:57 -05:00
|
|
|
* Set a basic authentication header
|
2017-02-07 13:11:42 -05:00
|
|
|
*/
|
2017-02-08 00:44:57 -05:00
|
|
|
public function setBasicAuth(string $username, string $password): self
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
2017-02-08 15:48:20 -05:00
|
|
|
$this->setAuth('basic', base64_encode($username . ':' . $password));
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Set the request body
|
|
|
|
*/
|
2023-10-26 11:10:21 -04:00
|
|
|
public function setBody(Form|string $body): self
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
2020-03-11 16:26:17 -04:00
|
|
|
$this->request->setBody($body);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set body as form fields
|
|
|
|
*
|
|
|
|
* @param array $fields Mapping of field names to values
|
|
|
|
*/
|
|
|
|
public function setFormFields(array $fields): self
|
|
|
|
{
|
2023-10-26 11:10:21 -04:00
|
|
|
$body = new Form;
|
|
|
|
|
|
|
|
array_walk($fields, fn ($content, $name) => $body->addField($name, $content));
|
2017-12-08 22:32:00 -05:00
|
|
|
|
|
|
|
return $this->setBody($body);
|
|
|
|
}
|
2018-01-10 16:34:25 -05:00
|
|
|
|
2017-12-08 22:32:00 -05:00
|
|
|
/**
|
|
|
|
* Unset a request header
|
|
|
|
*/
|
|
|
|
public function unsetHeader(string $name): self
|
|
|
|
{
|
2020-03-11 16:26:17 -04:00
|
|
|
$this->request->removeHeader($name);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2017-02-08 00:44:57 -05:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Set a request header
|
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
public function setHeader(string $name, ?string $value = NULL): self
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
2023-10-26 11:10:21 -04:00
|
|
|
if ($name === '')
|
|
|
|
{
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($value === NULL)
|
2017-12-08 22:32:00 -05:00
|
|
|
{
|
|
|
|
$this->unsetHeader($name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-11 16:26:17 -04:00
|
|
|
$this->request->setHeader($name, $value);
|
2017-12-08 22:32:00 -05:00
|
|
|
}
|
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Set multiple request headers
|
2017-02-08 00:44:57 -05:00
|
|
|
*
|
2017-02-07 13:11:42 -05:00
|
|
|
* name => value
|
|
|
|
*/
|
|
|
|
public function setHeaders(array $headers): self
|
|
|
|
{
|
2023-10-26 11:10:21 -04:00
|
|
|
array_walk($headers, fn ($value, $name) => $this->setHeader($name, $value));
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-08 15:48:20 -05:00
|
|
|
/**
|
|
|
|
* Set the request body
|
|
|
|
*/
|
2021-02-16 14:43:51 -05:00
|
|
|
public function setJsonBody(mixed $body): self
|
2017-02-08 15:48:20 -05:00
|
|
|
{
|
2022-03-03 17:26:09 -05:00
|
|
|
$requestBody = (is_string($body))
|
2022-03-03 13:25:10 -05:00
|
|
|
? $body
|
|
|
|
: Json::encode($body);
|
2017-12-08 22:32:00 -05:00
|
|
|
|
2017-02-08 15:48:20 -05:00
|
|
|
return $this->setBody($requestBody);
|
|
|
|
}
|
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Append a query string in array format
|
|
|
|
*/
|
|
|
|
public function setQuery(array $params): self
|
|
|
|
{
|
2017-02-08 00:44:57 -05:00
|
|
|
$this->query = http_build_query($params);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Return the promise for the current request
|
|
|
|
*
|
2022-03-03 17:26:09 -05:00
|
|
|
* @throws Throwable
|
2017-02-07 13:11:42 -05:00
|
|
|
*/
|
2017-12-08 22:32:00 -05:00
|
|
|
public function getFullRequest(): Request
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
|
|
|
$this->buildUri();
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2023-06-28 14:03:15 -04:00
|
|
|
$this->logger?->debug('API Request', [
|
|
|
|
'request_url' => $this->request->getUri(),
|
|
|
|
'request_headers' => $this->request->getHeaders(),
|
2023-10-26 11:10:21 -04:00
|
|
|
'request_body' => $this->request->getBody()
|
|
|
|
->getContent()
|
|
|
|
->read(),
|
2023-06-28 14:03:15 -04:00
|
|
|
]);
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this->request;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2018-01-10 16:34:25 -05:00
|
|
|
/**
|
|
|
|
* Get the data from the response of the passed request
|
|
|
|
*
|
2023-06-28 14:03:15 -04:00
|
|
|
* @throws Throwable
|
2018-01-10 16:34:25 -05:00
|
|
|
*/
|
2023-06-28 14:03:15 -04:00
|
|
|
public function getResponseData(Request $request): mixed
|
2018-01-10 16:34:25 -05:00
|
|
|
{
|
2018-11-29 11:00:50 -05:00
|
|
|
$response = getResponse($request);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2023-10-26 11:10:21 -04:00
|
|
|
return $response->getBody()->buffer();
|
2018-01-10 16:34:25 -05:00
|
|
|
}
|
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Create a new http request
|
|
|
|
*
|
2017-02-17 10:55:17 -05:00
|
|
|
* @throws InvalidArgumentException
|
2017-02-07 13:11:42 -05:00
|
|
|
*/
|
|
|
|
public function newRequest(string $type, string $uri): self
|
|
|
|
{
|
2020-05-04 17:13:03 -04:00
|
|
|
if ( ! in_array($type, $this->validMethods, TRUE))
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
2017-12-08 22:32:00 -05:00
|
|
|
throw new InvalidArgumentException('Invalid HTTP method');
|
2017-02-07 13:11:42 -05:00
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2021-02-17 20:02:51 -05:00
|
|
|
$realUrl = (str_contains($uri, '//'))
|
2017-12-08 22:32:00 -05:00
|
|
|
? $uri
|
|
|
|
: $this->baseUrl . $uri;
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-12-08 22:32:00 -05:00
|
|
|
$this->resetState($realUrl, $type);
|
2017-02-08 00:44:57 -05:00
|
|
|
$this->path = $uri;
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-12-08 22:32:00 -05:00
|
|
|
// Actually create the full url!
|
|
|
|
$this->buildUri();
|
|
|
|
|
2017-02-08 00:44:57 -05:00
|
|
|
if ( ! empty($this->defaultHeaders))
|
|
|
|
{
|
|
|
|
$this->setHeaders($this->defaultHeaders);
|
2017-02-08 15:48:20 -05:00
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
return $this;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Create the full request url
|
|
|
|
*/
|
2017-12-08 22:32:00 -05:00
|
|
|
private function buildUri(): Request
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
2021-02-17 20:02:51 -05:00
|
|
|
$url = (str_contains($this->path, '//'))
|
2017-02-07 13:11:42 -05:00
|
|
|
? $this->path
|
2017-02-08 00:44:57 -05:00
|
|
|
: $this->baseUrl . $this->path;
|
2017-02-07 13:11:42 -05:00
|
|
|
|
|
|
|
if ( ! empty($this->query))
|
|
|
|
{
|
|
|
|
$url .= '?' . $this->query;
|
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2020-03-11 16:26:17 -04:00
|
|
|
$this->request->setUri($url);
|
2017-12-08 22:32:00 -05:00
|
|
|
|
|
|
|
return $this->request;
|
2017-02-07 13:11:42 -05:00
|
|
|
}
|
2017-02-08 00:44:57 -05:00
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
/**
|
|
|
|
* Reset the class state for a new request
|
|
|
|
*/
|
2021-12-02 16:06:34 -05:00
|
|
|
private function resetState(?string $url, string $type = 'GET'): void
|
2017-02-07 13:11:42 -05:00
|
|
|
{
|
2017-12-08 22:32:00 -05:00
|
|
|
$requestUrl = $url ?: $this->baseUrl;
|
|
|
|
|
2017-02-07 13:11:42 -05:00
|
|
|
$this->path = '';
|
|
|
|
$this->query = '';
|
2020-03-11 16:26:17 -04:00
|
|
|
$this->request = new Request($requestUrl, $type);
|
2020-05-04 17:13:03 -04:00
|
|
|
$this->request->setInactivityTimeout(300000);
|
|
|
|
$this->request->setTlsHandshakeTimeout(300000);
|
2020-03-11 16:26:17 -04:00
|
|
|
$this->request->setTcpConnectTimeout(300000);
|
|
|
|
$this->request->setTransferTimeout(300000);
|
2017-02-07 13:11:42 -05:00
|
|
|
}
|
2022-03-03 17:26:09 -05:00
|
|
|
}
|